Newer
Older
Sébastien DA ROCHA
committed
<template>
<div v-frag>
<div class="row">
<div class="fourteen wide column">
<h1 class="ui header">
<div class="content">
{{ feature.title || feature.feature_id }}
<div class="ui icon right floated compact buttons">
<router-link
v-if="permissions && permissions.can_create_feature"
:to="{
name: 'ajouter-signalement',
params: {
slug_type_signal: $route.params.slug_type_signal,
},
}"
class="ui button button-hover-orange"
data-tooltip="Ajouter un signalement"
data-position="bottom left"
>
</router-link>
<router-link
v-if="
(permissions && permissions.can_update_feature) ||
:to="{
name: 'editer-signalement',
params: {
slug_signal: $route.params.slug_signal,
slug_type_signal: $route.params.slug_type_signal,
},
}"
class="ui button button-hover-orange"
>
<i class="inverted grey pencil alternate icon" />
</router-link>
<a
v-if="((permissions && permissions.can_update_feature) || isFeatureCreator) && isOnline"
id="feature-delete"
class="ui button button-hover-red"
<i class="inverted grey trash alternate icon" />
</a>
</div>

Timothee P
committed
<div class="sub header prewrap">
{{ feature.description }}
</div>
Sébastien DA ROCHA
committed
</div>
</h1>
</div>
Sébastien DA ROCHA
committed
</div>
<div class="row">
<div class="seven wide column">
<table class="ui very basic table">
<tbody>
<div
v-for="(field, index) in feature.feature_data"
:key="'field' + index"
>
<tr v-if="field">
<td>
<b>{{ field.label }}</b>
</td>
<td>
<b>
<i
v-if="field.field_type === 'boolean'"
:class="[
'icon',
field.value ? 'olive check' : 'grey times',
]"
<span v-else>
{{ field.value }}
</span>
</b>
</td>
</tr>
</div>
<tr>
<td>Auteur</td>
<td>{{ feature.display_creator }}</td>
</tr>
<tr>
<td>Statut</td>
Sébastien DA ROCHA
committed
<td>
<i
v-if="feature.status"
:class="['icon', statusIcon]"
/>
{{ statusLabel }}
</td>
</tr>
<tr>
<td>Date de création</td>
<td v-if="feature.created_on">
</td>
</tr>
<tr>
<td>Date de dernière modification</td>
<td v-if="feature.updated_on">
Sébastien DA ROCHA
committed
</td>
</tr>
</tbody>
</table>
<h3>Liaison entre signalements</h3>
<table class="ui very basic table">
<tbody>
<tr
v-for="(link, index) in linked_features"
:key="link.feature_to.title + index"
>
<td v-if="link.feature_to.feature_type_slug">
{{ link.relation_type_display }}
<a @click="pushNgo(link)">{{ link.feature_to.title }} </a>
({{ link.feature_to.display_creator }} -
{{ link.feature_to.created_on }})
Sébastien DA ROCHA
committed
</td>
</tr>
</tbody>
</table>
</div>
Sébastien DA ROCHA
committed
<div class="seven wide column">
</div>
Sébastien DA ROCHA
committed
</div>
<div class="row">
<div class="seven wide column">
<h2 class="ui header">
Pièces jointes
</h2>
Sébastien DA ROCHA
committed
<div
v-for="pj in attachments"
:key="pj.id"
class="ui divided items"
>
<div class="item">
Sébastien DA ROCHA
committed
<a
class="ui tiny image"
Sébastien DA ROCHA
committed
target="_blank"
Sébastien DA ROCHA
committed
>
<img
:src="
pj.extension === '.pdf'
? require('@/assets/img/pdf.png')
</a>
<div class="middle aligned content">
<a
class="header"
target="_blank"
:href="pj.attachment_file"
>{{
pj.title
}}</a>
<div class="description">
{{ pj.info }}
</div>
Sébastien DA ROCHA
committed
</div>
</div>
</div>
<i
v-if="attachments.length === 0"
>Aucune pièce jointe associée au signalement.</i>
Sébastien DA ROCHA
committed
</div>
<div class="seven wide column">
<h2 class="ui header">
Activité et commentaires
</h2>
Sébastien DA ROCHA
committed
<div
id="feed-event"
class="ui feed"
>
<div
v-for="(event, index) in events"
:key="'event' + index"
v-frag
>
<div
v-if="event.event_type === 'create'"
v-frag
>
<div
v-if="event.object_type === 'feature'"
class="event"
>
<div class="content">
<div class="summary">
<div class="date">
{{ event.created_on }}
</div>
Création du signalement
<span v-if="user">par {{ event.display_user }}</span>
</div>
</div>
</div>
<div
v-else-if="event.object_type === 'comment'"
class="event"
>
<div class="content">
<div class="summary">
<div class="date">
{{ event.created_on }}
</div>
Commentaire
<span v-if="user">par {{ event.display_user }}</span>
</div>
<div class="extra text">
{{ event.related_comment.comment }}
<div
v-if="event.related_comment.attachment"
v-frag
>
<br><a
:href="
DJANGO_BASE_URL +
target="_blank"
><i class="paperclip fitted icon" />
{{ event.related_comment.attachment.title }}</a>
</div>
Sébastien DA ROCHA
committed
</div>
</div>
</div>
</div>
<div
v-else-if="event.event_type === 'update'"
class="event"
>
Sébastien DA ROCHA
committed
<div class="content">
<div class="summary">
<div class="date">
{{ event.created_on }}
</div>
Signalement mis à jour
Sébastien DA ROCHA
committed
<span v-if="user">par {{ event.display_user }}</span>
</div>
</div>
</div>
</div>
</div>

