From 946f51e2f999ddebe5448f683cc0a3309d345b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poussard?= <tpoussard@neogeo.fr> Date: Fri, 24 Sep 2021 17:31:34 +0200 Subject: [PATCH] add permissions --- src/store/index.js | 76 ++-- src/store/modules/feature.js | 19 +- src/views/My_account.vue | 129 +++--- src/views/feature/Feature_detail.vue | 15 +- src/views/feature/Feature_list.vue | 420 ++++++++++-------- .../feature_type/Feature_type_detail.vue | 11 +- src/views/project/Project_detail.vue | 37 +- 7 files changed, 368 insertions(+), 339 deletions(-) diff --git a/src/store/index.js b/src/store/index.js index cb00de20..c06634cc 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -29,6 +29,8 @@ function updateAxiosHeader() { // ! À vérifier s'il y a un changement de token pendant l'éxécution de l'appli updateAxiosHeader(); +const noPermissions = { "can_view_project": true, "can_create_project": false, "can_update_project": false, "can_view_feature": true, "can_view_archived_feature": true, "can_create_feature": false, "can_update_feature": false, "can_delete_feature": false, "can_publish_feature": false, "can_create_feature_type": false, "can_view_feature_type": true, "is_project_administrator": false } + export default new Vuex.Store({ modules: { feature_type, @@ -39,7 +41,7 @@ export default new Vuex.Store({ error: null, logged: false, user: false, - configuration:null, + configuration: null, project_slug: null, projects: [], last_comments: [], @@ -51,7 +53,7 @@ export default new Vuex.Store({ mutations: { error(state, data) { - return state.error = data + state.error = data; }, SET_PROJECTS(state, projects) { state.projects = projects; @@ -72,30 +74,31 @@ export default new Vuex.Store({ state.users = payload; }, SET_COOKIE(state, cookie) { - state.cookie = cookie + state.cookie = cookie; }, SET_STATIC_PAGES(state, staticPages) { - state.staticPages = staticPages + state.staticPages = staticPages; }, SET_SSO(state, SSO_SETTED) { - state.SSO_SETTED = SSO_SETTED + state.SSO_SETTED = SSO_SETTED; }, SET_USER_LEVEL_PROJECTS(state, USER_LEVEL_PROJECTS) { - state.USER_LEVEL_PROJECTS = USER_LEVEL_PROJECTS + state.USER_LEVEL_PROJECTS = USER_LEVEL_PROJECTS; }, SET_LOGGED(state, value) { - state.logged = value + state.logged = value; }, SET_PROJECT_COMMENTS(state, last_comments) { - state.last_comments = last_comments + state.last_comments = last_comments; }, SET_USER_PERMISSIONS(state, userPermissions) { - state.user_permissions = userPermissions + state.user_permissions = userPermissions; }, }, getters: { project: state => state.projects.find((project) => project.slug === state.project_slug), + permissions: state => state.user_permissions ? state.user_permissions[state.project_slug] : noPermissions, project_types: state => state.projects.filter(projet => projet.is_project_type), project_user: state => state.projects.filter(projet => projet.creator === state.user.id), // todo: add id to user in api }, @@ -103,15 +106,15 @@ export default new Vuex.Store({ actions: { async GET_ALL_PROJECTS({ commit }) { function parseDate(date) { - let dateArr = date.split("/").reverse() - return new Date(dateArr[0], dateArr[1] - 1, dateArr[2]) + let dateArr = date.split("/").reverse(); + return new Date(dateArr[0], dateArr[1] - 1, dateArr[2]); } await axios .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`) .then((response) => { if (response.status === 200 && response.data) { const orderedProjects = response.data.sort((a, b) => parseDate(b.created_on) - parseDate(a.created_on)); - commit("SET_PROJECTS", orderedProjects) + commit("SET_PROJECTS", orderedProjects); } }) .catch((error) => { @@ -135,23 +138,25 @@ export default new Vuex.Store({ password: payload.password, }) .then((response) => { - commit('error', null) + commit('error', null); if (response && response.status === 200) { // * use stored previous route to go back after login if page not open on login at first - let routerHistory = '' - if (router.options.routerHistory[0] != undefined){ - routerHistory = router.options.routerHistory[0].name !== "login" ? router.options.routerHistory : "/" + let routerHistory = ''; + if (router.options.routerHistory[0] !== undefined) { + routerHistory = router.options.routerHistory[0].name !== "login" ? router.options.routerHistory : "/"; } else { - routerHistory = "/" + routerHistory = "/"; } commit("SET_USER", response.data.user); - router.push(routerHistory[routerHistory.length - 1] || "/") + router.push(routerHistory[routerHistory.length - 1] || "/"); dispatch("GET_USER_LEVEL_PROJECTS"); + dispatch("GET_USER_LEVEL_PERMISSIONS") } }) .catch((error) => { - if (error.response.status === 403) - commit('error', error.response.data.detail) + if (error.response.status === 403) { + commit('error', error.response.data.detail); + } commit("SET_USER", false); }); } @@ -166,7 +171,7 @@ export default new Vuex.Store({ commit("SET_USER", user); window.localStorage.setItem("user", JSON.stringify(user)); //? toujours nécessaire ? } - }) + }) .catch(() => { router.push({ name: "login" }); }); @@ -179,6 +184,7 @@ export default new Vuex.Store({ if (response && response.status === 200) { commit("SET_USER", false); // ? better false or null commit("SET_USER_LEVEL_PROJECTS", null); + commit("SET_USER_PERMISSIONS", null); } }) .catch((error) => { @@ -191,7 +197,7 @@ export default new Vuex.Store({ .get("./config/config.json") .then((response) => { if (response && response.status === 200) { - commit("SET_CONFIG", response.data) + commit("SET_CONFIG", response.data); } }) .catch((error) => { @@ -203,7 +209,7 @@ export default new Vuex.Store({ .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}user-level-projects/`) .then((response) => { if (response && response.status === 200) { - commit("SET_USER_LEVEL_PROJECTS", response.data) + commit("SET_USER_LEVEL_PROJECTS", response.data); } }) .catch((error) => { @@ -216,7 +222,7 @@ export default new Vuex.Store({ .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}user-permissions/`) .then((response) => { if (response && response.status === 200) { - commit("SET_USER_PERMISSIONS", response.data) + commit("SET_USER_PERMISSIONS", response.data); } }) .catch((error) => { @@ -237,7 +243,7 @@ export default new Vuex.Store({ .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${project_slug}/comments/`) .then((response) => { if (response && response.status === 200) { - commit("SET_PROJECT_COMMENTS", response.data.last_comments) + commit("SET_PROJECT_COMMENTS", response.data.last_comments); } }) .catch((error) => { @@ -245,6 +251,14 @@ export default new Vuex.Store({ }); }, + /* GET_PROJECT_USER({ commit }, project_slug) { + axios + .get(`${DJANGO_API_BASE}projects/${project_slug}/utilisateurs`) + .then((response) => (commit("SET_PROJECT_MEMBERS", response.data.members))) + .catch((error) => { + throw error; + }); + }, */ /* GET_PROJECT_FEATURES({ commit }, project_slug) { axios .get(`${DJANGO_API_BASE}projects/${project_slug}/feature`) @@ -252,7 +266,8 @@ export default new Vuex.Store({ .catch((error) => { throw error; }); - }, */ + }, + */ /* GET_PROJECT({ commit }, project_slug) { axios .get(`${DJANGO_API_BASE}projects/${project_slug}/project`) @@ -261,14 +276,7 @@ export default new Vuex.Store({ throw error; }); }, - GET_PROJECT_USER({ commit }, project_slug) { - axios - .get(`${DJANGO_API_BASE}projects/${project_slug}/utilisateurs`) - .then((response) => (commit("SET_PROJECT_MEMBERS", response.data.members))) - .catch((error) => { - throw error; - }); - }, */ + */ /* GET_COOKIE({ commit }, name) { let cookieValue = null; diff --git a/src/store/modules/feature.js b/src/store/modules/feature.js index 59313c88..b48b9ee9 100644 --- a/src/store/modules/feature.js +++ b/src/store/modules/feature.js @@ -7,7 +7,6 @@ axios.defaults.headers.common['X-CSRFToken'] = (name => { return (value != null) ? unescape(value[1]) : null; })('csrftoken'); -const DJANGO_API_BASE = this.state.configuration.VUE_APP_DJANGO_API_BASE const feature = { namespaced: true, @@ -65,9 +64,9 @@ const feature = { getters: { }, actions: { - GET_PROJECT_FEATURES({ commit, /* dispatch */ }, project_slug) { + GET_PROJECT_FEATURES({ commit, rootState }, project_slug) { axios - .get(`${DJANGO_API_BASE}projects/${project_slug}/feature/`) + .get(`${rootState.configuration.VUE_APP_DJANGO_API_BASE}projects/${project_slug}/feature/`) .then((response) => { if (response.status === 200 && response.data) { const features = response.data.features; @@ -101,7 +100,7 @@ const feature = { if (routeName === "editer-signalement") { axios - .put(`${DJANGO_API_BASE}features/${state.form.feature_id}/`, geojson) + .put(`${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/${state.form.feature_id}/`, geojson) .then((response) => { if (response.status === 200 && response.data) { router.push({ @@ -118,7 +117,7 @@ const feature = { }); } else { axios - .post(`${DJANGO_API_BASE}features/`, geojson) + .post(`${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/`, geojson) .then((response) => { if (response.status === 201 && response.data) { dispatch("SEND_ATTACHMENTS", response.data.id) @@ -137,7 +136,7 @@ const feature = { } }, - SEND_ATTACHMENTS({ state }, featureId) { + SEND_ATTACHMENTS({ state, rootState }, featureId) { for (let attacht of state.attachmentFormset) { let formdata = new FormData(); formdata.append("file", attacht.fileToImport, attacht.fileToImport.name); @@ -147,7 +146,7 @@ const feature = { } formdata.append("data", JSON.stringify(data)); axios - .post(`${DJANGO_API_BASE}features/${featureId}/attachments/`, formdata) + .post(`${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/${featureId}/attachments/`, formdata) .then((response) => { if (response.status === 200 && response.data) { console.log(response, response.data) @@ -165,9 +164,9 @@ const feature = { .getlinked_features(featureId) .then((data) => commit("SET_FEATURE_LINKS", data)); } */ - DELETE_FEATURE({ state }, feature_id) { + DELETE_FEATURE({ state, rootState }, feature_id) { console.log("Deleting feature:", feature_id, state) - const url=`${DJANGO_API_BASE}features/${feature_id}`; + const url=`${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/${feature_id}`; axios .delete(url, { }) @@ -181,7 +180,7 @@ const feature = { //console.log("post comment", data, state) /* axios - .post(`${DJANGO_API_BASE}feature_type/`, data) + .post(`${rootState.configuration.VUE_APP_DJANGO_API_BASE}feature_type/`, data) .then((response) => { const routerHistory = router.options.routerHistory commit("SET_USER", response.data.user); diff --git a/src/views/My_account.vue b/src/views/My_account.vue index c05a6d27..71f80d9e 100644 --- a/src/views/My_account.vue +++ b/src/views/My_account.vue @@ -49,68 +49,70 @@ <div class="ui divided items"> <div v-for="project in projects" :key="project.slug" class="item"> <!-- {% if permissions|lookup:project.slug %} --> - <div class="ui tiny image"> - <img - v-if="project.thumbnail" - class="ui small image" - :src=" - project.thumbnail.includes('default') - ? require('@/assets/img/default.png') - : DJANGO_BASE_URL + project.thumbnail + refreshId() - " - height="200" - /> - </div> - <div class="middle aligned content"> - <router-link - :to="{ - name: 'project_detail', - params: { slug: project.slug }, - }" - class="header" - >{{ project.title }}</router-link - > - <div class="description"> - <p>{{ project.description }}</p> + <div v-frag v-if="user_permissions[project.slug].can_view_project"> + <div class="ui tiny image"> + <img + v-if="project.thumbnail" + class="ui small image" + :src=" + project.thumbnail.includes('default') + ? require('@/assets/img/default.png') + : DJANGO_BASE_URL + project.thumbnail + refreshId() + " + height="200" + /> </div> - <div class="meta"> - <span class="right floated" - >Projet {{ project.moderation ? "" : "non" }} modéré</span + <div class="middle aligned content"> + <router-link + :to="{ + name: 'project_detail', + params: { slug: project.slug }, + }" + class="header" + >{{ project.title }}</router-link > - <span - >Niveau d'autorisation requis : - {{ project.access_level_pub_feature }}</span - ><br /> - <span> - Mon niveau d'autorisation : - <span v-if="USER_LEVEL_PROJECTS && project">{{ - USER_LEVEL_PROJECTS[project.slug] - }}</span> - <span v-if="user && user.is_administrator">{{ - "+ Gestionnaire métier" - }}</span> - </span> - </div> - <div class="meta"> - <span - class="right floated" - :data-tooltip="`Projet créé le ${project.created_on}`" - > - <i class="calendar icon"></i> {{ project.created_on }} - </span> - <span data-tooltip="Membres"> - {{ project.nb_contributors }} <i class="user icon"></i> - </span> - <span data-tooltip="Signalements"> - {{ project.nb_published_features }} <i - class="map marker icon" - ></i> - </span> - <span data-tooltip="Commentaires"> - {{ project.nb_published_features_comments }} <i - class="comment icon" - ></i> - </span> + <div class="description"> + <p>{{ project.description }}</p> + </div> + <div class="meta"> + <span class="right floated" + >Projet {{ project.moderation ? "" : "non" }} modéré</span + > + <span + >Niveau d'autorisation requis : + {{ project.access_level_pub_feature }}</span + ><br /> + <span> + Mon niveau d'autorisation : + <span v-if="USER_LEVEL_PROJECTS && project">{{ + USER_LEVEL_PROJECTS[project.slug] + }}</span> + <span v-if="user && user.is_administrator">{{ + "+ Gestionnaire métier" + }}</span> + </span> + </div> + <div class="meta"> + <span + class="right floated" + :data-tooltip="`Projet créé le ${project.created_on}`" + > + <i class="calendar icon"></i> {{ project.created_on }} + </span> + <span data-tooltip="Membres"> + {{ project.nb_contributors }} <i class="user icon"></i> + </span> + <span data-tooltip="Signalements"> + {{ project.nb_published_features }} <i + class="map marker icon" + ></i> + </span> + <span data-tooltip="Commentaires"> + {{ project.nb_published_features_comments }} <i + class="comment icon" + ></i> + </span> + </div> </div> </div> </div> @@ -287,7 +289,12 @@ export default { computed: { // todo : filter projects to user - ...mapState(["user", "projects", "USER_LEVEL_PROJECTS"]), + ...mapState([ + "user", + "projects", + "USER_LEVEL_PROJECTS", + "user_permissions", + ]), DJANGO_BASE_URL: () => process.env.VUE_APP_DJANGO_BASE, userFullname: function () { if (this.user.first_name || this.user.last_name) diff --git a/src/views/feature/Feature_detail.vue b/src/views/feature/Feature_detail.vue index 49e6841f..e24f5574 100644 --- a/src/views/feature/Feature_detail.vue +++ b/src/views/feature/Feature_detail.vue @@ -6,8 +6,8 @@ <div class="content"> {{ feature.title || feature.feature_id }} <div class="ui icon right floated compact buttons"> - <!-- {% if permissions|lookup:'can_create_feature' %} --> <router-link + v-if="permissions.can_create_feature" :to="{ name: 'ajouter-signalement', params: { slug_type_signal: $route.params.slug_type_signal }, @@ -18,8 +18,8 @@ > <i class="plus fitted icon"></i> </router-link> - <!-- {% endif %} {% if permissions|lookup:'can_update_feature' %} --> <router-link + v-if="permissions.can_update_feature" :to="{ name: 'editer-signalement', params: { @@ -31,20 +31,18 @@ > <i class="inverted grey pencil alternate icon"></i> </router-link> - <!-- {% endif %} {% if permissions|lookup:'can_delete_feature' %} --> <a + v-if="permissions.can_delete_feature" @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> @@ -263,8 +261,7 @@ </div> </div> - <!-- {% if permissions|lookup:'can_create_feature' %} --> - <div class="ui segment"> + <div v-if="permissions.can_create_feature" class="ui segment"> <form id="form-comment" class="ui form" @@ -301,7 +298,6 @@ id="attachment_file" @change="getAttachmentFileData($event)" /> - <!-- {{ comment_form.attachment_file.errors }} --> </div> <div class="field"> <input @@ -363,7 +359,7 @@ <script> import frag from "vue-frag"; -import { mapState } from "vuex"; +import { mapGetters, mapState } from "vuex"; import { mapUtil } from "@/assets/js/map-util.js"; import featureAPI from "@/services/feature-api"; const axios = require("axios"); @@ -405,6 +401,7 @@ export default { computed: { ...mapState(["user"]), + ...mapGetters(["permissions"]), ...mapState("feature", ["linked_features"]), DJANGO_BASE_URL: function () { return this.$store.state.configuration.VUE_APP_DJANGO_BASE; diff --git a/src/views/feature/Feature_list.vue b/src/views/feature/Feature_list.vue index 8d9572aa..8f31d20b 100644 --- a/src/views/feature/Feature_list.vue +++ b/src/views/feature/Feature_list.vue @@ -1,6 +1,12 @@ <template> <div class="fourteen wide column"> - <script type="application/javascript" :src="baseUrl+'/resources/leaflet-control-geocoder-1.13.0/Control.Geocoder.js'"></script> + <script + type="application/javascript" + :src=" + baseUrl + + '/resources/leaflet-control-geocoder-1.13.0/Control.Geocoder.js' + " + ></script> <div class="feature-list-container ui grid"> <div class="four wide column"> @@ -32,11 +38,11 @@ }} </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 + v-if="project && feature_types && permissions.can_create_feature" + class="item right" + > <div @click="showAddFeature = !showAddFeature" class=" @@ -73,28 +79,17 @@ </div> </div> </div> - + <div v-if="project && feature_types" class="item right"> - <div - - v-if="checkedFeatures.length" - class=" - ui - top - center - pointing - compact - button button-hover-red - " - data-tooltip="Effacer tous les types de signalements sélectionnés" - data-position="left center" - data-variation="mini" + <div + v-if="checkedFeatures.length" + class="ui top center pointing compact button button-hover-red" + data-tooltip="Effacer tous les types de signalements sélectionnés" + data-position="left center" + data-variation="mini" > - <i - class="grey trash icon" - @click="modalAllDelete()" - ></i> - </div> + <i class="grey trash icon" @click="modalAllDelete()"></i> + </div> </div> </div> </div> @@ -128,7 +123,12 @@ <div class="ui icon input"> <i class="search icon"></i> <div class="ui action input"> - <input type="text" name="title" v-model="form.title" @input="onFilterChange()" /> + <input + type="text" + name="title" + v-model="form.title" + @input="onFilterChange()" + /> <button type="button" class="ui teal icon button" @@ -147,44 +147,86 @@ <div v-show="showMap" class="ui tab active map-container" data-tab="map"> <div id="map"></div> - <SidebarLayers v-if="baseMaps && map"/> + <SidebarLayers v-if="baseMaps && map" /> </div> <div v-show="!showMap" data-tab="list" class="dataTables_wrapper no-footer"> <table id="table-features" class="ui compact table"> <thead> <tr> + <th class="center"></th> + <th class="center"> - + Statut + <i + :class="{ + down: isSortedAsc('statut'), + up: isSortedDesc('statut'), + }" + class="icon sort" + @click="changeSort('statut')" + /> + </th> + <th class="center"> + Type + <i + :class="{ down: isSortedAsc('type'), up: isSortedDesc('type') }" + class="icon sort" + @click="changeSort('type')" + /> + </th> + <th class="center"> + Nom + <i + :class="{ down: isSortedAsc('nom'), up: isSortedDesc('nom') }" + class="icon sort" + @click="changeSort('nom')" + /> + </th> + <th class="center"> + Dernière modification + <i + :class="{ + down: isSortedAsc('updated_on'), + up: isSortedDesc('updated_on'), + }" + class="icon sort" + @click="changeSort('updated_on')" + /> + </th> + <th class="center" v-if="user"> + Auteur + <i + :class="{ + down: isSortedAsc('display_creator'), + up: isSortedDesc('display_creator'), + }" + class="icon sort" + @click="changeSort('display_creator')" + /> </th> - - <th class="center">Statut <i :class="{ down: isSortedAsc('statut'),up:isSortedDesc('statut') }" class="icon sort" @click="changeSort('statut')"/></th> - <th class="center">Type <i :class="{ down: isSortedAsc('type'),up:isSortedDesc('type') }" class="icon sort" @click="changeSort('type')"/></th> - <th class="center">Nom <i :class="{ down: isSortedAsc('nom'),up:isSortedDesc('nom') }" class="icon sort" @click="changeSort('nom')"/></th> - <th class="center">Dernière modification <i :class="{ down: isSortedAsc('updated_on'),up:isSortedDesc('updated_on') }" class="icon sort" @click="changeSort('updated_on')"/></th> - <th class="center" v-if="user" >Auteur <i :class="{ down: isSortedAsc('display_creator'),up:isSortedDesc('display_creator') }" class="icon sort" @click="changeSort('display_creator')"/></th> </tr> </thead> <tbody> - <tr - v-for="(feature, index) in getPaginatedFeatures()" - :key="index" - > + <tr v-for="(feature, index) in getPaginatedFeatures()" :key="index"> <td class="center"> - <div class="ui checkbox"> - <input - type="checkbox" - :id="feature.id" - :value="feature.id" - v-model="checkedFeatures" - :checked="checkedFeatures[feature.id]" - > - <label></label> - </div> - </td> + <div class="ui checkbox"> + <input + type="checkbox" + :id="feature.id" + :value="feature.id" + v-model="checkedFeatures" + :checked="checkedFeatures[feature.id]" + /> + <label></label> + </div> + </td> <td class="center"> - <div v-if="feature.properties.status.value == 'archived'" data-tooltip="Archivé"> + <div + v-if="feature.properties.status.value == 'archived'" + data-tooltip="Archivé" + > <i class="grey archive icon"></i> </div> <div @@ -210,7 +252,9 @@ <router-link :to="{ name: 'details-type-signalement', - params: { feature_type_slug: feature.properties.feature_type.title }, + params: { + feature_type_slug: feature.properties.feature_type.title, + }, }" > {{ feature.properties.feature_type.title }} @@ -225,14 +269,14 @@ slug_signal: feature.properties.slug || feature.id, }, }" - >{{ getFeatureDisplayName(feature)}}</router-link + >{{ getFeatureDisplayName(feature) }}</router-link > </td> <td class="center"> <!-- |date:'Ymd' --> {{ feature.properties.updated_on }} </td> - <td class="center" v-if="user"> + <td class="center" v-if="user"> {{ feature.properties.display_creator }} </td> </tr> @@ -243,7 +287,7 @@ </tr> </tbody> </table> - <div + <div class="dataTables_info" id="table-features_info" role="status" @@ -251,11 +295,12 @@ > Affichage de l'élément {{ pagination.start + 1 }} à {{ pagination.end + 1 }} sur {{ getFilteredFeatures().length }} éléments - </div> + </div> <div class="dataTables_paginate paging_simple_numbers" id="table-features_paginate" - > <a + > + <a @click="toPreviousPage" class="paginate_button previous disabled" aria-controls="table-features" @@ -274,24 +319,23 @@ >{{index}}</a > </span> <span class="ellipsis">…</span> --> - <a - class="paginate_button next" - aria-controls="table-features" - data-dt-idx="7" - tabindex="0" - id="table-features_next" - @click="toNextPage" - >Suivant</a - > - - </div> + <a + class="paginate_button next" + aria-controls="table-features" + data-dt-idx="7" + tabindex="0" + id="table-features_next" + @click="toNextPage" + >Suivant</a + > + </div> </div> <!-- MODAL ALL DELETE FEATURE TYPE --> <div v-if="modalAllDeleteOpen" class="ui dimmer modals page transition visible active" style="display: flex !important" - > + > <div :class="[ 'ui mini modal subscription', @@ -301,23 +345,18 @@ <i @click="modalAllDeleteOpen = false" class="close icon"></i> <div class="ui icon header"> <i class="trash alternate icon"></i> - Êtes-vous sûr de vouloir effacer - <span v-if="checkedFeatures.length == 1"> - un signalement ? - </span> - <span v-else> - ces {{checkedFeatures.length}} signalements ? - </span> + Êtes-vous sûr de vouloir effacer + <span v-if="checkedFeatures.length == 1"> un signalement ? </span> + <span v-else> ces {{ checkedFeatures.length }} signalements ? </span> </div> <div class="actions"> - - <button - @click="deleteAllFeatureSelection()" - type="button" - class="ui red compact fluid button" - > - Confirmer la suppression - </button> + <button + @click="deleteAllFeatureSelection()" + type="button" + class="ui red compact fluid button" + > + Confirmer la suppression + </button> </div> </div> </div> @@ -377,13 +416,13 @@ export default { title: null, }, pagination: { - pagesize:15, + pagesize: 15, start: 0, end: 14, }, - sort:{ - column:'', - ascending:true + sort: { + column: "", + ascending: true, }, geojsonFeatures:[], featureLoading:false, @@ -400,27 +439,25 @@ export default { }, computed: { - ...mapGetters(["project"]), + ...mapGetters(["project", "permissions"]), ...mapState(["user"]), ...mapState("feature", ["features"]), ...mapState("feature_type", ["feature_types"]), - - baseMaps(){ + + baseMaps() { return this.$store.state.map.basemaps; }, - }, methods: { - modalAllDelete(){ + modalAllDelete() { return (this.modalAllDeleteOpen = !this.modalAllDeleteOpen); }, - deleteFeature(feature){ - const url=`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}features/${feature.feature_id}`; + deleteFeature(feature) { + const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}features/${feature.feature_id}`; axios - .delete(url, { - }) + .delete(url, {}) .then(() => { - if(!this.modalAllDeleteOpen){ + if (!this.modalAllDeleteOpen) { this.$router.go(); } }) @@ -428,93 +465,91 @@ export default { return false; }); }, - deleteAllFeatureSelection(){ - let feature = {} - this.checkedFeatures.forEach(feature_id => { - feature = {'feature_id': feature_id}; + deleteAllFeatureSelection() { + let feature = {}; + this.checkedFeatures.forEach((feature_id) => { + feature = { feature_id: feature_id }; this.deleteFeature(feature); }); this.modalAllDelete(); }, - getFeatureDisplayName(feature){ + getFeatureDisplayName(feature) { return feature.properties.title || feature.id; }, getPaginatedFeatures() { - let filterdFeatures=[...this.getFilteredFeatures()]; + let filterdFeatures = [...this.getFilteredFeatures()]; // Ajout du tri - if(this.sort.column!=''){ - filterdFeatures=filterdFeatures.sort((a,b)=>{ - let aProp=this.getFeatureDisplayName(a); - let bProp=this.getFeatureDisplayName(b); - if(this.sort.column=='statut') { - aProp=a.properties.status.value; - bProp=b.properties.status.value; - } - else if(this.sort.column=='type') { - aProp=a.properties.feature_type.title; - bProp=b.properties.feature_type.title; - } - else if(this.sort.column=='updated_on') { - aProp=a.properties.updated_on; - bProp=b.properties.updated_on; + if (this.sort.column != "") { + filterdFeatures = filterdFeatures.sort((a, b) => { + let aProp = this.getFeatureDisplayName(a); + let bProp = this.getFeatureDisplayName(b); + if (this.sort.column == "statut") { + aProp = a.properties.status.value; + bProp = b.properties.status.value; + } else if (this.sort.column == "type") { + aProp = a.properties.feature_type.title; + bProp = b.properties.feature_type.title; + } else if (this.sort.column == "updated_on") { + aProp = a.properties.updated_on; + bProp = b.properties.updated_on; + } else if (this.sort.column == "display_creator") { + aProp = a.properties.display_creator; + bProp = b.properties.display_creator; + } + + //ascending + if (this.sort.ascending) { + if (aProp < bProp) { + return -1; } - else if(this.sort.column=='display_creator') { - aProp=a.properties.display_creator; - bProp=b.properties.display_creator; + if (aProp > bProp) { + return 1; } - - //ascending - if(this.sort.ascending){ - if(aProp < bProp) { return -1; } - if(aProp > bProp) { return 1; } - return 0; + return 0; + } else { + //descending + if (aProp < bProp) { + return 1; } - else{ - //descending - if(aProp < bProp) { return 1; } - if(aProp > bProp) { return -1; } - return 0; + if (aProp > bProp) { + return -1; } - - - }) + return 0; + } + }); } - return filterdFeatures.slice( - this.pagination.start, - this.pagination.end - ); + return filterdFeatures.slice(this.pagination.start, this.pagination.end); }, - isSortedAsc(column){ - return this.sort.column==column && this.sort.ascending; + isSortedAsc(column) { + return this.sort.column == column && this.sort.ascending; }, - isSortedDesc(column){ - return this.sort.column==column && !this.sort.ascending; + isSortedDesc(column) { + return this.sort.column == column && !this.sort.ascending; }, - changeSort(column){ - if(this.sort.column==column){ - //changer order - this.sort.ascending=!this.sort.ascending; - }else{ - this.sort.column=column; - this.sort.ascending=true; + changeSort(column) { + if (this.sort.column == column) { + //changer order + this.sort.ascending = !this.sort.ascending; + } else { + this.sort.column = column; + this.sort.ascending = true; } - }, - onFilterStatusChange(newvalue){ - this.filterStatus=null; - if(newvalue){ - console.log("filter change",newvalue.value); - this.filterStatus=newvalue.value; + onFilterStatusChange(newvalue) { + this.filterStatus = null; + if (newvalue) { + console.log("filter change", newvalue.value); + this.filterStatus = newvalue.value; } this.onFilterChange(); }, - onFilterTypeChange(newvalue){ - this.filterType=null; - if(newvalue){ - console.log("filter change",newvalue); - this.filterType=newvalue; + onFilterTypeChange(newvalue) { + this.filterType = null; + if (newvalue) { + console.log("filter change", newvalue); + this.filterType = newvalue; } this.onFilterChange(); @@ -530,7 +565,7 @@ export default { }, getFilteredFeatures() { let results = this.geojsonFeatures; - if (this.filterType) { + if (this.filterType) { results = results.filter( (el) => el.properties.feature_type.title === this.filterType ); @@ -549,15 +584,14 @@ export default { .includes(this.form.title.toLowerCase()); } else return el.id.toLowerCase().includes(this.form.title.toLowerCase()); - } - - - ); + }); } return results; }, - getNbPages(){ - return Math.round(this.getFilteredFeatures().length/this.pagination.pagesize); + getNbPages() { + return Math.round( + this.getFilteredFeatures().length / this.pagination.pagesize + ); }, toPreviousPage() { if (this.pagination.start > 0) { @@ -571,7 +605,7 @@ export default { this.pagination.end += this.pagination.pagesize; } }, - loadFeatures(features){ + loadFeatures(features) { this.geojsonFeatures = features; console.log(this.geojsonFeatures); const urlParams = new URLSearchParams(window.location.search); @@ -591,11 +625,13 @@ export default { mapUtil.getMap().fitBounds(this.featureGroup.getBounds()); } this.form.type.choices = [ - //* converting Set to an Array with spread "..." - ...new Set(this.geojsonFeatures.map((el) => el.properties.feature_type.title)), //* use Set to eliminate duplicate values - ]; + //* converting Set to an Array with spread "..." + ...new Set( + this.geojsonFeatures.map((el) => el.properties.feature_type.title) + ), //* use Set to eliminate duplicate values + ]; }, - addGeocoders(){ + addGeocoders() { let geocoder; // Get the settings.py variable SELECTED_GEOCODER_PROVIDER. This way avoids XCC attacks @@ -635,23 +671,22 @@ export default { } }, mounted() { - - - - this.zoom = this.$route.query.zoom||''; - this.lat = this.$route.query.lat||''; - this.lng = this.$route.query.lng||''; - var mapDefaultViewCenter = this.$store.state.configuration.DEFAULT_MAP_VIEW.center; - var mapDefaultViewZoom = this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom; - - this.map=mapUtil.createMap({ - zoom:this.zoom, - lat:this.lat, - lng:this.lng, + this.zoom = this.$route.query.zoom || ""; + this.lat = this.$route.query.lat || ""; + this.lng = this.$route.query.lng || ""; + var mapDefaultViewCenter = + this.$store.state.configuration.DEFAULT_MAP_VIEW.center; + var mapDefaultViewZoom = + this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom; + + this.map = mapUtil.createMap({ + zoom: this.zoom, + lat: this.lat, + lng: this.lng, mapDefaultViewCenter, mapDefaultViewZoom, }); - + document.addEventListener("change-layers-order", (event) => { // Reverse is done because the first layer in order has to be added in the map in last. // Slice is done because reverse() changes the original array, so we make a copy first @@ -682,10 +717,9 @@ export default { setTimeout( function () { this.addGeocoders(); - }.bind(this), 1000) - - - + }.bind(this), + 1000 + ); }, }; </script> @@ -701,7 +735,7 @@ export default { z-index: 1; } -.center{ +.center { text-align: center !important; } diff --git a/src/views/feature_type/Feature_type_detail.vue b/src/views/feature_type/Feature_type_detail.vue index e342caab..764d774b 100644 --- a/src/views/feature_type/Feature_type_detail.vue +++ b/src/views/feature_type/Feature_type_detail.vue @@ -53,9 +53,7 @@ </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 v-if="permissions.can_create_feature" class="ui styled accordion"> <div @click="toggleShowImport" :class="['title', { active: showImport }]" @@ -111,7 +109,6 @@ > <i class="download icon"></i> Exporter </button> - <!-- // todo gérer export --> </div> </div> </div> @@ -174,8 +171,8 @@ <i class="right arrow icon"></i> Voir tous les signalements </router-link> - <!-- v-if="permissions.can_create_feature" --> <router-link + v-if="permissions.can_create_feature" :to="{ name: 'ajouter-signalement', params: { slug_type_signal: structure.slug }, @@ -184,7 +181,7 @@ > Ajouter un signalement </router-link> - <br /><!-- // ToDo : gérer permissions --> + <br /> </div> </div> </template> @@ -210,7 +207,7 @@ export default { }, computed: { - ...mapGetters(["project"]), + ...mapGetters(["project", "permissions"]), ...mapState("feature", ["features"]), ...mapState("feature_type", ["feature_types", "importFeatureTypeData"]), structure: function () { diff --git a/src/views/project/Project_detail.vue b/src/views/project/Project_detail.vue index 8bff78d9..61e144f8 100644 --- a/src/views/project/Project_detail.vue +++ b/src/views/project/Project_detail.vue @@ -37,8 +37,8 @@ <div class="content"> {{ project.title }} <div class="ui icon right floated compact buttons"> - <!-- {% if permissions|lookup:'can_view_project' %} --> <a + v-if="permissions.can_view_project" id="subscribe-button" class="ui button button-hover-green" data-tooltip="S'abonner au projet" @@ -48,10 +48,8 @@ > <i class="inverted grey envelope icon"></i> </a> - <!-- {% endif %} {% if project and - permissions|lookup:'can_update_project' %} --> <router-link - v-if="user" + v-if="permissions.can_update_project" :to="{ name: 'project_edit', params: { slug: project.slug } }" class="ui button button-hover-orange" data-tooltip="Modifier le projet" @@ -60,12 +58,10 @@ > <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> @@ -74,7 +70,6 @@ <div class="row"> <div class="seven wide column"> - <!-- // todo : Create endpoints for feature_types --> <div class="ui middle aligned divided list"> <div v-for="(type, index) in feature_types" @@ -82,7 +77,7 @@ class="item" > <div class="middle aligned content"> - <router-link + <router-link :to="{ name: 'details-type-signalement', params: { feature_type_slug: type.slug }, @@ -107,18 +102,18 @@ </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=" + <!-- // ? should we get type.is_editable ? --> + <!-- v-if=" project && permissions.can_create_feature && type.is_editable " --> <router-link + v-if="project && permissions.can_create_feature" :to="{ name: 'ajouter-signalement', params: { slug_type_signal: type.slug }, }" - v-if="project && permissions.can_create_feature" class=" ui compact @@ -131,7 +126,7 @@ data-tooltip="Ajouter un signalement" data-position="left center" data-variation="mini" - ><!-- // todo : adapt --> + > <i class="ui plus icon"></i> </router-link> <router-link @@ -152,7 +147,7 @@ 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 @@ -183,9 +178,10 @@ <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 + v-if="permissions.can_update_project" :to="{ name: 'ajouter-type-signalement', params: { slug: project.slug }, @@ -329,7 +325,6 @@ </h4> </div> <div class="center aligned extra content"> - <!-- {{ project.archive_feature|default_if_none:"0" }} jours --> {{ project.archive_feature }} jours </div> </div> @@ -343,7 +338,6 @@ </h4> </div> <div class="center aligned extra content"> - <!-- {{ project.delete_feature|default_if_none:"0" }} jours --> {{ project.delete_feature }} jours </div> </div> @@ -389,7 +383,7 @@ </div> </div> </div> - <span v-else-if="!permissions.can_view_project"> + <span v-else> <i class="icon exclamation triangle"></i> <span >Vous ne disposez pas des droits nécessaires pour consulter ce @@ -428,7 +422,6 @@ </div> </div> </div> - </div> </template> @@ -437,7 +430,6 @@ import frag from "vue-frag"; import { mapUtil } from "@/assets/js/map-util.js"; import { mapGetters, mapState } from "vuex"; import projectAPI from "@/services/project-api"; -//import addressAPI from '@/services/address-api'; const axios = require("axios"); @@ -469,17 +461,12 @@ export default { slug: this.$route.params.slug, isModalOpen: false, is_suscriber: false, - permissions: { - // ! fake, should be replaced by api's data - can_view_project: true, - can_create_feature: true, - }, tempMessage: null, }; }, computed: { - ...mapGetters(["project"]), + ...mapGetters(["project", "permissions"]), ...mapState("feature_type", ["feature_types"]), ...mapState("feature", ["features"]), ...mapState(["last_comments", "user"]), -- GitLab