Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • geocontrib/geocontrib-frontend
  • ext_matthieu/geocontrib-frontend
  • fnecas/geocontrib-frontend
  • MatthieuE/geocontrib-frontend
4 results
Show changes
<template>
<div v-frag>
<div class="row">
<div class="fourteen wide column">
<h1 class="ui header">
<div class="content">
{{ feature.title }}
<div class="ui icon right floated compact buttons">
<!-- {% if permissions|lookup:'can_create_feature' %} -->
<router-link
:to="{
name: 'ajouter-signalement',
params: { slug_type_signal: $route.params.slug_type_signal },
}"
class="ui button button-hover-orange"
data-tooltip="Ajouter un signalement"
data-position="bottom left"
>
<i class="plus fitted icon"></i>
</router-link>
<!-- {% endif %} {% if permissions|lookup:'can_update_feature' %} -->
<router-link
:to="{
name: 'editer-signalement',
params: { slug_signal: $route.params.slug_signal },
}"
class="ui button button-hover-orange"
>
<i class="inverted grey pencil alternate icon"></i>
</router-link>
<!-- {% endif %} {% if permissions|lookup:'can_delete_feature' %} -->
<a
@click="isCanceling = true"
id="feature-delete"
class="ui button button-hover-red"
>
<i class="inverted grey trash alternate icon"></i>
</a>
<!-- {% endif %} -->
</div>
<div class="ui hidden divider"></div>
<div class="sub header">
{{ feature.description }}
<!-- | linebreaks -->
</div>
</div>
</h1>
</div>
</div>
<div class="row">
<div class="seven wide column">
<table class="ui very basic table">
<tbody>
<tr v-for="field in feature_data" :key="field.label">
<td>
<b>{{ field.label }}</b>
</td>
<td>
<b>
<i
v-if="
field.field_type === 'boolean' && field.value === true
"
class="olive check icon"
></i>
<i
v-else-if="
field.field_type === 'boolean' && field.value === false
"
class="red times icon"
></i>
<span v-else>
{{ field.value }}
</span>
</b>
</td>
</tr>
<tr>
<td>Auteur</td>
<td>{{ feature.display_creator }}</td>
</tr>
<tr>
<td>Statut</td>
<td>
<i
v-if="feature.status === 'archived'"
class="grey archive icon"
></i>
<i
v-else-if="feature.status === 'pending'"
class="teal hourglass outline icon"
></i>
<i
v-else-if="feature.status === 'published'"
class="olive check icon"
></i>
<i
v-else-if="feature.status === 'draft'"
class="orange pencil alternate icon"
></i>
{{ feature.get_status_display }}
</td>
</tr>
<tr>
<td>Date de création</td>
<td v-if="feature.created_on">
{{ feature.created_on }}
</td>
</tr>
<tr>
<td>Date de dernière modification</td>
<td v-if="feature.updated_on">
{{ feature.updated_on }}
</td>
</tr>
<tr>
<td>Date d'archivage automatique</td>
<td v-if="feature.archived_on">
{{ feature.archived_on }}
</td>
</tr>
<tr>
<td>Date de suppression automatique</td>
<td v-if="feature.deletion_on">
{{ feature.deletion_on }}
</td>
</tr>
</tbody>
</table>
<!-- <small>{% for link in linked_features %} {% endfor %}</small> // ? EMPTY ?!??? -->
<h3>Liaison entre signalements</h3>
<table class="ui very basic table">
<tbody>
<tr
v-for="(link, index) in linked_features"
:key="link.feature_to.title + index"
>
<td>
{{ link.relation_type }}
<router-link
:to="{
name: 'details-signalement',
params: {
slug_type_signal: link.feature_to.feature_type.title,
slug_signal: link.feature_to.title,
},
}"
>{{ link.feature_to.title }}</router-link
>
({{ link.feature_to.creator }} -
{{ link.feature_to.created_on }})
</td>
</tr>
</tbody>
</table>
</div>
<div class="seven wide column">
<a
id="feature-list-link"
href="{% url 'geocontrib:feature_list' slug=feature.project.slug %}"
>
<div id="map"></div>
</a>
</div>
</div>
<div class="row">
<div class="seven wide column">
<h2 class="ui header">Pièces jointes</h2>
<div v-for="pj in attachments" :key="pj.title" class="ui divided items">
<div class="item">
<a
class="ui tiny image"
target="_blank"
:href="pj.attachment_file.url"
>
<img
v-if="pj.extension === '.pdf'"
src="{% static 'geocontrib/img/pdf.png' %}"
/>
<!-- // ? que faire ? -->
<img v-else :src="pj.attachment_file.url" />
</a>
<div class="middle aligned content">
<a
class="header"
target="_blank"
:href="pj.attachment_file.url"
>{{ pj.title }}</a
>
<div class="description">
{{ pj.info }}
</div>
</div>
</div>
</div>
<i v-if="attachments.length === 0"
>Aucune pièce jointe associée au signalement.</i
>
</div>
<div class="seven wide column">
<h2 class="ui header">Activité et commentaires</h2>
<div id="feed-event" class="ui feed">
<div v-frag v-for="(event, index) in events" :key="'event' + index">
<div v-frag v-if="event.event_type === 'create'">
<div v-if="event.object_type === 'feature'" class="event">
<div class="content">
<div class="summary">
<div class="date">
{{ event.created_on }}
</div>
Création du signalement
<span v-if="user">par {{ event.display_user }}</span>
</div>
</div>
</div>
<div v-else-if="event.object_type === 'comment'" class="event">
<div class="content">
<div class="summary">
<div class="date">
{{ event.created_on }}
</div>
Commentaire
<span v-if="user">par {{ event.display_user }}</span>
</div>
<div class="extra text">
{{ event.related_comment.comment }}
<div v-frag v-if="event.related_comment.attachments">
<div
v-frag
v-for="att in event.related_comment.attachments"
:key="att.title"
>
<br /><a :href="att.url" tarrget="_blank"
><i class="paperclip fitted icon"></i>
{{ att.title }}</a
>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else-if="event.event_type === 'update'" class="event">
<div class="content">
<div class="summary">
<div class="date">
{{ event.created_on }}
</div>
Signalement mis à jour
<span v-if="user">par {{ event.display_user }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- {% if permissions|lookup:'can_create_feature' %} -->
<div class="ui segment">
<form
id="form-comment"
class="ui form"
method="POST"
enctype="multipart/form-data"
>
<!-- action="{% url 'geocontrib:add_comment' slug=feature.project.slug feature_type_slug=feature.feature_type.slug feature_id=feature.feature_id%}" -->
<!-- {% for hidden in comment_form.hidden_fields %}
{{ hidden }}
{% endfor %} -->
<div
v-if="comment_form.non_field_errors"
class="alert alert-danger"
role="alert"
>
<span v-for="error in comment_form.non_field_errors" :key="error">
{{ error }}
</span>
</div>
<div class="required field">
<label :for="comment_form.comment.id_for_label"
>Ajouter un commentaire</label
>
{{ comment_form.comment.errors }}
<textarea
v-model="comment_form.comment.value"
:name="comment_form.comment.html_name"
rows="2"
></textarea>
</div>
<label>Pièce jointe (facultative)</label>
<div class="two fields">
<div class="field">
<label class="ui icon button" for="attachment_file">
<i class="paperclip icon"></i>
<span class="label">{{
comment_form.attachment_file.value
? comment_form.attachment_file.value
: "Sélectionner un fichier ..."
}}</span>
</label>
// todo : get image from "C:\\fakepath\..."
<input
type="file"
accept="application/pdf, image/jpeg, image/png"
style="display: none"
name="attachment_file"
id="attachment_file"
@change="getAttachmentFileData($event)"
/>
{{ comment_form.attachment_file.errors }}
</div>
<div class="field">
<input
v-model="comment_form.title.value"
type="text"
:name="comment_form.title.html_name"
:id="comment_form.title.id_for_label"
/>
{{ comment_form.title.errors }}
</div>
</div>
<button
@click="postComment"
type="button"
class="ui compact green icon button"
>
<i class="plus icon"></i> Poster le commentaire
</button>
</form>
</div>
</div>
</div>
<div
v-if="isCanceling"
class="ui dimmer modals page transition visible active"
style="display: flex !important"
>
<div
:class="[
'ui mini modal subscription',
{ 'active visible': isCanceling },
]"
>
<i @click="isCanceling = false" class="close icon"></i>
<div class="ui icon header">
<i class="trash alternate icon"></i>
Supprimer le signalement
</div>
<div class="actions">
<form
action="{% url 'geocontrib:feature_delete' slug=feature.project.slug feature_type_slug=feature.feature_type.slug feature_id=feature.feature_id %}"
method="POST"
>
<input type="hidden" name="_method" value="delete" />
<button
@click="deleteFeature"
type="button"
class="ui red compact fluid button"
>
Confirmer la suppression
</button>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import frag from "vue-frag";
import { mapState } from "vuex";
export default {
name: "Feature_detail",
directives: {
frag,
},
data() {
return {
isCanceling: false,
feature_data: [
{
field: {
label: "Publié",
field_type: "Boolean",
value: true,
},
},
],
mock_linked_features: [
{
relation_type: "Doublon",
feature_to: {
title: "Éolienne offshore",
creator: "Babar",
created_on: new Date().toDateString(),
feature_type: {
title: "Éolienne",
},
},
},
],
attachments: [
// TODO : Récupérer depuis l'api
{
attachment_file: {
url: "http://localhost:8000/media/user_1/albinoscom.jpg",
},
extension: "jpg",
title: "albinos",
info: "Drôle de bête",
},
],
// TODO : Récupérer depuis l'api
events: [
{
event_type: "create",
object_type: "feature",
created_on: new Date().toDateString(),
display_user: "Babar",
},
{
event_type: "create",
object_type: "comment",
created_on: new Date().toDateString(),
display_user: "Babar",
related_comment: {
attachments: [
{
title: "Albinos",
url: "http://localhost:8000/media/user_1/albinoscom.jpg",
},
],
},
},
{
event_type: "update",
object_type: "feature",
created_on: new Date().toDateString(),
display_user: "Babar",
},
],
comment_form: {
attachment_file: {
errors: null,
value: null,
},
title: {
id_for_label: "title",
html_name: "title",
errors: null,
value: null,
},
comment: {
id_for_label: "add-comment",
html_name: "add-comment",
errors: null,
value: null,
},
non_field_errors: [],
},
};
},
computed: {
...mapState(["user"]),
feature: function () {
return (
this.$store.state.feature.features.find(
(el) => el.feature_id === this.$route.params.slug_signal
) || []
);
},
linked_features: function () {
// todo: vérifier avec données réels si ça fonctionne correctement
return this.mock_linked_features.filter((el) => el.feature_to);
},
},
methods: {
postComment() {
const data = {
comment: this.comment_form.comment.value,
title: this.comment_form.title.value,
attachment_file: this.comment_form.attachment_file.value,
};
this.$store.dispatch("feature/POST_COMMENT");
console.log("POST comment", data);
},
getAttachmentFileData(evt) {
const input = evt.target.value;
const period = input.lastIndexOf(".");
const fileName = input.substring(0, period);
const fileExtension = input.substring(period + 1);
const shortName = fileName.slice(0, 10) + "[...]." + fileExtension;
this.comment_form.attachment_file.value = shortName;
this.comment_form.title.value = shortName;
},
deleteFeature() {
this.$store.dispatch(
"feature/DELETE_FEATURE",
this.$route.params.slug_signal
);
},
},
created() {
console.log;
if (!this.project) {
this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
}
this.$store.commit(
"feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
this.$route.params.slug_type_signal
);
},
mounted() {
this.$store.dispatch("map/INITIATE_MAP");
},
};
</script>
<style>
#map {
width: 100%;
height: 100%;
min-height: 250px;
}
#feed-event .event {
margin-bottom: 1em;
}
#feed-event .event .date {
margin-right: 1em !important;
}
#feed-event .event .extra.text {
margin-left: 107px;
margin-top: 0;
}
</style>
\ No newline at end of file
<template>
<div v-frag>
<div class="fourteen wide column">
<h1
v-if="feature && $router.history.current.name === 'editer-signalement'"
>
Mise à jour du signalement "{{ feature.title }}"
</h1>
<h1
v-else-if="
feature_type && $router.history.current.name === 'ajouter-signalement'
"
>
Création d'un signalement <small>[{{ feature_type.title }}]</small>
</h1>
<form
id="form-feature-edit"
action=""
method="post"
enctype="multipart/form-data"
class="ui form"
>
<!-- Feature Fields -->
<div class="two fields">
<div class="required field">
<label :for="form.title.id_for_label">{{ form.title.label }}</label>
<input
type="text"
required
:maxlength="form.title.field.max_length"
:name="form.title.html_name"
:id="form.title.id_for_label"
v-model="form.title.value"
@blur="updateStore"
/>
{{ form.title.errors }}
</div>
<div class="required field">
<label :for="form.status.id_for_label">{{
form.status.label
}}</label>
<Dropdown
:options="form.status.field.choices"
:selected="selected_status"
:selection.sync="selected_status"
/>
{{ form.status.errors }}
</div>
</div>
<div class="field">
<label :for="form.description.id_for_label">{{
form.description.label
}}</label>
<textarea
:name="form.description.html_name"
rows="5"
v-model="form.description.value"
@blur="updateStore"
></textarea>
{{ form.description.errors }}
</div>
<!-- Geom Field -->
<div class="field">
<label :for="form.geom.id_for_label">{{ form.geom.label }}</label>
<!-- Import GeoImage -->
<div v-frag v-if="feature_type && feature_type.geom_type === 'point'">
<p>
<button
id="add-geo-image"
type="button"
class="ui compact button"
>
<i class="file image icon"></i>Importer une image géoréférencée
</button>
Vous pouvez utiliser une image géoréférencée pour localiser le
signalement.
</p>
<p>
<button
id="create-point-geoposition"
type="button"
class="ui compact button"
>
<i class="ui map marker alternate icon"></i>Positionner le
signalement à partir de votre géolocalisation
</button>
</p>
<span id="erreur-geolocalisation" style="display: none">
<div class="ui negative message">
<div class="header">
Une erreur est survenue avec la fonctionnalité de
géolocalisation
</div>
<p id="erreur-geolocalisation-message"></p>
</div>
<br />
</span>
</div>
{{ form.geom.errors }}
<!-- Map -->
<input
type="hidden"
:name="form.geom.html_name"
:id="form.geom.id_for_label"
v-model="form.geom.value"
@blur="updateStore"
/>
<div class="ui tab active map-container" data-tab="map">
<div id="map"></div>
// todo: ajouter v-if
<!-- {% if serialized_base_maps|length > 0 %} {% include
"geocontrib/map-layers/sidebar-layers.html" with
basemaps=serialized_base_maps layers=serialized_layers
project=project.slug%} {% endif %} -->
<SidebarLayers />
</div>
</div>
<!-- Extra Fields -->
<div class="ui horizontal divider">DONNÉES MÉTIER</div>
// Todo: Récupérer les "extra_form" de l'API
<div
v-for="(field, index) in extra_form_with_values"
:key="field.field_type + index"
class="field"
>
<div v-frag v-if="field.field_type === 'char'">
<label for="field.name">{{ field.label }}</label>
<input
type="text"
:name="field.name"
:id="field.name"
v-model="field.value"
@blur="updateStore_extra_form"
/>
</div>
<div v-frag v-else-if="field.field_type === 'list'">
<label for="field.name">{{ field.label }}</label>
<Dropdown
:options="field.choices"
:selected="selected_extra_form_list"
:selection.sync="selected_extra_form_list"
/>
</div>
<div v-frag v-else-if="field.field_type === 'integer'">
<label for="field.name">{{ field.label }}</label>
<div class="ui input">
<!-- //* si click sur fléche dans champ input, pas de focus, donc pas de blur, donc utilisation de @change -->
<input
type="number"
:name="field.name"
:id="field.name"
v-model.number="field.value"
@change="updateStore_extra_form"
/>
</div>
</div>
<div v-frag v-else-if="field.field_type === 'boolean'">
<div class="ui checkbox">
<input
type="checkbox"
:checked="field.value"
:name="field.name"
:id="field.name"
@change="updateStore_extra_form"
/>
<label for="field.name">{{ field.label }}</label>
</div>
</div>
<div v-frag v-else-if="field.field_type === 'date'">
<label for="field.name">{{ field.label }}</label>
<input
type="date"
:name="field.name"
:id="field.name"
v-model="field.value"
@blur="updateStore_extra_form"
/>
</div>
<div v-frag v-else-if="field.field_type === 'decimal'">
<label for="field.name">{{ field.label }}</label>
<div class="ui input">
<input
type="number"
step=".01"
:name="field.name"
:id="field.name"
v-model.number="field.value"
@change="updateStore_extra_form"
/>
</div>
</div>
<div v-frag v-else-if="field.field_type === 'text'">
<label :for="field.name">{{ field.label }}</label>
<textarea
:name="field.name"
rows="3"
v-model="field.value"
@blur="updateStore_extra_form"
></textarea>
</div>
{{ field.errors }}
</div>
<!-- Pièces jointes -->
<div class="ui horizontal divider">PIÈCES JOINTES</div>
<!-- {{ attachment_formset.non_form_errors }} -->
<div id="formsets-attachment">
<!-- {{ attachment_formset.management_form }} -->
<FeatureAttachmentForm
v-for="form in attachmentFormset"
:key="form.dataKey"
:attachmentForm="form"
/>
</div>
<button
@click="add_attachement_formset"
id="add-attachment"
type="button"
class="ui compact basic button button-hover-green"
>
<i class="ui plus icon"></i>Ajouter une pièce jointe
</button>
<!-- Signalements liés -->
<div class="ui horizontal divider">SIGNALEMENTS LIÉS</div>
<!-- {{ linked_formset.non_form_errors }} -->
<div id="formsets-link">
<!-- {{ linked_formset.management_form }} -->
<FeatureLinkedForm
v-for="form in linkedFormset"
:key="form.dataKey"
:linkedForm="form"
:features="features"
/>
</div>
<button
@click="add_linked_formset"
id="add-link"
type="button"
class="ui compact basic button button-hover-green"
>
<i class="ui plus icon"></i>Ajouter une liaison
</button>
<div class="ui divider"></div>
<button @click="postForm" type="button" class="ui teal icon button">
<i class="white save icon"></i> Enregistrer les changements
</button>
</form>
</div>
</div>
</template>
<script>
import frag from "vue-frag";
import { mapGetters, mapState } from "vuex";
import FeatureAttachmentForm from "@/components/feature/FeatureAttachmentForm";
import FeatureLinkedForm from "@/components/feature/FeatureLinkedForm";
import Dropdown from "@/components/Dropdown.vue";
import SidebarLayers from "@/components/map-layers/SidebarLayers";
export default {
name: "Feature_edit",
directives: {
frag,
},
components: {
FeatureAttachmentForm,
FeatureLinkedForm,
Dropdown,
SidebarLayers,
},
computed: {
...mapState(["project"]),
...mapState("feature", [
"attachmentFormset",
"linkedFormset",
"features",
"extra_form",
]),
...mapGetters("feature_type", ["feature_type"]),
feature: function () {
return this.$store.state.feature.features.find(
(el) => el.feature_id === this.$route.params.slug_signal
);
},
selected_status: {
get() {
return this.form.status.value;
},
set(newValue) {
this.form.status.value = newValue;
this.updateStore();
},
},
extra_form_with_values: {
get() {
return this.extra_form.map((el) => {
return { ...el, value: el.value ? el.value : null };
});
},
set(newValue) {
console.log(newValue);
},
},
selected_extra_form_list: {
get() {
return this.extra_form_with_values.find(
(el) => el.field_type === "list"
).value;
},
set(newValue) {
const index = this.extra_form_with_values.findIndex(
(el) => el.field_type === "list"
);
this.extra_form_with_values[index].value = newValue;
this.updateStore_extra_form();
},
},
},
data() {
return {
attachmentDataKey: 0,
linkedDataKey: 0,
form: {
title: {
errors: null,
id_for_label: "name",
field: {
max_length: 30,
},
html_name: "name",
label: "Nom",
value: "",
},
status: {
errors: null,
id_for_label: "status",
field: {
choices: ["Brouillon", "Publié", "Archivé"],
},
html_name: "status",
label: "Statut",
value: "Brouillon",
},
description: {
errors: null,
id_for_label: "description",
html_name: "description",
label: "Description",
value: "",
},
geom: {
label: "Localisation",
},
},
};
},
methods: {
add_attachement_formset() {
this.$store.commit("feature/ADD_ATTACHMENT_FORM", this.attachmentDataKey); // * create an object with the counter in store
this.attachmentDataKey += 1; // * increment counter for key in v-for
},
add_linked_formset() {
this.$store.commit("feature/ADD_LINKED_FORM", this.linkedDataKey); // * create an object with the counter in store
this.linkedDataKey += 1; // * increment counter for key in v-for
},
updateStore() {
this.$store.commit("feature/UPDATE_FORM", {
title: this.form.title.value,
status: this.form.status.value,
description: this.form.description,
// ? geom ?
});
},
updateStore_extra_form() {
this.$store.commit(
"feature/UPDATE_EXTRA_FORM",
this.extra_form_with_values
);
},
postForm() {
if (this.form.title.value) {
this.form.title.errors = null;
this.$store.dispatch("feature/POST_FEATURE");
} else {
this.form.title.errors = "Veuillez compléter ce champ.";
}
},
},
created() {
if (!this.project) {
this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
}
this.$store.commit(
"feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
this.$route.params.slug_type_signal
);
},
mounted() {
if (this.$router.history.current.name === "editer-signalement") {
for (let el in this.feature) {
if (el && this.form[el]) this.form[el].value = this.feature[el];
}
}
this.$store.dispatch("map/INITIATE_MAP");
},
};
// TODO : add script from django and convert:
</script>
<style>
#map {
height: 70vh;
width: 100%;
border: 1px solid grey;
}
@media only screen and (max-width: 767px) {
#map {
height: 80vh;
}
}
/* // ! missing style in semantic.min.css, je ne comprends pas comment... */
.ui.right.floated.button {
float: right;
margin-right: 0;
margin-left: 0.25em;
}
/* // ! margin écrasé par class last-child first-child, pas normal ... */
.ui.segment {
margin: 1rem 0 !important;
}
</style>
\ No newline at end of file
<template>
<div class="fourteen wide column">
<div class="feature-list-container ui grid">
<div class="four wide column">
<h1>Signalements</h1>
</div>
<div class="twelve wide column">
<div class="ui secondary menu">
<a
@click="showMap = true"
:class="['item', { active: showMap }]"
data-tab="map"
data-tooltip="Carte"
><i class="map fitted icon"></i
></a>
<a
@click="showMap = false"
:class="['item', { active: !showMap }]"
data-tab="list"
data-tooltip="Liste"
><i class="list fitted icon"></i
></a>
<div class="item">
<h4>
{{ features.length }} signalement{{
features.length > 1 ? "s" : ""
}}
</h4>
</div>
<!-- {% if project and feature_types and
permissions|lookup:'can_create_feature' %} -->
<!-- v-if="project && feature_types && permissions" -->
<!-- //Todo: add permissions -->
<div v-if="project && feature_types" class="item right">
<div
@click="showAddSignal = !showAddSignal"
class="
ui
dropdown
top
right
pointing
compact
button button-hover-green
"
data-tooltip="Ajouter un signalement"
data-position="bottom left"
>
<i class="plus fitted icon"></i>
<div
v-if="showAddSignal"
class="menu transition visible"
style="z-index: 9999"
>
<div class="header">Ajouter un signalement du type</div>
<div class="scrolling menu text-wrap">
<router-link
:to="{
name: 'ajouter-signalement',
params: { slug_type_signal: type.title },
}"
v-for="type in feature_types"
:key="type.title"
class="item"
>
{{ type.title }}
</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<form id="form-filters" class="ui form grid" action="" method="get">
<div class="field wide four column">
<label>Type</label>
<Dropdown
:options="form.type.choices"
:selected="form.type.selected"
:selection.sync="form.type.selected"
:search="true"
/>
</div>
<div class="field wide four column">
<label>Statut</label>
<!-- //* giving an object mapped on key name -->
<Dropdown
:options="form.status.choices"
:selected="form.status.selected.name"
:selection.sync="form.status.selected"
:search="true"
/>
</div>
<div class="field wide four column">
<label>Nom</label>
<div class="ui icon input">
<i class="search icon"></i>
<div class="ui action input">
<input type="text" name="title" v-model="form.title" />
<button
type="button"
class="ui teal icon button"
id="submit-search"
>
<i class="search icon"></i>
</button>
</div>
</div>
</div>
<!-- map params, updated on map move // todo : brancher sur la carte probablement -->
<!-- <input type="hidden" name="zoom" :value="request.GET.zoom || ''" />
<input type="hidden" name="lat" :value="request.GET.lat || ''" />
<input type="hidden" name="lng" :value="request.GET.lng || ''" /> -->
</form>
<div v-show="showMap" class="ui tab active map-container" data-tab="map">
<div id="map"></div>
<!-- // todo: add v-if-->
<!-- {% if serialized_base_maps|length > 0 %} {% include
"geocontrib/map-layers/sidebar-layers.html" with
basemaps=serialized_base_maps layers=serialized_layers
project=project.slug%} {% endif %} -->
<SidebarLayers />
</div>
<div v-show="!showMap" data-tab="list">
<table id="table-features" class="ui compact table">
<thead>
<tr>
<th>Statut</th>
<th>Type</th>
<th>Nom</th>
<th>Dernière modification</th>
<th v-if="user">Auteur</th>
</tr>
</thead>
<tbody>
<tr v-for="feature in filteredFeatures" :key="feature.title">
<td class="dt-center" :data-order="feature.get_status_display">
<div v-if="feature.status == 'archived'" data-tooltip="Archivé">
<i class="grey archive icon"></i>
</div>
<div
v-else-if="feature.status == 'pending'"
data-tooltip="En attente de publication"
>
<i class="teal hourglass outline icon"></i>
</div>
<div
v-else-if="feature.status == 'published'"
data-tooltip="Publié"
>
<i class="olive check icon"></i>
</div>
<div
v-else-if="feature.status == 'draft'"
data-tooltip="Brouillon"
>
<i class="orange pencil alternate icon"></i>
</div>
</td>
<td>
<router-link
:to="{
name: 'details-type-signalement',
params: { feature_type_slug: feature.feature_type.title },
}"
>
{{ feature.feature_type.title }}
</router-link>
</td>
<td>
<router-link
:to="{
name: 'details-signalement',
params: {
slug_type_signal: feature.feature_type.title,
slug_signal: feature.title,
},
}"
>{{ feature.title }}</router-link
>
</td>
<td :data-order="feature.updated_on">
<!-- |date:'Ymd' -->
{{ feature.updated_on }}
</td>
<td v-if="user">
{{ feature.display_creator }}
</td>
</tr>
<tr v-if="filteredFeatures.length === 0" class="odd">
<td colspan="5" class="dataTables_empty" valign="top">
Aucune donnée disponible
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import { mapGetters, mapState } from "vuex";
import SidebarLayers from "@/components/map-layers/SidebarLayers";
import Dropdown from "@/components/Dropdown.vue";
export default {
name: "Feature_list",
components: {
SidebarLayers,
Dropdown,
},
data() {
return {
showMap: false,
showAddSignal: false,
form: {
type: {
selected: null,
choices: [],
},
status: {
selected: {
name: null,
value: null,
},
choices: [
{
name: "Brouillon",
value: "draft",
},
{
name: "En attente de publication",
value: "pending",
},
{
name: "Publié",
value: "published",
},
{
name: "Archivé",
value: "archived",
},
],
},
title: null,
},
};
},
computed: {
...mapGetters(["project"]),
...mapState(["user"]),
...mapState("feature", ["features"]),
...mapState("feature_type", ["feature_types"]),
filteredFeatures: function () {
let results = this.features;
if (this.form.type.selected) {
results = results.filter(
(el) => el.feature_type.title === this.form.type.selected
);
}
if (this.form.status.selected.value) {
results = results.filter(
(el) => el.status === this.form.status.selected.value
);
}
if (this.form.title) {
results = results.filter((el) =>
el.title.toLowerCase().includes(this.form.title.toLowerCase())
);
}
return results;
},
},
created() {
if (!this.project) {
//this.$store.dispatch("GET_PROJECT_MESSAGES", this.$route.params.slug);
this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
}
},
mounted() {
this.$store.dispatch("map/INITIATE_MAP");
this.form.type.choices = [
//* convert Set to an Array with spread "..."
...new Set(this.features.map((el) => el.feature_type.title)), //* use Set to eliminate duplicate values
];
},
// todo : add script
};
</script>
<style>
#map {
width: 100%;
min-height: 300px;
border: 1px solid grey;
/* To not hide the filters */
z-index: 1;
}
#form-filters,
.ui.centered > .row.feature-list-container {
justify-content: flex-start;
}
.feature-list-container .ui.menu:not(.vertical) .right.item {
padding-right: 0;
}
.map-container {
width: 80vw;
transform: translateX(-50%);
margin-left: 50%;
}
@media screen and (max-width: 767px) {
#form-filters > .field.column {
width: 100% !important;
}
.map-container {
width: 100%;
}
}
</style>
\ No newline at end of file
<template>
<!-- <div>
<h1>{{ feature_type_slug }}</h1>
{{ feature_type }}
</div> -->
<div v-if="structure" class="row">
<div class="five wide column">
<div class="ui attached secondary segment">
<h1 class="ui center aligned header">
<img
v-if="structure.geom_type == 'point'"
class="ui medium image"
src="@/assets/img/marker.png"
/>
<img
v-if="structure.geom_type == 'linestring'"
class="ui medium image"
src="@/assets/img/line.png"
/>
<img
v-if="structure.geom_type == 'polygon'"
class="ui medium image"
src="@/assets/img/polygon.png"
/>
{{ structure.title }}
</h1>
</div>
<div class="ui attached segment">
<div class="ui basic segment">
<div class="ui horizontal tiny statistic">
<div class="value">
{{ features.length }}
</div>
<div class="label">
Signalement{{ features.length > 1 ? "s" : "" }}
</div>
</div>
<h3 class="ui header">Champs</h3>
<div class="ui divided list">
<div
v-for="field in structure.customfield_set"
:key="field.label"
class="item"
>
<div class="right floated content">
<div class="description">{{ field.field_type }}</div>
</div>
<div class="content">{{ field.label }} ({{ field.name }})</div>
</div>
</div>
</div>
</div>
<div class="ui bottom attached secondary segment">
// ToDo : gérer permissions
<!-- <div v-if="permissions.can_create_feature" class="ui styled accordion"> -->
<div class="ui styled accordion">
<div
@click="showImport = !showImport"
:class="['title', { active: showImport }]"
>
<i class="dropdown icon"></i>
Importer des signalements
</div>
<div :class="['content', { active: showImport }]">
<div id="form-import-features" class="ui form">
<div class="field">
<label class="ui icon button" for="json_file">
<i class="file icon"></i>
<span class="label">{{ filenameToImport.name }}</span>
</label>
<input
type="file"
accept="application/json, .json, .geojson"
style="display: none"
name="json_file"
id="json_file"
@change="onFileChange"
/>
</div>
<button
:disabled="filenameToImport.size == 0"
@click="importGeoJson"
class="ui fluid teal icon button"
>
<i class="upload icon"></i> Lancer l'import
</button>
<ImportTask
v-if="importFeatureTypeData && importFeatureTypeData.length"
:data="importFeatureTypeData"
/>
</div>
</div>
<div
@click="showExport = !showExport"
:class="['title', { active: showExport }]"
>
<i class="dropdown icon"></i>
Exporter les signalements
</div>
<div :class="['content', { active: showExport }]">
<p>
Vous pouvez télécharger l'ensemble des signalements ayant le
statut publiés pour ce type.
</p>
<button
type="button"
class="ui fluid teal icon button"
@click="exportFeatures"
>
<i class="download icon"></i> Exporter
</button>
// todo gérer export
</div>
</div>
</div>
</div>
<div class="nine wide column">
<h3 class="ui header">Derniers signalements</h3>
<div
v-for="(feature, index) in features"
:key="feature.title + index"
class="ui small header"
>
<span v-if="feature.status == 'archived'" data-tooltip="Archivé">
<i class="grey archive icon"></i>
</span>
<span
v-else-if="feature.status == 'pending'"
data-tooltip="En attente de publication"
>
<i class="teal hourglass outline icon"></i>
</span>
<span v-else-if="feature.status == 'published'" data-tooltip="Publié">
<i class="olive check icon"></i>
</span>
<span v-else-if="feature.status == 'draft'" data-tooltip="Brouillon">
<i class="orange pencil alternate icon"></i>
</span>
<router-link
:to="{
name: 'details-signalement',
params: {
slug: project.slug,
slug_type_signal: feature.feature_type.slug,
slug_signal: feature.feature_id,
},
}"
>
{{ feature.title }}
</router-link>
<div class="sub header">
<div>
{{ feature.description.substring(0, 200) }}
</div>
<div>
Créé le {{ feature.created_on }}
<span v-if="$store.state.user.is_authenticated">
par {{ feature.display_creator }}</span
>
</div>
</div>
</div>
<router-link
v-if="project"
:to="{ name: 'liste-signalements', params: { slug: project.slug } }"
class="ui right labeled icon button"
>
<i class="right arrow icon"></i>
Voir tous les signalements
</router-link>
<!-- v-if="permissions.can_create_feature" -->
<router-link
:to="{
name: 'ajouter-signalement',
params: { slug_type_signal: structure.slug },
}"
class="ui icon button button-hover-green"
>
Ajouter un signalement
</router-link>
<br />// ToDo : gérer permissions
</div>
</div>
</template>
<script>
import { mapGetters, mapState } from "vuex";
import ImportTask from "@/components/ImportTask";
export default {
name: "Feature_type_detail",
components: {
ImportTask: ImportTask,
},
computed: {
...mapGetters(["project"]),
...mapState("feature", ["features"]),
...mapState("feature_type", ["feature_types", "importFeatureTypeData"]),
structure: function () {
// * je ne sais pas pourquoi ça s'appelle structure
return this.feature_types.find(
(el) => el.slug === this.$route.params.feature_type_slug
);
},
},
watch: {
structure(newVal, oldVal) {
if (newVal !== oldVal) {
this.$store.dispatch("feature_type/GET_IMPORTS", this.structure.slug);
}
},
},
data() {
return {
filenameToImport: {
name: "Sélectionner un fichier GeoJSON ...",
size: 0,
},
fileToImport: {},
showImport: false,
showExport: true,
};
},
methods: {
onFileChange(e) {
var files = e.target.files || e.dataTransfer.files;
if (!files.length) return;
this.filenameToImport = files[0]; // todo : remove this value from state as it stored
this.$store.commit("feature_type/SET_FILE_NAME_TO_IMPORT", this.filenameToImport)
//console.log(this.filenameToImport)
},
importGeoJson() {
this.$store.dispatch("feature_type/POST_FEATURES_FROM_GEOJSON", {
slug: this.$route.params.slug,
feature_type_slug: this.$route.params.feature_type_slug,
filenameToImport: this.filenameToImport,
});
},
exportFeatures() {
console.log("TEST", this.$store);
this.$store.dispatch(
"feature/EXPORT_FEATURES",
this.$route.params.slug_type_signal
);
},
},
created() {
if (!this.project) {
this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
}
this.$store.commit(
"feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
this.$route.params.slug_type_signal
);
},
};
</script>
\ No newline at end of file
<template>
<div v-frag>
<div id="message" class="fullwidth">
<div v-if="error" class="ui negative message">
<p><i class="cross icon"></i> {{ error }}</p>
</div>
</div>
<div class="fourteen wide column">
<form
id="form-type-edit"
action=""
method="post"
enctype="multipart/form-data"
class="ui form"
>
<h1 v-if="action === 'create'">
Créer un nouveau type de signalement pour le projet "{{
project.title
}}"
</h1>
<h1 v-if="feature_type && action === 'edit'">
Éditer le type de signalement "{{ feature_type.title }}" pour le
projet "{{ project.title }}"
</h1>
<p v-if="action === 'create'">
Ces champs par défaut existent pour tous les types de signalement:
</p>
<div class="two fields">
<div class="required field">
<label :for="form.title.id_for_label">{{ form.title.label }}</label>
<input
type="text"
required
:maxlength="form.title.field.max_length"
:name="form.title.html_name"
:id="form.title.id_for_label"
v-model="form.title.value"
@blur="updateStore"
/>
<ul id="errorlist" class="errorlist">
<li v-for="error in form.title.errors" :key="error">
{{ error }}
</li>
</ul>
</div>
<div class="required field">
<label :for="form.geom_type.id_for_label">{{
form.geom_type.label
}}</label>
<Dropdown
:options="geomTypeChoices"
:selected="selectedGeomType"
:selection.sync="selectedGeomType"
/>
<!-- {{ form.geom_type.errors }} -->
</div>
<div class="required field">
<label :for="form.color.id_for_label">{{ form.color.label }}</label>
<input
type="color"
required
style="width: 100%; height: 38px"
:name="form.color.html_name"
:id="form.color.id_for_label"
v-model="form.color.value"
@blur="updateStore"
/>
<!-- {{ form.color.errors }} -->
</div>
</div>
<!-- //* s'affiche après sélection d'option dans type de champ -->
<div
v-if="colorsStyleOptions.length > 0"
class="custom_style"
id="id_style_container"
>
<div class="list_selection" id="id_list_selection">
<Dropdown
:options="colorsStyleOptions"
:selected="selected_colors_style"
:selection.sync="selected_colors_style"
/>
</div>
<div class="colors_selection" id="id_colors_selection" hidden>
<div
v-for="(field, index) in form.colors_style.fields"
:key="'colors_style-' + index"
class="color-input"
>
<label>{{ field.label }}</label
><input type="color" v-model="field.value" @blur="updateStore" />
</div>
</div>
</div>
<span v-if="action === 'duplicate' || action === 'edit'"> </span>
<div id="formsets">
<FeatureTypeCustomForm
v-for="form in customForms"
:key="form.dataKey"
:dataKey="form.dataKey"
:customForm="form"
/>
</div>
<button
type="button"
@click="addCustomForm"
id="add-field"
class="ui compact basic button button-hover-green"
>
<i class="ui plus icon"></i>Ajouter un champ personnalisé
</button>
<div class="ui divider"></div>
<button
class="ui teal icon button"
type="button"
@click="postFeatureType"
>
<i class="white save icon"></i>
{{ action === "create" ? "Créer" : "Sauvegarder" }} le type de
signalement
</button>
<button
v-if="geojson"
class="ui teal icon button"
type="button"
@click="postFeatureTypeThenFeatures"
>
<i class="white save icon"></i>
Créer et importer le(s) signalement(s) du geojson
</button>
// TODO: Add check script for form & other scripts //
</form>
</div>
</div>
</template>
<script>
import frag from "vue-frag";
import { mapGetters, mapState, mapMutations } from "vuex";
import Dropdown from "@/components/Dropdown.vue";
import FeatureTypeCustomForm from "@/components/feature_type/FeatureTypeCustomForm.vue";
export default {
name: "Feature_type_edit",
directives: {
frag,
},
components: {
Dropdown,
FeatureTypeCustomForm,
},
props: ["geojson", "filenameToImport"],
data() {
return {
action: "create",
dataKey: 0,
error: null,
geomTypeChoices: [
{ value: "linestring", name: "Ligne" },
{ value: "point", name: "Point" },
{ value: "polygon", name: "Polygone" },
],
form: {
colors_style: {
value: null,
options: [],
fields: [],
},
color: {
id_for_label: "couleur",
label: "Couleur",
field: {
max_length: 128, // ! Vérifier la valeur dans django
},
html_name: "couleur",
value: "#000000",
},
title: {
errors: [],
id_for_label: "title",
label: "Titre",
field: {
max_length: 128, // ! Vérifier la valeur dans django
},
html_name: "title",
value: null,
},
geom_type: {
id_for_label: "geom_type",
label: "Type de géométrie",
field: {
//choices: ["Ligne", "Point", "Polygone"],
max_length: 128, // ! Vérifier la valeur dans django
},
html_name: "geom_type",
value: "Point",
},
},
reservedKeywords: [
// todo : add keywords for mapstyle (strokewidth...)
"title",
"description",
"status",
"created_on",
"updated_on",
"archived_on",
"deletion_on",
"feature_type",
],
};
},
computed: {
...mapGetters(["project"]),
...mapState("feature_type", ["customForms", "colorsStyleList"]),
...mapGetters("feature_type", ["feature_type"]),
selectedGeomType: {
get() {
const currentGeomType = this.geomTypeChoices.find(
(el) => el.value === this.form.geom_type.value
);
if (currentGeomType) {
return currentGeomType ? currentGeomType.name : null;
}
return null;
},
set(newValue) {
this.form.geom_type.value = newValue.value;
this.form = { ...this.form }; // ! quick & dirty fix for getter not updating because of Vue caveat https://vuejs.org/v2/guide/reactivity.html#For-Objects
this.updateStore();
},
},
selected_colors_style: {
get() {
return this.form.colors_style.value;
},
set(newValue) {
this.form.colors_style.value = newValue;
this.iniateColorsStyleFields();
this.updateStore();
},
},
colorsStyleOptions: function () {
return this.colorsStyleList.map((el) => el.label);
},
},
watch: {
// TODO: improve to update color selector at customForms change (doesn't work)
colorsStyleList: {
handler() {
this.iniateColorsStyleFields();
},
deep: true,
},
},
methods: {
...mapMutations("feature_type", ["RESET"]),
definePageType() {
if (this.$router.history.current.name === "ajouter-type-signalement") {
this.action = "create";
} else if (
this.$router.history.current.name === "editer-type-signalement"
) {
this.action = "edit";
} else if (
this.$router.history.current.name === "dupliquer-type-signalement"
) {
this.action = "duplicate";
}
},
addCustomForm(customForm) {
this.dataKey += 1; // * increment counter for key in v-for
let newCustomForm = {
dataKey: this.dataKey,
};
if (customForm) {
newCustomForm = { ...newCustomForm, ...customForm };
}
this.$store.commit("feature_type/ADD_CUSTOM_FORM", newCustomForm); // * create an object with the counter in store
},
iniateColorsStyleFields() {
const selected = this.colorsStyleList.find(
(el) => el.label === this.selected_colors_style
);
if (selected) {
let fields = [selected.options];
if (selected.field_type === "Liste de valeurs")
fields = selected.options.split(",");
this.form.colors_style.fields = fields.map((el) => {
return { label: el, value: "#000000" };
});
}
},
updateStore() {
this.$store.commit("feature_type/UPDATE_FORM", {
color: this.form.color,
title: this.form.title,
geom_type: this.form.geom_type,
colors_style: this.form.colors_style,
});
},
checkForm() {
if (this.form.title.value) {
this.form.title.errors = [];
return true;
} else if (
!this.form.title.errors.includes("Veuillez compléter ce champ.") // TODO : Gérer les autres champs
) {
this.form.title.errors.push("Veuillez compléter ce champ.");
document
.getElementById("errorlist")
.scrollIntoView({ block: "end", inline: "nearest" });
}
return false;
},
goBackToProject() {
this.$router.push({
name: "project_detail",
params: {
slug: this.project.slug,
message: "Le nouveau type de signalement a bien été importé",
},
});
},
postFeatureType() {
if (this.checkForm()) {
this.$store
.dispatch("feature_type/POST_FEATURE_TYPE")
.then(({ status }) => {
console.log(status);
if (status === 201) {
this.goBackToProject();
} else {
this.displayMessage(
"Une erreur est survenue lors de l'import du type de signalement"
);
}
});
}
},
postFeatures(feature_type_slug) {
this.$store
.dispatch("feature_type/POST_FEATURES_FROM_GEOJSON", {
slug: this.$route.params.slug,
feature_type_slug,
})
.then((response) => {
if (response) {
this.goBackToProject();
} else {
this.displayMessage(
"Une erreur est survenue lors de l'import de signalement."
);
}
});
},
async postFeatureTypeThenFeatures() {
if (this.checkForm()) {
await this.$store
.dispatch("feature_type/POST_FEATURE_TYPE")
.then(({ feature_type_slug }) => {
if (feature_type_slug) {
this.postFeatures(feature_type_slug);
}
});
}
},
displayMessage(message) {
this.error = message;
document
.getElementById("message")
.scrollIntoView({ block: "end", inline: "nearest" });
},
// * Methodes for geojson import * //
toNewFeatureType() {
this.$router.push({
name: "ajouter-type-signalement",
params: { geojson: this.jsonDict },
});
},
translateLabel(value) {
if (value == "LineString") {
return "linestring";
} else if (value == "Polygon") {
return "polygon";
}
return "point";
},
transformProperties(prop) {
const type = typeof prop;
const date = new Date(prop);
if (type === "boolean") {
return "boolean";
} else if (type === "number") {
return "integer";
} else if (type === "string") {
//* check if string is convertible to a number, then it should be a decimal
if (date instanceof Date && !isNaN(date.valueOf())) {
return "date";
} else if (!isNaN(parseFloat(prop))) {
return "decimal";
}
}
return "char"; //* string by default, most accepted type in database
},
importGeoJson() {
if (this.geojson.features && this.geojson.features.length) {
//* in order to get feature_type properties, the first feature is enough
const { properties, geometry } = this.geojson.features[0];
this.form.title.value = properties.feature_type;
this.form.geom_type.value = this.translateLabel(geometry.type);
this.updateStore(); //* register title & geom_type in store
//* loop properties to create a customForm for each of them
for (const [key, val] of Object.entries(properties)) {
//* check that the property is not a keyword from the backend or map style
// todo: add map style keywords
if (!this.reservedKeywords.includes(key)) {
const customForm = {
label: { value: key || "" },
name: { value: key || "" },
position: { value: this.dataKey }, // * use dataKey already incremented by addCustomForm
field_type: { value: this.transformProperties(val) }, // * guessed from the type
options: { value: [] }, // * not available in export
};
this.addCustomForm(customForm);
}
}
}
},
},
created() {
if (!this.project) {
this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
}
this.$store.commit(
"feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
this.$route.params.slug_type_signal
);
this.definePageType();
},
mounted() {
if (this.action === "edit" || this.action === "duplicate") {
for (const el in this.feature_type) {
// * find feature_type and fill form values
if (this.form[el]) this.form[el].value = this.feature_type[el];
}
if (this.action === "duplicate") {
//* replace original name with new default title
this.form.title.value += ` (Copie ${new Date()
.toLocaleString()
.slice(0, -3)
.replace(",", "")} )`;
this.updateStore(); // * initialize form in store in case this.form would not be modified
}
} else if (this.geojson) {
this.importGeoJson();
}
},
beforeDestroy() {
this.$store.commit("feature_type/EMPTY_FORM");
},
};
</script>
<style>
#add-field {
margin-top: 1em;
}
#id_style_container {
display: flex;
height: 100%;
}
#id_colors_selection {
display: flex;
flex-flow: row wrap;
align-items: center;
}
.color-input {
margin-left: 1em;
}
.color-input > label {
margin-right: 0.5em;
}
/* // * probleme avec le style récupéré, n'est jamais identique !???? */
#formsets {
margin-top: 1em !important;
}
</style>
\ No newline at end of file
<template>
<div v-frag>
<div v-frag v-if="permissions.can_view_project && project">
<div id="message" class="fullwidth">
<div v-if="tempMessage" class="ui positive message">
<!-- <i class="close icon"></i> -->
<!-- <div class="header">You are eligible for a reward</div> -->
<p><i class="check icon"></i> {{ tempMessage }}</p>
</div>
</div>
<div class="row">
<div class="four wide middle aligned column">
<img
class="ui small spaced image"
:src="
project.thumbnail.includes('default')
? require('@/assets/img/default.png')
: project.thumbnail
"
/>
<div class="ui hidden divider"></div>
<div class="ui basic teal label" data-tooltip="Membres">
<i class="user icon"></i>{{ project.nb_contributors }}
</div>
<div class="ui basic teal label" data-tooltip="Signalements">
<i class="map marker icon"></i>{{ project.nb_published_features }}
</div>
<div class="ui basic teal label" data-tooltip="Commentaires">
<i class="comment icon"></i
>{{ project.nb_published_features_comments }}
</div>
</div>
<div class="ten wide column">
<h1 class="ui header">
<div class="content">
{{ project.title }}
<div class="ui icon right floated compact buttons">
<!-- {% if permissions|lookup:'can_view_project' %} -->
<a
id="subscribe-button"
class="ui button button-hover-green"
data-tooltip="S'abonner au projet"
data-position="top center"
data-variation="mini"
@click="isModalOpen = true"
>
<i class="inverted grey envelope icon"></i>
</a>
<!-- {% endif %} {% if project and
permissions|lookup:'can_update_project' %} -->
<router-link
v-if="user"
:to="{ name: 'project_edit', params: { slug: project.slug } }"
class="ui button button-hover-orange"
data-tooltip="Modifier le projet"
data-position="top center"
data-variation="mini"
>
<i class="inverted grey pencil alternate icon"></i>
</router-link>
<!-- {% endif %} -->
</div>
<div class="ui hidden divider"></div>
<div class="sub header">
{{ project.description }}
<!-- {{ project.description | linebreaks }} -->
</div>
</div>
</h1>
</div>
</div>
<div class="row">
<div class="seven wide column">
<h3 class="ui header">Types de signalements</h3>
<!-- // todo : Create endpoints for feature_types -->
<div class="ui middle aligned divided list">
<div
v-for="(type, index) in feature_types"
:key="type.title + '-' + index"
class="item"
>
<div class="middle aligned content">
<router-link
:to="{
name: 'details-type-signalement',
params: { feature_type_slug: type.slug },
}"
>
<img
v-if="type.geom_type == 'point'"
class="list-image-type"
src="@/assets/img/marker.png"
/>
<img
v-if="type.geom_type == 'linestring'"
class="list-image-type"
src="@/assets/img/line.png"
/>
<img
v-if="type.geom_type == 'polygon'"
class="list-image-type"
src="@/assets/img/polygon.png"
/>
{{ type.title }}
</router-link>
<!-- {% if project and feature_types and
permissions|lookup:'can_create_feature' %} -->
<!-- // todo: add permissions.can_create_feature and type.is_editable -->
<!-- v-if="
project &&
permissions.can_create_feature &&
type.is_editable
" -->
<router-link
:to="{
name: 'ajouter-signalement',
params: { slug_type_signal: type.slug },
}"
v-if="project && permissions.can_create_feature"
class="
ui
compact
small
icon
right
floated
button button-hover-green
"
data-tooltip="Ajouter un signalement"
data-position="left center"
data-variation="mini"
><!-- // todo : adapt -->
<i class="ui plus icon"></i>
</router-link>
<router-link
:to="{
name: 'dupliquer-type-signalement',
params: { slug_type_signal: type.slug },
}"
v-if="project && permissions.can_create_feature"
class="
ui
compact
small
icon
right
floated
button button-hover-green
"
data-tooltip="Dupliquer un type de signalement"
data-position="left center"
data-variation="mini"
><!-- // todo : adapt -->
<i class="inverted grey copy alternate icon"></i>
</router-link>
<router-link
:to="{
name: 'editer-type-signalement',
params: { slug_type_signal: type.slug },
}"
v-if="project"
class="
ui
compact
small
icon
right
floated
button button-hover-green
"
data-tooltip="Éditer le type de signalement"
data-position="left center"
data-variation="mini"
>
<i class="inverted grey pencil alternate icon"></i>
</router-link>
<!-- {% endif %} -->
</div>
</div>
<div v-if="feature_types.length === 0">
<i> Le projet ne contient pas encore de type de signalements. </i>
</div>
</div>
<!-- // todo: gérer permissions: {% if project and permissions|lookup:'can_update_project' %} -->
<div class="nouveau-type-signalement">
<router-link
:to="{
name: 'ajouter-type-signalement',
params: { slug: project.slug },
}"
class="ui compact basic button button-hover-green"
>
<i class="ui plus icon"></i>Créer un nouveau type de signalement
</router-link>
</div>
<div class="nouveau-type-signalement">
<div class="ui compact basic button button-hover-green">
<div onclick="document.getElementById('json_file').click()">
<label class="ui" for="json_file">
<i class="ui plus icon"></i>
<span class="label"
>Créer un nouveau type de signalement à partir d'un
GeoJSON</span
>
</label>
<input
type="file"
accept="application/json, .json, .geojson"
style="display: none"
name="json_file"
id="json_file"
@change="onFileChange"
/>
</div>
</div>
<br />
<div id="button-import" v-if="filenameToImport.size > 0">
<button
:disabled="filenameToImport.size == 0"
@click="toNewFeatureType"
class="ui fluid teal icon button"
>
<i class="upload icon"></i> Lancer l'import avec le fichier
{{ filenameToImport.name }}
</button>
</div>
</div>
</div>
<div class="seven wide column">
<router-link
:to="{ name: 'liste-signalements', params: { slug: project.slug } }"
class="item"
>
<div id="map"></div>
</router-link>
</div>
</div>
<div class="row">
<div class="fourteen wide column">
<div class="ui two stackable cards">
<div class="red card">
<div class="content">
<div class="center aligned header">Derniers signalements</div>
<div class="center aligned description">
<div class="ui relaxed list">
<div
v-for="(item, index) in last_features"
:key="item.title + index"
class="item"
>
<div class="content">
<div>
<router-link
:to="{
name: 'details-signalement',
params: {
slug: project.slug,
slug_type_signal: item.feature_type.slug,
slug_signal: item.feature_id,
},
}"
>{{ item.title }}</router-link
>
</div>
<div class="description">
<i
>[{{ item.created_on | setDate
}}<span v-if="user && item.display_creator"
>, par {{ item.display_creator }}
</span>
]</i
>
</div>
</div>
</div>
<i v-if="last_features.length === 0"
>Aucun signalement pour le moment.</i
>
</div>
</div>
</div>
</div>
<div class="orange card">
<div class="content">
<div class="center aligned header">Derniers commentaires</div>
<div class="center aligned description">
<div class="ui relaxed list">
<div
v-for="(item, index) in last_comments"
:key="'comment ' + index"
class="item"
>
<div class="content">
<div>
<router-link :to="item.related_feature.feature_url"
>"{{ item.comment }}"</router-link
>
</div>
<div class="description">
<i
>[ {{ item.created_on
}}<span v-if="user && item.display_author"
>, par {{ item.display_author }}
</span>
]</i
>
</div>
</div>
</div>
<i v-if="!last_comments || last_comments.length === 0"
>Aucun commentaire pour le moment.</i
>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="fourteen wide column">
<div class="ui grey segment">
<h3 class="ui header">Paramètres du projet</h3>
<div class="ui five stackable cards">
<div class="card">
<div class="center aligned content">
<h4 class="ui center aligned icon header">
<i class="disabled grey archive icon"></i>
<div class="content">Délai avant archivage automatique</div>
</h4>
</div>
<div class="center aligned extra content">
<!-- {{ project.archive_feature|default_if_none:"0" }} jours -->
{{ project.archive_feature }} jours
</div>
</div>
<div class="card">
<div class="content">
<h4 class="ui center aligned icon header">
<i class="disabled grey trash alternate icon"></i>
<div class="content">
Délai avant suppression automatique
</div>
</h4>
</div>
<div class="center aligned extra content">
<!-- {{ project.delete_feature|default_if_none:"0" }} jours -->
{{ project.delete_feature }} jours
</div>
</div>
<div class="card">
<div class="content">
<h4 class="ui center aligned icon header">
<i class="disabled grey eye icon"></i>
<div class="content">
Visibilité des signalements publiés
</div>
</h4>
</div>
<div class="center aligned extra content">
{{ project.access_level_pub_feature }}
</div>
</div>
<div class="card">
<div class="content">
<h4 class="ui center aligned icon header">
<i class="disabled grey eye icon"></i>
<div class="content">
Visibilité des signalements archivés
</div>
</h4>
</div>
<div class="center aligned extra content">
{{ project.access_level_arch_feature }}
</div>
</div>
<div class="card">
<div class="content">
<h4 class="ui center aligned icon header">
<i class="disabled grey cogs icon"></i>
<div class="content">Modération</div>
</h4>
</div>
<div class="center aligned extra content">
{{ project.moderation ? "Oui" : "Non" }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- {% else %} -->
<span v-else-if="!permissions.can_view_project">
<i class="icon exclamation triangle"></i>
<span
>Vous ne disposez pas des droits nécessaires pour consulter ce
projet.</span
>
</span>
<!-- {% endif %} -->
<div
v-if="isModalOpen"
class="ui dimmer modals page transition visible active"
style="display: flex !important"
>
<div
:class="[
'ui mini modal subscription',
{ 'transition visible active': isModalOpen },
]"
>
<i @click="isModalOpen = false" class="close icon"></i>
<div class="ui icon header">
<i class="envelope icon"></i>
Notifications du projet
</div>
<div class="content">
<!-- {% if is_suscriber %} -->
<!-- <form
action="{% url 'geocontrib:subscription' slug=project.slug action='annuler' %}"
method="GET"
> -->
<button v-if="is_suscriber" class="ui red compact fluid button">
Se désabonner de ce projet
</button>
<!-- </form> -->
<!-- {% else %} -->
<!-- <form
action="{% url 'geocontrib:subscription' slug=project.slug action='ajouter' %}"
method="GET"
> -->
<button
v-else
@click="subsribeProject"
class="ui green compact fluid button"
>
<!-- <button type="submit" class="ui green compact fluid button"> -->
S'abonner à ce projet
</button>
<!-- </form> -->
<!-- {% endif %} -->
</div>
<!-- </div> -->
</div>
</div>
</div>
</template>
<script>
// import axios from 'axios';
import frag from "vue-frag";
import { mapGetters, mapState } from "vuex";
export default {
name: "Project_details",
props: ["message"],
directives: {
frag,
},
filters: {
setDate: function (value) {
let date = new Date(value);
let d = date.toLocaleDateString("fr", {
year: "2-digit",
month: "numeric",
day: "numeric",
});
return d;
},
},
data() {
return {
geojsonImport: [],
filenameToImport: { name: "", size: 0 },
slug: this.$route.params.slug,
isModalOpen: false,
is_suscriber: false,
permissions: {
can_view_project: true,
can_create_feature: true,
},
tempMessage: null,
};
},
computed: {
...mapGetters(["project"]),
...mapState("feature_type", ["feature_types"]),
...mapState(["last_comments", "user"]),
BASE_URL: () => process.env.VUE_APP_BASE_URL,
last_features: function () {
// * limit to last five element of array (looks sorted chronologically, but not sure...)
return this.$store.state.feature.features.slice(-5);
},
},
methods: {
toNewFeatureType() {
this.$router.push({
name: "ajouter-type-signalement",
params: {
geojson: this.geojsonImport,
filenameToImport: this.filenameToImport,
},
});
},
onFileChange(e) {
var files = e.target.files || e.dataTransfer.files;
if (!files.length) return;
this.filenameToImport = files[0];
// TODO : VALIDATION IF FILE IS JSON
if (this.filenameToImport.size > 0) {
const fr = new FileReader();
fr.onload = (e) => {
this.geojsonImport = JSON.parse(e.target.result);
};
fr.readAsText(this.filenameToImport);
//* stock filename to import features afterward
this.$store.commit(
"feature_type/SET_FILE_NAME_TO_IMPORT",
this.filenameToImport
);
}
},
subsribeProject() {
console.log("Subsribe to project");
},
},
created() {
this.$store.dispatch("GET_PROJECT_INFO", this.slug);
},
mounted() {
if (this.project) {
this.$store.dispatch("map/INITIATE_MAP");
}
if (this.message) {
this.tempMessage = this.message;
document
.getElementById("message")
.scrollIntoView({ block: "end", inline: "nearest" });
setTimeout(() => {
this.tempMessage = null;
}, 5000);
}
},
};
</script>
<style>
@import "../../assets/resources/semantic-ui-2.4.2/semantic.min.css";
#map {
width: 100%;
height: 100%;
min-height: 250px;
}
.list-image-type {
margin-right: 5px;
height: 25px;
vertical-align: bottom;
}
/* // ! missing style in semantic.min.css, je ne comprends pas comment... */
.ui.right.floated.button {
float: right;
margin: 0 0 0 1em;
}
.nouveau-type-signalement {
padding-top: 1em;
}
#button-import {
padding-top: 0.5em;
}
.fullwidth {
width: 100%;
}
</style>
\ No newline at end of file
<template>
<div class="fourteen wide column">
<form id="form-project-edit" class="ui form">
<h1>
<span v-if="action === 'edit'"
>Édition du projet "{{ form.title }}"</span
>
<span v-else-if="action === 'create'">Création d'un projet</span>
</h1>
<div class="ui horizontal divider">INFORMATIONS</div>
<div class="two fields">
<div class="required field">
<label for="title">Titre</label>
<!-- <small>{{ form.title.help_text }}</small
> --><!-- | safe // ? utile ? -->
<input
type="text"
required
maxlength="128"
name="title"
id="title"
v-model="form.title"
/>
<!-- {{ form.title.errors }} // ? des erreurs possibles ? -->
</div>
<div class="field">
<label>Illustration du projet</label>
<img
v-if="form.thumbnail"
class="ui small image"
id="form-input-file-logo"
:src="form.thumbnail"
/>
<label
@click.prevent="selectImg"
class="ui icon button"
for="thumbnail"
><!-- // todo : send image to the backend and display it after -->
<i class="file icon"></i>
<!-- // ? [...form.thumbnail.split("/")].pop() -->
<span class="label">{{
form.thumbnail
? form.thumbnail_name
: "Sélectionner une image ..."
}}</span>
</label>
<input
@change="processImgData"
class="file-selection"
type="file"
accept="image/jpeg, image/png"
style="display: none"
name="thumbnail"
id="thumbnail"
/>
<!-- {{ form.thumbnail.errors }} -->
</div>
</div>
<div class="field">
<label for="description">Description</label>
<textarea
v-model="form.description"
name="description"
rows="5"
></textarea>
<!-- {{ form.description.errors }} -->
</div>
<div class="ui horizontal divider">PARAMÈTRES</div>
<div class="four fields">
<div class="field">
<label for="archive_feature">Délai avant archivage</label>
<div class="ui right labeled input">
<input
type="number"
min="0"
style="padding: 1px 2px"
name="archive_feature"
id="archive_feature"
v-model="form.archive_feature"
/>
<div class="ui label">jour(s)</div>
</div>
<!-- {{ form.archive_feature.errors }} -->
</div>
<div class="field">
<label for="delete_feature">Délai avant suppression</label>
<div class="ui right labeled input">
<input
type="number"
min="0"
style="padding: 1px 2px"
name="delete_feature"
id="delete_feature"
v-model="form.delete_feature"
/>
<div class="ui label">jour(s)</div>
</div>
<!-- {{ form.delete_feature.errors }} -->
</div>
<div class="required field">
<label for="access_level_pub_feature"
>Visibilité des signalements publiés</label
>
<Dropdown
:options="access_level_pub_feature_choices"
:selected="form.access_level_pub_feature"
:selection.sync="form.access_level_pub_feature"
/>
</div>
<div class="required field">
<label for="access_level_arch_feature">
Visibilité des signalements archivés
</label>
<Dropdown
:options="access_level_arch_feature_choices"
:selected="form.access_level_arch_feature"
:selection.sync="form.access_level_arch_feature"
/>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input
type="checkbox"
:checked="form.moderation"
name="moderation"
id="moderation"
/>
<label for="moderation">Modération</label>
</div>
<!-- {{ form.moderation.errors }} -->
</div>
<div class="field">
<div class="ui checkbox">
<input
type="checkbox"
:checked="form.is_project_type"
name="is_project_type"
id="is_project_type"
/>
<label for="is_project_type">Est un projet type</label>
</div>
<!-- {{ form.is_project_type.errors }} -->
</div>
<div class="ui divider"></div>
<button @click="postForm" type="button" class="ui teal icon button">
<i class="white save icon"></i> Enregistrer les changements
</button>
</form>
</div>
</template>
<script>
import Dropdown from "@/components/Dropdown.vue";
import { mapGetters } from "vuex";
export default {
name: "Project_edit",
components: {
Dropdown,
},
data() {
return {
action: "create",
access_level_pub_feature_choices: [
"Utilisateur anonyme",
"Utilisateur connecté",
"Contributeur",
],
access_level_arch_feature_choices: [
"Utilisateur anonyme",
"Utilisateur connecté",
"Contributeur",
],
form: {
title: "",
slug: "",
created_on: "",
updated_on: "",
description: "",
moderation: false,
thumbnail: "https://via.placeholder.com/150", // todo : utiliser l'image par défaut
thumbnail_name: "", // todo: delete after getting image in jpg or png instead of data64 (require post to django)
creator: null,
access_level_pub_feature: "",
access_level_arch_feature: "",
archive_feature: 0,
delete_feature: 0,
nb_features: 0,
nb_published_features: 0,
nb_comments: 0,
nb_published_features_comments: 0,
nb_contributors: 0,
is_project_type: false,
},
};
},
computed: {
...mapGetters(["project"]),
},
/*
watch: {
project(newValue, oldValue) {
console.log("watch", newValue);
if (oldValue !== newValue) {
this.form = newValue;
}
},
}, */
methods: {
definePageType() {
if (this.$router.history.current.name === "project_create") {
this.action = "create";
} else if (this.$router.history.current.name === "project_edit") {
this.action = "edit";
} else if (this.$router.history.current.name === "project_create_from") {
this.action = "create_from";
}
},
truncate(n, len) {
var ext = n.substring(n.lastIndexOf(".") + 1, n.length).toLowerCase();
var filename = n.replace("." + ext, "");
if (filename.length <= len) {
return n;
}
filename = filename.substr(0, len) + (n.length > len ? "[...]" : "");
return filename + "." + ext;
},
processImgData(e) {
// * read image file
const file = e.target.files[0];
if (file) {
this.form.thumbnail_name = file.name;
}
let reader = new FileReader();
let _this = this;
reader.onload = function (e) {
_this.form.thumbnail = e.target.result;
};
reader.readAsDataURL(file);
// todo : send file to the back (?) (not in base64)
},
selectImg() {
// * call click on hidden input field
document.getElementsByClassName("file-selection")[0].click();
},
goBack() {
const routerHistory = this.$router.options.routerHistory;
this.$router.push(routerHistory[routerHistory.length - 1] || "/");
},
postForm() {
const data = JSON.stringify(this.project);
console.log("POST this data : ", data);
},
},
created() {
this.definePageType();
if (this.action !== "create") {
if (!this.project) {
this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
this.form = this.project;
} else {
this.form = this.project;
}
}
},
};
</script>
<style media="screen">
#form-input-file-logo {
margin-left: auto;
margin-right: auto;
}
.close.icon:hover {
cursor: pointer;
}
</style>
\ No newline at end of file
<template>
<div class="fourteen wide column">
<h1 class="ui header">Administration des fonds cartographiques</h1>
<form
id="form-layers"
action="."
method="post"
enctype="multipart/form-data"
class="ui form"
>
<!-- {{ formset.management_form }} -->
<div class="ui buttons">
<a
class="ui compact small icon left floated button green"
data-variation="mini"
@click="addBasemap"
>
<i class="ui plus icon"></i>
<span>Créer un fond cartographique</span>
</a>
</div>
<div class="ui">
<ProjectMappingBasemap
v-for="basemap in basemaps"
:key="basemap.dataKey"
:basemap="basemap"
/>
</div>
<button
@click="saveChanges"
type="button"
class="ui teal icon floated button"
>
<i class="white save icon"></i> Enregistrer les changements
</button>
</form>
</div>
</template>
<script>
import Project_mapping_basemap from "@/components/project/project_mapping_basemap.vue";
import { mapState, mapGetters } from "vuex";
export default {
name: "Project_mapping",
components: {
ProjectMappingBasemap: Project_mapping_basemap,
},
computed: {
...mapState("map", ["basemaps"]),
...mapGetters("map", ["basemapMaxId"]),
},
methods: {
addBasemap() {
this.$store.commit("map/CREATE_BASEMAP", this.basemapMaxId + 1);
},
saveChanges() {
this.$store.dispatch("map/SAVE_BASEMAPS");
},
},
created() {
if (!this.$store.getters.project) {
this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
}
},
};
</script>
\ No newline at end of file
<template>
<div class="fourteen wide column">
<h1 class="ui header">Gérer les membres</h1>
<form
id="form-members"
action="."
method="post"
enctype="multipart/form-data"
class="ui form"
>
<!-- {{ formset.non_form_errors }} -->
<table class="ui red table">
<thead>
<tr>
<th>Membre</th>
<th>Niveau d'autorisation</th>
</tr>
</thead>
<tbody>
<div v-frag v-for="member in projectMembers" :key="member.username">
<tr>
<td>
{{ member.last_name }} {{ member.first_name }}<br /><i>{{
member.username
}}</i>
</td>
<td>
<div class="required field">
<Dropdown
:options="levelOptions"
:selected="member.userLevel"
:selection.sync="member.userLevel"
:search="true"
/>
</div>
</td>
</tr>
</div>
</tbody>
</table>
<div class="ui divider"></div>
<button
@click="validateMembers"
type="button"
class="ui teal icon button"
>
<i class="white save icon"></i> Enregistrer les changements
</button>
</form>
</div>
</template>
<script>
import axios from "axios";
import frag from "vue-frag";
import { mapGetters } from "vuex";
import Dropdown from "@/components/Dropdown.vue";
export default {
name: "Project_members",
directives: {
frag,
},
components: {
Dropdown,
},
computed: {
...mapGetters(["project"]),
},
data() {
return {
projectMembers: [],
levelOptions: [
"Utilisateur connecté",
"Contributeur",
"Modérateur",
"Administrateur projet",
],
};
},
methods: {
validateMembers() {
const data = {
slug: this.project.slug,
data: this.projectMembers,
};
console.log("validateMembers", data);
/* axios
.post(`${DJANGO_API_BASE}projet/${payload.slug}/utilisateurs/`, payload.data)
.then((response) => {
const user = response.data.user;
})
.catch(() => {
router.push({ name: "login" });
}); */
},
async fetchMembers() {
return axios
.get(
`${process.env.VUE_APP_DJANGO_API_BASE}projet/${this.$route.params.slug}/utilisateurs`
)
.then((response) => response.data.members)
.catch((error) => {
throw error;
});
},
async populateMembers() {
await this.fetchMembers().then((members) => {
this.projectMembers = members.map((el) => {
return {
userLevel: el.userLevel ? el.userLevel : this.levelOptions[0],
...el,
};
});
});
},
},
created() {
if (!this.project) {
this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
}
this.populateMembers();
},
};
</script>
\ No newline at end of file
<template>
<div class="row">
<div class="seven wide column">
<h3 class="ui header">
Créer un projet à partir d'un modèle disponible:
</h3>
<div class="ui divided items">
<div v-for="project in project_types" :key="project.slug" class="item">
<div class="ui tiny image">
<img :src="project.thumbnail" />
</div>
<div class="middle aligned content">
<div class="description">
<router-link
:to="{
name: 'project_create_from',
params: {
slug: project.title,
},
}"
>{{ project.title }}</router-link
>
<p>{{ project.description }}</p>
<strong v-if="project.moderation">Projet modéré</strong>
<strong v-else>Projet non modéré</strong>
</div>
<div class="meta">
<span data-tooltip="Délai avant archivage">
{{ project.archive_feature }}&nbsp;<i class="box icon"></i>
</span>
<span data-tooltip="Délai avant suppression">
{{ project.archive_feature }}&nbsp;<i
class="trash alternate icon"
></i>
</span>
<span data-tooltip="Date de création">
{{ project.created_on }}&nbsp;<i class="calendar icon"></i>
</span>
</div>
<div class="meta">
<span data-tooltip="Visibilité des signalement publiés">
{{ project.access_level_pub_feature }}&nbsp;<i
class="eye icon"
></i>
</span>
<span data-tooltip="Visibilité des signalement archivés">
{{ project.access_level_arch_feature }}&nbsp;<i
class="archive icon"
></i>
</span>
</div>
</div>
</div>
<span v-if="!project_types || project_types.length === 0"
>Aucun projet type n'est défini.</span
>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "Project_type_list",
computed: {
...mapGetters(["project_types"]),
},
};
</script>
\ No newline at end of file
<template>
<div>
<div class="row">
<div class="fourteen wide column">
<img
class="ui centered small image"
src="@/assets/img/logo-neogeo-circle.png"
/>
<h2 class="ui center aligned icon header">
<div class="content">
{{ APPLICATION_NAME }}
<div class="sub header">{{ APPLICATION_ABSTRACT }}</div>
</div>
</h2>
</div>
</div>
<div class="row">
<div class="six wide column">
<h3 class="ui horizontal divider header">CONNEXION</h3>
<div v-if="form.errors" class="ui warning message">
<div class="header">
Les informations d'identification sont incorrectes.
</div>
NB: Seuls les comptes actifs peuvent se connecter.
</div>
<form class="ui form" role="form" type="post" @submit.prevent="login">
<div class="ui stacked secondary segment">
<div class="six field required">
<div class="ui left icon input">
<i class="user icon"></i>
<input
v-model="username_value"
type="text"
name="username"
placeholder="Utilisateur"
/>
</div>
</div>
<div class="six field required">
<div class="ui left icon input">
<i class="lock icon"></i>
<input
v-model="password_value"
type="password"
name="password"
placeholder="Mot de passe"
/>
</div>
</div>
<button class="ui fluid large teal submit button" type="submit">
Login
</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {
username_value: null,
password_value: null,
logged: false,
form: {
errors: null,
},
};
},
computed: {
LOGO_PATH: () => process.env.VUE_APP_LOGO_PATH,
APPLICATION_NAME: () => process.env.VUE_APP_APPLICATION_NAME,
APPLICATION_ABSTRACT: () => process.env.VUE_APP_APPLICATION_ABSTRACT,
},
methods: {
login() {
this.$store.dispatch("LOGIN", {
username: this.username_value,
password: this.password_value,
});
},
},
};
</script>
\ No newline at end of file
const webpack = require('webpack') const webpack = require('webpack');
const fs = require('fs') const fs = require('fs');
const packageJson = fs.readFileSync('./package.json') const packageJson = fs.readFileSync('./package.json');
const version = JSON.parse(packageJson).version || 0 const version = JSON.parse(packageJson).version || 0;
module.exports = { module.exports = {
configureWebpack: { publicPath: '/geocontrib/',
plugins: [ devServer: {
new webpack.DefinePlugin({ proxy: {
'process.env': { '^/api': {
PACKAGE_VERSION: '"' + version + '"' target: 'https://geocontrib.dev.neogeo.fr/api',
} ws: true,
}) changeOrigin: true
] }
}
},
pwa: {
workboxPluginMode: 'InjectManifest',
workboxOptions: {
swSrc: 'src/service-worker.js',
exclude: [
/\.map$/,
/config\/config.*\.json$/,
/manifest\.json$/
],
}, },
// the rest of your original module.exports code goes here iconPaths: {
} faviconSVG: null,
\ No newline at end of file favicon32: null,
favicon16: null,
appleTouchIcon: null,
maskIcon: null,
msTileImage: null,
},
themeColor: '#1da025'
},
configureWebpack: {
devtool: 'source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': {
PACKAGE_VERSION: '"' + version + '"'
}
})
]
},
transpileDependencies: [
// Add dependencies that use modern JavaScript syntax, based on encountered errors
'ol',
'color-rgba',
'color-parse',
'@sentry/browser',
'@sentry/core',
'@sentry/vue',
'@sentry-internal'
]
};
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
}
};
\ No newline at end of file