Newer
Older
Sébastien DA ROCHA
committed
<template>

Timothee P
committed
<div id="feature-detail">
<div class="row">

Timothee P
committed
:features-count="featuresCount"
:slug-signal="slugSignal"
:feature-type="featureType"
:fast-edition-mode="project.fast_edition_mode"

Timothee P
committed
:display-to-list-button="displayToListButton"

Timothee P
committed
:is-feature-creator="isFeatureCreator"
:can-edit-feature="canEditFeature"
@setIsDeleting="isDeleting = true"

Timothee P
committed
@tofeature="pushNgo"
</div>
Sébastien DA ROCHA
committed
</div>
<div class="row">

Timothee P
committed
:feature-type="featureType"
:fast-edition-mode="project.fast_edition_mode"

Timothee P
committed
:can-edit-feature="canEditFeature"

Timothee P
committed
@tofeature="pushNgo"
</div>
</div>
Sébastien DA ROCHA
committed
</div>
<div class="row">
<div class="eight wide column">
<FeatureAttachements
:attachments="attachments"
/>
Sébastien DA ROCHA
committed
</div>
<div class="eight wide column">
<FeatureComments
:events="events"
@fetchEvents="getFeatureEvents"
Sébastien DA ROCHA
committed
</div>
</div>
Sébastien DA ROCHA
committed
<div

Timothee P
committed
class="ui dimmer modals visible active"
Sébastien DA ROCHA
committed
>
<div
:class="[
'ui mini modal',
{ 'active visible': isDeleting },
]"
>
<div
v-if="isDeleting"
class="ui icon header"
>
<i
class="trash alternate icon"
aria-hidden="true"
/>
Supprimer le signalement
</div>
<div class="actions">
<button
type="button"
class="ui red compact fluid button"
@click="deleteFeature"
Sébastien DA ROCHA
committed
>
</div>
Sébastien DA ROCHA
committed
</div>
</div>
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<div
v-if="isLeaving"
class="ui dimmer modals visible active"
>
<div
:class="[
'ui mini modal',
{ 'active visible': isLeaving },
]"
>
<i
class="close icon"
aria-hidden="true"
@click="isLeaving = false"
/>
<div class="ui icon header">
<i
:class="[project.fast_edition_mode && hasUnsavedChange ? 'sign-out' : 'random', 'icon']"
aria-hidden="true"
/>
Abandonner {{
project.fast_edition_mode && hasUnsavedChange ?
'les modifications' :
'la vue signalement filtré'
}}
</div>
<div class="content">
{{
project.fast_edition_mode && hasUnsavedChange ?
'Les modifications apportées au signalement ne seront pas sauvegardées, continuer ?':
`Vous allez quittez la vue signalement filtré,
l\'ordre des signalements pourrait changer après édition d\'un signalement.`
}}
</div>
<div class="actions">
<button
type="button"
class="ui green compact button"
@click="stayOnPage"
>
Annuler
</button>
<button
type="button"
class="ui red compact button"
@click="leavePage"
>
Continuer
</button>
</div>
</div>
</div>
Sébastien DA ROCHA
committed
</div>
Pas de signalement correspondant trouvé
</div>
Sébastien DA ROCHA
committed
</div>
</template>
<script>

