Skip to content
Snippets Groups Projects
FeatureListTable.vue 18.5 KiB
Newer Older
<template>
  <div>
    <div class="table-mobile-buttons left-align">
      <FeatureListMassToggle />
    </div>
    <div
      data-tab="list"
      class="dataTables_wrapper no-footer"
Timothee P's avatar
Timothee P committed
    >
      <table
        id="table-features"
        class="ui compact table unstackable dataTable"
      >
        <thead>
          <tr>
            <th class="dt-center">
              <FeatureListMassToggle />
            </th>

            <th class="dt-center">
              <div
                class="pointer"
                @click="changeSort('status')"
              >
                Statut
                <i
                  :class="{
                    down: isSortedAsc('status'),
                    up: isSortedDesc('status'),
                  }"
                  class="icon sort"
                />
              </div>
            </th>
            <th class="dt-center">
              <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">
              <div
                class="pointer"
                @click="changeSort('title')"
              >
                Nom
                <i
                  :class="{
                    down: isSortedAsc('title'),
                    up: isSortedDesc('title'),
                  }"
                  class="icon sort"
                />
              </div>
            </th>
            <th class="dt-center">
              <div
                class="pointer"
                @click="changeSort('updated_on')"
              >
                Dernière modification
                <i
                  :class="{
                    down: isSortedAsc('updated_on'),
                    up: isSortedDesc('updated_on'),
                  }"
                  class="icon sort"
                />
              </div>
            </th>
            <th
              v-if="user"
              class="dt-center"
Timothee P's avatar
Timothee P committed
            >
              <div
                class="pointer"
                @click="changeSort('display_creator')"
              >
                Auteur
                <i
                  :class="{
                    down: isSortedAsc('display_creator'),
                    up: isSortedDesc('display_creator'),
                  }"
                  class="icon sort"
                />
              </div>
            </th>
            <th
              v-if="user"
              class="dt-center"
Timothee P's avatar
Timothee P committed
            >
              <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>
            </th>
          </tr>
        </thead>
        <tbody>
          <tr
            v-for="(feature, index) in paginatedFeatures"
            :key="index"
Timothee P's avatar
Timothee P committed
          >
            <td class="dt-center">
              <div
                :class="['ui checkbox', {disabled: !checkRights(feature)}]"
              >
                <input
Timothee P's avatar
Timothee P committed
                  :id="feature.feature_id"
Timothee P's avatar
Timothee P committed
                  :value="feature.feature_id"
                  :disabled="!checkRights(feature)"
                  name="select"
                  @input="storeClickedFeature(feature)"
                >
                <label for="select" />
              </div>
            </td>

            <td class="dt-center">
Timothee P's avatar
Timothee P committed
              <div
                v-if="feature.status === 'archived'"
                data-tooltip="Archivé"
              >
                <i class="grey archive icon" />
Timothee P's avatar
Timothee P committed
              <div
                v-else-if="feature.status === 'pending'"
                data-tooltip="En attente de publication"
              >
                <i class="teal hourglass outline icon" />
Timothee P's avatar
Timothee P committed
              <div
                v-else-if="feature.status === 'published'"
                data-tooltip="Publié"
              >
                <i class="olive check icon" />
