Skip to content
Snippets Groups Projects
Feature_list.vue 17.9 KiB
Newer Older
<template>
  <div class="fourteen wide column">
    <script type="application/javascript" src="/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"
          <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>
              {{ features.length }} signalement{{
                features.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">
              @click="showAddSignal = !showAddSignal"
              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="showAddSignal"
                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',
DESPRES Damien's avatar
DESPRES Damien committed
                      params: { slug_type_signal: type.slug },
                    v-for="(type, index) in feature_types"
                    :key="type.title + index"
                </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
          :options="form.type.choices"
          :selected="form.type.selected"
          :selection.sync="form.type.selected"
      </div>
      <div class="field wide four column">
        <label>Statut</label>
        <!--  //* giving an object mapped on key name -->
        <Dropdown
          :options="form.status.choices"
          :selected="form.status.selected.name"
          :selection.sync="form.status.selected"
      </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">
Timothee P's avatar
Timothee P committed
            <input type="text" name="title" v-model="form.title" />
            <button
              type="button"
              class="ui teal icon button"
              id="submit-search"
            >
              <i class="search icon"></i>
            </button>
          </div>
        </div>
      </div>
Timothee P's avatar
Timothee P committed
      <!-- map params, updated on map move // todo : brancher sur la carte probablement -->
DESPRES Damien's avatar
DESPRES Damien committed
        <input type="hidden" name="zoom" v-model="zoom" />
      <input type="hidden" name="lat" v-model="lat" />
      <input type="hidden" name="lng" v-model="lng" /> 
    <div v-show="showMap" class="ui tab active map-container" data-tab="map">
      <!-- // todo: add v-if-->
      <!--  {% if serialized_base_maps|length > 0 %} {% include
      "geocontrib/map-layers/sidebar-layers.html" with
      basemaps=serialized_base_maps layers=serialized_layers
      project=project.slug%} {% endif %} -->
    <div v-show="!showMap" data-tab="list" class="dataTables_wrapper no-footer">
      <table id="table-features" class="ui compact table">
        <thead>
          <tr>
            <th>Statut</th>
            <th>Type</th>
            <th>Nom</th>
            <th>Dernière modification</th>
Timothee P's avatar
Timothee P committed
            <th v-if="user">Auteur</th>
            v-for="(feature, index) in filteredFeatures"
Timothee P's avatar
Timothee P committed
            :key="feature.feature_id + index"
            <td class="dt-center" :data-order="feature.get_status_display">
              <div v-if="feature.status == 'archived'" data-tooltip="Archivé">
                <i class="grey archive icon"></i>
              </div>
              <div
                v-else-if="feature.status == 'pending'"
                data-tooltip="En attente de publication"
              >
                <i class="teal hourglass outline icon"></i>
              </div>
              <div
                v-else-if="feature.status == 'published'"
                data-tooltip="Publié"
              >
                <i class="olive check icon"></i>
              </div>
              <div
                v-else-if="feature.status == 'draft'"
                data-tooltip="Brouillon"
              >
                <i class="orange pencil alternate icon"></i>
              </div>
            </td>
            <td>
              <router-link
                :to="{
                  name: 'details-type-signalement',
                  params: { feature_type_slug: feature.feature_type.title },
                }"
              <router-link
                :to="{
                  name: 'details-signalement',
                  params: {
DESPRES Damien's avatar
DESPRES Damien committed
                    slug_type_signal: feature.feature_type.slug,
                    slug_signal: feature.title || feature.feature_id,
                >{{ feature.title || feature.feature_id }}</router-link
              >
            </td>
            <td :data-order="feature.updated_on">
              <!-- |date:'Ymd' -->
              {{ feature.updated_on }}
          <tr v-if="filteredFeatures.length === 0" class="odd">
            <td colspan="5" class="dataTables_empty" valign="top">
              Aucune donnée disponible
            </td>
          </tr>
        class="dataTables_info"
        id="table-features_info"
        role="status"
        aria-live="polite"
      >
        Affichage de l'élément {{ pagination.start + 1 }} à
        {{ pagination.end + 1 }} sur {{ filteredFeatures.length }} éléments
        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
            v-for="(page, index) in filteredFeatures.length"
            :key="'page' + index"
            class="paginate_button current"
            aria-controls="table-features"
            data-dt-idx="1"
            tabindex="0"
            >1</a
          > -->
          <!-- <span class="ellipsis"></span> -->
          <!-- <a
            class="paginate_button next"
            aria-controls="table-features"
            data-dt-idx="7"
            tabindex="0"
            id="table-features_next"
import { mapGetters, mapState } from "vuex";
DESPRES Damien's avatar
DESPRES Damien committed
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";
DESPRES Damien's avatar
DESPRES Damien committed
const axios = require("axios");

  components: {
    SidebarLayers,
          selected: {
            name: null,
            {
              name: "Brouillon",
              value: "draft",
            },
            {
              name: "En attente de publication",
              value: "pending",
            },
            {
              name: "Publié",
              value: "published",
            },
            {
              name: "Archivé",
              value: "archived",
            },
Timothee P's avatar
Timothee P committed
        title: null,
DESPRES Damien's avatar
DESPRES Damien committed
      map:null,
      zoom:null,
      lat:null,
      lng:null,
      showMap: true,
      showAddSignal: false,
Timothee P's avatar
Timothee P committed
    ...mapState(["user"]),
    ...mapState("feature", ["features"]),
    ...mapState("feature_type", ["feature_types"]),
    filteredFeatures: function () {
      let results = this.features;
      if (this.form.type.selected) {
        results = results.filter(
          (el) => el.feature_type.title === this.form.type.selected
        );
      }
      if (this.form.status.selected.value) {
        results = results.filter(
          (el) => el.status === this.form.status.selected.value
        );
      }
      if (this.form.title) {
        results = results.filter((el) =>
          el.title.toLowerCase().includes(this.form.title.toLowerCase())
        );
      }
      return results;
      return this.filteredFeatures.slice(
        this.pagination.start,
        this.pagination.end
      );
  methods: {
    toPreviousPage() {
      if (this.pagination.start > 0) {
        this.pagination.start = this.pagination.start - 15;
        this.pagination.end += 15;
      }
    },
    toNextPage() {
      if (this.pagination.end < this.filteredFeatures.length) {
        this.pagination.start += 15;
        this.pagination.end = this.pagination.end - 15;
      }
    },
DESPRES Damien's avatar
DESPRES Damien committed
    addGeocoders(){
      let geocoder;

      // Get the settings.py variable SELECTED_GEOCODER_PROVIDER. This way avoids XCC attacks
DESPRES Damien's avatar
DESPRES Damien committed
      const geocoderLabel = this.$store.state.configuration.SELECTED_GEOCODER.PROVIDER;
DESPRES Damien's avatar
DESPRES Damien committed
      if (geocoderLabel) {
        const LIMIT_RESULTS = 5;
DESPRES Damien's avatar
DESPRES Damien committed
        if (geocoderLabel === this.$store.state.configuration.GEOCODER_PROVIDERS.ADDOK) {
DESPRES Damien's avatar
DESPRES Damien committed
          geocoder = L.Control.Geocoder.addok({limit: LIMIT_RESULTS});
DESPRES Damien's avatar
DESPRES Damien committed
        } else if (geocoderLabel === this.$store.state.configuration.GEOCODER_PROVIDERS.PHOTON) {
DESPRES Damien's avatar
DESPRES Damien committed
          geocoder = L.Control.Geocoder.photon();
DESPRES Damien's avatar
DESPRES Damien committed
        } else if (geocoderLabel === this.$store.state.configuration.GEOCODER_PROVIDERS.NOMINATIM) {
DESPRES Damien's avatar
DESPRES Damien committed
          geocoder = L.Control.Geocoder.nominatim();
        }

        L.Control.geocoder({
          placeholder: 'Chercher une adresse...',
          geocoder: geocoder,
        }).addTo(this.map);
      }

  },
      //this.$store.dispatch("GET_PROJECT_MESSAGES", this.$route.params.slug);
      this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
DESPRES Damien's avatar
DESPRES Damien committed
    //this.$store.dispatch("map/INITIATE_MAP");
    this.zoom = this.$route.query.zoom||'';
    this.lat = this.$route.query.lat||'';
    this.lng = this.$route.query.lng||'';
DESPRES Damien's avatar
DESPRES Damien committed
    var mapDefaultViewCenter = this.$store.state.configuration.DEFAULT_MAP_VIEW.center;
    var mapDefaultViewZoom = this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom;
DESPRES Damien's avatar
DESPRES Damien committed

    this.map=mapUtil.createMap({
      zoom:this.zoom,
      lat:this.lat,
      lng:this.lng,
      mapDefaultViewCenter,
      mapDefaultViewZoom,
    });
    let self=this;
    // ------   Listen Sidebar events ---------- //
    // Listen custom events triggered by the sidebar-layers
    document.addEventListener('add-layers', (event) => {
      mapUtil.removeLayers(self.map);
      // 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
DESPRES Damien's avatar
DESPRES Damien committed
      mapUtil.addLayers(event.detail.slice().reverse(), this.$store.state.configuration.DEFAULT_BASE_MAP.SERVICE, this.$store.state.configuration.DEFAULT_BASE_MAP.OPTIONS);
DESPRES Damien's avatar
DESPRES Damien committed
    });

    document.addEventListener('update-opacity', (event) => {
      mapUtil.updateOpacity(event.detail.layerId, event.detail.opacity);
    });

    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 ----------

DESPRES Damien's avatar
DESPRES Damien committed
      const url=`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
DESPRES Damien's avatar
DESPRES Damien committed
      //const url=configuration.BASE_URL+"/test_data/features.json";?output=geojson
      axios.get(url)
        .then((response) => {
          const features = response.data.features;

          const urlParams = new URLSearchParams(window.location.search);
          const featureType = urlParams.get('feature_type');
          const featureStatus = urlParams.get('status');
          const featureTitle = urlParams.get('title');
          const featureGroup = mapUtil.addFeatures(features, {featureType, featureStatus, featureTitle});
          // Fit the map to bound only if no initial zoom and center are defined
          if ((this.lat === "" || this.lng === "" || this.zoom === "") && features.length > 0) {
            mapUtil.getMap().fitBounds(featureGroup.getBounds())
          }


        })
        .catch((error) => {
          throw error;
        });


      
      // Update zoom and center on each move
      mapUtil.addMapEventListener("moveend", () => {
        self.zoom=mapUtil.getMap().getZoom();
        self.lat=mapUtil.getMap().getCenter().lat;
        self.lng=mapUtil.getMap().getCenter().lng;
        //$formFilters.find("input[name=zoom]").val(mapUtil.getMap().getZoom())
        //$formFilters.find("input[name=lat]").val(mapUtil.getMap().getCenter().lat)
        //$formFilters.find("input[name=lng]").val(mapUtil.getMap().getCenter().lng)
      });

      // Check if at least one basemap exist. If not, use the default application
      const basemaps = undefined;//JSON.parse(document.getElementById('basemaps').textContent);
      if (!basemaps || basemaps && basemaps.length === 0) {
DESPRES Damien's avatar
DESPRES Damien committed
        mapUtil.addLayers(null, this.$store.state.configuration.DEFAULT_BASE_MAP.SERVICE, this.$store.state.configuration.DEFAULT_BASE_MAP.OPTIONS);
DESPRES Damien's avatar
DESPRES Damien committed
      }
      setTimeout(function () { 
        this.addGeocoders();
      }.bind(this), 1000)




    this.form.type.choices = [
      //* convert Set to an Array with spread "..."
      ...new Set(this.features.map((el) => el.feature_type.title)), //* use Set to eliminate duplicate values
    ];
DESPRES Damien's avatar
DESPRES Damien committed
  height: calc( 100vh - 300px );
  border: 1px solid grey;
  /* To not hide the filters */
  z-index: 1;
}

#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>