Timothee P
committed
import { mapState, mapActions, mapMutations, mapGetters } from 'vuex';
import featureAPI from '@/services/feature-api';
import FeatureHeader from '@/components/Feature/Detail/FeatureHeader';
import FeatureTable from '@/components/Feature/Detail/FeatureTable';
import FeatureAttachements from '@/components/Feature/Detail/FeatureAttachements';
import FeatureComments from '@/components/Feature/Detail/FeatureComments';
Sébastien DA ROCHA
committed
export default {
Sébastien DA ROCHA
committed
components: {
FeatureHeader,
FeatureTable,
FeatureAttachements,
FeatureComments

Timothee P
committed
beforeRouteEnter (to, from, next) {
// if the user edited the feature, coming from filtered features details browsing,
// display a button to turn back to the list view, in order to start again from the list
// because order changes after edition, depending the sort criteria
// in beforeRouteEnter, the component is not mounted and this doesn't exist yet, thus we store the value in window object
window.displayToListButton = false; // reinitialisation of the value
if (from.query.offset !== undefined) { // if a queryset for filtered features is stored in the route from
window.displayToListButton = true; // toggle the value to display the button
}
next(); // continue page loading
},
beforeRouteUpdate (to, from, next) {
if (this.hasUnsavedChange) {
this.confirmLeave(next); // prompt user that there is unsaved changes or that features order might change
} else {
next(); // continue navigation

Timothee P
committed
beforeRouteLeave (to, from, next) {
if (this.hasUnsavedChange || (from.query.offset >= 0 && to.name === 'editer-signalement')) {
this.confirmLeave(next); // prompt user that there is unsaved changes or that features order might change
} else {
next(); // continue navigation

Timothee P
committed
}
},
Sébastien DA ROCHA
committed
data() {
return {
Sébastien DA ROCHA
committed
comment_form: {
attachment_file: {
errors: null,
Sébastien DA ROCHA
committed
},
comment: {
id_for_label: 'add-comment',
html_name: 'add-comment',
errors: '',
Sébastien DA ROCHA
committed
value: null,
},
},
events: [],

Timothee P
committed
featureType: {},
featuresCount: null,
isDeleting: false,
isLeaving: false,

Timothee P
committed
slugSignal: '',

Timothee P
committed
displayToListButton: false,
Sébastien DA ROCHA
committed
};
},
computed: {

Timothee P
committed
...mapState([
'USER_LEVEL_PROJECTS',

Timothee P
committed
]),
'feature_types',
'feature_type',

Timothee P
committed
...mapGetters([
'permissions',
]),
hasUnsavedChange() {

Timothee P
committed
if (this.project.fast_edition_mode && this.form) {
if (this.form.title !== this.currentFeature.title) return true;
if (this.form.description.value !== this.currentFeature.description) return true;
if (this.form.status.value !== this.currentFeature.status) return true;
for (const xForm of this.$store.state.feature.extra_forms) {
const originalField = this.currentFeature.feature_data.find(el => el.label === xForm.label);
if (originalField && xForm.value !== originalField.value) return true;
}

Timothee P
committed
},
isFeatureCreator() {
if (this.currentFeature && this.user) {
return this.currentFeature.creator === this.user.id;
}
return false;
},
isModerator() {
return this.USER_LEVEL_PROJECTS && this.USER_LEVEL_PROJECTS[this.$route.params.slug] === 'Modérateur';
},
isAdministrator() {
return this.USER_LEVEL_PROJECTS && this.USER_LEVEL_PROJECTS[this.$route.params.slug] === 'Administrateur projet';
},

Timothee P
committed
canEditFeature() {
return (this.permissions && this.permissions.can_update_feature) ||
this.isFeatureCreator ||
this.isModerator ||
this.user.is_superuser;
},
canDeleteFeature() {
return (this.permissions && this.permissions.can_delete_feature && this.isFeatureCreator) ||
this.isFeatureCreator ||
this.isModerator ||
this.isAdministrator ||
this.user.is_superuser;
Sébastien DA ROCHA
committed
},
watch: {
'$route.query'(newValue, oldValue) {
if (newValue !== oldValue) { //* Navigate back or forward to the previous or next URL
this.initPage(); //* doesn't update the page at query changes, thus it is done manually here
}
},
},

Timothee P
committed
this.initPage();

Timothee P
committed
// when this is available, set the value with previously stored value in windows to pass it as a prop
this.displayToListButton = window.displayToListButton;
},
beforeDestroy() {
this.$store.commit('CLEAR_MESSAGES');
Sébastien DA ROCHA
committed
methods: {
...mapMutations([
'DISPLAY_LOADER',
'DISCARD_LOADER'
]),

Timothee P
committed
...mapMutations('feature', [
'SET_CURRENT_FEATURE'
]),
...mapMutations('feature-type', [
'SET_CURRENT_FEATURE_TYPE_SLUG'
]),
...mapActions('projects', [
'GET_PROJECT',
'GET_PROJECT_INFO'
]),

Timothee P
committed

Timothee P
committed
async initPage() {
await this.getPageInfo();
this.initMap();
},
async getPageInfo() {
if (this.$route.params.slug_signal && this.$route.params.slug_type_signal) { // if coming from the route with an id
this.SET_CURRENT_FEATURE_TYPE_SLUG(this.$route.params.slug_type_signal);

Timothee P
committed
this.slugSignal = this.$route.params.slug_signal;
this.featureType = this.feature_type;

Timothee P
committed
} //* 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(dans le cas du parcours des signalements filtrés)

Timothee P
committed
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();

Timothee P
committed
this.DISCARD_LOADER();
if (this.project.fast_edition_mode) {
this.$store.commit('feature/INIT_FORM');
this.$store.dispatch('feature/INIT_EXTRA_FORMS');
}

Timothee P
committed
},
confirmLeave(next) {
this.next = next;
this.isLeaving = true;
},
stayOnPage() {
this.isLeaving = false;
},
leavePage() {
this.next();

Timothee P
committed
},

Timothee P
committed
await this.getPageInfo();
mapService.removeFeatures();
this.addFeatureToMap();
},
pushNgo(newEntry) {
this.$router.push(newEntry) //* update the params or queries in the route/url
.then(() => {
this.reloadPage();
})
.catch(() => true); //* catch error if navigation get aborted (in beforeRouteUpdate)
},
slug: this.$route.params.slug,
Sébastien DA ROCHA
committed
deleteFeature() {
this.$store
.dispatch('feature/DELETE_FEATURE', { feature_id: this.currentFeature.feature_id })
if (response.status === 204) {
this.goBackToProject();
}
});
Sébastien DA ROCHA
committed
},

Timothee P
committed
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;
this.SET_CURRENT_FEATURE_TYPE_SLUG(feature_type.slug);

Timothee P
committed
return { feature_id };
}
return;
});
},
Sébastien DA ROCHA
committed
initMap() {
var mapDefaultViewCenter =
this.$store.state.configuration.DEFAULT_MAP_VIEW.center;
var mapDefaultViewZoom =
this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom;
Sébastien DA ROCHA
committed
mapDefaultViewCenter,
mapDefaultViewZoom,
maxZoom: this.project.map_max_zoom_level,
interactions : {
doubleClickZoom :false,
mouseWheelZoom: false,
dragPan: false
}
Sébastien DA ROCHA
committed
});
// Update link to feature list with map zoom and center
mapService.addMapEventListener('moveend', function () {
Sébastien DA ROCHA
committed
// update link to feature list with map zoom and center
/*var $featureListLink = $("#feature-list-link")
var baseUrl = $featureListLink.attr("href").split("?")[0]
$featureListLink.attr("href", baseUrl +`?zoom=${this.map.getZoom()}&lat=${this.map.getCenter().lat}&lng=${this.map.getCenter().lng}`)*/
});
// Load the layers.
// - if one basemap exists, we load the layers of the first one
// - if not, load the default map and service options
let layersToLoad = null;
var baseMaps = this.$store.state.map.basemaps;

