Skip to content
Snippets Groups Projects
FeatureListTable.vue 17.8 KiB
Newer Older
<template>
Timothee P's avatar
Timothee P committed
  <div
    data-tab="list"
    class="dataTables_wrapper no-footer"
  >
    <table
      id="table-features"
      class="ui compact table dataTable"
    >
      <thead>
        <tr>
          <th class="dt-center">
Timothee P's avatar
Timothee P committed
            <div
              class="switch-buttons pointer"
              :data-tooltip="`Passer en mode ${mode === 'modify' ? 'suppression':'édition'}`"
              @click="switchMode"
            >
              <div><i :class="['icon pencil', {disabled: mode !== 'modify'}]" /></div>
              <span class="grey">|&nbsp;</span>
Timothee P's avatar
Timothee P committed
              <div><i :class="['icon trash', {disabled: mode !== 'delete'}]" /></div>
          <th class="dt-center">
Timothee P's avatar
Timothee P committed
            <div
              class="pointer"
              @click="changeSort('status')"
            >
              Statut
              <i
                :class="{
                  down: isSortedAsc('status'),
                  up: isSortedDesc('status'),
                }"
                class="icon sort"
              />
          </th>
          <th class="dt-center">
Timothee P's avatar
Timothee P committed
            <div
              class="pointer"
              @click="changeSort('feature_type')"
            >
              Type
              <i
                :class="{
                  down: isSortedAsc('feature_type'),
                  up: isSortedDesc('feature_type'),
                }"
                class="icon sort"
              />
            </div>
          </th>
          <th class="dt-center">
Timothee P's avatar
Timothee P committed
            <div
              class="pointer"
              @click="changeSort('title')"
            >
              Nom
              <i
                :class="{
                  down: isSortedAsc('title'),
                  up: isSortedDesc('title'),
                }"
                class="icon sort"
              />
          </th>
          <th class="dt-center">
Timothee P's avatar
Timothee P committed
            <div
              class="pointer"
              @click="changeSort('updated_on')"
            >
              Dernière modification
              <i
                :class="{
                  down: isSortedAsc('updated_on'),
                  up: isSortedDesc('updated_on'),
                }"
                class="icon sort"
              />
          </th>
Timothee P's avatar
Timothee P committed
          <th
            v-if="user"
            class="dt-center"
          >
            <div
              class="pointer"
              @click="changeSort('display_creator')"
            >
              Auteur
              <i
                :class="{
                  down: isSortedAsc('display_creator'),
                  up: isSortedDesc('display_creator'),
                }"
                class="icon sort"
              />
            </div>
          </th>
Timothee P's avatar
Timothee P committed
          <th
            v-if="user"
            class="dt-center"
          >
            <div
              class="pointer"
              @click="changeSort('display_last_editor')"
            >
              Dernier éditeur
              <i
                :class="{
                  down: isSortedAsc('display_last_editor'),
                  up: isSortedDesc('display_last_editor'),
                }"
                class="icon sort"
              />
            </div>
        </tr>
      </thead>
      <tbody>
Timothee P's avatar
Timothee P committed
        <tr
          v-for="(feature, index) in paginatedFeatures"
          :key="index"
        >
          <td class="dt-center">
              :class="['ui checkbox', {disabled: !checkRights(feature)}]"
              <input
                :id="feature.id"
Timothee P's avatar
Timothee P committed
                v-model="checked"
                type="checkbox"
                :value="feature.id"
                :disabled="!checkRights(feature)"
                name="select"
