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 ||
isModerator
: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) || -->
v-if="
isFeatureCreator || permissions.is_project_super_contributor
"
@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>

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-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" ref="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" :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
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
<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
"
target="_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");
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');
Sébastien DA ROCHA
committed
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", "USER_LEVEL_PROJECTS"]),
...mapGetters(["permissions", "project"]),
...mapState("feature", ["linked_features", "statusChoices"]),
DJANGO_BASE_URL() {
return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
},
feature() {
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;
},
isModerator() {
return this.USER_LEVEL_PROJECTS &&
this.USER_LEVEL_PROJECTS[this.project.slug] === "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
);
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,
commentId: response.data.id,
title: response.data.comment,
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;
},
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
537
538
539
540
541
542
543
544
545
546
547
548
549
550
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(this.$refs.map, {
Sébastien DA ROCHA
committed
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,
{},
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
},
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() {

Timothee P
committed
this.$store.commit("DISPLAY_LOADER", "Recherche du signalement");
if (!this.project) {
this.$store
.dispatch("GET_PROJECT_INFO", this.$route.params.slug)

Timothee P
committed
.then(() => {
this.$store.commit("DISCARD_LOADER");
});
} else {

Timothee P
committed
this.$store.commit("DISCARD_LOADER");
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;
}

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