diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000000000000000000000000000000000..8783eda81682fd23bb8226256569cdf2cbb774c8 Binary files /dev/null and b/dump.rdb differ diff --git a/package-lock.json b/package-lock.json index f8d9c588927356a3fc1d970f356e0f95f2e09dbb..b9feb38fa0dfc01131688a519bd57ae63f2bb9a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "geocontrib-frontend", - "version": "0.1.0", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "0.1.0", + "version": "2.0.0", "dependencies": { "@turf/flip": "^6.5.0", "axios": "^0.21.1", @@ -13,6 +13,7 @@ "leaflet": "^1.5.1", "leaflet-draw": "^1.0.4", "register-service-worker": "^1.7.1", + "sortablejs": "^1.14.0", "vue": "^2.6.11", "vue-frag": "^1.1.5", "vue-router": "^3.2.0", @@ -11965,6 +11966,11 @@ "node": ">=0.10.0" } }, + "node_modules/sortablejs": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -24967,6 +24973,11 @@ } } }, + "sortablejs": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", diff --git a/package.json b/package.json index 82643726b78a2c83bc544034804ddfc071f621a3..2478d2e70f01edc50b444a82211943ea7f3e4e3b 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "leaflet": "^1.5.1", "leaflet-draw": "^1.0.4", "register-service-worker": "^1.7.1", + "sortablejs": "^1.14.0", "vue": "^2.6.11", "vue-frag": "^1.1.5", "vue-router": "^3.2.0", diff --git a/public/index.html b/public/index.html index bf81abbcae21b265ed11b24c7af08383604145a2..81a966830c494ff1621342f894af73bc9a031577 100644 --- a/public/index.html +++ b/public/index.html @@ -13,7 +13,11 @@ <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> - <div id="app"></div> + <div id="app"> + <div class="ui active dimmer"> + <div class="ui indeterminate text loader">En cours de chargement ...</div> + </div> + </div> <!-- built files will be auto injected --> </body> </html> diff --git a/src/App.vue b/src/App.vue index 253411d263b7ed5c2e2cebced7d6825c896c41c4..af09408b2e3ebd0d516dbe4a0b23bd1836698e8d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,7 +8,7 @@ class="ui mini right spaced image" src="@/assets/img/logo-neogeo-circle.png" /> - <!-- :src="LOGO_PATH" --> + <!-- :src="LOGO_PATH" --> {{ getApplicationName() }} </router-link> @@ -44,6 +44,7 @@ </router-link> <router-link + v-if="user.is_administrator" :to="{ name: 'project_mapping', params: { slug: project.slug }, @@ -53,6 +54,7 @@ <i class="map icon"></i>Fonds cartographiques </router-link> <router-link + v-if="user.is_administrator" :to="{ name: 'project_members', params: { slug: project.slug }, @@ -61,14 +63,12 @@ > <i class="users icon"></i>Membres </router-link> - <!-- {% endif %} --> </div> </div> <div class="right menu"> <router-link v-if="user" to="/my_account/" class="item"> {{ userFullname || user.username || "Utilisateur inconnu" }} - <!-- // ? full_name n'existe pas côté django... --> </router-link> <div v-if="user && user.is_administrator" @@ -81,8 +81,6 @@ <span v-if="user.is_administrator"> Gestionnaire métier </span> </div> <!-- // todo : find out SSO_SETTED in django --> - <!-- // ? condition bizarre (if !sso_setted pui elif !sso_setted) ? --> - <!-- <a class="item" href="{% url 'geocontrib:logout' %}"><i class="ui logout icon"></i></a> --> <a v-if="user && !SSO_SETTED" @click="logout" class="item" ><i class="ui logout icon"></i> </a> @@ -95,8 +93,6 @@ </header> <main> <div class="ui stackable grid centered container"> - <!-- // todo : add messages --> - <!-- {% if messages %} --> <div v-if="messages" class="row"> <div class="fourteen wide column"> <div @@ -117,7 +113,6 @@ </div> </div> </div> - <!-- {% endif %} --> <router-view /> <!-- //* Les views sont injectées ici --> </div> @@ -134,7 +129,6 @@ </template> <script> - import frag from "vue-frag"; import { mapState } from "vuex"; import { mapGetters } from "vuex"; @@ -154,7 +148,13 @@ export default { }, computed: { - ...mapState(["projects", "user", "SSO_SETTED", "USER_LEVEL_PROJECTS","configuration"]), + ...mapState([ + "projects", + "user", + "SSO_SETTED", + "USER_LEVEL_PROJECTS", + "configuration", + ]), ...mapGetters(["project"]), //LOGO_PATH: () => require(`${configuration.VUE_APP_LOGO_PATH}`), //APPLICATION_NAME: () => this.configuration.VUE_APP_APPLICATION_NAME, diff --git a/src/assets/js/map-util.js b/src/assets/js/map-util.js index 2bb0f78e4a4cc104f50751ca06cd87412feaf234..89acc5e36ddc956b36bb996065b2988299592335 100644 --- a/src/assets/js/map-util.js +++ b/src/assets/js/map-util.js @@ -32,18 +32,19 @@ L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({ getFeatureInfo: function (evt) { if (this.wmsParams.basemapId != undefined) { const queryableLayerSelected = document.getElementById(`queryable-layers-selector-${this.wmsParams.basemapId}`).getElementsByClassName('selected')[0].innerHTML; - if (queryableLayerSelected === this.wmsParams.title) { + if (queryableLayerSelected.trim() === this.wmsParams.title.trim()) { // Make an AJAX request to the server and hope for the best var params = this.getFeatureInfoUrl(evt.latlng); var showResults = L.Util.bind(this.showGetFeatureInfo, this); - axios.post( + axios.get( window.proxy_url, { - data: params, + params: params, //dataType: "json", } - .then(data => { + ).then(response => { + let data=response.data; var err = typeof data === 'object' ? null : data; if (data.features || err) { showResults(err, evt.latlng, data); @@ -55,7 +56,7 @@ L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({ //xhr.responseText; //console.log(status) } - )) + ) } } @@ -176,7 +177,31 @@ const mapUtil = { return map; }, + addGeocoders: function(configuration){ + let geocoder; + const geocoderLabel = configuration.SELECTED_GEOCODER.PROVIDER; + if (geocoderLabel) { + const LIMIT_RESULTS = 5; + if ( + geocoderLabel === configuration.GEOCODER_PROVIDERS.ADDOK + ) { + geocoder = L.Control.Geocoder.addok({ limit: LIMIT_RESULTS }); + } else if ( + geocoderLabel === configuration.GEOCODER_PROVIDERS.PHOTON + ) { + geocoder = L.Control.Geocoder.photon(); + } else if ( + geocoderLabel === configuration.GEOCODER_PROVIDERS.NOMINATIM + ) { + geocoder = L.Control.Geocoder.nominatim(); + } + L.Control.geocoder({ + placeholder: "Chercher une adresse...", + geocoder: geocoder, + }).addTo(map); + } + }, addLayers: function (layers, serviceMap, optionsMap) { if (layers) { layers.forEach((layer) => { diff --git a/src/components/Dropdown.vue b/src/components/Dropdown.vue index 0ca8e093114e5bfc7bfac0e1dc348f2b3b2d9528..d55ed84c1d11548e2e5836839fef1f08c2b14f38 100644 --- a/src/components/Dropdown.vue +++ b/src/components/Dropdown.vue @@ -33,9 +33,7 @@ processedOptions ? 'item' : 'message', { 'active selected': option === selected }, ]" - > - {{ option }} - </div> + >{{ option }}</div> </div> </div> </template> diff --git a/src/components/map-layers/SidebarLayers.vue b/src/components/map-layers/SidebarLayers.vue index 9df2338dce40fc89bd337a1011da9e8bd6af6d2a..d1120637b947ea8df5d629c4f1133823b27fcfe2 100644 --- a/src/components/map-layers/SidebarLayers.vue +++ b/src/components/map-layers/SidebarLayers.vue @@ -47,7 +47,7 @@ <div v-for="basemap in baseMaps" - :id="`list-${basemap.id}`" + :key="`list-${basemap.id}`" class="basemaps-items ui accordion styled" > @@ -58,15 +58,26 @@ > {{ basemap.title }} </div> + <div :id="`queryable-layers-selector-${basemap.id}`" v-if="isQueryable(basemap)"> + <b>Couche requêtable</b> + <Dropdown + @update:selection="onQueryLayerChange($event)" + :options="getQueryableLayers(basemap)" + :selected="selectedQueryLayer" + :search="true" + /> + + </div> <div :class="{ active: isActive(basemap) }" class="content" - data-basemap-index="0" + :id="`list-${basemap.id}`" + :data-basemap-index="basemap.id" > <div v-for="(layer, index) in basemap.layers" :key="basemap.id + '-' + layer.id + '-' + index" - class="layer-item transition visible" + class="layer-item transition visible item list-group-item" :data-id="layer.id" > <p class="layer-handle-sort"> <i class="th icon"></i>{{ layer.title }} @@ -94,11 +105,18 @@ <script> import { mapUtil } from "@/assets/js/map-util.js"; +import Dropdown from "@/components/Dropdown.vue"; +import Sortable from 'sortablejs'; export default { name: "SidebarLayers", + components: { + Dropdown, + }, data() { return { + selectedQueryLayer:null, + activeBasemap:null, baseMaps: [], layers: [], expanded: false, @@ -111,8 +129,20 @@ export default { activateGroup(basemap) { this.baseMaps.forEach((basemap) => (basemap.active = false)); basemap.active = true; + this.activeBasemap=basemap; basemap.title += " "; //weird!! Force refresh this.addLayers(basemap); + + let mapOptions = localStorage.getItem('geocontrib-map-options') || {}; + mapOptions = mapOptions.length ? JSON.parse(mapOptions) : {}; + let project=this.$route.params.slug; + mapOptions[project] = { + ...mapOptions[project], + 'current-basemap-index': basemap.id + }; + localStorage.setItem('geocontrib-map-options', JSON.stringify(mapOptions)); + + }, updateOpacity(event, layer) { console.log(event.target.value, layer); @@ -122,7 +152,118 @@ export default { getOpacity(opacity) { return Math.round(parseFloat(opacity) * 100); }, + onQueryLayerChange(layer){ + console.log(layer); + this.selectedQueryLayer=layer.name; + }, + isQueryable(baseMap){ + let queryableLayer=baseMap.layers.filter(l => l.queryable === true); + return queryableLayer.length>0; + }, + onlayerMove(event){ + console.log(event) + // Get the names of the current layers in order. + const currentLayersNamesInOrder = Array.from(document.getElementsByClassName('layer-item transition visible')).map(el => el.children[0].innerText); + + // Create an array to put the layers in order. + let movedLayers = []; + + for (const layerName of currentLayersNamesInOrder) { + movedLayers.push(this.activeBasemap.layers.filter(el => el.title === layerName)[0]); + } + + // Remove existing layers undefined + movedLayers = movedLayers.filter(function(x) { + return x !== undefined; + }); + const eventOrder = new CustomEvent('change-layers-order', { + detail: { + layers: movedLayers + } + }) + document.dispatchEvent(eventOrder); + // Save the basemaps options into the localstorage + console.log(this.baseMaps) + this.setLocalstorageMapOptions(this.baseMaps) + }, + setLocalstorageMapOptions(basemaps) { + let mapOptions = localStorage.getItem('geocontrib-map-options') || {}; + mapOptions = mapOptions.length ? JSON.parse(mapOptions) : {}; + let project=this.$route.params.slug; + mapOptions[project] = { + ...mapOptions[project], + 'basemaps': basemaps + }; + localStorage.setItem('geocontrib-map-options', JSON.stringify(mapOptions)); + }, + + initSortable(){ + this.baseMaps.forEach((basemap) => { + new Sortable(document.getElementById(`list-${basemap.id}`), { + animation: 150, + handle: '.layer-handle-sort', // The element that is active to drag + ghostClass: 'blue-background-class', + dragClass: 'white-opacity-background-class', + onEnd: this.onlayerMove.bind(this) + }); + }); + }, + + // Check if there are changes in the basemaps settings. Changes are detected if: + // - one basemap has been added or deleted + // - one layer has been added or deleted to a basemap + areChangesInBasemaps(basemapFromServer, basemapFromLocalstorage={}) { + let isSameBasemaps = false; + let isSameLayers = true; + let isSameTitles = true; + + // Compare the length and the id values of the basemaps + const idBasemapsServer = basemapFromServer.map(b => b.id).sort(); + const idBasemapsLocalstorage = basemapFromLocalstorage.length ? basemapFromLocalstorage.map(b => b.id).sort() : {}; + isSameBasemaps = (idBasemapsServer.length === idBasemapsLocalstorage.length + && idBasemapsServer.every((value, index) => value === idBasemapsLocalstorage[index])) + + // For each basemap, compare the length and id values of the layers + outer_block: { + for(let basemapServer of basemapFromServer) { + let idLayersServer = basemapServer.layers.map(b => b.id).sort(); + if (basemapFromLocalstorage.length){ + for (let basemapLocalstorage of basemapFromLocalstorage) { + if (basemapServer.id === basemapLocalstorage.id) { + let idLayersLocalstorage = basemapLocalstorage.layers.map(b => b.id).sort(); + isSameLayers = (idLayersServer.length === idLayersLocalstorage.length + && idLayersServer.every((value, index) => value === idLayersLocalstorage[index])); + if (!isSameLayers) { + break outer_block; + } + } + } + } + } + + // Compare basemaps titles + const titlesBasemapsServer = basemapFromServer.map(b => b.title).sort(); + const titlesBasemapsLocalstorage = basemapFromLocalstorage.length ? basemapFromLocalstorage.map(b => b.title).sort() : {}; + + isSameTitles = titlesBasemapsServer.every((title, index) => title === titlesBasemapsLocalstorage[index]); + + if (!isSameTitles) { + break outer_block; + } + } + return !(isSameBasemaps && isSameLayers && isSameTitles); + }, + getQueryableLayers(baseMap){ + let queryableLayer=baseMap.layers.filter(l => l.queryable === true); + return queryableLayer.map(x=>{ + return { + name:x.title, + value:x + } + }); + }, addLayers(baseMap) { + baseMap.layers.forEach((layer) => { var layerOptions = this.layers.find((l) => l.id == layer.id); console.log(layerOptions); @@ -137,9 +278,33 @@ export default { }, mounted() { this.baseMaps = this.$store.state.map.basemaps; + let project=this.$route.params.slug; + const mapOptions = JSON.parse(localStorage.getItem('geocontrib-map-options')) || {}; + if (mapOptions + && mapOptions[project]) { + + // If already in the storage, we need to check if the admin did some + // modification in the basemaps on the server side. The rule is: if one layer has been added + // or deleted in the server, then we reset the localstorage. + const baseMapsFromLocalstorage = mapOptions[project]['basemaps']; + const areChanges = this.areChangesInBasemaps(this.baseMaps, baseMapsFromLocalstorage); + + if (areChanges) { + mapOptions[project] = { + 'map-options': this.baseMaps, + 'current-basemap-index': 0, + }; + localStorage.setItem('geocontrib-map-options', JSON.stringify(mapOptions)); + } else { + this.baseMaps = baseMapsFromLocalstorage; + } + } + + this.layers = this.$store.state.map.layers; if (this.baseMaps.length > 0) { this.baseMaps[0].active = true; + this.activeBasemap=this.baseMaps[0]; this.addLayers(this.baseMaps[0]); } else { mapUtil.addLayers( @@ -148,6 +313,11 @@ export default { this.$store.state.configuration.DEFAULT_BASE_MAP.OPTIONS ); } + setTimeout(this.initSortable.bind(this),1000) + + + + }, }; </script> diff --git a/src/components/project/ProjectMappingContextLayer.vue b/src/components/project/ProjectMappingContextLayer.vue index 12bbd9f74c32e8c32a4cdc3a3e8b02f42850e638..9a766ca27c1a80d0ffb2ec5f67187d951db2179b 100644 --- a/src/components/project/ProjectMappingContextLayer.vue +++ b/src/components/project/ProjectMappingContextLayer.vue @@ -68,20 +68,25 @@ export default { selectedLayer: { get() { const matchingLayer = this.retrieveLayer(this.layer.title); - return { - name: matchingLayer ? matchingLayer.service : "", - value: this.layer ? this.layer.title : "", - }; + if (matchingLayer != undefined){ + return { + name: matchingLayer != undefined ? matchingLayer.service : "", + value: this.layer ? this.layer.title : "", + }; + } + return [] }, set(newValue) { - const matchingLayer = this.retrieveLayer(this.layer.title); - this.updateLayer({ - ...this.layer, - service: newValue.name, - title: newValue.value, - id: matchingLayer.id, - }); + const matchingLayer = this.retrieveLayer(newValue.title); + if (matchingLayer != undefined){ + this.updateLayer({ + ...this.layer, + service: newValue.name, + title: newValue.value, + id: matchingLayer.id, + }); + } }, }, @@ -96,7 +101,7 @@ export default { availableLayers: function () { return this.layers.map((el) => { - return { name: el.service, value: el.title }; + return { id: el.id, name: el.service, value: el.title, title: el.title }; }); }, @@ -109,7 +114,7 @@ export default { methods: { retrieveLayer(title) { - return this.layers.find((el) => el.title === title); + return this.layers.find((el) => el.title === title); }, removeLayer() { @@ -130,12 +135,14 @@ export default { mounted() { const matchingLayer = this.retrieveLayer(this.layer.title); - this.updateLayer({ - ...this.layer, - service: matchingLayer.service, - title: matchingLayer.title, - id: matchingLayer.id, - }); + if (matchingLayer != undefined){ + this.updateLayer({ + ...this.layer, + service: matchingLayer.service, + title: matchingLayer.title, + id: matchingLayer.id, + }); + } }, }; </script> \ No newline at end of file diff --git a/src/main.js b/src/main.js index 0dc0f54a41b1aa150371eb2267f8eb8d4128990d..22fdd2b3b48d5fa8a340a744abf90bded82bf99a 100644 --- a/src/main.js +++ b/src/main.js @@ -14,7 +14,8 @@ Vue.config.productionTip = false axios.get("/config/config.json") .then((response) => { if (response && response.status === 200) { - store.commit("SET_CONFIG", response.data) + store.commit("SET_CONFIG", response.data); + window.proxy_url=response.data.VUE_APP_DJANGO_API_BASE+"proxy/" axios.all([store.dispatch("USER_INFO"), store.dispatch("GET_ALL_PROJECTS"), store.dispatch("GET_STATIC_PAGES"), diff --git a/src/services/feature-api.js b/src/services/feature-api.js index 988a0a3a3fca8c0923295ac0e0de67700aed09ef..49e40e12cb27cfe4043577a7cc66ca307cbf64a7 100644 --- a/src/services/feature-api.js +++ b/src/services/feature-api.js @@ -4,6 +4,20 @@ import store from '../store' const baseUrl = store.state.configuration.VUE_APP_DJANGO_API_BASE; const featureAPI = { + async getFeatureEvents(featureId) { + const response = await axios.get( + `${baseUrl}features/${featureId}/events/` + ); + if ( + response.status === 200 && + response.data + ) { + return response.data; + } else { + return null; + } + }, + async getFeatureAttachments(featureId) { const response = await axios.get( `${baseUrl}features/${featureId}/attachments/` diff --git a/src/store/index.js b/src/store/index.js index cb00de2006643cd9aeaa14a981a9b12726994edb..5035d682a0b278a2279c895d9deb3318b400fd56 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -3,7 +3,6 @@ const axios = require("axios"); import Vue from 'vue'; import Vuex from 'vuex'; import router from '../router' -//import modules from './modules'; import feature_type from "./modules/feature_type" import feature from "./modules/feature" import map from "./modules/map" @@ -29,6 +28,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 +40,7 @@ export default new Vuex.Store({ error: null, logged: false, user: false, - configuration:null, + configuration: null, project_slug: null, projects: [], last_comments: [], @@ -51,7 +52,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,54 +73,55 @@ 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 + project_user: state => state.projects.filter(projet => projet.creator === state.user.id), }, actions: { - async GET_ALL_PROJECTS({ commit }) { + 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 + return 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) => { throw error; }); }, - async GET_STATIC_PAGES({ commit }) { - await axios + GET_STATIC_PAGES({ commit }) { + return axios .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}flat-pages/`) .then((response) => (commit("SET_STATIC_PAGES", response.data))) .catch((error) => { @@ -135,50 +137,58 @@ 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); }); } }, - USER_INFO({ commit }) { - axios - .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}user_info/`) - .then((response) => { - if (response && response.status === 200) { - const user = response.data.user; - commit("SET_USER", user); - window.localStorage.setItem("user", JSON.stringify(user)); //? toujours nécessaire ? - } - }) - .catch(() => { - router.push({ name: "login" }); - }); + USER_INFO({ state, commit }) { + if (!state.user) { + axios + .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}user_info/`) + .then((response) => { + if (response && response.status === 200) { + const user = response.data.user; + commit("SET_USER", user); + //window.localStorage.setItem("user", JSON.stringify(user)); // ? nécessaire ? + } + }) + .catch(() => { + router.push({ name: "login" }); + }); + } }, - LOGOUT({ commit }) { // ? logout se fait bien dans django ? + LOGOUT({ commit, dispatch }) { + // const pageNoRedirect = ["liste-signalements", "details-type-signalement", "details-signalement", "project_detail", "mentions", "aide", "index"] axios .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}logout/`) .then((response) => { if (response && response.status === 200) { - commit("SET_USER", false); // ? better false or null + commit("SET_USER", false); commit("SET_USER_LEVEL_PROJECTS", null); + dispatch("GET_USER_LEVEL_PERMISSIONS"); + // if (!pageNoRedirect.includes(router.history.current.name)) router.push("/"); + router.push("/"); } }) .catch((error) => { @@ -191,19 +201,20 @@ 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) => { throw error; }); }, + GET_USER_LEVEL_PROJECTS({ commit }) { - axios + return axios .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) => { @@ -212,11 +223,11 @@ export default new Vuex.Store({ }, GET_USER_LEVEL_PERMISSIONS({ commit }) { - axios + return axios .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) => { @@ -224,70 +235,26 @@ export default new Vuex.Store({ }); }, - GET_PROJECT_INFO({ commit, dispatch }, slug) { + GET_PROJECT_INFO({ state, commit, dispatch }, slug) { commit("SET_PROJECT_SLUG", slug); dispatch("GET_PROJECT_LAST_MESSAGES", slug); dispatch("feature_type/GET_PROJECT_FEATURE_TYPES", slug); dispatch("feature/GET_PROJECT_FEATURES", slug); - dispatch("map/GET_BASEMAPS", slug); + if (state.user) dispatch("map/GET_BASEMAPS", slug); }, GET_PROJECT_LAST_MESSAGES({ commit }, project_slug) { - axios + return axios .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) => { throw error; }); }, - - /* GET_PROJECT_FEATURES({ commit }, project_slug) { - axios - .get(`${DJANGO_API_BASE}projects/${project_slug}/feature`) - .then((response) => commit("feature/SET_FEATURES", response.data.features)) - .catch((error) => { - throw error; - }); - }, */ - /* GET_PROJECT({ commit }, project_slug) { - axios - .get(`${DJANGO_API_BASE}projects/${project_slug}/project`) - .then((response) => commit("SET_PROJECT", response.data)) - .catch((error) => { - 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; - - if (document.cookie && document.cookie !== "") { - const cookies = document.cookie.split(";"); - for (let i = 0; i < cookies.length; i++) { - const cookie = cookies[i].trim(); - - // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) === name + "=") { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - - break; - } - } - } - commit("SET_COOKIE", cookieValue); - }, */ - } }); diff --git a/src/store/modules/feature.js b/src/store/modules/feature.js index 03c124a1b8bb0345fcf56332e85c5e2bfd6b2ca2..60df940e2d32e0468710621e3c9e1bf6c21693ef 100644 --- a/src/store/modules/feature.js +++ b/src/store/modules/feature.js @@ -67,9 +67,9 @@ const feature = { getters: { }, actions: { - GET_PROJECT_FEATURES({ commit, /* dispatch */ }, project_slug) { + GET_PROJECT_FEATURES({ commit, rootState }, project_slug) { axios - .get(`${this.state.configuration.VUE_APP_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; @@ -103,14 +103,15 @@ const feature = { if (routeName === "editer-signalement") { axios - .put(`${this.state.configuration.VUE_APP_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({ - name: "project_detail", + name: "details-signalement", params: { - slug: rootState.project_slug, - message: "Le signalement a été mis à jour", + slug_type_signal: rootState.feature_type.current_feature_type_slug, + slug_signal: state.form.feature_id, + //message: "Le signalement a été mis à jour", }, }); } @@ -120,15 +121,16 @@ const feature = { }); } else { axios - .post(`${this.state.configuration.VUE_APP_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) router.push({ - name: "project_detail", + name: "details-signalement", params: { - slug: rootState.project_slug, - message: "Le signalement a été crée", + slug_type_signal: rootState.feature_type.current_feature_type_slug, + slug_signal: response.data.id, + //message: "Le signalement a été crée", }, }); } @@ -139,7 +141,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); @@ -149,7 +151,7 @@ const feature = { } formdata.append("data", JSON.stringify(data)); axios - .post(`${this.state.configuration.VUE_APP_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) @@ -167,14 +169,23 @@ const feature = { .getlinked_features(featureId) .then((data) => commit("SET_FEATURE_LINKS", data)); } */ - //DELETE_FEATURE({ state }, feature_slug) { - //console.log("Deleting feature:", feature_slug, state) + DELETE_FEATURE({ state, rootState }, feature_id) { + console.log("Deleting feature:", feature_id, state) + const url = `${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/${feature_id}`; + axios + .delete(url, { + }) + .then() + .catch(() => { + return false; + }); + }, // POST_COMMENT({ state }, data) { //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); @@ -192,4 +203,4 @@ const feature = { } -export default feature \ No newline at end of file +export default feature diff --git a/src/store/modules/feature_type.js b/src/store/modules/feature_type.js index f4148c1783d7d4c6ecc97ea7186b0bb418d12296..68ef9d09cb92ca9acb634d1faf87b83cb28593c4 100644 --- a/src/store/modules/feature_type.js +++ b/src/store/modules/feature_type.js @@ -71,7 +71,7 @@ const feature_type = { }, actions: { GET_PROJECT_FEATURE_TYPES({ commit }, project_slug) { - axios + return axios .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${project_slug}/feature-types/`) .then((response) => commit("SET_FEATURE_TYPES", response.data.feature_types)) .catch((error) => { diff --git a/src/store/modules/map.js b/src/store/modules/map.js index f29ef9f4d128729e027c6364005edcad240fce6c..719eb697116a3ff7bc211f4bd0a8093de6ab34fc 100644 --- a/src/store/modules/map.js +++ b/src/store/modules/map.js @@ -88,7 +88,7 @@ const map = { actions: { GET_LAYERS({ commit }) { - axios + return axios .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}layers/`) .then((response) => (commit("SET_LAYERS", response.data))) .catch((error) => { @@ -97,9 +97,11 @@ const map = { }, GET_BASEMAPS({ commit }, project_slug) { - axios + return axios .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}base-maps/?project__slug=${project_slug}`) - .then((response) => (commit("SET_BASEMAPS", response.data))) + .then((response) => { + commit("SET_BASEMAPS", response.data) + }) .catch((error) => { throw error; }); @@ -153,43 +155,59 @@ const map = { SAVE_BASEMAPS({ state, rootState, dispatch }, newBasemapIds) { - for (let basemap of state.basemaps) { - basemap["project"] = rootState.project_slug - // TODO: différencier PUT & POST - console.log(newBasemapIds.includes(basemap.id), newBasemapIds, basemap.id); - if (newBasemapIds.includes(basemap.id)) { - axios - .post(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}base-maps/`, basemap) - .then((response) => (console.log(response.data))) - .catch((error) => { - throw error; - }); - } else { - axios - .put(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}base-maps/${basemap.id}/`, basemap) - .then((response) => (console.log(response.data))) - .catch((error) => { - throw error; - }); + return new Promise((resolve, reject) => { + for (let basemap of state.basemaps) { + basemap["project"] = rootState.project_slug + // TODO: différencier PUT & POST + if (newBasemapIds.includes(basemap.id)) { + axios + .post(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}base-maps/`, basemap) + .then((response) => { + resolve(response); + }) + .catch((error) => { + reject(error); + throw error; + }); + } else { + axios + .put(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}base-maps/${basemap.id}/`, basemap) + .then((response) => { + resolve(response); + }) + .catch((error) => { + reject(error); + throw error; + }); + } } - } - //* delete in the backend the basemaps that was rewoved from the front - for (let basemapId of state.basemapsToDelete) { - dispatch("DELETE_BASEMAP", basemapId); - } + //* delete in the backend the basemaps that was rewoved from the front + for (let basemapId of state.basemapsToDelete) { + dispatch("DELETE_BASEMAP", basemapId) + .then((response) =>{ + resolve(response); + }); + } + state.basemapsToDelete = [] + }); }, DELETE_BASEMAP({ commit }, basemapId) { - axios - .delete(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}base-maps/`, basemapId) + return new Promise((resolve, reject) => { + let url = `${this.state.configuration.VUE_APP_DJANGO_API_BASE}base-maps/` + basemapId + axios + .delete(url) .then((response) => { if (response && response.status === 200) { commit("REMOVE_BASEMAP_ID_TO_DELETE", basemapId) } + resolve(response); }) .catch((error) => { + reject(error); throw error; }); + }); } }, } diff --git a/src/views/My_account.vue b/src/views/My_account.vue index c05a6d27d60e16d6ba70681c7620b924ecc8ba88..71f80d9e272087dbeb08401e63c94ac73219d8eb 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 8b2c516d33814f65220cc0b7a795a4c281fc5060..e24f5574b2dc64d6cdfa7a363cb68eb4b8fbf7b5 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> @@ -235,17 +233,15 @@ </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" + <div v-frag v-if="event.related_comment.attachment"> + <br /><a + :href=" + DJANGO_BASE_URL + event.related_comment.attachment.url + " + tarrget="_blank" + ><i class="paperclip fitted icon"></i> + {{ event.related_comment.attachment.title }}</a > - <br /><a :href="att.url" tarrget="_blank" - ><i class="paperclip fitted icon"></i> - {{ att.title }}</a - > - </div> </div> </div> </div> @@ -265,23 +261,13 @@ </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" method="POST" enctype="multipart/form-data" > - <!-- <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 @@ -312,7 +298,6 @@ id="attachment_file" @change="getAttachmentFileData($event)" /> - <!-- {{ comment_form.attachment_file.errors }} --> </div> <div class="field"> <input @@ -374,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"); @@ -416,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; @@ -459,12 +445,27 @@ export default { this.comment_form.attachment_file.value = shortName; this.comment_form.title.value = shortName; }, + goBackToProject(message) { + this.$router.push({ + name: "project_detail", + params: { + slug: this.$store.state.project_slug, + message, + }, + }); + }, deleteFeature() { this.$store.dispatch( "feature/DELETE_FEATURE", - this.$route.params.slug_signal - ); + this.feature.feature_id + ) + .then(() => { + this.$store.dispatch( + "feature/GET_PROJECT_FEATURES" + ) + this.goBackToProject(); + }); }, initMap() { @@ -539,6 +540,12 @@ export default { }); }, + getFeatureEvents() { + featureAPI + .getFeatureEvents(this.$route.params.slug_signal) + .then((data) => (this.events = data)); + }, + getFeatureAttachments() { featureAPI .getFeatureAttachments(this.$route.params.slug_signal) @@ -562,7 +569,7 @@ export default { "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG", this.$route.params.slug_type_signal ); - + this.getFeatureEvents(); this.getFeatureAttachments(); this.getLinkedFeatures(); }, diff --git a/src/views/feature/Feature_edit.vue b/src/views/feature/Feature_edit.vue index 892a1e914c6bef03326823e1dc10b3e8606cee0e..da49900b25a1c4df3df16735e001afe25307738a 100644 --- a/src/views/feature/Feature_edit.vue +++ b/src/views/feature/Feature_edit.vue @@ -1,5 +1,6 @@ <template> <div v-frag> + <script type="application/javascript" :src="baseUrl+'/resources/leaflet-control-geocoder-1.13.0/Control.Geocoder.js'"></script> <div class="fourteen wide column"> <h1 v-if="feature && currentRouteName === 'editer-signalement'"> Mise à jour du signalement "{{ feature.title || feature.feature_id }}" @@ -41,7 +42,7 @@ form.status.label }}</label> <Dropdown - :options="statusChoices" + :options="statusChoicesFilter" :selected="selected_status.name" :selection.sync="selected_status" /> @@ -226,7 +227,7 @@ <script> import frag from "vue-frag"; -import { mapGetters, mapState } from "vuex"; +import { mapState } from "vuex"; import FeatureAttachmentForm from "@/components/feature/FeatureAttachmentForm"; import FeatureLinkedForm from "@/components/feature/FeatureLinkedForm"; import FeatureExtraForm from "@/components/feature/FeatureExtraForm"; @@ -264,6 +265,8 @@ export default { data() { return { map: null, + feature_type:null, + baseUrl:this.$store.state.configuration.BASE_URL, file: null, showGeoRef: false, showGeoPositionBtn: true, @@ -271,13 +274,24 @@ export default { erreurUploadMessage: null, attachmentDataKey: 0, linkedDataKey: 0, + statusChoicesFilter:[], statusChoices: [ { name: "Brouillon", value: "draft", }, - { name: "Publié", value: "published" }, - { name: "Archivé", value: "archived" }, + { + name: "Publié", + value: "published" + }, + { + name: "Archivé", + value: "archived" + }, + { + name: "En attente de publication", + value: "pending" + }, ], form: { title: { @@ -316,7 +330,7 @@ export default { }, computed: { - ...mapState(["project"]), + // ...mapState(["project"]), ...mapState("map", ["basemaps"]), ...mapState("feature", [ "attachmentFormset", @@ -325,8 +339,6 @@ export default { "extra_form", "linked_features", ]), - ...mapGetters("feature_type", ["feature_type"]), - field_title() { if (this.feature_type) { if (this.feature_type.title_optional) { @@ -358,11 +370,6 @@ export default { }, watch: { - feature_type() { - this.onFeatureTypeLoaded(); - this.initExtraForms(); - }, - feature(newValue) { if (this.$route.name === "editer-signalement") { this.initForm(); @@ -372,6 +379,20 @@ export default { }, methods: { + makeStatusChoicesFilter(){ + let newStatusChoices = this.statusChoices + if (this.project){ + if (!this.project.moderation){ + newStatusChoices = [] + this.statusChoices.forEach(function(status) { + if (status.value !== 'pending') { + newStatusChoices.push(status) + } + }); + } + } + this.statusChoicesFilter = newStatusChoices + }, initForm() { if (this.currentRouteName === "editer-signalement") { for (let key in this.feature) { @@ -430,12 +451,30 @@ export default { "Content-Type": "multipart/form-data", }, }) - .then(function () { - console.log("SUCCESS!!"); + .then(function (response) { + console.log("SUCCESS!!",response.data); + if(response.data.geom.indexOf('POINT')>=0){ + let regexp=/POINT\s\((.*)\s(.*)\)/; + var arr = regexp.exec(response.data.geom); + + let json={ "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [arr[1], arr[2]] + }, + "properties": { + } + }; + self.updateMap(self.map, json) + self.updateGeomField(json) + // Set Attachment + //self.addAttachment(self.file) + } + }) - .catch(function () { + .catch(function (response) { console.log("FAILURE!!"); - self.erreurUploadMessage = "FAILURE!!"; + self.erreurUploadMessage = response.data.message; }); }, @@ -552,16 +591,6 @@ export default { return isValid; }, - goBackToProject(message) { - this.$router.push({ - name: "project_detail", - params: { - slug: this.$store.state.project_slug, - message, - }, - }); - }, - postForm() { let is_valid = true; if (!this.feature_type.title_optional) { @@ -792,8 +821,6 @@ export default { }, initMap() { - //console.log(drawnItems); - //console.log(configuration); var mapDefaultViewCenter = this.$store.state.configuration.DEFAULT_MAP_VIEW.center; var mapDefaultViewZoom = @@ -864,8 +891,10 @@ export default { created() { if (!this.project) { - this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug); + this.project = this.$store.state.projects.find((project) => project.slug === this.$store.state.project_slug); + this.makeStatusChoicesFilter(); } + this.$store.commit( "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG", this.$route.params.slug_type_signal @@ -882,8 +911,25 @@ export default { }, mounted() { - this.initForm(); - this.initMap(); + let ftSlug=this.$route.params.slug_type_signal; + this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug).then(data=>{ + console.log(data) + this.initForm(); + this.initMap(); + this.feature_type=this.$store.state.feature_type.feature_types.find( + (el) => el.slug === ftSlug + ); + this.onFeatureTypeLoaded(); + this.initExtraForms(); + + setTimeout( + function () { + mapUtil.addGeocoders(this.$store.state.configuration); + }.bind(this), 1000); + }) + + + }, }; </script> diff --git a/src/views/feature/Feature_list.vue b/src/views/feature/Feature_list.vue index 8a2764cc2e69f5e25edc37309d966c198239711d..ad790844bbaefd7005a43b0484d01e1c94874490 100644 --- a/src/views/feature/Feature_list.vue +++ b/src/views/feature/Feature_list.vue @@ -1,11 +1,21 @@ <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"> <h1>Signalements</h1> </div> <div class="twelve wide column"> + <div class="ui dimmer" :class="[ { active: featureLoading }]"> + <div class="ui large text loader">Chargement</div> + </div> <div class="ui secondary menu"> <a @click="showMap = true" @@ -28,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=" @@ -69,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> @@ -124,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" @@ -143,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 @@ -206,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 }} @@ -221,7 +269,7 @@ slug_signal: feature.properties.slug || feature.id, }, }" - >{{ getFeatureDisplayName(feature)}}</router-link + >{{ getFeatureDisplayName(feature) }}</router-link > </td> <td class="center"> @@ -229,7 +277,7 @@ {{ feature.properties.updated_on }} </td> <td class="center" v-if="user"> - {{ feature.properties.display_creator }} + {{ feature.properties.creator.username }} </td> </tr> <tr v-if="getFilteredFeatures().length === 0" class="odd"> @@ -239,7 +287,7 @@ </tr> </tbody> </table> - <div + <div class="dataTables_info" id="table-features_info" role="status" @@ -247,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" @@ -270,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', @@ -297,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> @@ -322,8 +365,6 @@ <script> import { mapGetters, mapState } from "vuex"; - -import L from "leaflet"; import { mapUtil } from "@/assets/js/map-util.js"; import SidebarLayers from "@/components/map-layers/SidebarLayers"; import Dropdown from "@/components/Dropdown.vue"; @@ -373,15 +414,16 @@ export default { title: null, }, pagination: { - pagesize:15, + pagesize: 15, start: 0, end: 14, }, - sort:{ - column:'', - ascending:true + sort: { + column: "", + ascending: true, }, geojsonFeatures:[], + featureLoading:false, filterStatus:null, filterType:null, baseUrl:this.$store.state.configuration.BASE_URL, @@ -395,27 +437,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(); } }) @@ -423,93 +463,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(); @@ -525,7 +563,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 ); @@ -544,15 +582,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) { @@ -566,9 +603,8 @@ 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); const featureType = urlParams.get("feature_type"); const featureStatus = urlParams.get("status"); @@ -586,40 +622,11 @@ 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 - ]; - }, - addGeocoders(){ - let geocoder; - - // Get the settings.py variable SELECTED_GEOCODER_PROVIDER. This way avoids XCC attacks - const geocoderLabel = - this.$store.state.configuration.SELECTED_GEOCODER.PROVIDER; - if (geocoderLabel) { - const LIMIT_RESULTS = 5; - if ( - geocoderLabel === - this.$store.state.configuration.GEOCODER_PROVIDERS.ADDOK - ) { - geocoder = L.Control.Geocoder.addok({ limit: LIMIT_RESULTS }); - } else if ( - geocoderLabel === - this.$store.state.configuration.GEOCODER_PROVIDERS.PHOTON - ) { - geocoder = L.Control.Geocoder.photon(); - } else if ( - geocoderLabel === - this.$store.state.configuration.GEOCODER_PROVIDERS.NOMINATIM - ) { - geocoder = L.Control.Geocoder.nominatim(); - } - - L.Control.geocoder({ - placeholder: "Chercher une adresse...", - geocoder: geocoder, - }).addTo(this.map); - } + //* converting Set to an Array with spread "..." + ...new Set( + this.geojsonFeatures.map((el) => el.properties.feature_type.title) + ), //* use Set to eliminate duplicate values + ]; }, }, @@ -630,23 +637,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 @@ -654,29 +660,29 @@ export default { }); // --------- End sidebar events ---------- - console.log(this.$store.state.map.geojsonFeatures); if(this.$store.state.map.geojsonFeatures){ this.loadFeatures(this.$store.state.map.geojsonFeatures); } else{ const url=`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`; + this.featureLoading=true; axios.get(url) .then((response) => { this.loadFeatures(response.data.features); + this.featureLoading=false; }) .catch((error) => { + this.featureLoading=false; throw error; + }); } - setTimeout( function () { - this.addGeocoders(); - }.bind(this), 1000) - + mapUtil.addGeocoders(this.$store.state.configuration); + }.bind(this), 1000); - }, }; </script> @@ -692,7 +698,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 cd18f5ec2fa0f784d0cf5452bbaef8081649ba32..764d774b093e625b0703557700d1b0b7a049d793 100644 --- a/src/views/feature_type/Feature_type_detail.vue +++ b/src/views/feature_type/Feature_type_detail.vue @@ -29,7 +29,7 @@ <div class="ui basic segment"> <div class="ui horizontal tiny statistic"> <div class="value"> - {{ features.length }} + {{ feature_type_features.length }} </div> <div class="label"> Signalement{{ features.length > 1 ? "s" : "" }} @@ -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 () { @@ -222,8 +219,15 @@ export default { } return null; }, + + feature_type_features: function () { + return this.features.filter( + (el) => el.feature_type.slug === this.$route.params.feature_type_slug + ); + }, + lastFeatures: function () { - return this.features.slice(0, 5); + return this.feature_type_features.slice(0, 5); }, }, @@ -250,7 +254,7 @@ export default { }, exportFeatures() { const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature-type/${this.$route.params.feature_type_slug}/export/`; - console.log(url) + console.log(url); window.open(url); }, }, diff --git a/src/views/flatpages/Default.vue b/src/views/flatpages/Default.vue index af38296848360fd63ff2959cb145688a120ce05a..9c8443814c424080936686fbc796b77fc955070d 100644 --- a/src/views/flatpages/Default.vue +++ b/src/views/flatpages/Default.vue @@ -1,7 +1,6 @@ <template> <div v-if="flatpage" class="row"> <div class="ten wide column"> - {{ this.$route.params.url }} <h1>{{ flatpage.title }}</h1> <div v-html="flatpage.content"></div> </div> @@ -9,13 +8,17 @@ </template> <script> +import { mapState } from "vuex"; + export default { name: "Default", + computed: { + ...mapState(["staticPages"]), flatpage() { - if (this.$store.state.staticPages) { - return this.$store.state.staticPages.find( - (page) => page.url === this.$route.path + if (this.staticPages) { + return this.staticPages.find( + (page) => page.url === `/${this.$route.name}/` ); } return null; diff --git a/src/views/flatpages/with_right_menu.vue b/src/views/flatpages/with_right_menu.vue index 645789602cf33c1172b03956d752edc0d29ed58f..c04a20b34a1990deba6f54b482f0eb40f0ec27c7 100644 --- a/src/views/flatpages/with_right_menu.vue +++ b/src/views/flatpages/with_right_menu.vue @@ -23,18 +23,23 @@ </template> <script> +import { mapState } from "vuex"; + export default { name: "With_right_menu", + data() { return { sections: [], }; }, + computed: { + ...mapState(["staticPages"]), flatpage() { - if (this.$store.state.staticPages) { - return this.$store.state.staticPages.find( - (page) => page.url === this.$route.path + if (this.staticPages) { + return this.staticPages.find( + (page) => page.url === `/${this.$route.name}/` ); } return null; diff --git a/src/views/project/Project_detail.vue b/src/views/project/Project_detail.vue index 8bff78d9e0d9dbb71fe8672f0eeb4899fb0d4414..bff3fa9291819c0539257083011c7a90d960670e 100644 --- a/src/views/project/Project_detail.vue +++ b/src/views/project/Project_detail.vue @@ -9,6 +9,16 @@ <p><i class="check icon"></i> {{ tempMessage }}</p> </div> </div> + <div id="message_info" class="fullwidth"> + <div v-if="infoMessage" class="ui info message" style="text-align: left;"> + <div class="header"> + <i class="info circle icon"></i> Informations + </div> + <ul class="list"> + {{ infoMessage }} + </ul> + </div> + </div> <div class="row"> <div class="four wide middle aligned column"> @@ -37,8 +47,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 +58,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 +68,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 +80,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 +87,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 +112,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 +136,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 +157,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 +188,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 +335,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 +348,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 +393,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 +432,6 @@ </div> </div> </div> - </div> </template> @@ -437,7 +440,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"); @@ -464,22 +466,18 @@ export default { data() { return { + infoMessage: '', geojsonImport: [], fileToImport: { name: "", size: 0 }, 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"]), @@ -530,18 +528,30 @@ export default { suscribe: !this.is_suscriber, projectSlug: this.$route.params.slug, }) - .then((data) => (this.is_suscriber = data.is_suscriber)); + .then((data) => { + this.is_suscriber = data.is_suscriber; + this.isModalOpen = false; + if (this.is_suscriber) + this.infoMessage = 'Vous êtes maintenant abonné aux notifications de ce projet.'; + else + this.infoMessage = "Vous ne recevrez plus les notifications de ce projet."; + setTimeout(function(){ + this.infoMessage = ''; + }.bind(this), 3000); + }); }, }, created() { this.$store.dispatch("GET_PROJECT_INFO", this.slug); - projectAPI - .getProjectSubscription({ projectSlug: this.$route.params.slug }) - .then((data) => (this.is_suscriber = data.is_suscriber)); + if (this.user) { + projectAPI + .getProjectSubscription({ projectSlug: this.$route.params.slug }) + .then((data) => (this.is_suscriber = data.is_suscriber)); + } }, mounted() { - if (this.project) { + if (this.project && this.permissions.can_view_project) { this.$store.dispatch("map/INITIATE_MAP"); const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`; let self = this; diff --git a/src/views/project/Project_edit.vue b/src/views/project/Project_edit.vue index 72a6344c09054bf111b1fd2f44b4a4fe9b5e596c..8fefe1f87880bb0e53291585b512e651a6bd61eb 100644 --- a/src/views/project/Project_edit.vue +++ b/src/views/project/Project_edit.vue @@ -172,12 +172,11 @@ import Dropdown from "@/components/Dropdown.vue"; import { mapGetters } from "vuex"; -axios.defaults.headers.common['X-CSRFToken'] = (name => { - var re = new RegExp(name + "=([^;]+)"); - var value = re.exec(document.cookie); - return (value != null) ? unescape(value[1]) : null; - })('csrftoken'); - +axios.defaults.headers.common["X-CSRFToken"] = ((name) => { + var re = new RegExp(name + "=([^;]+)"); + var value = re.exec(document.cookie); + return value != null ? unescape(value[1]) : null; +})("csrftoken"); export default { name: "Project_edit", @@ -284,30 +283,28 @@ export default { postProjectThumbnail(projectSlug) { //* send img to the backend when feature_type is created - if (this.fileToImport.size > 0) { - let formData = new FormData(); - formData.append("file", this.fileToImport); - const url = - this.$store.state.configuration.VUE_APP_DJANGO_API_BASE + - "projects/" + - projectSlug + - "/thumbnail/"; - return axios - .put(url, formData, { - headers: { - "Content-Type": "multipart/form-data", - }, - }) - .then((response) => { - if (response && response.status === 200) { - //dispatch("GET_IMPORTS", feature_type_slug); // ? Besoin de vérifier le statut de l'import ? - this.goBackNrefresh(projectSlug); - } - }) - .catch((error) => { - throw error; - }); - } + let formData = new FormData(); + formData.append("file", this.fileToImport); + const url = + this.$store.state.configuration.VUE_APP_DJANGO_API_BASE + + "projects/" + + projectSlug + + "/thumbnail/"; + return axios + .put(url, formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }) + .then((response) => { + if (response && response.status === 200) { + //dispatch("GET_IMPORTS", feature_type_slug); // ? Besoin de vérifier le statut de l'import ? + this.goBackNrefresh(projectSlug); + } + }) + .catch((error) => { + throw error; + }); }, checkForm() { @@ -331,8 +328,6 @@ export default { async postForm() { if (!this.checkForm()) return; - // todo: check form - //let url = `${configuration.VUE_APP_DJANGO_API_BASE}projects/`; const projectData = { title: this.form.title, description: this.form.description, @@ -371,11 +366,12 @@ export default { ) .then((response) => { if (response && response.status === 200) { - //* send thumbnail after feature_type was created - if (this.fileToImport) + //* send thumbnail after feature_type was updated + if (this.fileToImport.size > 0) { this.postProjectThumbnail(this.project.slug); - } else { - this.goBackNrefresh(this.project.slug); + } else { + this.goBackNrefresh(this.project.slug); + } } }) .catch((error) => { diff --git a/src/views/project/Project_mapping.vue b/src/views/project/Project_mapping.vue index f7a0b2ec7de6d78074f1a13137cc291484b3f194..0a94b5a82f1f41a2ff965afbc1bb64a88b867b04 100644 --- a/src/views/project/Project_mapping.vue +++ b/src/views/project/Project_mapping.vue @@ -21,7 +21,7 @@ </a> </div> - <div class="ui"> + <div v-if="basemaps" class="ui"> <ProjectMappingBasemap v-for="basemap in basemaps" :key="basemap.id" @@ -70,7 +70,11 @@ export default { saveChanges() { // ToDo : check if values are filled - this.$store.dispatch("map/SAVE_BASEMAPS", this.newBasemapIds); + this.$store.dispatch("map/SAVE_BASEMAPS", this.newBasemapIds) + .then((res) => { + console.log('res', res) + }); + this.newBasemapIds = []; }, }, diff --git a/src/views/project/Project_members.vue b/src/views/project/Project_members.vue index 4ad7e25a33e03e953737276abc35048100cdc25c..4e88ca6367b4a5ada50ab7069aaf68c687968e35 100644 --- a/src/views/project/Project_members.vue +++ b/src/views/project/Project_members.vue @@ -9,7 +9,6 @@ enctype="multipart/form-data" class="ui form" > - <!-- {{ formset.non_form_errors }} --> <table class="ui red table"> <thead> <tr> @@ -18,25 +17,23 @@ </tr> </thead> <tbody> - <div v-frag v-for="member in projectMembers" :key="member.username"> - <tr> - <td> - {{ member.last_name }} {{ member.first_name }}<br /><i>{{ - member.username - }}</i> - </td> - <td> - <div class="required field"> - <Dropdown - :options="levelOptions" - :selected="member.userLevel" - :selection.sync="member.userLevel" - :search="true" - /> - </div> - </td> - </tr> - </div> + <tr v-for="member in projectMembers" :key="member.username"> + <td> + {{ member.user.last_name }} {{ member.user.first_name }}<br /><i + >{{ member.user.username }}</i + > + </td> + <td> + <div class="required field"> + <Dropdown + :options="levelOptions" + :selected="member.userLevel.name" + :selection.sync="member.userLevel" + :search="true" + /> + </div> + </td> + </tr> </tbody> </table> @@ -59,7 +56,6 @@ import frag from "vue-frag"; import { mapGetters } from "vuex"; import Dropdown from "@/components/Dropdown.vue"; - export default { name: "Project_members", @@ -70,52 +66,65 @@ export default { Dropdown, }, - computed: { - ...mapGetters(["project"]), - }, - data() { return { projectMembers: [], levelOptions: [ - "Utilisateur connecté", - "Contributeur", - "Modérateur", - "Administrateur projet", + { name: "Utilisateur connecté", value: "logged_user" }, + { name: "Contributeur", value: "contributor" }, + { name: "Modérateur", value: "moderator" }, + { name: "Administrateur projet", value: "admin" }, ], }; }, + + computed: { + ...mapGetters(["project"]), + }, + methods: { validateMembers() { - // const data = { - // slug: this.project.slug, - // data: this.projectMembers, - // }; - // console.log("validateMembers", data); - /* axios - .post(`${DJANGO_API_BASE}projet/${payload.slug}/utilisateurs/`, payload.data) + const data = this.projectMembers.map((member) => { + return { + user: member.user, + level: { + display: member.userLevel.name, + codename: member.userLevel.value, + }, + }; + }); + + axios + .put( + `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.project.slug}/utilisateurs/`, + data + ) .then((response) => { - const user = response.data.user; + if (response.status === 200) { + this.$store.dispatch("GET_USER_LEVEL_PROJECTS"); //* update user status in top right menu + } }) - .catch(() => { - router.push({ name: "login" }); - }); */ + .catch((error) => { + throw error; + }); }, + async fetchMembers() { return axios .get( `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/utilisateurs` ) - .then((response) => response.data.members) + .then((response) => response.data) .catch((error) => { throw error; }); }, + async populateMembers() { await this.fetchMembers().then((members) => { this.projectMembers = members.map((el) => { return { - userLevel: el.userLevel ? el.userLevel : this.levelOptions[0], + userLevel: { name: el.level.display, value: el.level.codename }, ...el, }; });