<template> <div data-tab="list" class="dataTables_wrapper no-footer" > <table id="table-features" class="ui compact table dataTable" > <thead> <tr> <th class="dt-center"> <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">| </span> <div><i :class="['icon trash', {disabled: mode !== 'delete'}]" /></div> </div> </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.id" v-model="checked" type="checkbox" :value="feature.id" :disabled="!checkRights(feature)" name="select" @input="storeClickedFeature(feature)" > <label for="select" /> </div> </td> <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> <td v-if="user" class="dt-center" > {{ getUserName(feature) }} </td> <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> <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> </template> <script> import { mapState, mapGetters } from 'vuex'; export default { name: 'FeatureListTable', props: { paginatedFeatures: { type: Array, default: null, }, checkedFeatures: { type: Array, default: null, }, clickedFeatures: { type: Array, default: null, }, featuresCount: { type: Number, default: 0, }, pagination: { type: Object, default: null, }, sort: { type: Object, default: null, }, mode: { type: String, default: null, } }, computed: { ...mapGetters(['permissions']), ...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); }, }, 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]; }, 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.$store.commit('feature/UPDATE_CHECKED_FEATURES', []); }, methods: { storeClickedFeature(feature) { 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'], 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); } else { return false; } }, checkRights(feature) { switch (this.mode) { 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', []); 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) { //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); } .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>