diff --git a/src/components/Feature/Detail/FeatureHeader.vue b/src/components/Feature/Detail/FeatureHeader.vue index e88a92390520fd209e48e830a19a3a9a09ce0901..5f8aee17b915cfa90f33ac88c7f765ad1bcb67b5 100644 --- a/src/components/Feature/Detail/FeatureHeader.vue +++ b/src/components/Feature/Detail/FeatureHeader.vue @@ -4,47 +4,92 @@ <div class="content"> {{ currentFeature.title || currentFeature.feature_id }} <div class="ui icon right floated compact buttons"> + <span + v-if="featuresCount" + id="feature-count" + class="ui button tiny-margin basic" + > + {{ parseInt($route.query.offset) + 1 }} sur {{ featuresCount }} + </span> + <button + v-if="queryparams" + id="previous-feature" + :class="['ui button button-hover-green tiny-margin', { disabled: queryparams.previous < 0 }]" + data-tooltip="Voir le précédent signalement" + data-position="bottom center" + @click="toFeature('previous')" + > + <i + class="angle left fitted icon" + aria-hidden="true" + /> + </button> + <button + v-if="queryparams" + id="next-feature" + :class="[ + 'ui button button-hover-green tiny-margin', + { disabled: queryparams.next >= featuresCount } + ]" + data-tooltip="Voir le prochain signalement" + data-position="bottom center" + @click="toFeature('next')" + > + <i + class="angle right fitted icon" + aria-hidden="true" + /> + </button> + <router-link v-if="permissions && permissions.can_create_feature" + id="add-feature" :to="{ name: 'ajouter-signalement', params: { - slug_type_signal: $route.params.slug_type_signal, + slug_type_signal: $route.params.slug_type_signal || featureType.slug, }, }" - class="ui button button-hover-orange" + class="ui button button-hover-green tiny-margin" data-tooltip="Ajouter un signalement" - data-position="bottom left" + data-position="bottom center" > <i - class="plus fitted icon" + class="plus icon" aria-hidden="true" /> </router-link> + <router-link - v-if=" - (permissions && permissions.can_update_feature) || + v-if="slugSignal && + ((permissions && permissions.can_update_feature) || isFeatureCreator || - isModerator + isModerator) " + id="edit-feature" :to="{ name: 'editer-signalement', params: { - slug_signal: $route.params.slug_signal, - slug_type_signal: $route.params.slug_type_signal, + slug_signal: slugSignal, + slug_type_signal: $route.params.slug_type_signal || featureType.slug, }, }" - class="ui button button-hover-orange" + class="ui button button-hover-orange tiny-margin" + data-tooltip="Éditer le signalement" + data-position="bottom center" > <i class="inverted grey pencil alternate icon" aria-hidden="true" /> </router-link> + <a v-if="((permissions && permissions.can_update_feature) || isFeatureCreator) && isOnline" id="currentFeature-delete" - class="ui button button-hover-red" + class="ui button button-hover-red tiny-margin" + data-tooltip="Supprimer le signalement" + data-position="bottom right" @click="$emit('setIsCancelling')" > <i @@ -70,6 +115,21 @@ export default { name: 'FeatureHeader', + props: { + featuresCount : { + type: Number, + default: null, + }, + slugSignal: { + type: String, + default: '', + }, + featureType: { + type: Object, + default: () => {}, + }, + }, + computed: { ...mapState([ 'user', @@ -94,7 +154,34 @@ export default { isModerator() { return this.USER_LEVEL_PROJECTS && this.USER_LEVEL_PROJECTS[this.$route.params.slug] === 'Modérateur'; }, - } + queryparams() { + return this.$route.query.offset >= 0 ? { + previous: parseInt(this.$route.query.offset) - 1, + next: parseInt(this.$route.query.offset) + 1 + } : null; + }, + }, + + methods: { + toFeature(direction) { + this.$emit('tofeature', { + name: 'details-signalement-filtre', + params: { + slug_type_signal: this.currentFeature.feature_type.slug, + }, + query: { + ...this.$route.query, + offset: this.queryparams[direction] + } + }); + } + } }; </script> + +<style> +#next-feature { + margin-right: .5rem !important; +} +</style> \ No newline at end of file diff --git a/src/components/Feature/Detail/FeatureTable.vue b/src/components/Feature/Detail/FeatureTable.vue index 2c805e90276ecfc3833e2cbcaf59654c1193998d..bfb939f9ff74921a3d7b15359c69e37cf996db97 100644 --- a/src/components/Feature/Detail/FeatureTable.vue +++ b/src/components/Feature/Detail/FeatureTable.vue @@ -5,12 +5,12 @@ aria-describedby="Table des données du signalement" > <tbody> - <tr v-if="feature_type"> + <tr v-if="feature_type || featureType"> <td> <strong> Type de signalement </strong> </td> <td> - <FeatureTypeLink :feature-type="feature_type" /> + <FeatureTypeLink :feature-type="feature_type || featureType" /> </td> </tr> <tr @@ -93,7 +93,10 @@ <td v-if="link.feature_to.feature_type_slug" > - <a @click="pushNgo(link)">{{ link.feature_to.title }} </a> + <a + class="pointer" + @click="toFeature(link)" + >{{ link.feature_to.title }} </a> ({{ link.feature_to.display_creator }} - {{ link.feature_to.created_on }}) </td> @@ -112,7 +115,7 @@ import { statusChoices } from '@/utils'; export default { name: 'FeatureTable', - + components: { FeatureTypeLink }, @@ -125,6 +128,13 @@ export default { }, }, + props: { + featureType: { + type: Object, + default: () => {}, + }, + }, + computed: { ...mapState('feature', [ 'currentFeature', @@ -158,8 +168,14 @@ export default { }, methods: { - pushNgo(link) { - this.$emit('push-n-go', link); + toFeature(link) { + this.$emit('tofeature', { + name: 'details-signalement', + params: { + slug_type_signal: link.feature_to.feature_type_slug, + slug_signal: link.feature_to.feature_id, + }, + }); } } diff --git a/src/components/Project/FeaturesListAndMap/FeatureListTable.vue b/src/components/Project/FeaturesListAndMap/FeatureListTable.vue index b90db16cef95bc7011971ee8c67e80045a4e9944..abd24d46d52794b1d39f5f0c3af90c0d2d603146 100644 --- a/src/components/Project/FeaturesListAndMap/FeatureListTable.vue +++ b/src/components/Project/FeaturesListAndMap/FeatureListTable.vue @@ -214,11 +214,11 @@ <td class="dt-center"> <router-link :to="{ - name: 'details-signalement', + name: 'details-signalement-filtre', params: { slug_type_signal: feature.feature_type.slug, - slug_signal: feature.slug || feature.feature_id, }, + query: queryparams }" > {{ feature.title || feature.feature_id }} @@ -376,6 +376,10 @@ export default { type: Object, default: null, }, + queryparams: { + type: Object, + default: null, + } }, computed: { diff --git a/src/router/index.js b/src/router/index.js index a1c91228b4e5218d3837b5e85256d456d20d2cfe..8fe8e9d76322ca63af83c24d4c95a744059aec64 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -77,6 +77,11 @@ const routes = [ name: 'project_members', component: () => import('../views/Project/ProjectMembers.vue') }, + { + path: `/${projectBase}/:slug/signalement-filtre/`, + name: 'details-signalement-filtre', + component: () => import('../views/Feature/FeatureDetail.vue') + }, // * FEATURE TYPE { path: `/${projectBase}/:slug/type-signalement/ajouter/`, diff --git a/src/services/map-service.js b/src/services/map-service.js index 5cbfa07a91efe3ebaa54a1e188bec922a89c2652..0bb14fa3a7276e2d9ce9a354d9eb4e0e3e584e2a 100644 --- a/src/services/map-service.js +++ b/src/services/map-service.js @@ -480,10 +480,15 @@ const mapService = { olLayer.setZIndex(29); this.map.addLayer(olLayer); this.olLayer = olLayer; + this.drawSource = drawSource; this.featureTypes = featureTypes; // store featureTypes for popups return drawSource; }, + removeFeatures: function () { + this.drawSource.clear(); + }, + addMapEventListener: function (eventName, callback) { this.map.on(eventName, callback); }, diff --git a/src/views/Feature/FeatureDetail.vue b/src/views/Feature/FeatureDetail.vue index 00e6ecd143cdbb88040c330978503d3b41773820..0e6f85f9b94ea4fcf7303d30f94e94bb3d5a2f4e 100644 --- a/src/views/Feature/FeatureDetail.vue +++ b/src/views/Feature/FeatureDetail.vue @@ -7,14 +7,19 @@ <div class="row"> <div class="sixteen wide column"> <FeatureHeader + :features-count="featuresCount" + :slug-signal="slugSignal" + :feature-type="featureType" @setIsCancelling="isCanceling = true" + @tofeature="pushNgo" /> </div> </div> <div class="row"> <div class="eight wide column"> <FeatureTable - @pushNGo="pushNgo" + :feature-type="featureType" + @tofeature="pushNgo" /> </div> <div class="eight wide column"> @@ -52,8 +57,7 @@ </div> <div v-if="isCanceling" - class="ui dimmer modals transition visible active" - style="display: flex !important" + class="ui dimmer modals visible active" > <div :class="[ @@ -131,7 +135,10 @@ export default { }, }, events: [], + featureType: {}, + featuresCount: null, isCanceling: false, + slugSignal: '', }; }, @@ -148,41 +155,13 @@ export default { }, created() { - this.SET_CURRENT_FEATURE_TYPE_SLUG(this.$route.params.slug_type_signal); - this.getFeatureEvents(); - this.getFeatureAttachments(); - this.getLinkedFeatures(); + if (this.$route.params.slug_type_signal) { + this.SET_CURRENT_FEATURE_TYPE_SLUG(this.$route.params.slug_type_signal); + } }, mounted() { - this.DISPLAY_LOADER('Recherche du signalement'); - if (!this.project) { - // Chargements des features et infos projet en cas d'arrivée directe sur la page ou de refresh - axios.all([ - this.GET_PROJECT(this.$route.params.slug), - this.GET_PROJECT_INFO(this.$route.params.slug), - this.GET_PROJECT_FEATURE({ - project_slug: this.$route.params.slug, - feature_id: this.$route.params.slug_signal - })]) - .then(() => { - this.DISCARD_LOADER(); - this.initMap(); - }); - } - if (!this.currentFeature || this.currentFeature.feature_id !== this.$route.params.slug_signal) { - this.GET_PROJECT_FEATURE({ - project_slug: this.$route.params.slug, - feature_id: this.$route.params.slug_signal - }) - .then(() => { - this.DISCARD_LOADER(); - this.initMap(); - }); - } else { - this.DISCARD_LOADER(); - this.initMap(); - } + this.initPage(); }, beforeDestroy() { @@ -194,6 +173,9 @@ export default { 'DISPLAY_LOADER', 'DISCARD_LOADER' ]), + ...mapMutations('feature', [ + 'SET_CURRENT_FEATURE' + ]), ...mapMutations('feature-type', [ 'SET_CURRENT_FEATURE_TYPE_SLUG' ]), @@ -206,17 +188,46 @@ export default { 'GET_PROJECT_FEATURES' ]), - pushNgo(link) { - this.$router.push({ - name: 'details-signalement', - params: { - slug_type_signal: link.feature_to.feature_type_slug, - slug_signal: link.feature_to.feature_id, - }, - }); + async initPage() { + await this.getPageInfo(); + this.initMap(); + }, + + async getPageInfo() { + if (this.$route.params.slug_signal) { // if coming from the route with an id + this.slugSignal = this.$route.params.slug_signal; + } //* else it would be retrieve after fetchFilteredFeature with offset + this.DISPLAY_LOADER('Recherche du signalement'); + let promises = []; + //* Chargements des features et infos projet en cas d'arrivée directe sur la page ou de refresh + if (!this.project) { + promises.push( + this.GET_PROJECT(this.$route.params.slug), + this.GET_PROJECT_INFO(this.$route.params.slug), + ); + } + //* changement de requête selon s'il y a un id ou un offset + if (this.$route.query.offset >= 0) { + promises.push(this.fetchFilteredFeature()); + } else if (!this.currentFeature || this.currentFeature.feature_id !== this.slugSignal) { + promises.push( + this.GET_PROJECT_FEATURE({ + project_slug: this.$route.params.slug, + feature_id: this.slugSignal, + }) + ); + } + await axios.all(promises); this.getFeatureEvents(); this.getFeatureAttachments(); this.getLinkedFeatures(); + this.DISCARD_LOADER(); + }, + + async pushNgo(newEntry) { + this.$router.push(newEntry); //* update the params or queries in the route/url + await this.getPageInfo(); + mapService.removeFeatures(); this.addFeatureToMap(); }, @@ -243,6 +254,25 @@ export default { }); }, + fetchFilteredFeature() { + const queryString = new URLSearchParams(this.$route.query); + const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature-paginated/?limit=1&${queryString}`; + return featureAPI.getPaginatedFeatures(url) + .then((data) => { + if (data && data.results && data.results[0]) { + this.featuresCount = data.count; + this.previous = data.previous; + this.next = data.next; + this.SET_CURRENT_FEATURE(data.results[0]); + const { feature_id, feature_type } = data.results[0]; + this.slugSignal = feature_id; + this.featureType = feature_type; + return { feature_id }; + } + return; + }); + }, + initMap() { var mapDefaultViewCenter = this.$store.state.configuration.DEFAULT_MAP_VIEW.center; @@ -298,7 +328,7 @@ export default { addFeatureToMap() { const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/` + - `?feature_id=${this.$route.params.slug_signal}&output=geojson`; + `?feature_id=${this.slugSignal}&output=geojson`; axios .get(url) .then((response) => { @@ -319,19 +349,19 @@ export default { getFeatureEvents() { featureAPI - .getFeatureEvents(this.$route.params.slug_signal) + .getFeatureEvents(this.slugSignal) .then((data) => (this.events = data)); }, getFeatureAttachments() { featureAPI - .getFeatureAttachments(this.$route.params.slug_signal) + .getFeatureAttachments(this.slugSignal) .then((data) => (this.attachments = data)); }, getLinkedFeatures() { featureAPI - .getFeatureLinks(this.$route.params.slug_signal) + .getFeatureLinks(this.slugSignal) .then((data) => this.$store.commit('feature/SET_LINKED_FEATURES', data) ); diff --git a/src/views/Project/FeaturesListAndMap.vue b/src/views/Project/FeaturesListAndMap.vue index 5bb4915fd29e6a1c662930de4613b6dcfca42f39..f86c9244e9e77fd13ad692680ef7208400f8ab04 100644 --- a/src/views/Project/FeaturesListAndMap.vue +++ b/src/views/Project/FeaturesListAndMap.vue @@ -48,6 +48,7 @@ :features-count="featuresCount" :pagination="pagination" :sort="sort" + :queryparams="queryparams" @update:page="handlePageChange" @update:sort="handleSortChange" /> @@ -142,9 +143,10 @@ export default { paginatedFeatures: [], pagination: { ...initialPagination }, projectSlug: this.$route.params.slug, + queryparams: {}, showMap: true, sort: { - column: '', + column: 'updated_on', ascending: true, }, zoom: null, @@ -175,42 +177,6 @@ export default { }, }, - watch: { - /*map(newValue) { - if (newValue && this.paginatedFeatures && this.paginatedFeatures.length) { - if (this.currentLayer) { - this.map.removeLayer(this.currentLayer); - } - this.currentLayer = mapService.addFeatures( - this.paginatedFeatures, - {}, - this.feature_types - ); - } - },*/ - /*paginatedFeatures: { - deep: true, - handler(newValue, oldValue) { - if (newValue && newValue.length && newValue !== oldValue && this.map) { - if (this.currentLayer) { - this.map.removeLayer(this.currentLayer); - this.currentLayer = null; - } - this.currentLayer = mapService.addFeatures( - newValue, - {}, - this.feature_types - ); - } else if (newValue && newValue.length === 0) { - if (this.currentLayer) { - this.map.removeLayer(this.currentLayer); - this.currentLayer = null; - } - } - } - },*/ - }, - mounted() { if (!this.project) { // Chargements des features et infos projet en cas d'arrivée directe sur la page ou de refresh @@ -390,35 +356,40 @@ export default { }, buildQueryString() { - let urlParams = ''; + let queryString = ''; const typeFilter = this.getFeatureTypeSlug(this.form.type.selected); const statusFilter = this.form.status.selected.value; + this.queryparams['offset'] = this.pagination.start; if (typeFilter) { - urlParams += `&feature_type_slug=${typeFilter}`; + this.queryparams['feature_type_slug'] = typeFilter; + queryString += `&feature_type_slug=${typeFilter}`; } if (statusFilter) { - urlParams += `&status__value=${statusFilter}`; + this.queryparams['status__value'] = statusFilter; + queryString += `&status__value=${statusFilter}`; } if (this.form.title) { - urlParams += `&title=${this.form.title}`; + this.queryparams['title'] = this.form.title; + queryString += `&title=${this.form.title}`; } if (this.sort.column) { - urlParams += `&ordering=${ - this.sort.ascending ? '-' : '' - }${this.getAvalaibleField(this.sort.column)}`; + let ordering = `${this.sort.ascending ? '-' : ''}${this.getAvalaibleField(this.sort.column)}`; + this.queryparams['ordering'] = ordering; + queryString += `&ordering=${ordering}`; } - return urlParams; + return queryString; }, fetchPagedFeatures(newUrl) { let url = `${this.API_BASE_URL}projects/${this.projectSlug}/feature-paginated/?limit=${this.pagination.pagesize}&offset=${this.pagination.start}`; //* if receiving next & previous url (// todo : might be not used anymore, to check) if (newUrl && typeof newUrl === 'string') { - //newUrl = newUrl.replace("8000", "8010"); //* for dev uncomment to use proxy link + //newUrl = newUrl.replace("8000", "8010"); //* for dev uncomment when using proxy link url = newUrl; } const queryString = this.buildQueryString(); + url += queryString; this.$store.commit( 'DISPLAY_LOADER',