scinote-web/app/javascript/vue/shared/datatable/table.vue

580 lines
16 KiB
Vue
Raw Normal View History

<template>
<div class="flex flex-col h-full">
2023-11-10 20:34:36 +08:00
<div class="relative flex flex-col flex-grow z-10">
2023-11-24 18:08:28 +08:00
<Toolbar
:toolbarActions="toolbarActions"
@toolbar:action="emitAction"
:searchValue="searchValue"
@search:change="setSearchValue"
:activePageUrl="activePageUrl"
:archivedPageUrl="archivedPageUrl"
:currentViewMode="currentViewMode"
2023-12-01 07:01:08 +08:00
:currentViewRender="currentViewRender"
:viewRenders="viewRenders"
2023-11-24 18:08:28 +08:00
:filters="filters"
2023-12-12 19:17:38 +08:00
:columnDefs="columnDefs"
:tableState="tableState"
2024-01-12 00:42:17 +08:00
:order="order"
2023-11-24 18:08:28 +08:00
@applyFilters="applyFilters"
2023-12-01 07:01:08 +08:00
@setTableView="switchViewRender('table')"
@setCardsView="switchViewRender('cards')"
2024-01-12 00:42:17 +08:00
@sort="applyOrder"
2023-12-12 19:17:38 +08:00
@hideColumn="hideColumn"
@showColumn="showColumn"
@pinColumn="pinColumn"
@unPinColumn="unPinColumn"
@reorderColumns="reorderColumns"
@resetColumnsToDefault="resetColumnsToDefault"
2023-11-24 18:08:28 +08:00
/>
<div v-if="currentViewRender === 'cards'" ref="cardsContainer" @scroll="handleScroll"
2023-12-12 19:17:38 +08:00
class="flex-grow basis-64 overflow-y-auto overflow-x-visible p-2 -ml-2">
2023-12-01 07:01:08 +08:00
<div class="grid grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
<slot v-for="element in rowData" :key="element.id" name="card" :dtComponent="this" :params="element"></slot>
</div>
</div>
<ag-grid-vue
2023-12-01 07:01:08 +08:00
v-if="currentViewRender === 'table'"
2023-11-10 20:34:36 +08:00
class="ag-theme-alpine w-full flex-grow h-full z-10"
2024-01-09 01:33:28 +08:00
:class="{'opacity-0': initializing }"
2023-12-05 03:59:16 +08:00
:columnDefs="extendedColumnDefs"
:rowData="rowData"
:defaultColDef="defaultColDef"
:rowSelection="'multiple'"
2023-11-10 20:34:36 +08:00
:suppressRowTransform="true"
:gridOptions="gridOptions"
2023-11-10 20:34:36 +08:00
:suppressRowClickSelection="true"
2023-12-12 19:17:38 +08:00
:getRowClass="getRowClass"
@grid-ready="onGridReady"
@first-data-rendered="onFirstDataRendered"
@sortChanged="setOrder"
2023-12-12 19:17:38 +08:00
@columnResized="saveTableState"
@columnMoved="onColumnMoved"
@bodyScroll="handleScroll"
2024-02-07 19:23:21 +08:00
@columnPinned="handlePin"
@columnVisible="handleVisibility"
@rowSelected="setSelectedRows"
2023-11-10 20:34:36 +08:00
@cellClicked="clickCell"
:CheckboxSelectionCallback="withCheckboxes"
>
</ag-grid-vue>
2024-01-09 01:33:28 +08:00
<div v-if="dataLoading" class="flex absolute top-0 items-center justify-center w-full flex-grow h-full z-10">
<img src="/images/medium/loading.svg" alt="Loading" class="p-4 rounded-xl bg-sn-white" />
2023-12-12 19:17:38 +08:00
</div>
2023-12-11 16:18:22 +08:00
<ActionToolbar
v-if="selectedRows.length > 0 && actionsUrl"
:actionsUrl="actionsUrl"
:params="actionsParams"
@toolbar:action="emitAction" />
</div>
<div v-if="scrollMode == 'pages'" class="flex items-center py-4" :class="{'opacity-0': initializing }">
<div class="mr-auto">
<Pagination
:totalPage="totalPage"
:currentPage="page"
@setPage="setPage"
></Pagination>
</div>
<div class="flex items-center gap-4">
{{ i18n.t('datatable.show') }}
<div class="w-36">
<SelectDropdown
:value="perPage"
:options="perPageOptions"
@change="setPerPage"
></SelectDropdown>
</div>
</div>
</div>
</div>
</template>
<script>
2023-12-11 16:18:22 +08:00
import { AgGridVue } from 'ag-grid-vue3';
import PerfectScrollbar from 'vue3-perfect-scrollbar';
import axios from '../../../packs/custom_axios.js';
import SelectDropdown from '../select_dropdown.vue';
import Pagination from './pagination.vue';
import CustomHeader from './tableHeader';
import ActionToolbar from './action_toolbar.vue';
import Toolbar from './toolbar.vue';
2023-11-10 20:34:36 +08:00
import RowMenuRenderer from './row_menu_renderer.vue';
export default {
2023-12-11 16:18:22 +08:00
name: 'App',
props: {
withCheckboxes: {
type: Boolean,
2023-12-12 19:17:38 +08:00
default: true
},
2023-11-10 20:34:36 +08:00
withRowMenu: {
type: Boolean,
2023-12-12 19:17:38 +08:00
default: false
2023-11-10 20:34:36 +08:00
},
tableId: {
type: String,
2023-12-12 19:17:38 +08:00
required: true
},
columnDefs: {
type: Array,
2023-12-12 19:17:38 +08:00
default: () => []
},
dataUrl: {
type: String,
2023-12-12 19:17:38 +08:00
required: true
},
actionsUrl: {
2023-12-12 19:17:38 +08:00
type: String
},
toolbarActions: {
type: Object,
2023-12-12 19:17:38 +08:00
required: true
},
reloadingTable: {
type: Boolean,
2023-12-12 19:17:38 +08:00
default: false
2023-11-24 18:08:28 +08:00
},
activePageUrl: {
2023-12-12 19:17:38 +08:00
type: String
2023-11-24 18:08:28 +08:00
},
archivedPageUrl: {
2023-12-12 19:17:38 +08:00
type: String
2023-11-24 18:08:28 +08:00
},
currentViewMode: {
type: String,
2023-12-12 19:17:38 +08:00
default: 'active'
2023-11-24 18:08:28 +08:00
},
2023-12-01 07:01:08 +08:00
viewRenders: {
2023-12-12 19:17:38 +08:00
type: Object
2023-12-01 07:01:08 +08:00
},
2023-11-24 18:08:28 +08:00
filters: {
type: Array,
2023-12-12 19:17:38 +08:00
default: () => []
},
scrollMode: {
type: String,
2024-01-23 18:08:42 +08:00
default: 'pages'
2023-12-12 19:17:38 +08:00
}
},
data() {
return {
rowData: [],
gridApi: null,
columnApi: null,
defaultColDef: {
2023-12-12 19:17:38 +08:00
resizable: true
},
perPage: 20,
page: 1,
order: null,
totalPage: 0,
selectedRows: [],
keepSelection: false,
searchValue: '',
2023-11-24 18:08:28 +08:00
initializing: true,
2023-12-01 07:01:08 +08:00
activeFilters: {},
currentViewRender: 'table',
2023-12-11 16:18:22 +08:00
cardCheckboxes: [],
dataLoading: true,
lastPage: false,
tableState: null,
userSettingsUrl: null,
fetchedTableState: null,
gridReady: false
};
},
components: {
AgGridVue,
SelectDropdown,
PerfectScrollbar,
Pagination,
agColumnHeader: CustomHeader,
ActionToolbar,
2023-11-10 20:34:36 +08:00
Toolbar,
2023-12-12 19:17:38 +08:00
RowMenuRenderer
},
computed: {
perPageOptions() {
2023-12-11 16:18:22 +08:00
return [10, 20, 50, 100].map((value) => ([value, `${value} ${this.i18n.t('datatable.rows')}`]));
},
actionsParams() {
return {
2023-12-12 19:17:38 +08:00
items: JSON.stringify(this.selectedRows.map((row) => ({ id: row.id, type: row.type })))
2023-12-11 16:18:22 +08:00
};
},
gridOptions() {
return {
2024-02-01 18:45:13 +08:00
suppressCellFocus: true,
rowHeight: 40,
headerHeight: 40,
getRowId: (params) => `e2e-TB-row-${params.data.id}`
2023-12-11 16:18:22 +08:00
};
2023-12-05 03:59:16 +08:00
},
extendedColumnDefs() {
2023-12-11 16:18:22 +08:00
const columns = this.columnDefs.map((column) => ({
...column,
2024-01-26 20:24:11 +08:00
minWidth: column.minWidth || 110,
2023-12-11 16:18:22 +08:00
cellRendererParams: {
2023-12-12 19:17:38 +08:00
dtComponent: this
2023-12-11 16:18:22 +08:00
},
2024-02-12 19:29:50 +08:00
pinned: (column.field === 'name' ? 'left' : null),
2023-12-12 19:17:38 +08:00
comparator: () => false
2023-12-11 16:18:22 +08:00
}));
2023-11-10 20:34:36 +08:00
2023-12-05 03:59:16 +08:00
if (this.withCheckboxes) {
columns.unshift({
2023-12-11 16:18:22 +08:00
field: 'checkbox',
2023-12-05 03:59:16 +08:00
headerCheckboxSelection: true,
headerCheckboxSelectionFilteredOnly: true,
checkboxSelection: true,
2023-12-12 19:17:38 +08:00
suppressMovable: true,
2024-02-01 18:45:13 +08:00
width: 40,
minWidth: 40,
maxWidth: 40,
resizable: true,
2024-01-23 18:08:42 +08:00
pinned: 'left',
lockPosition: 'left'
2023-12-05 03:59:16 +08:00
});
}
2023-11-10 20:34:36 +08:00
2023-12-05 03:59:16 +08:00
if (this.withRowMenu) {
columns.push({
2023-12-11 16:18:22 +08:00
field: 'rowMenu',
2023-12-05 03:59:16 +08:00
headerName: '',
width: 42,
minWidth: 42,
resizable: false,
sortable: false,
2023-12-12 19:17:38 +08:00
suppressMovable: true,
2023-12-05 03:59:16 +08:00
cellRenderer: 'RowMenuRenderer',
cellRendererParams: {
2023-12-12 19:17:38 +08:00
dtComponent: this
2023-12-05 03:59:16 +08:00
},
2023-12-11 16:18:22 +08:00
cellStyle: {
padding: 0,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
2023-12-12 19:17:38 +08:00
overflow: 'visible'
}
2023-12-05 03:59:16 +08:00
});
}
return columns;
},
stateKey() {
return `${this.tableId}_${this.currentViewMode}_state`;
2023-12-12 19:17:38 +08:00
}
},
watch: {
reloadingTable() {
if (this.reloadingTable) {
this.updateTable();
}
2023-12-11 16:18:22 +08:00
},
2023-12-12 19:17:38 +08:00
currentViewRender() {
2024-01-12 00:42:17 +08:00
this.columnApi = null;
this.gridApi = null;
2023-12-12 19:17:38 +08:00
this.saveTableState();
},
perPage() {
this.saveTableState();
},
fetchedTableState(newValue) {
if (newValue !== null && this.gridReady) {
this.applyTableState(newValue);
}
2023-12-12 19:17:38 +08:00
}
},
created() {
this.userSettingsUrl = document.querySelector('meta[name="user-settings-url"]').getAttribute('content');
this.fetchTableState();
},
mounted() {
this.loadData();
window.addEventListener('resize', this.resize);
},
beforeDestroy() {
window.removeEventListener('resize', this.resize);
},
methods: {
handleScroll() {
2024-01-26 20:24:11 +08:00
if (this.scrollMode === 'pages') return;
let target = null;
if (this.currentViewRender === 'cards') {
target = this.$refs.cardsContainer;
} else {
target = document.querySelector('.ag-body-viewport');
}
if (target.scrollTop + target.clientHeight >= target.scrollHeight - 50) {
if (this.dataLoading || this.lastPage) return;
this.dataLoading = true;
this.page += 1;
this.loadData();
}
},
2024-02-07 19:23:21 +08:00
handlePin(event) {
if (event.pinned === 'right') {
this.columnApi.setColumnPinned(event.column.colId, null);
}
this.saveTableState();
},
handleVisibility(event) {
if (!event.visible && event.source !== 'api') {
this.columnApi.setColumnVisible(event.column.colId, true);
}
this.saveTableState();
},
fetchTableState() {
axios.get(this.userSettingsUrl, { params: { key: this.stateKey } })
.then((response) => {
if (response.data.data) {
this.fetchedTableState = response.data.data;
if (this.gridReady && this.fetchedTableState) {
this.applyTableState(this.fetchedTableState);
}
2024-02-07 19:23:21 +08:00
} else {
this.initializing = false;
2024-02-07 19:23:21 +08:00
this.saveTableState();
}
});
},
applyTableState(state) {
const { currentViewRender, columnsState, perPage, order } = state;
this.tableState = state;
this.currentViewRender = currentViewRender;
this.columnsState = columnsState;
this.perPage = perPage;
this.order = order;
if (this.order) {
this.tableState.columnsState.forEach((column) => {
const updatedColumn = column;
updatedColumn.sort = this.order.column === column.colId ? this.order.dir : null;
return updatedColumn;
});
}
this.columnApi.applyColumnState({
state: this.tableState.columnsState,
applyOrder: true
});
setTimeout(() => {
this.initializing = false;
}, 200);
},
2023-12-12 19:17:38 +08:00
getRowClass() {
if (this.currentViewMode === 'archived') {
2024-02-01 18:45:13 +08:00
return '!bg-sn-super-light-grey';
2023-12-12 19:17:38 +08:00
}
return '';
},
formatData(data) {
2023-12-11 16:18:22 +08:00
return data.map((item) => ({
...item.attributes,
id: item.id,
2023-12-12 19:17:38 +08:00
type: item.type
2023-12-11 16:18:22 +08:00
}));
},
resize() {
if (this.tableState) return;
2024-01-26 20:24:11 +08:00
this.columnApi?.autoSizeAllColumns();
},
updateTable() {
if (this.scrollMode === 'pages') {
this.loadData();
} else {
this.reloadTable();
}
},
reloadTable(clearSelection = true) {
2023-12-12 19:17:38 +08:00
if (this.dataLoading) return;
this.dataLoading = true;
if (clearSelection) this.selectedRows = [];
2023-12-12 19:17:38 +08:00
this.page = 1;
this.loadData();
2023-12-12 19:17:38 +08:00
},
2024-01-09 01:33:28 +08:00
loadData(reload = false) {
axios
.get(this.dataUrl, {
params: {
per_page: this.perPage,
page: this.page,
order: this.order,
2023-11-24 18:08:28 +08:00
search: this.searchValue,
view_mode: this.currentViewMode,
2023-12-12 19:17:38 +08:00
filters: this.activeFilters
}
})
.then((response) => {
2024-01-09 01:33:28 +08:00
if (reload) {
if (this.gridApi) this.gridApi.setRowData([]);
2024-01-09 01:33:28 +08:00
this.rowData = [];
}
if (this.scrollMode === 'pages') {
if (this.gridApi) this.gridApi.setRowData(this.formatData(response.data.data));
this.rowData = this.formatData(response.data.data);
} else {
this.handleInfiniteScroll(response);
2023-12-12 19:17:38 +08:00
}
this.totalPage = response.data.meta.total_pages;
this.$emit('tableReloaded');
2023-12-12 19:17:38 +08:00
this.dataLoading = false;
this.restoreSelection();
this.handleScroll();
2023-12-11 16:18:22 +08:00
});
},
handleInfiniteScroll(response) {
const newRows = this.rowData.slice();
this.formatData(response.data.data).forEach((row) => {
newRows.push(row);
});
this.rowData = newRows;
if (this.gridApi) {
const viewport = document.querySelector('.ag-body-viewport');
const { scrollTop } = viewport;
this.gridApi.setRowData(this.rowData);
this.$nextTick(() => {
viewport.scrollTop = scrollTop;
});
}
this.lastPage = !response.data.meta.next_page;
},
onGridReady(params) {
this.gridApi = params.api;
this.columnApi = params.columnApi;
this.gridReady = true;
if (this.fetchedTableState) {
this.applyTableState(this.fetchedTableState);
}
},
2023-12-11 16:18:22 +08:00
onFirstDataRendered() {
this.resize();
},
setPerPage(value) {
this.perPage = value;
2023-12-01 07:01:08 +08:00
this.page = 1;
this.lastPage = false;
this.reloadTable(false);
},
setPage(page) {
this.page = page;
this.loadData(false);
},
setOrder() {
2023-12-12 19:17:38 +08:00
const orderState = this.getOrder(this.columnApi.getColumnState());
2023-12-11 16:18:22 +08:00
const [order] = orderState;
this.order = order;
2023-12-12 19:17:38 +08:00
this.saveTableState();
this.reloadTable(false);
},
2023-12-12 19:17:38 +08:00
saveTableState() {
if (this.initializing) {
return;
}
const columnsState = this.columnApi ? this.columnApi.getColumnState() : this.tableState?.columnsState || [];
2023-12-12 19:17:38 +08:00
const tableState = {
columnsState,
2024-01-12 00:42:17 +08:00
order: this.order,
2023-12-12 19:17:38 +08:00
currentViewRender: this.currentViewRender,
perPage: this.perPage
};
const settings = {
key: this.stateKey,
data: tableState
};
axios.put(this.userSettingsUrl, { settings: [settings] });
this.tableState = tableState;
},
restoreSelection() {
if (this.gridApi) {
this.gridApi.forEachNode((node) => {
if (this.selectedRows.find((row) => row.id === node.data.id)) {
node.setSelected(true);
}
});
}
},
setSelectedRows(e) {
if (!this.rowData.find((row) => row.id === e.data.id)) return;
if (e.node.isSelected()) {
this.selectedRows.push(e.data);
} else {
this.selectedRows = this.selectedRows.filter((row) => row.id !== e.data.id);
}
},
emitAction(action) {
this.$emit(action.name, action, this.selectedRows);
},
setSearchValue(value) {
this.searchValue = value;
2023-12-12 19:17:38 +08:00
this.reloadTable();
2023-11-10 20:34:36 +08:00
},
clickCell(e) {
2023-12-05 03:59:16 +08:00
if (e.column.colId !== 'rowMenu' && e.column.userProvidedColDef.notSelectable !== true) {
2023-12-11 16:18:22 +08:00
e.node.setSelected(true);
2023-11-10 20:34:36 +08:00
}
2023-11-24 18:08:28 +08:00
},
applyFilters(filters) {
this.activeFilters = filters;
2023-12-12 19:17:38 +08:00
this.reloadTable();
2023-12-01 07:01:08 +08:00
},
switchViewRender(view) {
if (this.currentViewRender === view) return;
this.currentViewRender = view;
this.initializing = true;
this.selectedRows = [];
2023-12-11 16:18:22 +08:00
},
2023-12-12 19:17:38 +08:00
hideColumn(column) {
this.columnApi.setColumnVisible(column.field, false);
},
showColumn(column) {
this.columnApi.setColumnVisible(column.field, true);
},
pinColumn(column) {
this.columnApi.setColumnPinned(column.field, 'left');
},
unPinColumn(column) {
this.columnApi.setColumnPinned(column.field, null);
},
reorderColumns(columns) {
this.columnApi.moveColumns(columns, 1);
this.saveTableState();
},
resetColumnsToDefault() {
this.columnApi.resetColumnState();
this.columnApi.autoSizeAllColumns();
this.saveTableState();
},
2023-12-12 19:17:38 +08:00
getOrder(columnsState) {
if (!columnsState) return null;
return columnsState.filter((column) => column.sort)
.map((column) => ({
column: column.colId,
dir: column.sort
}));
2024-01-12 00:42:17 +08:00
},
applyOrder(column, dir) {
this.order = {
column,
dir
};
this.saveTableState();
this.reloadTable(false);
},
onColumnMoved(event) {
if (event.finished) {
this.saveTableState();
}
2023-12-12 19:17:38 +08:00
}
}
};
</script>