Newer
Older
Sébastien DA ROCHA
committed
<template>
<div v-frag>
<div v-frag v-if="feature">
<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"
>
<i class="plus fitted icon"></i>
</router-link>
<router-link
v-if="
(permissions && permissions.can_update_feature) ||
isFeatureCreator
"
: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"></i>
</router-link>
<!-- (permissions && 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>
</div>
<div class="ui hidden divider"></div>
<div class="sub header">
{{ 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-frag
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',
]"
></i>
<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]"></i>
{{ 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>
<tr>
<td>Date d'archivage automatique</td>
<td v-if="feature.archived_on">
{{ feature.archived_on }}
</td>
</tr>
<tr>
<td>Date de suppression automatique</td>
<td v-if="feature.deletion_on">
{{ feature.deletion_on }}
</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 id="map"></div>
</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"
>{{ 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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
<div id="feed-event" class="ui feed">
<div v-frag v-for="(event, index) in events" :key="'event' + index">
<div v-frag v-if="event.event_type === 'create'">
<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-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
>
</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>
<div
v-if="permissions && permissions.can_create_feature"
class="ui segment"
>
<form
id="form-comment"
class="ui form"
method="POST"
enctype="multipart/form-data"
>
<div class="required field">
<label :for="comment_form.comment.id_for_label"
>Ajouter un commentaire</label
>
{{ comment_form.comment.errors }}
<textarea
v-model="comment_form.comment.value"
:name="comment_form.comment.html_name"
rows="2"
></textarea>
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"></i>
<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"
id="attachment_file"
/>
</div>
<div class="field">
<input
v-model="comment_form.title.value"
type="text"
:name="comment_form.title.html_name"
:id="comment_form.title.id_for_label"
/>
{{ comment_form.title.errors }}
</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
@click="postComment"
type="button"
class="ui compact green icon button"
>
<i class="plus icon"></i> 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 @click="isCanceling = false" class="close icon"></i>
<div class="ui icon header">
<i class="trash alternate icon"></i>
Supprimer le signalement
</div>
<div class="actions">
<form
action="{% url 'geocontrib:feature_delete' slug=feature.project.slug feature_type_slug=feature.feature_type.slug feature_id=feature.feature_id %}"
method="POST"
Sébastien DA ROCHA
committed
>
<input type="hidden" name="_method" value="delete" />
<button
@click="deleteFeature"
type="button"
class="ui red compact fluid button"
>
Confirmer la suppression
</button>
</form>
</div>
Sébastien DA ROCHA
committed
</div>
</div>
</div>
<div v-frag v-else>Pas de signalement correspondant trouvé</div>
Sébastien DA ROCHA
committed
</div>
</template>
<script>
import frag from "vue-frag";
Sébastien DA ROCHA
committed
import { mapUtil } from "@/assets/js/map-util.js";
import featureAPI from "@/services/feature-api";
Sébastien DA ROCHA
committed
const axios = require("axios");
export default {
name: "Feature_detail",
directives: {
frag,
},
data() {
return {
isCanceling: false,
Sébastien DA ROCHA
committed
events: [],
comment_form: {
attachment_file: {
errors: null,
value: null,
Sébastien DA ROCHA
committed
},
title: {
id_for_label: "title",
html_name: "title",
errors: null,
value: null,
},
comment: {
id_for_label: "add-comment",
html_name: "add-comment",
errors: null,
value: null,
},
non_field_errors: [],
},
};
},
computed: {
...mapState(["user"]),
...mapState("feature", ["linked_features", "statusChoices"]),
DJANGO_BASE_URL: function () {
return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
},
Sébastien DA ROCHA
committed
feature: function () {
const result = this.$store.state.feature.features.find(
(el) => el.feature_id === this.$route.params.slug_signal
Sébastien DA ROCHA
committed
);
return result;
Sébastien DA ROCHA
committed
},
isFeatureCreator() {
if (this.feature && this.user) {
return this.feature.creator === this.user.id;
}
return 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
);
return status ? status.name : "";
},
Sébastien DA ROCHA
committed
},
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
methods: {
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,
},
});
this.getFeatureEvents();
this.getFeatureAttachments();
this.getLinkedFeatures();
this.addFeatureToMap();
},
Sébastien DA ROCHA
committed
postComment() {
featureAPI
.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.title.file,
comment: response.data.id,
})
.then(() => {
Sébastien DA ROCHA
committed
},
confirmComment() {
this.$store.commit("DISPLAY_MESSAGE", "Ajout du commentaire confirmé");
this.getFeatureEvents(); //* display new comment on the page
this.comment_form.attachment_file.file = null;
this.comment_form.attachment_file.value = null;
this.comment_form.title.file = null;
this.comment_form.title.value = null;
this.comment_form.comment.value = null;
},
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
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);
URL.revokeObjectURL(image.src);
},
onFileChange(e) {
// * read image file
const files = e.target.files || e.dataTransfer.files;
const _this = this; //* 'this' is different in onload function
function handleFile(isValid) {
if (isValid) {
const period = files[0].name.lastIndexOf(".");
const fileName = files[0].name.substring(0, period);
const fileExtension = files[0].name.substring(period + 1);
const shortName = fileName.slice(0, 10) + "[...]." + fileExtension;
_this.comment_form.attachment_file.file = files[0]; //* store the file to post later
_this.comment_form.attachment_file.value = shortName; //* for display
_this.comment_form.title.value = shortName;
_this.comment_form.attachment_file.errors = null;
} else {
_this.comment_form.attachment_file.errors =
"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
},
goBackToProject(message) {
this.$router.push({
name: "project_detail",
params: {
slug: this.$store.state.project_slug,
message,
},
});
},
Sébastien DA ROCHA
committed
deleteFeature() {
this.$store
.dispatch("feature/DELETE_FEATURE", this.feature.feature_id)
.then((response) => {
if (response.status === 204) {
this.$store.dispatch(
"feature/GET_PROJECT_FEATURES",
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({
mapDefaultViewCenter,
mapDefaultViewZoom,
});
// Update link to feature list with map zoom and center
mapUtil.addMapEventListener("moveend", function () {
// 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,
this.$store.state.configuration.DEFAULT_BASE_MAP.OPTIONS
);
mapUtil.getMap().dragging.disable();
mapUtil.getMap().doubleClickZoom.disable();
mapUtil.getMap().scrollWheelZoom.disable();
this.addFeatureToMap();
},
addFeatureToMap() {
const currentFeatureId = this.$route.params.slug_signal;
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}features/${currentFeatureId}/?feature_type__slug=${this.$route.params.slug_type_signal}&output=geojson`;
axios
.get(url)
Sébastien DA ROCHA
committed
.then((response) => {
const feature = response.data;
if (feature) {
const currentFeature = [feature];
const featureGroup = mapUtil.addFeatures(currentFeature);
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
},
created() {
this.$store.commit(
"feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
this.$route.params.slug_type_signal
);
this.getFeatureEvents();
this.getFeatureAttachments();
this.getLinkedFeatures();
Sébastien DA ROCHA
committed
},
mounted() {
if (!this.project) {
this.$store
.dispatch("GET_PROJECT_INFO", this.$route.params.slug)
.then((data) => {
console.log(data);
this.initMap();
});
} else {
this.initMap();
}
Sébastien DA ROCHA
committed
},
beforeDestroy() {
this.$store.commit("CLEAR_MESSAGES");
},
Sébastien DA ROCHA
committed
};
</script>
<style>
#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;
}
</style>