<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="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 avec 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="importGeoJson" 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", 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 { choices: { boolean: "Booléen", string: "Chaîne de caractères", date: "Date", list: "Liste de valeurs", integer: "Nombre entier", decimal: "Nombre décimal", text: "Texte multiligne", }, labelCustomForm: [ 'title', 'description', 'status', 'created_on', 'updated_on', 'archived_on', 'deletion_on', 'feature_type', ], jsonStr: '', jsonDict: [], filenameToImport: {name: '', size: 0}, slug: this.$route.params.slug, isModalOpen: false, is_suscriber: false, permissions: { can_view_project: true, can_create_feature: true, }, }; }, computed: { ...mapGetters(["project"]), ...mapState("feature_type", ["form", "customForms", "feature_types"]), ...mapState(["last_comments", "user"]), BASE_URL: () => process.env.VUE_APP_BASE_URL, last_features: function () { // TODO : Filter les dernières return this.$store.state.feature.features; }, }, created() { this.$store.dispatch("GET_PROJECT_INFO", this.slug); }, mounted() { if (this.project) { this.$store.dispatch("map/INITIATE_MAP"); } }, methods: { updateStore() { // FIRST : TEST AVEC FORM this.$store.commit("feature_type/UPDATE_FORM", { title: this.form.title.value, geom_type: this.form.geom_type.value, }); // AFTER : AND CUSTOM FORM }, toNewFeatureType(){ this.$router.push({ name: 'ajouter-type-signalement', }); }, toFormatGeom(value){ if (value == 'LineString'){ console.log('LineString', value) return 'Ligne'; } else if(value == 'Polygon'){ return 'Polygone' } return value; }, isLabelProperties(value){ return this.labelCustomForm.includes(value) }, searchValue(key){ if (key in this.choices) return this.choices[key] }, capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); }, importGeoJson() { if (Object.entries(this.jsonDict).length){ // TODO : VALIDATION IF A JSONDICT HAS NOT FEATURES for (const [, value] of Object.entries(this.jsonDict['features'])) { this.form.title.value = value['properties']['feature_type'] this.form.geom_type.value = this.toFormatGeom(value['geometry']['type']) // TODO : FILL A ARRAY CUSTOMFORM -> VOIR AVEC TIM let customForm = { label : { value : ''}, name : { value : ''}, field_type : { value : '', field : {choices : ''}}, } // FOR for (const [k, val] of Object.entries(value['properties'])) { if (k=='title'){ customForm['label']['value'] = val customForm['name']['value'] = val } if (! this.isLabelProperties(k)){ let typeof_val = this.searchValue(typeof val) customForm['field_type']['value'] = typeof_val } //ENDFOR } this.addCustomForm(customForm); } // GO TO NEW FORM this.toNewFeatureType() } }, addCustomForm(customForm) { this.$store.commit("feature_type/ADD_CUSTOM_FORM", customForm); // * create an object with the counter in store }, 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.jsonDict = JSON.parse(e.target.result); this.jsonStr = JSON.stringify(this.jsonDict, null, 2); } fr.readAsText(this.filenameToImport); } }, subsribeProject() { console.log("Subsribe to project"); }, getFeatureSlug(url) { return url.split("type-signalement")[1].split("signalement"); }, }, }; </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; } </style>