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"
action="{% url 'geocontrib:add_comment' slug=feature.project.slug feature_type_slug=feature.feature_type.slug feature_id=feature.feature_id%}"
class="ui form"
method="POST"
enctype="multipart/form-data"
>
<!-- {% 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.title === 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() {
if (!this.project) {
this.$store.commit("SET_PROJECT_SLUG", 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="$router.history.current.name === 'editer-signalement'">
Mise à jour du signalement "{{ feature.title }}"
</h1>
<h1 v-else-if="$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.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.title === this.$route.params.slug_signal
);
},
selected_status: {
// getter
get() {
return this.form.status.value;
},
// setter
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: {
// getter
get() {
return this.extra_form_with_values.find(
(el) => el.field_type === "list"
).value;
},
// setter
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.commit("SET_PROJECT_SLUG", 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") {
console.log(this.feature);
for (let el in this.feature) {
console.log(el);
console.log(this.feature[el]);
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="selected_type"
:selection.sync="selected_type"
/>
</div>
<div class="field wide four column">
<label>Statut</label>
<Dropdown
:options="form.status.choices"
:selected="selected_status"
:selection.sync="selected_status"
/>
</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" :value="request.GET.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 -->
<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" class="ui tab" 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>
{% if user.is_authenticated %}
<th>Auteur</th>
{% endif %}
</tr>
</thead>
<tbody>
<tr v-for="feature in features" :key="feature.title">
<td :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>
<a
href="{% url 'geocontrib:feature_type_detail' slug=project.slug feature_type_slug=feature.feature_type.slug %}"
>
{{ feature.feature_type.title }}
</a>
</td>
<td>
<a
href="{% url 'geocontrib:feature_detail' slug=project.slug feature_type_slug=feature.feature_type.slug feature_id=feature.feature_id %}"
>{{ feature.title }}</a
>
</td>
<td :data-order="feature.updated_on">
<!-- |date:'Ymd' -->
{{ feature.updated_on }}
</td>
{% if user.is_authenticated %}
<td>
{{ feature.display_creator }}
</td>
{% endif %}
</tr>
{% endfor %}
</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 {
request: {
// ? D'où ça doit venir ?
GET: {
feature_type: null,
status: null,
title: null,
type: null,
},
},
showMap: true,
showAddSignal: false,
form: {
type: {
value: null,
choices: [],
},
status: {
value: null,
choices: [
"Brouillon",
"En attente de publication",
"Publié",
"Archivé",
],
},
},
};
},
computed: {
...mapGetters(["project"]),
...mapState(["status_choices"]),
...mapState("feature", ["features"]),
...mapState("feature_type", ["feature_types"]),
selected_type: {
// getter
get() {
return this.form.type.value;
},
// setter
set(newValue) {
this.form.type.value = newValue;
//this.updateStore();
},
},
selected_status: {
// getter
get() {
return this.form.status.value;
},
// setter
set(newValue) {
this.form.status.value = newValue;
//this.updateStore();
},
},
},
created() {
if (!this.project) {
this.$store.commit("SET_PROJECT_SLUG", this.$route.params.slug);
}
},
mounted() {
this.$store.dispatch("map/INITIATE_MAP");
this.form.type.choices = new Set(
...[this.features.map((el) => el.feature_type.title)]
);
},
// 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 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 }]">
<form
id="form-import-features"
action="{% url 'geocontrib:import_from_geojson' slug=project.slug feature_type_slug=feature_type.slug %}"
method="post"
enctype="multipart/form-data"
class="ui form"
>
<div class="field">
<label
@click="importGeoJson"
class="ui icon button"
for="json_file"
>
<i class="file icon"></i>
<span class="label">Sélectionner un fichier GeoJSON ...</span>
</label>
<input
type="file"
accept="application/json, .json, .geojson"
style="display: none"
name="json_file"
id="json_file"
/>
</div>
// todo import file
<button type="submit" class="ui fluid teal icon button">
<i class="upload icon"></i> Lancer l'import
</button>
</form>
</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>
<a
class="ui fluid teal icon button"
href="{% url 'api:project-export' slug=project.slug feature_type_slug=feature_type.slug %}"
>
<i class="download icon"></i> Exporter
</a>
// 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>
<a
href="{% url 'geocontrib:feature_detail' slug=project.slug feature_type_slug=feature.feature_type.slug feature_id=feature.feature_id %}"
>
{{ feature.title }}
</a>
<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.title },
}"
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";
export default {
name: "Feature_type_detail",
computed: {
...mapGetters(["project"]),
...mapState("feature", ["features"]),
...mapState("feature_type", ["feature_types"]),
structure: function () {
// * je ne sais pas pourquoi ça s'appelle structure
return this.feature_types.find(
(el) => el.title === this.$route.params.feature_type_slug
);
},
},
data() {
return {
showImport: false,
showExport: true,
};
},
methods: {
toggleImport() {
console.log("toggleImport");
},
importGeoJson() {
console.log("Comment faire pour importer le geoJson ?");
},
},
created() {
if (!this.project) {
this.$store.commit("SET_PROJECT_SLUG", this.$route.params.slug);
}
},
};
</script>
\ No newline at end of file
<template>
<div v-frag>
<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="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 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="form.geom_type.field.choices"
:selected="selected_geom_type"
:selection.sync="selected_geom_type"
/>
<!-- {{ 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'">
// todo : Fetch customForm associated to this feature_type //
</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="postForm">
<i class="white save icon"></i>
{{ action === "create" ? "Créer" : "Sauvegarder" }} le type de
signalement
</button>
// TODO: Add check script for form & other scripts //
</form>
</div>
</div>
</template>
<script>
import frag from "vue-frag";
import { mapGetters, mapState } 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,
},
computed: {
...mapGetters(["project"]),
...mapState("feature_type", ["customForms", "colorsStyleList"]),
...mapGetters("feature_type", ["feature_type"]),
/* form() {
return this.action === "create" ? this.empty_form : this.empty_form;
}, */
selected_geom_type: {
// getter
get() {
return this.form.geom_type.value;
},
// setter
set(newValue) {
this.form.geom_type.value = newValue;
this.updateStore();
},
},
selected_colors_style: {
// getter
get() {
return this.form.colors_style.value;
},
// setter
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(/* newVal, oldVal */) {
this.iniateColorsStyleFields();
},
deep: true,
},
},
data() {
return {
action: "create",
dataKey: 0,
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",
},
},
};
},
methods: {
definePageType() {
console.log(this.$router.history.current.name);
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() {
this.dataKey += 1; // * increment counter for key in v-for
this.$store.commit("feature_type/ADD_CUSTOM_FORM", this.dataKey); // * 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" };
});
}
},
postForm() {
if (this.form.title.value) {
this.form.title.errors = [];
this.$store.dispatch("feature_type/POST_FEATURE_TYPE");
} 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.");
}
},
updateStore() {
this.$store.commit("feature_type/UPDATE_FORM", {
color: this.form.color.value,
title: this.form.title.value,
geom_type: this.form.geom_type.value,
colors_style: this.form.colors_style,
});
},
},
created() {
if (!this.project) {
this.$store.commit("SET_PROJECT_SLUG", 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 wasn't modified
}
},
/* checkform() {
let form_idx = $('#id_form-TOTAL_FORMS').val();
for (var i=0; i <= form_idx;i++ ){
let id_form_options = 'id_form-' + i + '-options';
let id_form_options_elem = document.getElementById(id_form_options);
if (typeof id_form_options_elem !== null && id_form_options_elem !== 'undefined'){
let id_form_options_val = document.getElementById(id_form_options);
if (id_form_options_val != undefined && id_form_options_val != null){
let new_id_form_options_val = document.getElementById(id_form_options).value.replace(/^,|,$/g, '');
document.getElementById(id_form_options).value = new_id_form_options_val;
}
}
}
} */
};
</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 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="$store.state.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.title },
}"
href="{% url 'geocontrib:feature_type_detail' slug=project.slug 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.title },
}"
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.title },
}"
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.title },
}"
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' %} -->
<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="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">
<!-- {% for item in last_features %} -->
<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_type_signal: item.feature_type.title,
slug_signal: item.title,
},
}"
>{{ item.title }}</router-link
>
</div>
<div class="description">
<i
>[{{ item.created_on }}
{{
$store.state.user
? `, par
${item.display_creator}`
: ""
}}
]</i
>
</div>
</div>
</div>
<!-- {% empty %} -->
<i v-if="last_features.length === 0"
>Aucun signalement pour le moment.</i
>
<!-- {% endfor %} -->
</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 in last_comments"
:key="item.id"
class="item"
>
// ? item.id exists ?
<div class="content">
<div>
<!-- // todo : adapt -->
<a href="item.related_feature.feature_url"
>"{{ item.comment }}"</a
>
</div>
<div class="description">
<i
>[ {{ item.created_on }}
<span v-if="user.is_authenticated">
, par {{ item.display_user }}
</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>
<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 frag from "vue-frag";
import { mapGetters, mapState } from "vuex";
export default {
name: "Project_details",
directives: {
frag,
},
data() {
return {
slug: this.$route.params.slug,
isModalOpen: false,
is_suscriber: false,
last_comments: [],
permissions: {
can_view_project: true,
can_create_feature: true,
},
};
},
computed: {
...mapGetters(["project"]),
...mapState("feature_type", ["feature_types"]),
BASE_URL: () => process.env.VUE_APP_BASE_URL,
last_features: function () {
return this.$store.state.feature.features;
},
},
created() {
this.$store.commit("SET_PROJECT_SLUG", this.slug);
},
mounted() {
if (this.project) {
this.$store.dispatch("map/INITIATE_MAP");
}
},
methods: {
subsribeProject() {
console.log("Subsribe to project");
},
},
};
</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;
}
</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 === 'update'"
>Édition du projet "{{ form.title }}"</span
><!-- // todo : [pour UPDATE] récupérer project à éditer -->
<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.prevent="postForm" 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";
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: "Vuetification (Copie-23/07/2021 09:19)",
slug: "6-vuetification-copie-23072021-0919",
created_on: "23/07/2021",
updated_on: "29/07/2021",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
moderation: true,
thumbnail: "http://localhost:8000/media/user_1/albinoscom.jpg",
thumbnail_name: "albinoscom", // todo: delete after getting image in jpg or png instead of data64 (require post to django)
creator: 1,
access_level_pub_feature: "Utilisateur anonyme",
access_level_arch_feature: "Utilisateur anonyme",
archive_feature: 0,
delete_feature: 0,
nb_features: 2,
nb_published_features: 1,
nb_comments: 0,
nb_published_features_comments: 0,
nb_contributors: 2,
is_project_type: false,
},
};
},
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.project && this.action !== "create") {
this.$store.commit("SET_PROJECT_SLUG", this.$route.params.slug);
}
},
};
</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"
:data-add-form="formset.prefix + '-ADD'"
>
<i class="ui plus icon"></i>
<span>Créer un fond cartographique</span>
</a>
</div>
<div class="ui" :data-segments="formset.prefix + '-SEGMENTS'">
<!-- {% for form in formset.forms %} {% include
'geocontrib/project/project_mapping_basemap.html' with formset=formset
form=form is_empty=False %} {% endfor %} -->
<ProjectMappingBasemap
v-for="form in formset.forms"
:key="form.id"
:form="formset.empty_form"
/>
// * need form datas from back
<fieldset disabled="disabled" style="display: none">
<div
class="formset_hidden"
:data-empty-form="formset.prefix + '-EMPTY'"
style="display: none"
>
<ProjectMappingBasemap :form="formset.empty_form" />
<!-- {% include 'geocontrib/project/project_mapping_basemap.html' with
formset=formset form=formset.empty_form is_empty=True %} -->
</div>
</fieldset>
</div>
<button type="submit" class="ui teal icon floated button">
<i class="white save icon"></i> Enregistrer les changements
</button>
</form>
</div>
</template>
<script>
// todo: add script |-> import (or not ?) from "@/assets/js/nested_formset_handlers.js"
//import Project_mapping_basemap from "@/components/project/project_mapping_basemap.vue"
import Project_mapping_basemap from "@/components/project/project_mapping_basemap.vue";
export default {
name: "Project_mapping",
components: {
ProjectMappingBasemap: Project_mapping_basemap,
},
data() {
return {
formset: {
non_form_errors: null,
empty_form: {
non_form_errors: null,
management_form: null,
forms: [
{
title: {
value: "test",
},
},
],
prefix: null,
hidden_fields: null,
title: {
id_for_label: null,
field: {
max_length: null,
},
},
nested: {
management_form: null,
},
},
management_form: null,
forms: [],
prefix: null,
hidden_fields: null,
title: {
id_for_label: null,
field: {
max_length: null,
},
},
nested: {
management_form: null,
},
},
};
},
};
</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">
<tbody>
<thead>
<tr>
<th>Membre</th>
<th>{{ formset.empty_form.level.label }}</th>
</tr>
</thead>
<div id="formsets-members">
{{ formset.management_form }}
<!-- {% for form in formset %} {% if not form.DELETE.value %} {% for
hidden in form.hidden_fields %} -->
// ! À adapter une fois défini comment faire le formulaire
<!-- <div v-frag v-for="form in Object.entries(formset)" :key="form.level.id_for_label">
<div v-frag v-if="!form.DELETE.value">
<span v-for="hidden in form.hidden_fields" :key="hidden">
{{ hidden }}
</span>
<tr>
<td>
{{ form.last_name.value }} {{ form.first_name.value
}}<br /><i>{{ form.username.value }}</i>
</td>
<td>
<div class="required field">
<div class="ui selection search dropdown">
<input
type="hidden"
:name="form.level.html_name"
:id="form.level.id_for_label"
:value="form.level.value"
/>
<div class="default text"></div>
<i class="dropdown icon"></i>
<div class="menu">
{% for x,y in form.level.field.choices %}
<div
v-for="(x, y) in form.level.field.choices"
:key="y"
class="item"
:data-value="x"
:selected="form.level.value === x"
>
{{ y }}
</div>
</div>
</div>
{{ form.level.errors }}
</div>
</td>
</tr>
</div>
</div> -->
</div>
</tbody>
</table>
<div class="ui divider"></div>
<button type="submit" class="ui teal icon button">
<i class="white save icon"></i> Enregistrer les changements
</button>
</form>
</div>
</template>
<script>
import frag from "vue-frag";
export default {
name: "Project_members",
directives: {
frag,
},
data() {
return {
formset: {
non_form_errors: null,
empty_form: {
level: {
label: null,
},
non_form_errors: null,
management_form: null,
forms: [
{
title: {
value: "test",
},
},
],
prefix: null,
hidden_fields: null,
title: {
id_for_label: null,
field: {
max_length: null,
},
},
nested: {
management_form: null,
},
},
management_form: null,
forms: [],
prefix: null,
hidden_fields: null,
title: {
id_for_label: null,
field: {
max_length: null,
},
},
nested: {
management_form: null,
},
},
};
},
};
</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