Timothee P's avatar
Timothee P committed
                @input="storeClickedFeature(feature)"
              >
              <label for="select" />
          <td class="dt-center">
            <div
              v-if="feature.properties.status.value === 'archived'"
              data-tooltip="Archivé"
            >
              <i class="grey archive icon" />
            </div>
            <div
              v-else-if="feature.properties.status.value === 'pending'"
              data-tooltip="En attente de publication"
            >
              <i class="teal hourglass outline icon" />
            </div>
            <div
              v-else-if="feature.properties.status.value === 'published'"
              data-tooltip="Publié"
            >
              <i class="olive check icon" />
            </div>
            <div
              v-else-if="feature.properties.status.value === 'draft'"
              data-tooltip="Brouillon"
            >
              <i class="orange pencil alternate icon" />
            </div>
          </td>
          <td class="dt-center">
            <router-link
              :to="{
                name: 'details-type-signalement',
                params: {
                  feature_type_slug: feature.properties.feature_type.slug,
                },
              }"
            >
              {{ feature.properties.feature_type.title }}
            </router-link>
          </td>
          <td class="dt-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="dt-center">
            {{ feature.properties.updated_on }}
          </td>
Timothee P's avatar
Timothee P committed
          <td
            v-if="user"
            class="dt-center"
          >
            {{ getUserName(feature) }}
          </td>
Timothee P's avatar
Timothee P committed
          <td
            v-if="user"
            class="dt-center"
          >
            {{ feature.properties.display_last_editor }}
          </td>
        </tr>
        <tr
          v-if="featuresCount === 0"
          class="odd"
        >
          <td
            colspan="5"
            class="dataTables_empty"
            valign="top"
          >
            Aucune donnée disponible
          </td>
        </tr>
      </tbody>
    </table>
Timothee P's avatar
Timothee P committed
    <div
Timothee P's avatar
Timothee P committed
      v-if="pageNumbers.length > 1"
      id="table-features_info"
      role="status"
      aria-live="polite"
    >
      Affichage de l'élément {{ pagination.start + 1 }} à
      {{ displayedPageEnd }}
Timothee P's avatar
Timothee P committed
      sur {{ featuresCount }} éléments
    </div>
    <div
Timothee P's avatar
Timothee P committed
      v-if="pageNumbers.length > 1"
      id="table-features_paginate"
      class="dataTables_paginate paging_simple_numbers"
        id="table-features_previous"
        :class="[
          'paginate_button previous',
          { disabled: pagination.currentPage === 1 },
        ]"
        aria-controls="table-features"
        data-dt-idx="0"
        tabindex="0"
        @click="$emit('update:page', 'previous')"
      >Précédent</a>
      <span>
        <span v-if="pagination.currentPage >= 5">
          <a
            key="page1"
            class="paginate_button"
            aria-controls="table-features"
            data-dt-idx="1"
            tabindex="0"
            @click="$emit('update:page', 1)"
          >{{ 1 }}</a>
          <span class="ellipsis"></span>
        </span>
          v-for="pageNumber in displayedPageNumbers"
Timothee P's avatar
Timothee P committed
          :key="'page' + pageNumber"
          :class="[
            'paginate_button',
Timothee P's avatar
Timothee P committed
            { current: pageNumber === pagination.currentPage },
          ]"
          aria-controls="table-features"
          data-dt-idx="1"
          tabindex="0"
          @click="$emit('update:page', pageNumber)"
        >{{ pageNumber }}</a>
        <span v-if="(lastPageNumber - pagination.currentPage) >= 4">
          <span class="ellipsis"></span>
          <a
            :key="'page' + lastPageNumber"
            class="paginate_button"
            aria-controls="table-features"
            data-dt-idx="1"
            tabindex="0"
            @click="$emit('update:page', lastPageNumber)"
          >{{ lastPageNumber }}</a>
      </span>
      <a
        id="table-features_next"
        :class="[
          'paginate_button next',
Timothee P's avatar
Timothee P committed
          { disabled: pagination.currentPage === pageNumbers.length },
        aria-controls="table-features"
        data-dt-idx="7"
        tabindex="0"
        @click="$emit('update:page', 'next')"
      >Suivant</a>
Timothee P's avatar
Timothee P committed
    </div>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex';