Timothee P
committed
var layers = this.$store.state.map.availableLayers;
Sébastien DA ROCHA
committed
if (baseMaps && baseMaps.length > 0) {
Sébastien DA ROCHA
committed
layersToLoad = baseMaps[basemapIndex].layers;
layersToLoad.forEach((layerToLoad) => {
layers.forEach((layer) => {
if (layer.id === layerToLoad.id) {
layerToLoad = Object.assign(layerToLoad, layer);
}
});
});
layersToLoad.reverse();
}
Sébastien DA ROCHA
committed
layersToLoad,
this.$store.state.configuration.DEFAULT_BASE_MAP_SERVICE,

Timothee P
committed
this.$store.state.configuration.DEFAULT_BASE_MAP_OPTIONS,
this.$store.state.configuration.DEFAULT_BASE_MAP_SCHEMA_TYPE,
Sébastien DA ROCHA
committed
);
this.addFeatureToMap();
},
addFeatureToMap() {
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/` +

Timothee P
committed
`?feature_id=${this.slugSignal}&output=geojson`;
axios
.get(url)
Sébastien DA ROCHA
committed
.then((response) => {
if (response.data.features.length > 0) {
response.data.features,
mapService.fitExtent(buffer(featureGroup.getExtent(),200));
Sébastien DA ROCHA
committed
}
})
.catch((error) => {
throw error;
});
getFeatureEvents() {
featureAPI

Timothee P
committed
.getFeatureEvents(this.slugSignal)
.then((data) => (this.events = data));
},
getFeatureAttachments() {
featureAPI

Timothee P
committed
.getFeatureAttachments(this.slugSignal)
.then((data) => (this.attachments = data));
},
getLinkedFeatures() {
featureAPI

Timothee P
committed
.getFeatureLinks(this.slugSignal)
this.$store.commit('feature/SET_LINKED_FEATURES', data)
Sébastien DA ROCHA
committed
},
};
</script>
Sébastien DA ROCHA
committed
#map {
width: 100%;
height: 100%;
min-height: 250px;
max-height: 70vh;
}

Timothee P
committed
.prewrap {
white-space: pre-wrap;
}