Timothee P
committed
v-if="permissions && permissions.can_create_feature && isOnline"
<form
id="form-comment"
class="ui form"
>
<div class="required field">
<label
:for="comment_form.comment.id_for_label"
>Ajouter un commentaire</label>
<ul
v-if="comment_form.comment.errors"
class="errorlist"
<li>
{{ comment_form.comment.errors }}
</li>
</ul>
<textarea
v-model="comment_form.comment.value"
:name="comment_form.comment.html_name"
rows="2"
Sébastien DA ROCHA
committed
</div>
<label>Pièce jointe (facultative)</label>
<div class="two fields">
<div class="field">
<label
class="ui icon button"
for="attachment_file"
>
<i class="paperclip icon" />
<span class="label">{{
comment_form.attachment_file.value
? comment_form.attachment_file.value
: "Sélectionner un fichier ..."
}}</span>
</label>
<input
type="file"
accept="application/pdf, image/jpeg, image/png"
style="display: none"
name="attachment_file"
</div>
<div class="field">
<input
type="text"
</div>
Sébastien DA ROCHA
committed
</div>
<ul
v-if="comment_form.attachment_file.errors"
class="errorlist"
>
<li>
{{ comment_form.attachment_file.errors }}
</li>
</ul>
<button
type="button"
class="ui compact green icon button"
<i class="plus icon" /> Poster le commentaire
</button>
</form>
</div>
Sébastien DA ROCHA
committed
</div>
</div>
<div
v-if="isCanceling"
class="ui dimmer modals page transition visible active"
style="display: flex !important"
Sébastien DA ROCHA
committed
>
<div
:class="[
'ui mini modal subscription',
{ 'active visible': isCanceling },
]"
>
<i
class="close icon"
@click="isCanceling = false"
/>
<div class="ui icon header">
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>
</div>
<div
v-else
v-frag
>
Pas de signalement correspondant trouvé
</div>
Sébastien DA ROCHA
committed
</div>
</template>
<script>
import frag from 'vue-frag';
import { mapGetters, mapState, mapActions } from 'vuex';
import { mapUtil } from '@/assets/js/map-util.js';
import featureAPI from '@/services/feature-api';
Sébastien DA ROCHA
committed
export default {
Sébastien DA ROCHA
committed
directives: {
frag,
},
filters: {
formatDate(value) {
let date = new Date(value);
date = date.toLocaleString().replace(',', '');
return date.substr(0, date.length - 3); //* quick & dirty way to remove seconds from date
},
},
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: [],
isCanceling: false,
projectSlug: this.$route.params.slug,
Sébastien DA ROCHA
committed
};
},
computed: {

Timothee P
committed
'USER_LEVEL_PROJECTS',
'isOnline',
...mapGetters([
'permissions',
]),
...mapState('feature', [
'linked_features',
'statusChoices'
]),
DJANGO_BASE_URL() {
return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
},
feature() {
return result;
Sébastien DA ROCHA
committed
},
isFeatureCreator() {
if (this.feature && this.user) {
return this.feature.creator === this.user.id;
}
return false;
},
isModerator() {
this.USER_LEVEL_PROJECTS[this.projectSlug] === 'Modérateur'
? true
: false;
},
statusIcon() {
switch (this.feature.status) {
case 'archived':
return 'grey archive';
case 'pending':
return 'teal hourglass outline';
case 'published':
return 'olive check';
case 'draft':
return 'orange pencil alternate';
default:
return '';
}
},
statusLabel() {
const status = this.statusChoices.find(
(el) => el.value === this.feature.status
);
Sébastien DA ROCHA
committed
},
this.$route.params.slug_type_signal
);
this.getFeatureEvents();
this.getFeatureAttachments();
this.getLinkedFeatures();
},
mounted() {
this.$store.commit('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.$store
.dispatch('projects/GET_PROJECT', this.$route.params.slug),
this.$store
.dispatch('projects/GET_PROJECT_INFO', this.$route.params.slug),
this.$store.dispatch('feature/GET_PROJECT_FEATURE', {
project_slug: this.$route.params.slug,
feature_id: this.$route.params.slug_signal
})])
.then(() => {
this.$store.commit('DISCARD_LOADER');
this.initMap();
});
} if (!this.feature || this.feature.feature_id !== this.$route.params.slug_signal) {
this.$store.dispatch('feature/GET_PROJECT_FEATURE', {
project_slug: this.$route.params.slug,
feature_id: this.$route.params.slug_signal
})
.then(() => {
this.$store.commit('DISCARD_LOADER');
this.initMap();
});
} else {
this.$store.commit('DISCARD_LOADER');
this.initMap();
}
},
beforeDestroy() {
this.$store.commit('CLEAR_MESSAGES');
Sébastien DA ROCHA
committed
methods: {
...mapActions('feature', [
'GET_PROJECT_FEATURES'
]),

Timothee P
committed
pushNgo(link) {
this.$router.push({
params: {
slug_type_signal: link.feature_to.feature_type_slug,
slug_signal: link.feature_to.feature_id,
},
});
this.getFeatureEvents();
this.getFeatureAttachments();
this.getLinkedFeatures();
this.addFeatureToMap();
},
this.comment_form.comment.errors = 'Le commentaire ne peut pas être vide';
return false;
}
return true;
},
Sébastien DA ROCHA
committed
postComment() {
.postComment({
featureId: this.$route.params.slug_signal,
comment: this.comment_form.comment.value,
})
.then((response) => {
if (response && this.comment_form.attachment_file.file) {
featureAPI
.postCommentAttachment({
featureId: this.$route.params.slug_signal,
file: this.comment_form.attachment_file.file,
fileName: this.comment_form.attachment_file.fileName,
title: this.comment_form.attachment_file.title,
commentId: response.data.id,
})
.then(() => {
this.confirmComment();
});
} else {
this.confirmComment();
}
});
Sébastien DA ROCHA
committed
},
this.$store.commit('DISPLAY_MESSAGE', { comment: 'Ajout du commentaire confirmé', level: 'positive' });
this.getFeatureEvents(); //* display new comment on the page
this.comment_form.attachment_file.file = null;
this.comment_form.attachment_file.fileName = '';
this.comment_form.attachment_file.title = '';
validateImgFile(files, handleFile) {
let url = window.URL || window.webkitURL;
let image = new Image();
image.onload = function () {
handleFile(true);
};
image.onerror = function () {
handleFile(false);
};
image.src = url.createObjectURL(files);
},
onFileChange(e) {
// * read image file
const files = e.target.files || e.dataTransfer.files;
this.comment_form.attachment_file.file = files[0]; //* store the file to post afterwards
this.comment_form.attachment_file.fileName = title; //* name of the file
const fileExtension = title.substring(title.lastIndexOf('.') + 1);
title = title.slice(0, 10) + '[...].' + fileExtension;
}
this.comment_form.attachment_file.title = title; //* title for display
this.comment_form.attachment_file.errors = null;
"Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu.";
}
if (files.length) {
//* exception for pdf
if (files[0].type === 'application/pdf') {
handleFile(true);
} else {
this.comment_form.attachment_file.errors = null;
//* check if file is an image and pass callback to handle file
this.validateImgFile(files[0], handleFile);
}
}
Sébastien DA ROCHA
committed
},
slug: this.$route.params.slug,
Sébastien DA ROCHA
committed
deleteFeature() {
this.$store

Timothee P
committed
.dispatch('feature/DELETE_FEATURE', { feature_id: this.feature.feature_id })
.then((response) => {
if (response.status === 204) {
this.GET_PROJECT_FEATURES({
project_slug: this.$route.params.slug
});
this.goBackToProject();
}
});
Sébastien DA ROCHA
committed
},
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;
this.map = mapUtil.createMap(this.$refs.map, {
Sébastien DA ROCHA
committed
mapDefaultViewCenter,
mapDefaultViewZoom,
});
// Update link to feature list with map zoom and center
mapUtil.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();
}
mapUtil.addLayers(
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
);
mapUtil.getMap().dragging.disable();
mapUtil.getMap().doubleClickZoom.disable();
mapUtil.getMap().scrollWheelZoom.disable();
this.addFeatureToMap();
},
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`;
axios
.get(url)
Sébastien DA ROCHA
committed
.then((response) => {
if (response.data.features.length > 0) {
const featureGroup = mapUtil.addFeatures(
response.data.features,
{},
true,
this.$store.state.feature_type.feature_types
);
mapUtil
.getMap()
.fitBounds(featureGroup.getBounds(), { padding: [25, 25] });
Sébastien DA ROCHA
committed
}
})
.catch((error) => {
throw error;
});
getFeatureEvents() {
featureAPI
.getFeatureEvents(this.$route.params.slug_signal)
.then((data) => (this.events = data));
},
getFeatureAttachments() {
featureAPI
.getFeatureAttachments(this.$route.params.slug_signal)
.then((data) => (this.attachments = data));
},
getLinkedFeatures() {
featureAPI
.getFeatureLinks(this.$route.params.slug_signal)
.then((data) =>
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;
}
#feed-event .event {
margin-bottom: 1em;
}
#feed-event .event .date {
margin-right: 1em !important;
}
#feed-event .event .extra.text {
margin-left: 107px;
margin-top: 0;
}

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