export default {
  name: 'FeatureListTable',
Timothee P's avatar
Timothee P committed
  props: {
    paginatedFeatures: {
      type: Array,
      default: null,
    },
    checkedFeatures: {
      type: Array,
      default: null,
    },
Timothee P's avatar
Timothee P committed
    clickedFeatures: {
      type: Array,
      default: null,
    },
Timothee P's avatar
Timothee P committed
    featuresCount: {
      type: Number,
      default: 0,
    },
    pagination: {
      type: Object,
      default: null,
    },
    sort: {
      type: Object,
      default: null,
    },
Timothee P's avatar
Timothee P committed
    mode: {
      type: String,
      default: null,
    }
Timothee P's avatar
Timothee P committed
  },

  computed: {
    ...mapGetters(['permissions']),
Timothee P's avatar
Timothee P committed
    ...mapState(['user', 'USER_LEVEL_PROJECTS']),
    ...mapState('projects', ['project']),
    userStatus() {
      return this.USER_LEVEL_PROJECTS[this.project.slug];
    },

    checked: {
      get() {
        return this.checkedFeatures;
      },
      set(newChecked) {
        this.$store.commit('feature/UPDATE_CHECKED_FEATURES', newChecked);
Timothee P's avatar
Timothee P committed

    displayedPageEnd() {
      return this.featuresCount <= this.pagination.end
        ? this.featuresCount
        : this.pagination.end;
    },

    pageNumbers() {
      const totalPages = Math.ceil(
        this.featuresCount / this.pagination.pagesize
      );
      return [...Array(totalPages).keys()].map((pageNumb) => {
        ++pageNumb;
        return pageNumb;
      });
    },

    lastPageNumber() {
      return this.pageNumbers.slice(-1)[0];
Timothee P's avatar
Timothee P committed

    displayedPageNumbers() {
      //* s'il y a moins de 5 pages, renvoyer toutes les pages
Timothee P's avatar
Timothee P committed
      if (this.lastPageNumber < 5) return this.pageNumbers;
      //* si la page courante est inférieur à 5, la liste commence à l'index 0 et on retourne 5 pages
      let firstPageInList = 0;
      let pagesQuantity = 5;
      //* à partir de la 5ième page et jusqu'à la 4ième page avant la fin : n'afficher que 3 page entre les ellipses et la page courante doit être au milieu
      if (this.pagination.currentPage >= 5 && !((this.lastPageNumber - this.pagination.currentPage) < 4)) {
        firstPageInList = this.pagination.currentPage - 2;
        pagesQuantity = 3;
      //* à partir de 4 résultat avant la fin afficher seulement les 5 derniers résultats
      if ((this.lastPageNumber - this.pagination.currentPage) < 4) {
        firstPageInList = this.lastPageNumber - 5;
      }
      return this.pageNumbers.slice(firstPageInList, firstPageInList + pagesQuantity);
    },
Timothee P's avatar
Timothee P committed
  destroyed() {
    this.$store.commit('feature/UPDATE_CHECKED_FEATURES', []);
  },

  methods: {
Timothee P's avatar
Timothee P committed
      this.$emit('update:clickedFeatures', [...this.clickedFeatures, { feature_id: feature.id, feature_type: feature.properties.feature_type.slug }]);
    canDeleteFeature(feature) {
      if (this.userStatus === 'Administrateur projet') return true; //* can delete all
      //* others can delete only their own features
      return feature.properties.creator.username === this.user.username;
    canEditFeature(feature) {
      const permissions = {
        'Administrateur projet' : ['draft', 'pending', 'published', 'archived'],
        Modérateur : ['draft', 'pending', 'published'],
        'Super Contributeur' : ['draft', 'pending', 'published'],
      if (this.userStatus === 'Contributeur' && feature.properties.creator.username !== this.user.username) {
        return false;
      } else if (permissions[this.userStatus]) {
        return permissions[this.userStatus].includes(feature.properties.status.value);
Timothee P's avatar
Timothee P committed
        return false;
    checkRights(feature) {
      switch (this.mode) {
Timothee P's avatar
Timothee P committed
      case 'modify':
        return this.canEditFeature(feature);
      case 'delete':
        return this.canDeleteFeature(feature);
      } 
    },

    switchMode() {
      this.$emit('update:mode', this.mode === 'modify' ? 'delete' : 'modify');
      this.$emit('update:clickedFeatures', []);
Timothee P's avatar
Timothee P committed
      this.$store.commit('feature/UPDATE_CHECKED_FEATURES', []);
    getUserName(feature) {
      if (!feature.properties.creator) {
        return ' ---- ';
      return feature.properties.creator.username || ' ---- ';
    getFeatureDisplayName(feature) {
      return feature.properties.title || feature.id;
    },

    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) {
Timothee P's avatar
Timothee P committed
        //changer only order
        this.$emit('update:sort', {
          column: this.sort.column,
          ascending: !this.sort.ascending,
        });
Timothee P's avatar
Timothee P committed
      } else { // change column and reset order
        this.$emit('update:sort', { column, ascending: true });
      }
    },
  },
};
</script>

<style scoped>
/* datatables */
Timothee P's avatar
Timothee P committed
.dataTables_wrapper {
  position: relative;
  clear: both;
}
table.dataTable th.dt-center, table.dataTable td.dt-center, table.dataTable td.dataTables_empty {
  text-align: center;
}
.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:hover {
  color: white !important;
  border: 1px solid #111;
  background-color: #585858;
  background: -webkit-gradient(
    linear,
    left top,
    left bottom,
    color-stop(0%, #585858),
    color-stop(100%, #111)
  );
  background: -webkit-linear-gradient(top, #585858 0%, #111 100%);
  background: -moz-linear-gradient(top, #585858 0%, #111 100%);
  background: -ms-linear-gradient(top, #585858 0%, #111 100%);
  background: -o-linear-gradient(top, #585858 0%, #111 100%);
  background: linear-gradient(to bottom, #585858 0%, #111 100%);
}
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,
.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;
Timothee P's avatar
Timothee P committed
.dataTables_wrapper .dataTables_paginate .ellipsis {
  padding: 0 1em;
}

i.icon.sort:not(.down):not(.up) {
  color: rgb(220, 220, 220);
}
.pointer:hover {
  cursor: pointer;
}
.switch-buttons {
  display: flex;
  justify-content: center;
  align-items: baseline;
}

.grey {
  color: #bbbbbb;
}
.ui.dropdown .menu .left.menu, .ui.dropdown > .left.menu .menu {
  margin-right: 0 !important;
}
/* 
Max width before this PARTICULAR table gets nasty
This query will take effect for any screen smaller than 760px
and also iPads specifically.
*/
@media only screen and (max-width: 760px),
  (min-device-width: 768px) and (max-device-width: 1024px) {
  /* Force table to not be like tables anymore */
  table,
  thead,
  tbody,
  th,
  td,
  tr {
    display: block;
  }

  /* Hide table headers (but not display: none;, for accessibility) */
  thead tr {
    position: absolute;
    top: -9999px;
    left: -9999px;
  }

  tr {
    border: 1px solid #ccc;
  }

  td {
    /* Behave  like a "row" */
    border: none;
    border-bottom: 1px solid #eee;
    position: relative;
    padding-left: 50%;
  }

  td:before {
    /* Now like a table header */
    position: absolute;
    /* Top/left values mimic padding */
    /* top: 6px; */
    left: 6px;
    /* width: 45%; */
    padding-right: 10px;
    white-space: nowrap;
  }

  /*
	Label the data
	*/
  td:nth-of-type(1):before {
    content: "";
  }
  td:nth-of-type(2):before {
    content: "Statut";
  }
  td:nth-of-type(3):before {
    content: "Type";
  }
  td:nth-of-type(4):before {
    content: "Nom";
  }
  td:nth-of-type(5):before {
    content: "Dernière modification";
  }
  td:nth-of-type(6):before {
    content: "Auteur";
  }

  table.dataTable th.dt-center, table.dataTable td.dt-center, table.dataTable td.dataTables_empty {
    text-align: right;
  }

  #table-features {
    margin-left: 1em;
    width: calc(100% - 1em);
  }

  .ui.checkbox {
    position: absolute;
    left: -1.75em;
    top: 5em;
  }
}
</style>