Something went wrong on our end
-
DESPRES Damien authoredDESPRES Damien authored
Feature_list.vue 20.58 KiB
<template>
<div class="fourteen wide column">
<script type="application/javascript" :src="baseUrl+'/resources/leaflet-control-geocoder-1.13.0/Control.Geocoder.js'"></script>
<div class="feature-list-container ui grid">
<div class="four wide column">
<h1>Signalements</h1>
</div>
<div class="twelve wide column">
<div class="ui secondary menu">
<a
@click="showMap = true"
:class="['item', { active: showMap }]"
data-tab="map"
data-tooltip="Carte"
><i class="map fitted icon"></i
></a>
<a
@click="showMap = false"
:class="['item', { active: !showMap }]"
data-tab="list"
data-tooltip="Liste"
><i class="list fitted icon"></i
></a>
<div class="item">
<h4>
{{ getFilteredFeatures().length }} signalement{{
getFilteredFeatures().length > 1 ? "s" : ""
}}
</h4>
</div>
<!-- {% if project and feature_types and
permissions|lookup:'can_create_feature' %} -->
<!-- v-if="project && feature_types && permissions" -->
<!-- //Todo: add permissions -->
<div v-if="project && feature_types" class="item right">
<div
@click="showAddFeature = !showAddFeature"
class="
ui
dropdown
top
right
pointing
compact
button button-hover-green
"
data-tooltip="Ajouter un signalement"
data-position="bottom left"
>
<i class="plus fitted icon"></i>
<div
v-if="showAddFeature"
class="menu transition visible"
style="z-index: 9999"
>
<div class="header">Ajouter un signalement du type</div>
<div class="scrolling menu text-wrap">
<router-link
:to="{
name: 'ajouter-signalement',
params: { slug_type_signal: type.slug },
}"
v-for="(type, index) in feature_types"
:key="type.slug + index"
class="item"
>
{{ type.title }}
</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<form id="form-filters" class="ui form grid" action="" method="get">
<div class="field wide four column">
<label>Type</label>
<Dropdown
@update:selection="onFilterTypeChange($event)"
:options="form.type.choices"
:selected="form.type.selected"
:selection.sync="form.type.selected"
:search="true"
/>
</div>
<div class="field wide four column">
<label>Statut</label>
<!-- //* giving an object mapped on key name -->
<Dropdown
@update:selection="onFilterStatusChange($event)"
:options="form.status.choices"
:selected="form.status.selected.name"
:selection.sync="form.status.selected"
:search="true"
/>
</div>
<div class="field wide four column">
<label>Nom</label>
<div class="ui icon input">
<i class="search icon"></i>
<div class="ui action input">
<input type="text" name="title" v-model="form.title" @input="onFilterChange()" />
<button
type="button"
class="ui teal icon button"
id="submit-search"
>
<i class="search icon"></i>
</button>
</div>
</div>
</div>
<!-- map params, updated on map move // todo : brancher sur la carte probablement -->
<input type="hidden" name="zoom" v-model="zoom" />
<input type="hidden" name="lat" v-model="lat" />
<input type="hidden" name="lng" v-model="lng" />
</form>
<div v-show="showMap" class="ui tab active map-container" data-tab="map">
<div id="map"></div>
<SidebarLayers v-if="baseMaps && map"/>
</div>
<div v-show="!showMap" data-tab="list" class="dataTables_wrapper no-footer">
<table id="table-features" class="ui compact table">
<thead>
<tr>
<th class="center">Statut <i :class="{ down: isSortedAsc('statut'),up:isSortedDesc('statut') }" class="icon sort" @click="changeSort('statut')"/></th>
<th class="center">Type <i :class="{ down: isSortedAsc('type'),up:isSortedDesc('type') }" class="icon sort" @click="changeSort('type')"/></th>
<th class="center">Nom <i :class="{ down: isSortedAsc('nom'),up:isSortedDesc('nom') }" class="icon sort" @click="changeSort('nom')"/></th>
<th class="center">Dernière modification <i :class="{ down: isSortedAsc('updated_on'),up:isSortedDesc('updated_on') }" class="icon sort" @click="changeSort('updated_on')"/></th>
<th class="center" v-if="user" >Auteur <i :class="{ down: isSortedAsc('display_creator'),up:isSortedDesc('display_creator') }" class="icon sort" @click="changeSort('display_creator')"/></th>
</tr>
</thead>
<tbody>
<tr
v-for="(feature, index) in getPaginatedFeatures()"
:key="index"
>
<td class="center">
<div v-if="feature.properties.status.value == 'archived'" data-tooltip="Archivé">
<i class="grey archive icon"></i>
</div>
<div
v-else-if="feature.properties.status.value == 'pending'"
data-tooltip="En attente de publication"
>
<i class="teal hourglass outline icon"></i>
</div>
<div
v-else-if="feature.properties.status.value == 'published'"
data-tooltip="Publié"
>
<i class="olive check icon"></i>
</div>
<div
v-else-if="feature.properties.status.value == 'draft'"
data-tooltip="Brouillon"
>
<i class="orange pencil alternate icon"></i>
</div>
</td>
<td class="center">
<router-link
:to="{
name: 'details-type-signalement',
params: { feature_type_slug: feature.properties.feature_type.title },
}"
>
{{ feature.properties.feature_type.title }}
</router-link>
</td>
<td class="center">
<router-link
:to="{
name: 'details-signalement',
params: {
slug_type_signal: feature.properties.feature_type.slug,
slug_signal: feature.properties.slug || feature.id,
},
}"
>{{ getFeatureDisplayName(feature)}}</router-link
>
</td>
<td class="center">
<!-- |date:'Ymd' -->
{{ feature.properties.updated_on }}
</td>
<td class="center" v-if="user">
{{ feature.properties.display_creator }}
</td>
</tr>
<tr v-if="getFilteredFeatures().length === 0" class="odd">
<td colspan="5" class="dataTables_empty" valign="top">
Aucune donnée disponible
</td>
</tr>
</tbody>
</table>
<div
class="dataTables_info"
id="table-features_info"
role="status"
aria-live="polite"
>
Affichage de l'élément {{ pagination.start + 1 }} à
{{ pagination.end + 1 }} sur {{ getFilteredFeatures().length }} éléments
</div>
<div
class="dataTables_paginate paging_simple_numbers"
id="table-features_paginate"
> <a
@click="toPreviousPage"
class="paginate_button previous disabled"
aria-controls="table-features"
data-dt-idx="0"
tabindex="0"
id="table-features_previous"
>Précédent</a
>
<!-- <span> <a
v-for="(page, index) in getNbPages()"
:key="'page' + index"
class="paginate_button current"
aria-controls="table-features"
data-dt-idx="1"
tabindex="0"
>{{index}}</a
> </span>
<span class="ellipsis">…</span> -->
<a
class="paginate_button next"
aria-controls="table-features"
data-dt-idx="7"
tabindex="0"
id="table-features_next"
@click="toNextPage"
>Suivant</a
>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapState } from "vuex";
import L from "leaflet";
import { mapUtil } from "@/assets/js/map-util.js";
import SidebarLayers from "@/components/map-layers/SidebarLayers";
import Dropdown from "@/components/Dropdown.vue";
const axios = require("axios");
export default {
name: "Feature_list",
components: {
SidebarLayers,
Dropdown,
},
data() {
return {
form: {
type: {
selected: null,
choices: [],
},
status: {
selected: {
name: null,
value: null,
},
choices: [
{
name: "Brouillon",
value: "draft",
},
{
name: "En attente de publication",
value: "pending",
},
{
name: "Publié",
value: "published",
},
{
name: "Archivé",
value: "archived",
},
],
},
title: null,
},
pagination: {
pagesize:15,
start: 0,
end: 14,
},
sort:{
column:'',
ascending:true
},
geojsonFeatures:[],
filterStatus:null,
filterType:null,
baseUrl:this.$store.state.configuration.BASE_URL,
map:null,
zoom:null,
lat:null,
lng:null,
showMap: true,
showAddFeature: false,
};
},
computed: {
...mapGetters(["project"]),
...mapState(["user"]),
...mapState("feature", ["features"]),
...mapState("feature_type", ["feature_types"]),
baseMaps(){
return this.$store.state.map.basemaps;
},
},
methods: {
getFeatureDisplayName(feature){
return feature.properties.title || feature.id;
},
getPaginatedFeatures() {
let filterdFeatures=[...this.getFilteredFeatures()];
// Ajout du tri
if(this.sort.column!=''){
filterdFeatures=filterdFeatures.sort((a,b)=>{
let aProp=this.getFeatureDisplayName(a);
let bProp=this.getFeatureDisplayName(b);
if(this.sort.column=='statut') {
aProp=a.properties.status.value;
bProp=b.properties.status.value;
}
else if(this.sort.column=='type') {
aProp=a.properties.feature_type.title;
bProp=b.properties.feature_type.title;
}
else if(this.sort.column=='updated_on') {
aProp=a.properties.updated_on;
bProp=b.properties.updated_on;
}
else if(this.sort.column=='display_creator') {
aProp=a.properties.display_creator;
bProp=b.properties.display_creator;
}
//ascending
if(this.sort.ascending){
if(aProp < bProp) { return -1; }
if(aProp > bProp) { return 1; }
return 0;
}
else{
//descending
if(aProp < bProp) { return 1; }
if(aProp > bProp) { return -1; }
return 0;
}
})
}
return filterdFeatures.slice(
this.pagination.start,
this.pagination.end
);
},
isSortedAsc(column){
return this.sort.column==column && this.sort.ascending;
},
isSortedDesc(column){
return this.sort.column==column && !this.sort.ascending;
},
changeSort(column){
if(this.sort.column==column){
//changer order
this.sort.ascending=!this.sort.ascending;
}else{
this.sort.column=column;
this.sort.ascending=true;
}
},
onFilterStatusChange(newvalue){
this.filterStatus=null;
if(newvalue){
console.log("filter change",newvalue.value);
this.filterStatus=newvalue.value;
}
this.onFilterChange();
},
onFilterTypeChange(newvalue){
this.filterType=null;
if(newvalue){
console.log("filter change",newvalue);
this.filterType=newvalue;
}
this.onFilterChange();
},
onFilterChange() {
var features = this.getFilteredFeatures();
this.featureGroup.clearLayers();
this.featureGroup = mapUtil.addFeatures(features, {});
if (features.length > 0) {
mapUtil.getMap().fitBounds(this.featureGroup.getBounds());
}
},
getFilteredFeatures() {
let results = this.geojsonFeatures;
if (this.filterType) {
results = results.filter(
(el) => el.properties.feature_type.title === this.filterType
);
}
if (this.filterStatus) {
console.log("filter by" + this.filterStatus);
results = results.filter(
(el) => el.properties.status.value === this.filterStatus
);
}
if (this.form.title) {
results = results.filter((el) => {
if (el.properties.title) {
return el.properties.title
.toLowerCase()
.includes(this.form.title.toLowerCase());
} else
return el.id.toLowerCase().includes(this.form.title.toLowerCase());
}
);
}
return results;
},
getNbPages(){
return Math.round(this.getFilteredFeatures().length/this.pagination.pagesize);
},
toPreviousPage() {
if (this.pagination.start > 0) {
this.pagination.start -= this.pagination.pagesize;
this.pagination.end -= this.pagination.pagesize;
}
},
toNextPage() {
if (this.pagination.end < this.getFilteredFeatures().length) {
this.pagination.start += this.pagination.pagesize;
this.pagination.end += this.pagination.pagesize;
}
},
loadFeatures(features){
this.geojsonFeatures = features;
console.log(this.geojsonFeatures);
const urlParams = new URLSearchParams(window.location.search);
const featureType = urlParams.get("feature_type");
const featureStatus = urlParams.get("status");
const featureTitle = urlParams.get("title");
this.featureGroup = mapUtil.addFeatures(this.geojsonFeatures, {
featureType,
featureStatus,
featureTitle,
});
// Fit the map to bound only if no initial zoom and center are defined
if (
(this.lat === "" || this.lng === "" || this.zoom === "") &&
this.geojsonFeatures.length > 0
) {
mapUtil.getMap().fitBounds(this.featureGroup.getBounds());
}
this.form.type.choices = [
//* converting Set to an Array with spread "..."
...new Set(this.geojsonFeatures.map((el) => el.properties.feature_type.title)), //* use Set to eliminate duplicate values
];
},
addGeocoders(){
let geocoder;
// Get the settings.py variable SELECTED_GEOCODER_PROVIDER. This way avoids XCC attacks
const geocoderLabel =
this.$store.state.configuration.SELECTED_GEOCODER.PROVIDER;
if (geocoderLabel) {
const LIMIT_RESULTS = 5;
if (
geocoderLabel ===
this.$store.state.configuration.GEOCODER_PROVIDERS.ADDOK
) {
geocoder = L.Control.Geocoder.addok({ limit: LIMIT_RESULTS });
} else if (
geocoderLabel ===
this.$store.state.configuration.GEOCODER_PROVIDERS.PHOTON
) {
geocoder = L.Control.Geocoder.photon();
} else if (
geocoderLabel ===
this.$store.state.configuration.GEOCODER_PROVIDERS.NOMINATIM
) {
geocoder = L.Control.Geocoder.nominatim();
}
L.Control.geocoder({
placeholder: "Chercher une adresse...",
geocoder: geocoder,
}).addTo(this.map);
}
},
},
created() {
if (!this.project) {
//this.$store.dispatch("GET_PROJECT_MESSAGES", this.$route.params.slug);
this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
}
},
mounted() {
this.zoom = this.$route.query.zoom||'';
this.lat = this.$route.query.lat||'';
this.lng = this.$route.query.lng||'';
var mapDefaultViewCenter = this.$store.state.configuration.DEFAULT_MAP_VIEW.center;
var mapDefaultViewZoom = this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom;
this.map=mapUtil.createMap({
zoom:this.zoom,
lat:this.lat,
lng:this.lng,
mapDefaultViewCenter,
mapDefaultViewZoom,
});
document.addEventListener("change-layers-order", (event) => {
// Reverse is done because the first layer in order has to be added in the map in last.
// Slice is done because reverse() changes the original array, so we make a copy first
mapUtil.updateOrder(event.detail.layers.slice().reverse());
});
// --------- End sidebar events ----------
console.log(this.$store.state.map.geojsonFeatures);
if(this.$store.state.map.geojsonFeatures){
this.loadFeatures(this.$store.state.map.geojsonFeatures);
}
else{
const url=`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
axios.get(url)
.then((response) => {
this.loadFeatures(response.data.features);
})
.catch((error) => {
throw error;
});
}
setTimeout(
function () {
this.addGeocoders();
}.bind(this), 1000)
},
};
</script>
<style>
#map {
width: 100%;
min-height: 300px;
height: calc(100vh - 300px);
border: 1px solid grey;
/* To not hide the filters */
z-index: 1;
}
.center{
text-align: center !important;
}
#form-filters,
.ui.centered > .row.feature-list-container {
justify-content: flex-start;
}
.feature-list-container .ui.menu:not(.vertical) .right.item {
padding-right: 0;
}
.map-container {
width: 80vw;
transform: translateX(-50%);
margin-left: 50%;
}
@media screen and (max-width: 767px) {
#form-filters > .field.column {
width: 100% !important;
}
.map-container {
width: 100%;
}
}
/* datatables */
.dataTables_wrapper {
position: relative;
clear: both;
}
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter,
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_processing,
.dataTables_wrapper .dataTables_paginate {
color: #333;
}
.dataTables_wrapper .dataTables_info {
clear: both;
float: left;
padding-top: 0.755em;
}
.dataTables_wrapper .dataTables_paginate {
float: right;
text-align: right;
padding-top: 0.25em;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current,
.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
color: #333 !important;
border: 1px solid #979797;
background-color: white;
background: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0%, #fff),
color-stop(100%, #dcdcdc)
);
background: -webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);
background: -moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);
background: -ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);
background: -o-linear-gradient(top, #fff 0%, #dcdcdc 100%);
background: linear-gradient(to bottom, #fff 0%, #dcdcdc 100%);
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
box-sizing: border-box;
display: inline-block;
min-width: 1.5em;
padding: 0.5em 1em;
margin-left: 2px;
text-align: center;
text-decoration: none !important;
cursor: pointer;
color: #333 !important;
border: 1px solid transparent;
border-radius: 2px;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
cursor: default;
color: #666 !important;
border: 1px solid transparent;
background: transparent;
box-shadow: none;
}
</style>