<template> <div> <div class="table-mobile-buttons left-align"> <FeatureListMassToggle /> </div> <div data-tab="list" class="dataTables_wrapper no-footer" > <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" > <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" > <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" > <td class="dt-center"> <div :class="['ui checkbox', {disabled: !checkRights(feature)}]" > <input :id="feature.feature_id" v-model="checked" type="checkbox" :value="feature.feature_id" :disabled="!checkRights(feature)" name="select" @input="storeClickedFeature(feature)" > <label for="select" /> </div> </td> <td class="dt-center"> <div v-if="feature.status === 'archived'" data-tooltip="Archivé" > <i class="grey archive icon" /> </div> <div v-else-if="feature.status === 'pending'" data-tooltip="En attente de publication" > <i class="teal hourglass outline icon" /> </div> <div v-else-if="feature.status === 'published'" data-tooltip="Publié" > <i class="olive check icon" /> </div> <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: { feature_type_slug: feature.feature_type.slug, }, }" > {{ feature.feature_type.title }} </router-link> </td> <td class="dt-center"> <router-link :to="{ name: 'details-signalement', params: { slug_type_signal: feature.feature_type.slug, slug_signal: feature.slug || feature.feature_id, }, }" > {{ feature.title || feature.feature_id }} </router-link> </td> <td class="dt-center"> {{ feature.updated_on | formatDate }} </td> <td v-if="user" class="dt-center" > {{ feature.display_creator || ' ---- ' }} </td> <td v-if="user" class="dt-center" > {{ 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" > <a 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> <a 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> </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> </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); }, }, components: { FeatureListMassToggle, }, props: { paginatedFeatures: { type: Array, default: null, }, pageNumbers: { type: Array, default: null, }, checkedFeatures: { type: Array, default: null, }, featuresCount: { type: Number, default: 0, }, pagination: { type: Object, default: null, }, sort: { type: Object, default: null, }, }, computed: { ...mapGetters(['permissions']), ...mapState(['user', 'USER_LEVEL_PROJECTS']), ...mapState('projects', ['project']), ...mapState('feature', ['clickedFeatures', 'massMode']), userStatus() { return this.USER_LEVEL_PROJECTS[this.$route.params.slug]; }, checked: { get() { return this.checkedFeatures; }, set(newChecked) { this.$store.commit('feature/UPDATE_CHECKED_FEATURES', newChecked); }, }, displayedPageEnd() { return this.featuresCount <= this.pagination.end ? this.featuresCount : this.pagination.end; }, lastPageNumber() { return this.pageNumbers.slice(-1)[0]; }, displayedPageNumbers() { //* s'il y a moins de 5 pages, renvoyer toutes les pages 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); }, }, destroyed() { this.UPDATE_CHECKED_FEATURES([]); }, methods: { ...mapMutations('feature', [ 'UPDATE_CLICKED_FEATURES', 'UPDATE_CHECKED_FEATURES', ]), storeClickedFeature(feature) { 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'], Contributeur : ['draft', 'pending', 'published'], }; if (this.userStatus === 'Contributeur' && feature.display_creator !== this.user.username) { return false; } else if (permissions[this.userStatus]) { return permissions[this.userStatus].includes(feature.status); } else { return false; } }, checkRights(feature) { switch (this.massMode) { case 'modify': return this.canEditFeature(feature); case 'delete': return this.canDeleteFeature(feature); } }, switchMode() { this.$emit('update:mode', this.mode === 'modify' ? 'delete' : 'modify'); this.UPDATE_CLICKED_FEATURES([]); 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) { //changer only order this.$emit('update:sort', { column: this.sort.column, ascending: !this.sort.ascending, }); } else { // change column and reset order this.$emit('update:sort', { column, ascending: true }); } }, }, }; </script> <style scoped> /* datatables */ .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; } .dataTables_wrapper .dataTables_paginate .ellipsis { padding: 0 1em; } i.icon.sort:not(.down):not(.up) { color: rgb(220, 220, 220); } .grey { color: #bbbbbb; } .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; } tr { /* style as a card */ 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; } } </style>