Timothee P's avatar
Timothee P committed
              <div
                v-else-if="feature.status === 'draft'"
                data-tooltip="Brouillon"
              >
                <i class="orange pencil alternate icon" />
              </div>
            </td>
            <td class="dt-center">
              <router-link
                :to="{
                  name: 'details-type-signalement',
                  params: {
Timothee P's avatar
Timothee P committed
                    feature_type_slug: feature.feature_type.slug,
Timothee P's avatar
Timothee P committed
                {{ feature.feature_type.title }}
              </router-link>
            </td>
            <td class="dt-center">
              <router-link
                :to="{
                  name: 'details-signalement',
                  params: {
Timothee P's avatar
Timothee P committed
                    slug_type_signal: feature.feature_type.slug,
                    slug_signal: feature.slug || feature.feature_id,
Timothee P's avatar
Timothee P committed
              >
Timothee P's avatar
Timothee P committed
                {{ feature.title || feature.feature_id }}
Timothee P's avatar
Timothee P committed
              {{ feature.updated_on | formatDate }}
Timothee P's avatar
Timothee P committed
              {{ feature.display_creator || ' ---- ' }}
Timothee P's avatar
Timothee P committed
              {{ feature.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>
      <div
        v-if="pageNumbers.length > 1"
        id="table-features_info"
        class="dataTables_info"
        role="status"
        aria-live="polite"
      >
        Affichage de l'élément {{ pagination.start + 1 }} à
        {{ displayedPageEnd }}
        sur {{ featuresCount }} éléments
      </div>
      <div
        v-if="pageNumbers.length > 1"
        id="table-features_paginate"
        class="dataTables_paginate paging_simple_numbers"
      >
          :class="[
            'paginate_button previous',
            { disabled: pagination.currentPage === 1 },
          ]"
          aria-controls="table-features"
          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"
            :key="'page' + pageNumber"
            :class="[
              'paginate_button',
              { 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',
            { disabled: pagination.currentPage === pageNumbers.length },
          ]"
          aria-controls="table-features"
          data-dt-idx="7"
          tabindex="0"
          @click="$emit('update:page', 'next')"
        >Suivant</a>
      </div>
Timothee P's avatar
Timothee P committed
    </div>
  </div>
</template>

<script>
import { mapState, mapGetters, mapMutations } from 'vuex';
import FeatureListMassToggle from '@/components/feature/FeatureListMassToggle';
import { formatStringDate } from '@/utils';
export default {
  name: 'FeatureListTable',
  filters: {
    formatDate(value) {
      return formatStringDate(value);
    },
Timothee P's avatar
Timothee P committed
  },
Timothee P's avatar
Timothee P committed
  props: {
    paginatedFeatures: {
      type: Array,
      default: null,
    },
Timothee P's avatar
Timothee P committed
      type: Array,
      default: null,
    },
    checkedFeatures: {
Timothee P's avatar
Timothee P committed
      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,
    },
  },

  computed: {
    ...mapGetters(['permissions']),
Timothee P's avatar
Timothee P committed
    ...mapState(['user', 'USER_LEVEL_PROJECTS']),
    ...mapState('projects', ['project']),
    ...mapState('feature', ['clickedFeatures', 'massMode']),
      return this.USER_LEVEL_PROJECTS[this.$route.params.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;
    },

    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() {
Timothee P's avatar
Timothee P committed
  },

  methods: {
    ...mapMutations('feature', [
      'UPDATE_CLICKED_FEATURES',
      'UPDATE_CHECKED_FEATURES',
    ]),

      this.UPDATE_CLICKED_FEATURES([
        ...this.clickedFeatures,
        { feature_id: feature.feature_id, feature_type: feature.feature_type.slug }
    canDeleteFeature(feature) {
      if (this.userStatus === 'Administrateur projet') return true; //* can delete all
      //* others can delete only their own features
      return feature.display_creator === 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.display_creator !== this.user.username) {
        return false;
        return permissions[this.userStatus].includes(feature.status);
Timothee P's avatar
Timothee P committed
        return false;
    checkRights(feature) {
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');
Timothee P's avatar
Timothee P committed
      this.UPDATE_CLICKED_FEATURES([]);
Timothee P's avatar
Timothee P committed
      this.$store.commit('feature/UPDATE_CHECKED_FEATURES', []);

    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);
}
.ui.dropdown .menu .left.menu, .ui.dropdown > .left.menu .menu {
  margin-right: 0 !important;
}

.table-mobile-buttons {
  margin-bottom: 1em;
}
@media only screen and (min-width: 761px) {
  .table-mobile-buttons {
    display: none !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) {
  .table-mobile-buttons {
    display: flex !important;
    }
  /* hide table border */
  .ui.table {
    border: none !important;
  }
  /* 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;
  }

    border: 1px solid #ccc;
    border-radius: 7px;
    margin-bottom: 3vh;
    padding: 0 1vw .5em 1vw;
    box-shadow: rgba(50, 50, 50, 0.1) 2px 5px 10px ;
  }

  td {
    /* Behave  like a "row" */
    border: none;
    border-bottom: 1px solid #eee;
    position: relative;
    padding-left: 50%;
  }
  .ui.table tr td {
    border-top: none;
  }
  .ui.compact.table td {
    padding: .2em;
  }
  td:nth-of-type(1) {
    border: none !important;
    padding: .25em !important;
  }
  td:nth-of-type(7) {
    border-bottom: none !important;
  }
  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";
  }
  td:nth-of-type(7):before {
    content: "Dernier éditeur";
  }
  table.dataTable th.dt-center, table.dataTable td.dt-center, table.dataTable td.dataTables_empty {
    text-align: right;
  }

  .ui.checkbox {
    position: absolute;
    left: calc(-1vw - .75em);
    top: -.75em;
  }
  .dataTables_wrapper .dataTables_info,
  .dataTables_wrapper .dataTables_paginate {
    width: 100%;
    text-align: center;
    margin: .5em 0;
  }
}
@media only screen and (max-width: 410px) {
  .ui.table tr td {
    border: none;