1<template> 2 <b-container fluid="xl"> 3 <page-title /> 4 <b-row class="align-items-start"> 5 <b-col sm="8" xl="6" class="d-sm-flex align-items-end mb-4"> 6 <search 7 :placeholder="$t('pagePostCodeLogs.table.searchLogs')" 8 @change-search="onChangeSearchInput" 9 @clear-search="onClearSearchInput" 10 /> 11 <div class="ms-sm-4"> 12 <table-cell-count 13 :filtered-items-count="filteredRows" 14 :total-number-of-cells="allLogs.length" 15 ></table-cell-count> 16 </div> 17 </b-col> 18 <b-col sm="8" md="7" xl="6"> 19 <table-date-filter @change="onChangeDateTimeFilter" /> 20 </b-col> 21 </b-row> 22 <b-row> 23 <b-col xl="12" class="text-end"> 24 <b-button 25 variant="link" 26 :disabled="allLogs.length === 0" 27 @click="deleteAllLogs" 28 > 29 <icon-delete /> {{ $t('global.action.deleteAll') }} 30 </b-button> 31 <b-button 32 variant="primary" 33 :disabled="allLogs.length === 0" 34 :download="exportFileNameByDate()" 35 :href="href" 36 > 37 <icon-export /> 38 {{ $t('pagePostCodeLogs.button.exportAll') }} 39 </b-button> 40 </b-col> 41 </b-row> 42 <b-row> 43 <b-col> 44 <table-toolbar 45 ref="toolbar" 46 :selected-items-count=" 47 Array.isArray(selectedRows) ? selectedRows.length : 0 48 " 49 @clear-selected="clearSelectedRows($refs.table)" 50 > 51 <template #toolbar-buttons> 52 <table-toolbar-export 53 :data="batchExportData" 54 :file-name="exportFileNameByDate()" 55 /> 56 </template> 57 </table-toolbar> 58 <b-table 59 id="table-post-code-logs" 60 ref="table" 61 responsive="md" 62 selectable 63 no-select-on-click 64 sort-icon-left 65 hover 66 must-sort 67 thead-class="table-light" 68 :sort-desc="[true]" 69 show-empty 70 :sort-by="['id']" 71 :fields="fields" 72 :items="filteredLogs" 73 :empty-text="$t('global.table.emptyMessage')" 74 :empty-filtered-text="$t('global.table.emptySearchMessage')" 75 :per-page="perPage" 76 :current-page="currentPage" 77 :filter="searchFilter" 78 :busy="isBusy" 79 @filtered="onFiltered" 80 @row-selected="onRowSelected($event, filteredLogs.length)" 81 > 82 <!-- Checkbox column --> 83 <template #head(checkbox)> 84 <b-form-checkbox 85 v-model="tableHeaderCheckboxModel" 86 data-test-id="postCode-checkbox-selectAll" 87 :indeterminate="tableHeaderCheckboxIndeterminate" 88 @change="onChangeHeaderCheckbox($refs.table, $event)" 89 > 90 <span class="visually-hidden-focusable"> 91 {{ $t('global.table.selectAll') }} 92 </span> 93 </b-form-checkbox> 94 </template> 95 <template #cell(checkbox)="row"> 96 <b-form-checkbox 97 v-model="row.rowSelected" 98 :data-test-id="`postCode-checkbox-selectRow-${row.index}`" 99 @change="toggleSelectRow($refs.table, row.index)" 100 > 101 <span class="visually-hidden-focusable"> 102 {{ $t('global.table.selectItem') }} 103 </span> 104 </b-form-checkbox> 105 </template> 106 <!-- Date column --> 107 <template #cell(date)="{ value }"> 108 <p class="mb-0">{{ $filters.formatDate(value) }}</p> 109 <p class="mb-0">{{ $filters.formatTime(value) }}</p> 110 </template> 111 112 <!-- Actions column --> 113 <template #cell(actions)="row"> 114 <table-row-action 115 v-for="(action, index) in row.item.actions" 116 :key="index" 117 :value="action.value" 118 :title="action.title" 119 :row-data="row.item" 120 :btn-icon-only="true" 121 :export-name="exportFileNameByDate(action.value)" 122 :download-location=" 123 action.value === 'download' ? '' : row.item.uri 124 " 125 :download-in-new-tab="false" 126 :show-button="action.value === 'download' ? true : false" 127 @click-table-action="onRowAction(action.value, row.item)" 128 > 129 <template #icon> 130 <icon-export v-if="action.value === 'export'" /> 131 <icon-download v-if="action.value === 'download'" /> 132 </template> 133 </table-row-action> 134 </template> 135 </b-table> 136 </b-col> 137 </b-row> 138 139 <!-- Table pagination --> 140 <b-row> 141 <b-col sm="6"> 142 <b-form-group 143 class="table-pagination-select" 144 :label="$t('global.table.itemsPerPage')" 145 label-for="pagination-items-per-page" 146 > 147 <b-form-select 148 id="pagination-items-per-page" 149 v-model="perPage" 150 :options="itemsPerPageOptions" 151 /> 152 </b-form-group> 153 </b-col> 154 <b-col sm="6"> 155 <b-pagination 156 v-model="currentPage" 157 first-number 158 last-number 159 :per-page="perPage" 160 :total-rows="getTotalRowCount(filteredRows)" 161 aria-controls="table-post-code-logs" 162 /> 163 </b-col> 164 </b-row> 165 </b-container> 166</template> 167 168<script> 169import IconDelete from '@carbon/icons-vue/es/trash-can/20'; 170import IconDownload from '@carbon/icons-vue/es/download/20'; 171import IconExport from '@carbon/icons-vue/es/document--export/20'; 172import { omit } from 'lodash'; 173import PageTitle from '@/components/Global/PageTitle'; 174import Search from '@/components/Global/Search'; 175import TableCellCount from '@/components/Global/TableCellCount'; 176import TableDateFilter from '@/components/Global/TableDateFilter'; 177import TableRowAction from '@/components/Global/TableRowAction'; 178import TableToolbar from '@/components/Global/TableToolbar'; 179import TableToolbarExport from '@/components/Global/TableToolbarExport'; 180import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin'; 181import TableFilterMixin from '@/components/Mixins/TableFilterMixin'; 182import BVPaginationMixin, { 183 currentPage, 184 perPage, 185 itemsPerPageOptions, 186} from '@/components/Mixins/BVPaginationMixin'; 187import BVTableSelectableMixin, { 188 selectedRows, 189 tableHeaderCheckboxModel, 190 tableHeaderCheckboxIndeterminate, 191} from '@/components/Mixins/BVTableSelectableMixin'; 192import BVToastMixin from '@/components/Mixins/BVToastMixin'; 193import TableSortMixin from '@/components/Mixins/TableSortMixin'; 194import TableRowExpandMixin, { 195 expandRowLabel, 196} from '@/components/Mixins/TableRowExpandMixin'; 197import SearchFilterMixin, { 198 searchFilter, 199} from '@/components/Mixins/SearchFilterMixin'; 200import { useI18n } from 'vue-i18n'; 201import i18n from '@/i18n'; 202import { useModal } from 'bootstrap-vue-next'; 203 204export default { 205 components: { 206 IconDelete, 207 IconExport, 208 IconDownload, 209 PageTitle, 210 Search, 211 TableCellCount, 212 TableRowAction, 213 TableToolbar, 214 TableToolbarExport, 215 TableDateFilter, 216 }, 217 mixins: [ 218 BVPaginationMixin, 219 BVTableSelectableMixin, 220 BVToastMixin, 221 LoadingBarMixin, 222 TableFilterMixin, 223 TableSortMixin, 224 TableRowExpandMixin, 225 SearchFilterMixin, 226 ], 227 beforeRouteLeave(to, from, next) { 228 // Hide loader if the user navigates to another page 229 // before request is fulfilled. 230 this.hideLoader(); 231 next(); 232 }, 233 setup() { 234 const bvModal = useModal(); 235 return { bvModal }; 236 }, 237 data() { 238 return { 239 $t: useI18n().t, 240 isBusy: true, 241 fields: [ 242 { 243 key: 'checkbox', 244 sortable: false, 245 }, 246 { 247 key: 'date', 248 label: i18n.global.t('pagePostCodeLogs.table.created'), 249 sortable: true, 250 }, 251 { 252 key: 'timeStampOffset', 253 label: i18n.global.t('pagePostCodeLogs.table.timeStampOffset'), 254 }, 255 { 256 key: 'bootCount', 257 label: i18n.global.t('pagePostCodeLogs.table.bootCount'), 258 }, 259 { 260 key: 'postCode', 261 label: i18n.global.t('pagePostCodeLogs.table.postCode'), 262 }, 263 { 264 key: 'actions', 265 label: '', 266 tdClass: 'text-end text-nowrap', 267 }, 268 ], 269 expandRowLabel, 270 activeFilters: [], 271 currentPage: currentPage, 272 filterStartDate: null, 273 filterEndDate: null, 274 itemsPerPageOptions: itemsPerPageOptions, 275 perPage: perPage, 276 searchFilter: searchFilter, 277 searchTotalFilteredRows: 0, 278 selectedRows: selectedRows, 279 tableHeaderCheckboxModel: tableHeaderCheckboxModel, 280 tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate, 281 }; 282 }, 283 computed: { 284 href() { 285 return `data:text/json;charset=utf-8,${this.exportAllLogsString()}`; 286 }, 287 filteredRows() { 288 return this.searchFilter 289 ? this.searchTotalFilteredRows 290 : this.filteredLogs.length; 291 }, 292 allLogs() { 293 return this.$store.getters['postCodeLogs/allPostCodes'].map( 294 (postCodes) => { 295 return { 296 ...postCodes, 297 actions: [ 298 { 299 value: 'export', 300 title: i18n.global.t('pagePostCodeLogs.action.exportLogs'), 301 }, 302 { 303 value: 'download', 304 title: i18n.global.t('pagePostCodeLogs.action.downloadDetails'), 305 }, 306 ], 307 }; 308 }, 309 ); 310 }, 311 batchExportData() { 312 return this.selectedRows.map((row) => omit(row, 'actions')); 313 }, 314 filteredLogsByDate() { 315 return this.getFilteredTableDataByDate( 316 this.allLogs, 317 this.filterStartDate, 318 this.filterEndDate, 319 ); 320 }, 321 filteredLogs() { 322 return this.getFilteredTableData( 323 this.filteredLogsByDate, 324 this.activeFilters, 325 ); 326 }, 327 }, 328 created() { 329 this.startLoader(); 330 this.$store.dispatch('postCodeLogs/getPostCodesLogData').finally(() => { 331 this.endLoader(); 332 this.isBusy = false; 333 }); 334 }, 335 methods: { 336 onRowAction(action, item) { 337 if (action === 'download') { 338 this.$store 339 .dispatch('postCodeLogs/downloadEntry', item.uri) 340 .then((blob) => { 341 const link = document.createElement('a'); 342 link.href = URL.createObjectURL(blob); 343 link.download = this.exportFileNameByDate('download'); 344 link.click(); 345 URL.revokeObjectURL(link.href); 346 }) 347 .catch(({ message }) => this.errorToast(message)); 348 } 349 }, 350 async deleteAllLogs() { 351 const deleteConfirmed = await this.confirmDialog( 352 i18n.global.t('pageEventLogs.modal.deleteAllMessage'), 353 { 354 title: i18n.global.t('pageEventLogs.modal.deleteAllTitle'), 355 okTitle: i18n.global.t('global.action.delete'), 356 okVariant: 'danger', 357 cancelTitle: i18n.global.t('global.action.cancel'), 358 autoFocusButton: 'cancel', 359 }, 360 ); 361 if (deleteConfirmed) { 362 this.$store 363 .dispatch('postCodeLogs/deleteAllPostCodeLogs', this.allLogs) 364 .then((message) => this.successToast(message)) 365 .catch(({ message }) => this.errorToast(message)); 366 } 367 }, 368 exportAllLogsString() { 369 { 370 return this.$store.getters['postCodeLogs/allPostCodes'].map( 371 (postCodes) => { 372 const allLogsString = JSON.stringify(postCodes); 373 return allLogsString; 374 }, 375 ); 376 } 377 }, 378 onFilterChange({ activeFilters }) { 379 this.activeFilters = activeFilters; 380 }, 381 onChangeDateTimeFilter({ fromDate, toDate }) { 382 this.filterStartDate = fromDate; 383 this.filterEndDate = toDate; 384 }, 385 onFiltered(filteredItems) { 386 this.searchTotalFilteredRows = filteredItems.length; 387 }, 388 // Create export file name based on date and action 389 exportFileNameByDate(value) { 390 let date = new Date(); 391 date = 392 date.toISOString().slice(0, 10) + 393 '_' + 394 date.toString().split(':').join('-').split(' ')[4]; 395 let fileName; 396 if (value === 'download') { 397 fileName = i18n.global.t('pagePostCodeLogs.downloadFilePrefix'); 398 } else if (value === 'export') { 399 fileName = i18n.global.t('pagePostCodeLogs.exportFilePrefix'); 400 } else { 401 fileName = i18n.global.t('pagePostCodeLogs.allExportFilePrefix'); 402 } 403 return fileName + date; 404 }, 405 confirmDialog(message, options = {}) { 406 return this.$confirm({ message, ...options }); 407 }, 408 }, 409}; 410</script> 411