@ackplus/react-tanstack-data-table 1.1.15 → 1.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -8
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/lib/components/data-table-view.d.ts +7 -0
- package/dist/lib/components/data-table-view.d.ts.map +1 -0
- package/dist/lib/components/data-table-view.js +151 -0
- package/dist/lib/components/toolbar/data-table-toolbar.d.ts.map +1 -1
- package/dist/lib/components/toolbar/data-table-toolbar.js +14 -1
- package/dist/lib/components/toolbar/table-export-control.d.ts.map +1 -1
- package/dist/lib/components/toolbar/table-export-control.js +0 -2
- package/dist/lib/data-table.d.ts +0 -3
- package/dist/lib/data-table.d.ts.map +1 -1
- package/dist/lib/data-table.js +9 -1646
- package/dist/lib/features/selection.feature.d.ts.map +1 -1
- package/dist/lib/features/selection.feature.js +1 -2
- package/dist/lib/hooks/index.d.ts +3 -0
- package/dist/lib/hooks/index.d.ts.map +1 -0
- package/dist/lib/hooks/index.js +5 -0
- package/dist/lib/hooks/use-data-table-engine.d.ts +104 -0
- package/dist/lib/hooks/use-data-table-engine.d.ts.map +1 -0
- package/dist/lib/hooks/use-data-table-engine.js +961 -0
- package/dist/lib/types/data-table-api.d.ts +1 -1
- package/dist/lib/types/data-table-api.d.ts.map +1 -1
- package/dist/lib/types/data-table.types.d.ts +1 -2
- package/dist/lib/types/data-table.types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/lib/components/data-table-view.tsx +386 -0
- package/src/lib/components/toolbar/data-table-toolbar.tsx +15 -1
- package/src/lib/components/toolbar/table-export-control.tsx +0 -2
- package/src/lib/data-table.tsx +17 -2201
- package/src/lib/features/selection.feature.ts +1 -3
- package/src/lib/hooks/index.ts +2 -0
- package/src/lib/hooks/use-data-table-engine.ts +1282 -0
- package/src/lib/types/data-table-api.ts +1 -1
- package/src/lib/types/data-table.types.ts +1 -2
package/src/lib/data-table.tsx
CHANGED
|
@@ -1,2213 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Main DataTable Component
|
|
2
|
+
* Main DataTable Component (thin wrapper)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - TanStack Table for table logic
|
|
7
|
-
* - TypeScript for type safety
|
|
4
|
+
* Composes useDataTableEngine + DataTableProvider + DataTableView.
|
|
5
|
+
* Preserves forwardRef API and DataTableApi surface.
|
|
8
6
|
*/
|
|
9
|
-
import {
|
|
10
|
-
Table,
|
|
11
|
-
TableContainer,
|
|
12
|
-
TableBody,
|
|
13
|
-
Box,
|
|
14
|
-
Paper,
|
|
15
|
-
useTheme,
|
|
16
|
-
} from '@mui/material';
|
|
17
|
-
import {
|
|
18
|
-
getCoreRowModel,
|
|
19
|
-
useReactTable,
|
|
20
|
-
SortingState,
|
|
21
|
-
getSortedRowModel,
|
|
22
|
-
ColumnOrderState,
|
|
23
|
-
ColumnPinningState,
|
|
24
|
-
getPaginationRowModel,
|
|
25
|
-
Updater,
|
|
26
|
-
} from '@tanstack/react-table';
|
|
7
|
+
import React, { forwardRef, useImperativeHandle } from 'react';
|
|
27
8
|
|
|
28
|
-
// Import custom features
|
|
29
|
-
import { ColumnFilterFeature, getCombinedFilteredRowModel } from './features/column-filter.feature';
|
|
30
|
-
import { SelectionFeature, SelectionState } from './features';
|
|
31
|
-
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
32
|
-
import React, { useState, useCallback, useMemo, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// Import from new organized structure
|
|
36
9
|
import { DataTableProvider } from './contexts/data-table-context';
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import { DataTablePagination } from './components/pagination';
|
|
42
|
-
import { DataTableRow, LoadingRows, EmptyDataRow } from './components/rows';
|
|
43
|
-
import { DataTableToolbar, BulkActionsToolbar } from './components/toolbar';
|
|
44
|
-
import {
|
|
45
|
-
DataFetchMeta,
|
|
46
|
-
DataMutationAction,
|
|
47
|
-
DataMutationContext,
|
|
48
|
-
DataRefreshOptions,
|
|
49
|
-
DataTableProps,
|
|
50
|
-
} from './types/data-table.types';
|
|
51
|
-
import { ColumnFilterState, ExportPhase, ExportProgressPayload, ExportStateChange, TableFiltersForFetch, TableState } from './types';
|
|
52
|
-
import { DataTableApi, DataTableExportApiOptions } from './types/data-table-api';
|
|
53
|
-
import {
|
|
54
|
-
createExpandingColumn,
|
|
55
|
-
createSelectionColumn,
|
|
56
|
-
} from './utils/special-columns.utils';
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// Static default initial state - defined outside component
|
|
61
|
-
const DEFAULT_INITIAL_STATE = {
|
|
62
|
-
sorting: [],
|
|
63
|
-
pagination: {
|
|
64
|
-
pageIndex: 0,
|
|
65
|
-
pageSize: 10,
|
|
66
|
-
},
|
|
67
|
-
selectionState: { ids: [], type: 'include' } as SelectionState,
|
|
68
|
-
globalFilter: '',
|
|
69
|
-
expanded: {},
|
|
70
|
-
columnOrder: [],
|
|
71
|
-
columnPinning: {
|
|
72
|
-
left: [],
|
|
73
|
-
right: [],
|
|
74
|
-
},
|
|
75
|
-
columnVisibility: {},
|
|
76
|
-
columnSizing: {},
|
|
77
|
-
columnFilter: {
|
|
78
|
-
filters: [],
|
|
79
|
-
logic: 'AND',
|
|
80
|
-
pendingFilters: [],
|
|
81
|
-
pendingLogic: 'AND',
|
|
82
|
-
} as ColumnFilterState,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Main DataTable component with all features
|
|
87
|
-
*/
|
|
88
|
-
export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(function DataTable<T extends Record<string, any>>({
|
|
89
|
-
initialState,
|
|
90
|
-
columns,
|
|
91
|
-
data = [],
|
|
92
|
-
totalRow = 0,
|
|
93
|
-
idKey = 'id' as keyof T,
|
|
94
|
-
extraFilter = null,
|
|
95
|
-
footerFilter = null,
|
|
96
|
-
|
|
97
|
-
// Data management mode (MUI DataGrid style)
|
|
98
|
-
dataMode = 'client',
|
|
99
|
-
initialLoadData = true,
|
|
100
|
-
onFetchData, // callback to fetch data from the server need to with response { data: T[], total: number }
|
|
101
|
-
onFetchStateChange, // callback to fetch data from the server no need to resonce , this for filter data
|
|
102
|
-
onDataChange, // callback to change data
|
|
103
|
-
onDataStateChange, // callback to change data state
|
|
104
|
-
|
|
105
|
-
// Selection props
|
|
106
|
-
enableRowSelection = false,
|
|
107
|
-
enableMultiRowSelection = true,
|
|
108
|
-
selectMode = 'page',
|
|
109
|
-
isRowSelectable,
|
|
110
|
-
onSelectionChange,
|
|
111
|
-
|
|
112
|
-
// Row click props
|
|
113
|
-
onRowClick,
|
|
114
|
-
selectOnRowClick = false,
|
|
115
|
-
|
|
116
|
-
// Bulk action props
|
|
117
|
-
enableBulkActions = false,
|
|
118
|
-
bulkActions,
|
|
119
|
-
|
|
120
|
-
// Column resizing props
|
|
121
|
-
enableColumnResizing = false,
|
|
122
|
-
columnResizeMode = 'onChange',
|
|
123
|
-
onColumnSizingChange,
|
|
124
|
-
|
|
125
|
-
// Column ordering props
|
|
126
|
-
enableColumnDragging = false,
|
|
127
|
-
onColumnDragEnd,
|
|
128
|
-
|
|
129
|
-
// Column pinning props
|
|
130
|
-
enableColumnPinning = false,
|
|
131
|
-
onColumnPinningChange,
|
|
132
|
-
|
|
133
|
-
// Column visibility props
|
|
134
|
-
onColumnVisibilityChange,
|
|
135
|
-
enableColumnVisibility = true,
|
|
136
|
-
|
|
137
|
-
// Expandable rows props
|
|
138
|
-
enableExpanding = false,
|
|
139
|
-
getRowCanExpand,
|
|
140
|
-
renderSubComponent,
|
|
141
|
-
|
|
142
|
-
// Pagination props
|
|
143
|
-
enablePagination = false,
|
|
144
|
-
paginationMode = 'client',
|
|
145
|
-
|
|
146
|
-
// Filtering props
|
|
147
|
-
enableGlobalFilter = true,
|
|
148
|
-
enableColumnFilter = false,
|
|
149
|
-
filterMode = 'client',
|
|
150
|
-
|
|
151
|
-
// Sorting props
|
|
152
|
-
enableSorting = true,
|
|
153
|
-
sortingMode = 'client',
|
|
154
|
-
onSortingChange,
|
|
155
|
-
|
|
156
|
-
//export props
|
|
157
|
-
exportFilename = 'export',
|
|
158
|
-
exportConcurrency = 'cancelAndRestart',
|
|
159
|
-
exportChunkSize = 1000,
|
|
160
|
-
exportStrictTotalCheck = false,
|
|
161
|
-
exportSanitizeCSV = true,
|
|
162
|
-
onExportProgress,
|
|
163
|
-
onExportComplete,
|
|
164
|
-
onExportError,
|
|
165
|
-
onServerExport,
|
|
166
|
-
onExportCancel,
|
|
167
|
-
onExportStateChange,
|
|
168
|
-
|
|
169
|
-
// Styling props
|
|
170
|
-
enableHover = true,
|
|
171
|
-
enableStripes = false,
|
|
172
|
-
tableProps = {},
|
|
173
|
-
fitToScreen = true,
|
|
174
|
-
tableSize: initialTableSize = 'medium',
|
|
175
|
-
|
|
176
|
-
// Sticky header/footer props
|
|
177
|
-
enableStickyHeaderOrFooter = false,
|
|
178
|
-
maxHeight = '400px',
|
|
179
|
-
|
|
180
|
-
// Virtualization props
|
|
181
|
-
enableVirtualization = false,
|
|
182
|
-
estimateRowHeight = 52,
|
|
183
|
-
|
|
184
|
-
// Toolbar props
|
|
185
|
-
enableTableSizeControl = true,
|
|
186
|
-
enableExport = false,
|
|
187
|
-
enableReset = true,
|
|
188
|
-
enableRefresh = false,
|
|
189
|
-
|
|
190
|
-
// Loading and empty states
|
|
191
|
-
loading = false,
|
|
192
|
-
emptyMessage = 'No data available',
|
|
193
|
-
skeletonRows = 5,
|
|
194
|
-
|
|
195
|
-
// Column filters props
|
|
196
|
-
onColumnFiltersChange,
|
|
197
|
-
onPaginationChange,
|
|
198
|
-
onGlobalFilterChange,
|
|
199
|
-
|
|
200
|
-
// Slots
|
|
201
|
-
slots = {},
|
|
202
|
-
slotProps = {},
|
|
203
|
-
|
|
204
|
-
// Logging
|
|
205
|
-
logging,
|
|
206
|
-
|
|
207
|
-
}: DataTableProps<T>, ref: React.Ref<DataTableApi<T>>) {
|
|
208
|
-
// Convert mode-based props to boolean flags for internal use
|
|
209
|
-
const isServerMode = dataMode === 'server';
|
|
210
|
-
const isServerPagination = paginationMode === 'server' || isServerMode;
|
|
211
|
-
const isServerFiltering = filterMode === 'server' || isServerMode;
|
|
212
|
-
const isServerSorting = sortingMode === 'server' || isServerMode;
|
|
213
|
-
|
|
214
|
-
const theme = useTheme();
|
|
215
|
-
const logger = useMemo(() => createLogger('DataTable', logging), [logging]);
|
|
216
|
-
|
|
217
|
-
useEffect(() => {
|
|
218
|
-
if (logger.isLevelEnabled('info')) {
|
|
219
|
-
logger.info('mounted', {
|
|
220
|
-
dataMode,
|
|
221
|
-
paginationMode,
|
|
222
|
-
filterMode,
|
|
223
|
-
sortingMode,
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
return () => {
|
|
227
|
-
if (logger.isLevelEnabled('info')) {
|
|
228
|
-
logger.info('unmounted');
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
}, [logger, dataMode, paginationMode, filterMode, sortingMode]);
|
|
232
|
-
|
|
233
|
-
// -------------------------------
|
|
234
|
-
// Memoized values (grouped together)
|
|
235
|
-
// -------------------------------
|
|
236
|
-
const initialStateConfig = useMemo(() => {
|
|
237
|
-
const config = {
|
|
238
|
-
...DEFAULT_INITIAL_STATE,
|
|
239
|
-
...initialState,
|
|
240
|
-
};
|
|
241
|
-
if (logger.isLevelEnabled('info')) {
|
|
242
|
-
logger.info('initialStateConfig', { config });
|
|
243
|
-
}
|
|
244
|
-
return config;
|
|
245
|
-
}, [initialState, logger]);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const initialSelectionState = useMemo(() => {
|
|
249
|
-
return initialStateConfig.selectionState || DEFAULT_INITIAL_STATE.selectionState;
|
|
250
|
-
}, [initialStateConfig.selectionState]);
|
|
251
|
-
|
|
252
|
-
// -------------------------------
|
|
253
|
-
// State hooks (grouped together)
|
|
254
|
-
// -------------------------------
|
|
255
|
-
// const [fetchLoading, setFetchLoading] = useState(false);
|
|
256
|
-
const [sorting, setSorting] = useState<SortingState>(initialState?.sorting || DEFAULT_INITIAL_STATE.sorting);
|
|
257
|
-
const [pagination, setPagination] = useState(initialState?.pagination || DEFAULT_INITIAL_STATE.pagination);
|
|
258
|
-
const [globalFilter, setGlobalFilter] = useState(initialState?.globalFilter || DEFAULT_INITIAL_STATE.globalFilter);
|
|
259
|
-
const [selectionState, setSelectionState] = useState<SelectionState>(initialState?.selectionState || DEFAULT_INITIAL_STATE.selectionState);
|
|
260
|
-
const [columnFilter, setColumnFilter] = useState<ColumnFilterState>(initialState?.columnFilter || DEFAULT_INITIAL_STATE.columnFilter);
|
|
261
|
-
const [expanded, setExpanded] = useState({});
|
|
262
|
-
const [tableSize, setTableSize] = useState<DataTableSize>(initialTableSize || 'medium');
|
|
263
|
-
const [columnOrder, setColumnOrder] = useState<ColumnOrderState>(initialState?.columnOrder || DEFAULT_INITIAL_STATE.columnOrder);
|
|
264
|
-
const [columnPinning, setColumnPinning] = useState<ColumnPinningState>(initialState?.columnPinning || DEFAULT_INITIAL_STATE.columnPinning);
|
|
265
|
-
const [columnVisibility, setColumnVisibility] = useState<Record<string, boolean>>(initialState?.columnVisibility || DEFAULT_INITIAL_STATE.columnVisibility);
|
|
266
|
-
const [columnSizing, setColumnSizing] = useState<Record<string, number>>(initialState?.columnSizing || DEFAULT_INITIAL_STATE.columnSizing);
|
|
267
|
-
const [serverData, setServerData] = useState<T[] | null>(null);
|
|
268
|
-
const [serverTotal, setServerTotal] = useState(0);
|
|
269
|
-
const [exportController, setExportController] = useState<AbortController | null>(null);
|
|
270
|
-
const [exportProgress, setExportProgress] = useState<ExportProgressPayload>({});
|
|
271
|
-
const [exportPhase, setExportPhase] = useState<ExportPhase | null>(null);
|
|
272
|
-
const [queuedExportCount, setQueuedExportCount] = useState(0);
|
|
273
|
-
|
|
274
|
-
// -------------------------------
|
|
275
|
-
// Ref hooks (grouped together)
|
|
276
|
-
// -------------------------------
|
|
277
|
-
const tableContainerRef = useRef<HTMLDivElement>(null);
|
|
278
|
-
const internalApiRef = useRef<DataTableApi<T>>(null);
|
|
279
|
-
const exportControllerRef = useRef<AbortController | null>(null);
|
|
280
|
-
const exportQueueRef = useRef<Promise<void>>(Promise.resolve());
|
|
281
|
-
|
|
282
|
-
const isExternallyControlledData = useMemo(
|
|
283
|
-
() => !onFetchData && (!!onDataChange || !!onFetchStateChange),
|
|
284
|
-
[onFetchData, onDataChange, onFetchStateChange]
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
const { debouncedFetch, isLoading: fetchLoading } = useDebouncedFetch(onFetchData);
|
|
288
|
-
const tableData = useMemo(() => {
|
|
289
|
-
if (isExternallyControlledData) return data;
|
|
290
|
-
return serverData !== null ? serverData : data;
|
|
291
|
-
}, [isExternallyControlledData, serverData, data]);
|
|
292
|
-
const tableTotalRow = useMemo(
|
|
293
|
-
() => (isExternallyControlledData ? (totalRow || data.length) : (serverData !== null ? serverTotal : totalRow || data.length)),
|
|
294
|
-
[isExternallyControlledData, serverData, serverTotal, totalRow, data]
|
|
295
|
-
);
|
|
296
|
-
const tableLoading = useMemo(() => onFetchData ? (loading || fetchLoading) : loading, [onFetchData, loading, fetchLoading]);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const enhancedColumns = useMemo(
|
|
300
|
-
() => {
|
|
301
|
-
let columnsMap = [...columns];
|
|
302
|
-
if (enableExpanding) {
|
|
303
|
-
const expandingColumnMap = createExpandingColumn<T>({
|
|
304
|
-
...(slotProps?.expandColumn && typeof slotProps.expandColumn === 'object' ? slotProps.expandColumn : {}),
|
|
305
|
-
});
|
|
306
|
-
columnsMap = [expandingColumnMap, ...columnsMap];
|
|
307
|
-
}
|
|
308
|
-
if (enableRowSelection) {
|
|
309
|
-
const selectionColumnMap = createSelectionColumn<T>({
|
|
310
|
-
...(slotProps?.selectionColumn && typeof slotProps.selectionColumn === 'object' ? slotProps.selectionColumn : {}),
|
|
311
|
-
multiSelect: enableMultiRowSelection,
|
|
312
|
-
});
|
|
313
|
-
columnsMap = [selectionColumnMap, ...columnsMap];
|
|
314
|
-
}
|
|
315
|
-
const enhancedColumns = withIdsDeep(columnsMap);
|
|
316
|
-
if (logger.isLevelEnabled('info')) {
|
|
317
|
-
logger.info('enhancedColumns', { enhancedColumns });
|
|
318
|
-
}
|
|
319
|
-
return enhancedColumns;
|
|
320
|
-
}, [columns, enableExpanding, enableRowSelection, logger, slotProps.expandColumn, slotProps.selectionColumn, enableMultiRowSelection]);
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const isExporting = useMemo(() => exportController !== null, [exportController]);
|
|
324
|
-
const isSomeRowsSelected = useMemo(() => {
|
|
325
|
-
if (!enableBulkActions || !enableRowSelection) return false;
|
|
326
|
-
if (selectionState.type === 'exclude') {
|
|
327
|
-
return selectionState.ids.length < tableTotalRow;
|
|
328
|
-
} else {
|
|
329
|
-
return selectionState.ids.length > 0;
|
|
330
|
-
}
|
|
331
|
-
}, [enableBulkActions, enableRowSelection, selectionState, tableTotalRow]);
|
|
332
|
-
const selectedRowCount = useMemo(() => {
|
|
333
|
-
if (!enableBulkActions || !enableRowSelection) return 0;
|
|
334
|
-
if (selectionState.type === 'exclude') {
|
|
335
|
-
return tableTotalRow - selectionState.ids.length;
|
|
336
|
-
} else {
|
|
337
|
-
return selectionState.ids.length;
|
|
338
|
-
}
|
|
339
|
-
}, [enableBulkActions, enableRowSelection, selectionState, tableTotalRow]);
|
|
340
|
-
// -------------------------------
|
|
341
|
-
// Callback hooks (grouped together)
|
|
342
|
-
// -------------------------------
|
|
343
|
-
const fetchData = useCallback(async (
|
|
344
|
-
overrides: Partial<TableState> = {},
|
|
345
|
-
options?: { delay?: number; meta?: DataFetchMeta }
|
|
346
|
-
) => {
|
|
347
|
-
const filters: Partial<TableFiltersForFetch> = {
|
|
348
|
-
globalFilter,
|
|
349
|
-
pagination,
|
|
350
|
-
columnFilter,
|
|
351
|
-
sorting,
|
|
352
|
-
...overrides,
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
if(onFetchStateChange) {
|
|
356
|
-
onFetchStateChange(filters, options?.meta);
|
|
357
|
-
}
|
|
358
|
-
if (!onFetchData) {
|
|
359
|
-
if (logger.isLevelEnabled('debug')) {
|
|
360
|
-
logger.debug('onFetchData not provided, skipping fetch', { overrides, columnFilter, sorting, pagination });
|
|
361
|
-
}
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (logger.isLevelEnabled('info')) {
|
|
366
|
-
logger.info('Requesting data', {
|
|
367
|
-
filters,
|
|
368
|
-
reason: options?.meta?.reason,
|
|
369
|
-
force: options?.meta?.force,
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
try {
|
|
374
|
-
const delay = options?.delay ?? 300; // respects 0
|
|
375
|
-
const result = await debouncedFetch(filters, {
|
|
376
|
-
debounceDelay: delay,
|
|
377
|
-
meta: options?.meta,
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
if (logger.isLevelEnabled('info')) {
|
|
381
|
-
logger.info('Fetch resolved', {
|
|
382
|
-
rows: result?.data?.length ?? 0,
|
|
383
|
-
total: result?.total,
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (result && Array.isArray(result.data) && result.total !== undefined) {
|
|
388
|
-
setServerData(result.data);
|
|
389
|
-
setServerTotal(result.total);
|
|
390
|
-
} else if (logger.isLevelEnabled('warn')) {
|
|
391
|
-
logger.warn('Fetch handler returned unexpected shape', result);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return result;
|
|
395
|
-
} catch (error) {
|
|
396
|
-
logger.error('Fetch failed', error);
|
|
397
|
-
throw error;
|
|
398
|
-
}
|
|
399
|
-
}, [
|
|
400
|
-
onFetchData,
|
|
401
|
-
globalFilter,
|
|
402
|
-
pagination,
|
|
403
|
-
columnFilter,
|
|
404
|
-
sorting,
|
|
405
|
-
debouncedFetch,
|
|
406
|
-
logger,
|
|
407
|
-
onFetchStateChange,
|
|
408
|
-
]);
|
|
409
|
-
|
|
410
|
-
const normalizeRefreshOptions = useCallback((
|
|
411
|
-
options?: boolean | DataRefreshOptions,
|
|
412
|
-
fallbackReason: string = 'refresh'
|
|
413
|
-
) => {
|
|
414
|
-
if (typeof options === 'boolean') {
|
|
415
|
-
return {
|
|
416
|
-
resetPagination: options,
|
|
417
|
-
force: false,
|
|
418
|
-
reason: fallbackReason,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
return {
|
|
423
|
-
resetPagination: options?.resetPagination ?? false,
|
|
424
|
-
force: options?.force ?? false,
|
|
425
|
-
reason: options?.reason ?? fallbackReason,
|
|
426
|
-
};
|
|
427
|
-
}, []);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const handleSelectionStateChange = useCallback((updaterOrValue) => {
|
|
431
|
-
setSelectionState((prevState) => {
|
|
432
|
-
const next =
|
|
433
|
-
typeof updaterOrValue === 'function' ? updaterOrValue(prevState) : updaterOrValue;
|
|
434
|
-
onSelectionChange?.(next);
|
|
435
|
-
return next;
|
|
436
|
-
});
|
|
437
|
-
}, [onSelectionChange]);
|
|
438
|
-
|
|
439
|
-
const handleColumnFilterStateChange = useCallback((filterState: ColumnFilterState) => {
|
|
440
|
-
if (!filterState || typeof filterState !== 'object') return;
|
|
441
|
-
|
|
442
|
-
setColumnFilter(filterState);
|
|
443
|
-
onColumnFiltersChange?.(filterState);
|
|
444
|
-
return filterState;
|
|
445
|
-
}, [onColumnFiltersChange]);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const resetPageToFirst = useCallback(() => {
|
|
449
|
-
if (logger.isLevelEnabled('info')) {
|
|
450
|
-
logger.info('Resetting to first page due to state change', {
|
|
451
|
-
previousPageIndex: pagination.pageIndex,
|
|
452
|
-
pageSize: pagination.pageSize,
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
const newPagination = { pageIndex: 0, pageSize: pagination.pageSize };
|
|
456
|
-
setPagination(newPagination);
|
|
457
|
-
onPaginationChange?.(newPagination);
|
|
458
|
-
return newPagination;
|
|
459
|
-
}, [pagination, logger, onPaginationChange]);
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const handleSortingChange = useCallback((updaterOrValue: any) => {
|
|
463
|
-
|
|
464
|
-
setSorting((prev) => {
|
|
465
|
-
const next = typeof updaterOrValue === 'function' ? updaterOrValue(prev) : updaterOrValue;
|
|
466
|
-
const cleaned = next.filter((s: any) => s?.id);
|
|
467
|
-
onSortingChange?.(cleaned);
|
|
468
|
-
const nextPagination = resetPageToFirst();
|
|
469
|
-
if (isServerMode || isServerSorting) {
|
|
470
|
-
fetchData({ sorting: cleaned, pagination: nextPagination }, { delay: 0 });
|
|
471
|
-
}
|
|
472
|
-
return cleaned;
|
|
473
|
-
});
|
|
474
|
-
}, [onSortingChange, isServerMode, isServerSorting, resetPageToFirst, fetchData]);
|
|
475
|
-
|
|
476
|
-
const handleColumnOrderChange = useCallback((updatedColumnOrder: Updater<ColumnOrderState>) => {
|
|
477
|
-
const newColumnOrder = typeof updatedColumnOrder === 'function'
|
|
478
|
-
? updatedColumnOrder(columnOrder)
|
|
479
|
-
: updatedColumnOrder;
|
|
480
|
-
setColumnOrder(newColumnOrder);
|
|
481
|
-
if (onColumnDragEnd) {
|
|
482
|
-
onColumnDragEnd(newColumnOrder);
|
|
483
|
-
}
|
|
484
|
-
}, [onColumnDragEnd, columnOrder]);
|
|
485
|
-
|
|
486
|
-
const handleColumnPinningChange = useCallback(
|
|
487
|
-
(updater: Updater<ColumnPinningState>) => {
|
|
488
|
-
setColumnPinning((prev) => {
|
|
489
|
-
const next = typeof updater === "function" ? updater(prev) : updater;
|
|
490
|
-
// keep direct callback here (optional)
|
|
491
|
-
onColumnPinningChange?.(next);
|
|
492
|
-
return next;
|
|
493
|
-
});
|
|
494
|
-
},
|
|
495
|
-
[onColumnPinningChange]
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
// Column visibility change handler - same pattern as column order
|
|
499
|
-
const handleColumnVisibilityChange = useCallback((updater: any) => {
|
|
500
|
-
setColumnVisibility((prev) => {
|
|
501
|
-
const next = typeof updater === 'function' ? updater(prev) : updater;
|
|
502
|
-
onColumnVisibilityChange?.(next);
|
|
503
|
-
return next;
|
|
504
|
-
});
|
|
505
|
-
}, [onColumnVisibilityChange]);
|
|
506
|
-
|
|
507
|
-
// Column sizing change handler - same pattern as column order
|
|
508
|
-
const handleColumnSizingChange = useCallback((updater: any) => {
|
|
509
|
-
setColumnSizing((prev) => {
|
|
510
|
-
const next = typeof updater === 'function' ? updater(prev) : updater;
|
|
511
|
-
onColumnSizingChange?.(next);
|
|
512
|
-
return next;
|
|
513
|
-
});
|
|
514
|
-
}, [onColumnSizingChange]);
|
|
515
|
-
|
|
516
|
-
const handlePaginationChange = useCallback((updater: any) => {
|
|
517
|
-
setPagination((prev) => {
|
|
518
|
-
const next = typeof updater === 'function' ? updater(prev) : updater;
|
|
519
|
-
onPaginationChange?.(next);
|
|
520
|
-
if (isServerMode || isServerPagination) {
|
|
521
|
-
fetchData({ pagination: next }, { delay: 0 });
|
|
522
|
-
}
|
|
523
|
-
return next;
|
|
524
|
-
});
|
|
525
|
-
}, [isServerMode, isServerPagination, fetchData, onPaginationChange]);
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const handleGlobalFilterChange = useCallback((updaterOrValue: any) => {
|
|
530
|
-
setGlobalFilter((prev) => {
|
|
531
|
-
const next = typeof updaterOrValue === 'function' ? updaterOrValue(prev) : updaterOrValue;
|
|
532
|
-
|
|
533
|
-
onGlobalFilterChange?.(next);
|
|
534
|
-
|
|
535
|
-
if (isServerMode || isServerFiltering) {
|
|
536
|
-
const nextPagination = { pageIndex: 0, pageSize: pagination.pageSize };
|
|
537
|
-
setPagination(nextPagination);
|
|
538
|
-
fetchData({ globalFilter: next, pagination: nextPagination }, { delay: 0 });
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
return next;
|
|
542
|
-
});
|
|
543
|
-
}, [isServerMode, isServerFiltering, onGlobalFilterChange, fetchData, pagination.pageSize]);
|
|
544
|
-
|
|
545
|
-
const onColumnFilterChangeHandler = useCallback((updater: any) => {
|
|
546
|
-
const currentState = columnFilter;
|
|
547
|
-
const newState = typeof updater === 'function'
|
|
548
|
-
? updater(currentState)
|
|
549
|
-
: updater;
|
|
550
|
-
const legacyFilterState = {
|
|
551
|
-
filters: newState.filters,
|
|
552
|
-
logic: newState.logic,
|
|
553
|
-
pendingFilters: newState.pendingFilters,
|
|
554
|
-
pendingLogic: newState.pendingLogic
|
|
555
|
-
};
|
|
556
|
-
handleColumnFilterStateChange(legacyFilterState);
|
|
557
|
-
}, [columnFilter, handleColumnFilterStateChange]);
|
|
558
|
-
|
|
559
|
-
const onColumnFilterApplyHandler = useCallback((appliedState: ColumnFilterState) => {
|
|
560
|
-
const pagination = resetPageToFirst();
|
|
561
|
-
if (isServerFiltering) {
|
|
562
|
-
fetchData({
|
|
563
|
-
columnFilter: appliedState,
|
|
564
|
-
pagination,
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
onColumnFiltersChange?.(appliedState);
|
|
569
|
-
}, [resetPageToFirst, isServerFiltering, fetchData, onColumnFiltersChange]);
|
|
570
|
-
|
|
571
|
-
// -------------------------------
|
|
572
|
-
// Table creation (after callbacks/memo)
|
|
573
|
-
// -------------------------------
|
|
574
|
-
const table = useReactTable({
|
|
575
|
-
_features: [ColumnFilterFeature, SelectionFeature],
|
|
576
|
-
data: tableData,
|
|
577
|
-
columns: enhancedColumns,
|
|
578
|
-
// Use merged initial state so built-in reset helpers align with our controlled state defaults
|
|
579
|
-
initialState: initialStateConfig,
|
|
580
|
-
state: {
|
|
581
|
-
...(enableSorting ? { sorting } : {}),
|
|
582
|
-
...(enablePagination ? { pagination } : {}),
|
|
583
|
-
...(enableGlobalFilter ? { globalFilter } : {}),
|
|
584
|
-
...(enableExpanding ? { expanded } : {}),
|
|
585
|
-
...(enableColumnDragging ? { columnOrder } : {}),
|
|
586
|
-
...(enableColumnPinning ? { columnPinning } : {}),
|
|
587
|
-
...(enableColumnVisibility ? { columnVisibility } : {}),
|
|
588
|
-
...(enableColumnResizing ? { columnSizing } : {}),
|
|
589
|
-
...(enableColumnFilter ? { columnFilter } : {}),
|
|
590
|
-
...(enableRowSelection ? { selectionState } : {}),
|
|
591
|
-
},
|
|
592
|
-
// Selection options (same pattern as column filter)
|
|
593
|
-
// Add custom features
|
|
594
|
-
selectMode: selectMode,
|
|
595
|
-
enableAdvanceSelection: !!enableRowSelection,
|
|
596
|
-
isRowSelectable: isRowSelectable,
|
|
597
|
-
...(enableRowSelection ? { onSelectionStateChange: handleSelectionStateChange } : {}),
|
|
598
|
-
// Column filter
|
|
599
|
-
enableAdvanceColumnFilter: enableColumnFilter,
|
|
600
|
-
onColumnFilterChange: onColumnFilterChangeHandler, // Handle column filters change
|
|
601
|
-
onColumnFilterApply: onColumnFilterApplyHandler, // Handle when filters are actually applied
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
...(enableSorting ? { onSortingChange: handleSortingChange } : {}),
|
|
605
|
-
...(enablePagination ? { onPaginationChange: handlePaginationChange } : {}),
|
|
606
|
-
...(enableGlobalFilter ? { onGlobalFilterChange: handleGlobalFilterChange } : {}),
|
|
607
|
-
...(enableExpanding ? { onExpandedChange: setExpanded } : {}),
|
|
608
|
-
...(enableColumnDragging ? { onColumnOrderChange: handleColumnOrderChange } : {}),
|
|
609
|
-
...(enableColumnPinning ? { onColumnPinningChange: handleColumnPinningChange } : {}),
|
|
610
|
-
...(enableColumnVisibility ? { onColumnVisibilityChange: handleColumnVisibilityChange } : {}),
|
|
611
|
-
...(enableColumnResizing ? { onColumnSizingChange: handleColumnSizingChange } : {}),
|
|
612
|
-
|
|
613
|
-
// Row model
|
|
614
|
-
getCoreRowModel: getCoreRowModel(),
|
|
615
|
-
...(enableSorting ? { getSortedRowModel: getSortedRowModel() } : {}),
|
|
616
|
-
...(enableColumnFilter || enableGlobalFilter ? { getFilteredRowModel: getCombinedFilteredRowModel<T>() } : {}),
|
|
617
|
-
// Only use getPaginationRowModel for client-side pagination
|
|
618
|
-
...(enablePagination && !isServerPagination ? { getPaginationRowModel: getPaginationRowModel() } : {}),
|
|
619
|
-
// Sorting
|
|
620
|
-
enableSorting: enableSorting,
|
|
621
|
-
manualSorting: isServerSorting,
|
|
622
|
-
// Filtering
|
|
623
|
-
manualFiltering: isServerFiltering,
|
|
624
|
-
// Column resizing
|
|
625
|
-
enableColumnResizing: enableColumnResizing,
|
|
626
|
-
columnResizeMode: columnResizeMode,
|
|
627
|
-
columnResizeDirection: theme.direction,
|
|
628
|
-
// Column pinning
|
|
629
|
-
enableColumnPinning: enableColumnPinning,
|
|
630
|
-
// Expanding
|
|
631
|
-
...(enableExpanding ? { getRowCanExpand: getRowCanExpand } : {}),
|
|
632
|
-
// Pagination
|
|
633
|
-
manualPagination: isServerPagination,
|
|
634
|
-
autoResetPageIndex: false, // Prevent automatic page reset on state changes
|
|
635
|
-
// pageCount: enablePagination ? Math.ceil(tableTotalRow / pagination.pageSize) : -1,
|
|
636
|
-
rowCount: enablePagination ? (tableTotalRow ?? tableData.length) : tableData.length,
|
|
637
|
-
// Row ID
|
|
638
|
-
getRowId: (row: any, index: number) => generateRowId(row, index, idKey),
|
|
639
|
-
// Debug
|
|
640
|
-
debugAll: false, // Disabled for production
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
// Compute width after table is created so column resizing is safe and reflects changes
|
|
644
|
-
const allLeafColumns = table.getAllLeafColumns();
|
|
645
|
-
const visibleLeafColumns = table.getVisibleLeafColumns();
|
|
646
|
-
const hasExplicitSizing = allLeafColumns.some((column) => {
|
|
647
|
-
const { size, minSize, maxSize } = column.columnDef;
|
|
648
|
-
return size !== undefined || minSize !== undefined || maxSize !== undefined;
|
|
649
|
-
});
|
|
650
|
-
const useFixedLayout = fitToScreen || enableColumnResizing || hasExplicitSizing;
|
|
651
|
-
const tableTotalSize = table.getTotalSize();
|
|
652
|
-
const tableWidth = fitToScreen ? '100%' : (useFixedLayout ? tableTotalSize : '100%');
|
|
653
|
-
const tableStyle = {
|
|
654
|
-
width: tableWidth,
|
|
655
|
-
minWidth: fitToScreen ? tableTotalSize : undefined,
|
|
656
|
-
tableLayout: useFixedLayout ? 'fixed' : 'auto',
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
// -------------------------------
|
|
661
|
-
// Virtualization and row memo
|
|
662
|
-
// -------------------------------
|
|
663
|
-
// Note: globalFilter is needed in dependencies to trigger recalculation when filter changes
|
|
664
|
-
// The table object is stable, so we need to depend on the filter state directly
|
|
665
|
-
const rows = table.getRowModel().rows;
|
|
666
|
-
const rowVirtualizer = useVirtualizer({
|
|
667
|
-
count: rows.length,
|
|
668
|
-
getScrollElement: () => tableContainerRef.current,
|
|
669
|
-
estimateSize: () => estimateRowHeight,
|
|
670
|
-
overscan: 10,
|
|
671
|
-
enabled: enableVirtualization && !enablePagination && rows.length > 0,
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
// -------------------------------
|
|
676
|
-
// Callbacks (after table creation)
|
|
677
|
-
// -------------------------------
|
|
678
|
-
const handleColumnReorder = useCallback((draggedColumnId: string, targetColumnId: string) => {
|
|
679
|
-
const currentOrder = columnOrder.length > 0 ? columnOrder : enhancedColumns.map((col, index) => {
|
|
680
|
-
if (col.id) return col.id;
|
|
681
|
-
const anyCol = col as any;
|
|
682
|
-
if (anyCol.accessorKey && typeof anyCol.accessorKey === 'string') {
|
|
683
|
-
return anyCol.accessorKey;
|
|
684
|
-
}
|
|
685
|
-
return `column_${index}`;
|
|
686
|
-
});
|
|
687
|
-
const draggedIndex = currentOrder.indexOf(draggedColumnId);
|
|
688
|
-
const targetIndex = currentOrder.indexOf(targetColumnId);
|
|
689
|
-
if (draggedIndex === -1 || targetIndex === -1) return;
|
|
690
|
-
const newOrder = [...currentOrder];
|
|
691
|
-
newOrder.splice(draggedIndex, 1);
|
|
692
|
-
newOrder.splice(targetIndex, 0, draggedColumnId);
|
|
693
|
-
handleColumnOrderChange(newOrder);
|
|
694
|
-
}, [columnOrder, enhancedColumns, handleColumnOrderChange]);
|
|
695
|
-
|
|
696
|
-
// -------------------------------
|
|
697
|
-
// Effects (after callbacks)
|
|
698
|
-
// -------------------------------
|
|
699
|
-
useEffect(() => {
|
|
700
|
-
if (!isExternallyControlledData || serverData === null) return;
|
|
701
|
-
setServerData(null);
|
|
702
|
-
setServerTotal(0);
|
|
703
|
-
}, [isExternallyControlledData, serverData]);
|
|
704
|
-
|
|
705
|
-
useEffect(() => {
|
|
706
|
-
if (initialLoadData && (onFetchData || onFetchStateChange)) {
|
|
707
|
-
if (logger.isLevelEnabled('info')) {
|
|
708
|
-
logger.info('Initial data load triggered', { initialLoadData });
|
|
709
|
-
}
|
|
710
|
-
fetchData({}, {
|
|
711
|
-
delay: 0,
|
|
712
|
-
meta: { reason: 'initial' },
|
|
713
|
-
});
|
|
714
|
-
} else if (logger.isLevelEnabled('debug')) {
|
|
715
|
-
logger.debug('Skipping initial data load', {
|
|
716
|
-
initialLoadData,
|
|
717
|
-
hasOnFetchData: !!onFetchData
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
721
|
-
}, []);
|
|
722
|
-
|
|
723
|
-
useEffect(() => {
|
|
724
|
-
if (enableColumnDragging && columnOrder.length === 0) {
|
|
725
|
-
const initialOrder = enhancedColumns.map((col, index) => {
|
|
726
|
-
if (col.id) return col.id;
|
|
727
|
-
const anyCol = col as any;
|
|
728
|
-
if (anyCol.accessorKey && typeof anyCol.accessorKey === 'string') {
|
|
729
|
-
return anyCol.accessorKey;
|
|
730
|
-
}
|
|
731
|
-
return `column_${index}`;
|
|
732
|
-
});
|
|
733
|
-
setColumnOrder(initialOrder);
|
|
734
|
-
}
|
|
735
|
-
}, [enableColumnDragging, enhancedColumns, columnOrder.length]);
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const lastSentRef = useRef<string>("");
|
|
739
|
-
|
|
740
|
-
const emitTableState = useCallback(() => {
|
|
741
|
-
if (!onDataStateChange) return;
|
|
742
|
-
|
|
743
|
-
const live = table.getState();
|
|
744
|
-
const liveColumnFilter = live.columnFilter;
|
|
745
|
-
|
|
746
|
-
// only keep what you persist/store
|
|
747
|
-
const payload = {
|
|
748
|
-
sorting: live.sorting,
|
|
749
|
-
pagination: live.pagination,
|
|
750
|
-
globalFilter: live.globalFilter,
|
|
751
|
-
columnFilter: liveColumnFilter,
|
|
752
|
-
columnVisibility: live.columnVisibility,
|
|
753
|
-
columnSizing: live.columnSizing,
|
|
754
|
-
columnOrder: live.columnOrder,
|
|
755
|
-
columnPinning: live.columnPinning,
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
const key = JSON.stringify(payload);
|
|
759
|
-
if (key === lastSentRef.current) return;
|
|
760
|
-
|
|
761
|
-
lastSentRef.current = key;
|
|
762
|
-
onDataStateChange(payload);
|
|
763
|
-
}, [onDataStateChange, table]);
|
|
764
|
-
|
|
765
|
-
useEffect(() => {
|
|
766
|
-
emitTableState();
|
|
767
|
-
}, [
|
|
768
|
-
emitTableState,
|
|
769
|
-
sorting,
|
|
770
|
-
pagination,
|
|
771
|
-
globalFilter,
|
|
772
|
-
columnFilter,
|
|
773
|
-
columnVisibility,
|
|
774
|
-
columnSizing,
|
|
775
|
-
columnOrder,
|
|
776
|
-
columnPinning,
|
|
777
|
-
]);
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
const getResetState = useCallback((): Partial<TableState> => {
|
|
781
|
-
const resetSorting = initialStateConfig.sorting || [];
|
|
782
|
-
const resetGlobalFilter = initialStateConfig.globalFilter ?? '';
|
|
783
|
-
const resetColumnFilter = initialStateConfig.columnFilter;
|
|
784
|
-
|
|
785
|
-
const resetPagination = enablePagination
|
|
786
|
-
? (initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 })
|
|
787
|
-
: undefined;
|
|
788
|
-
|
|
789
|
-
return {
|
|
790
|
-
sorting: resetSorting,
|
|
791
|
-
globalFilter: resetGlobalFilter,
|
|
792
|
-
columnFilter: resetColumnFilter,
|
|
793
|
-
...(resetPagination ? { pagination: resetPagination } : {}),
|
|
794
|
-
};
|
|
795
|
-
}, [initialStateConfig, enablePagination]);
|
|
796
|
-
|
|
797
|
-
const applyDataMutation = useCallback((
|
|
798
|
-
action: DataMutationAction,
|
|
799
|
-
updater: (rows: T[]) => T[],
|
|
800
|
-
details: Partial<Omit<DataMutationContext<T>, 'action' | 'previousData' | 'nextData'>> = {}
|
|
801
|
-
) => {
|
|
802
|
-
const previousData = [...tableData];
|
|
803
|
-
const nextData = updater(previousData);
|
|
804
|
-
|
|
805
|
-
if (nextData === previousData) return nextData;
|
|
806
|
-
|
|
807
|
-
const nextTotal = Math.max(0, tableTotalRow + (nextData.length - previousData.length));
|
|
808
|
-
|
|
809
|
-
if (!isExternallyControlledData) {
|
|
810
|
-
setServerData(nextData);
|
|
811
|
-
setServerTotal(nextTotal);
|
|
812
|
-
}
|
|
813
|
-
onDataChange?.(nextData, {
|
|
814
|
-
action,
|
|
815
|
-
previousData,
|
|
816
|
-
nextData,
|
|
817
|
-
totalRow: nextTotal,
|
|
818
|
-
...details,
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
if (logger.isLevelEnabled('debug')) {
|
|
822
|
-
logger.debug('Applied data mutation', {
|
|
823
|
-
action,
|
|
824
|
-
previousCount: previousData.length,
|
|
825
|
-
nextCount: nextData.length,
|
|
826
|
-
totalRow: nextTotal,
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
return nextData;
|
|
831
|
-
}, [isExternallyControlledData, logger, onDataChange, tableData, tableTotalRow]);
|
|
832
|
-
|
|
833
|
-
const triggerRefresh = useCallback(async (
|
|
834
|
-
options?: boolean | DataRefreshOptions,
|
|
835
|
-
fallbackReason: string = 'refresh'
|
|
836
|
-
) => {
|
|
837
|
-
const normalizedOptions = normalizeRefreshOptions(options, fallbackReason);
|
|
838
|
-
const nextPagination = enablePagination
|
|
839
|
-
? {
|
|
840
|
-
pageIndex: normalizedOptions.resetPagination ? 0 : pagination.pageIndex,
|
|
841
|
-
pageSize: pagination.pageSize,
|
|
842
|
-
}
|
|
843
|
-
: undefined;
|
|
844
|
-
|
|
845
|
-
const shouldUpdatePagination = !!nextPagination
|
|
846
|
-
&& (nextPagination.pageIndex !== pagination.pageIndex || nextPagination.pageSize !== pagination.pageSize);
|
|
847
|
-
|
|
848
|
-
if (nextPagination && shouldUpdatePagination) {
|
|
849
|
-
setPagination(nextPagination);
|
|
850
|
-
onPaginationChange?.(nextPagination);
|
|
851
|
-
}
|
|
10
|
+
import { useDataTableEngine } from './hooks/use-data-table-engine';
|
|
11
|
+
import { DataTableView } from './components/data-table-view';
|
|
12
|
+
import type { DataTableProps } from './types/data-table.types';
|
|
13
|
+
import type { DataTableApi } from './types/data-table-api';
|
|
852
14
|
|
|
853
|
-
await fetchData(
|
|
854
|
-
nextPagination ? { pagination: nextPagination } : {},
|
|
855
|
-
{
|
|
856
|
-
delay: 0,
|
|
857
|
-
meta: {
|
|
858
|
-
reason: normalizedOptions.reason,
|
|
859
|
-
force: normalizedOptions.force,
|
|
860
|
-
},
|
|
861
|
-
}
|
|
862
|
-
);
|
|
863
|
-
return;
|
|
864
|
-
}, [normalizeRefreshOptions, enablePagination, pagination, onPaginationChange, fetchData]);
|
|
865
15
|
|
|
866
|
-
|
|
867
|
-
|
|
16
|
+
export const DataTable = forwardRef(function DataTable<T extends Record<string, any>>(
|
|
17
|
+
props: DataTableProps<T>,
|
|
18
|
+
ref: React.Ref<DataTableApi<T>>,
|
|
19
|
+
) {
|
|
20
|
+
const engine = useDataTableEngine<T>(props);
|
|
868
21
|
|
|
869
|
-
|
|
870
|
-
setGlobalFilter(resetState.globalFilter ?? '');
|
|
871
|
-
setColumnFilter(resetState.columnFilter as any);
|
|
22
|
+
useImperativeHandle(ref, () => engine.refs.apiRef.current!, []);
|
|
872
23
|
|
|
873
|
-
if (resetState.pagination) {
|
|
874
|
-
setPagination(resetState.pagination);
|
|
875
|
-
onPaginationChange?.(resetState.pagination);
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
setSelectionState(initialSelectionState);
|
|
879
|
-
setExpanded({});
|
|
880
|
-
|
|
881
|
-
// layout state
|
|
882
|
-
setColumnVisibility(initialStateConfig.columnVisibility || {});
|
|
883
|
-
setColumnSizing(initialStateConfig.columnSizing || {});
|
|
884
|
-
setColumnOrder(initialStateConfig.columnOrder || []);
|
|
885
|
-
setColumnPinning(initialStateConfig.columnPinning || { left: [], right: [] });
|
|
886
|
-
|
|
887
|
-
const resetOptions = normalizeRefreshOptions({
|
|
888
|
-
resetPagination: true,
|
|
889
|
-
force: true,
|
|
890
|
-
reason: 'reset',
|
|
891
|
-
}, 'reset');
|
|
892
|
-
|
|
893
|
-
void fetchData(resetState, {
|
|
894
|
-
delay: 0,
|
|
895
|
-
meta: {
|
|
896
|
-
reason: resetOptions.reason,
|
|
897
|
-
force: resetOptions.force,
|
|
898
|
-
},
|
|
899
|
-
});
|
|
900
|
-
}, [getResetState, initialSelectionState, initialStateConfig, onPaginationChange, normalizeRefreshOptions, fetchData]);
|
|
901
|
-
|
|
902
|
-
const setExportControllerSafely = useCallback((
|
|
903
|
-
value: AbortController | null | ((current: AbortController | null) => AbortController | null)
|
|
904
|
-
) => {
|
|
905
|
-
setExportController((current) => {
|
|
906
|
-
const next = typeof value === 'function' ? (value as any)(current) : value;
|
|
907
|
-
exportControllerRef.current = next;
|
|
908
|
-
return next;
|
|
909
|
-
});
|
|
910
|
-
}, []);
|
|
911
|
-
|
|
912
|
-
const handleExportProgressInternal = useCallback((progress: ExportProgressPayload) => {
|
|
913
|
-
setExportProgress(progress || {});
|
|
914
|
-
onExportProgress?.(progress);
|
|
915
|
-
}, [onExportProgress]);
|
|
916
|
-
|
|
917
|
-
const handleExportStateChangeInternal = useCallback((state: ExportStateChange) => {
|
|
918
|
-
setExportPhase(state.phase);
|
|
919
|
-
if (
|
|
920
|
-
state.processedRows !== undefined
|
|
921
|
-
|| state.totalRows !== undefined
|
|
922
|
-
|| state.percentage !== undefined
|
|
923
|
-
) {
|
|
924
|
-
setExportProgress({
|
|
925
|
-
processedRows: state.processedRows,
|
|
926
|
-
totalRows: state.totalRows,
|
|
927
|
-
percentage: state.percentage,
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
onExportStateChange?.(state);
|
|
931
|
-
}, [onExportStateChange]);
|
|
932
|
-
|
|
933
|
-
const runExportWithPolicy = useCallback(
|
|
934
|
-
async (
|
|
935
|
-
options: {
|
|
936
|
-
format: 'csv' | 'excel';
|
|
937
|
-
filename: string;
|
|
938
|
-
mode: 'client' | 'server';
|
|
939
|
-
execute: (controller: AbortController) => Promise<void>;
|
|
940
|
-
}
|
|
941
|
-
) => {
|
|
942
|
-
const { format, filename, mode, execute } = options;
|
|
943
|
-
|
|
944
|
-
const startExecution = async () => {
|
|
945
|
-
const controller = new AbortController();
|
|
946
|
-
setExportProgress({});
|
|
947
|
-
setExportControllerSafely(controller);
|
|
948
|
-
try {
|
|
949
|
-
await execute(controller);
|
|
950
|
-
} finally {
|
|
951
|
-
setExportControllerSafely((current) => (current === controller ? null : current));
|
|
952
|
-
}
|
|
953
|
-
};
|
|
954
|
-
|
|
955
|
-
if (exportConcurrency === 'queue') {
|
|
956
|
-
setQueuedExportCount((prev) => prev + 1);
|
|
957
|
-
const runQueued = async (): Promise<void> => {
|
|
958
|
-
setQueuedExportCount((prev) => Math.max(0, prev - 1));
|
|
959
|
-
await startExecution();
|
|
960
|
-
};
|
|
961
|
-
const queuedPromise = exportQueueRef.current
|
|
962
|
-
.catch(() => undefined)
|
|
963
|
-
.then(runQueued);
|
|
964
|
-
exportQueueRef.current = queuedPromise;
|
|
965
|
-
return queuedPromise;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
const activeController = exportControllerRef.current;
|
|
969
|
-
if (activeController) {
|
|
970
|
-
if (exportConcurrency === 'ignoreIfRunning') {
|
|
971
|
-
handleExportStateChangeInternal({
|
|
972
|
-
phase: 'error',
|
|
973
|
-
mode,
|
|
974
|
-
format,
|
|
975
|
-
filename,
|
|
976
|
-
message: 'An export is already running',
|
|
977
|
-
code: 'EXPORT_IN_PROGRESS',
|
|
978
|
-
endedAt: Date.now(),
|
|
979
|
-
});
|
|
980
|
-
onExportError?.({
|
|
981
|
-
message: 'An export is already running',
|
|
982
|
-
code: 'EXPORT_IN_PROGRESS',
|
|
983
|
-
});
|
|
984
|
-
return;
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
if (exportConcurrency === 'cancelAndRestart') {
|
|
988
|
-
activeController.abort();
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
await startExecution();
|
|
993
|
-
},
|
|
994
|
-
[
|
|
995
|
-
exportConcurrency,
|
|
996
|
-
handleExportStateChangeInternal,
|
|
997
|
-
onExportError,
|
|
998
|
-
setExportControllerSafely,
|
|
999
|
-
]
|
|
1000
|
-
);
|
|
1001
|
-
|
|
1002
|
-
const dataTableApi = useMemo(() => {
|
|
1003
|
-
// helpers (avoid repeating boilerplate)
|
|
1004
|
-
const buildInitialOrder = () =>
|
|
1005
|
-
enhancedColumns.map((col, index) => {
|
|
1006
|
-
if ((col as any).id) return (col as any).id as string;
|
|
1007
|
-
const anyCol = col as any;
|
|
1008
|
-
if (anyCol.accessorKey && typeof anyCol.accessorKey === "string") return anyCol.accessorKey;
|
|
1009
|
-
return `column_${index}`;
|
|
1010
|
-
});
|
|
1011
|
-
|
|
1012
|
-
const applyColumnOrder = (next: ColumnOrderState) => {
|
|
1013
|
-
// handleColumnOrderChange supports both Updater<ColumnOrderState> and array in your impl
|
|
1014
|
-
handleColumnOrderChange(next as any);
|
|
1015
|
-
};
|
|
1016
|
-
|
|
1017
|
-
const applyPinning = (next: ColumnPinningState) => {
|
|
1018
|
-
handleColumnPinningChange(next as any);
|
|
1019
|
-
};
|
|
1020
|
-
|
|
1021
|
-
const applyVisibility = (next: Record<string, boolean>) => {
|
|
1022
|
-
handleColumnVisibilityChange(next as any);
|
|
1023
|
-
};
|
|
1024
|
-
|
|
1025
|
-
const applySizing = (next: Record<string, number>) => {
|
|
1026
|
-
handleColumnSizingChange(next as any);
|
|
1027
|
-
};
|
|
1028
|
-
|
|
1029
|
-
const applyPagination = (next: any) => {
|
|
1030
|
-
handlePaginationChange(next);
|
|
1031
|
-
};
|
|
1032
|
-
|
|
1033
|
-
const applySorting = (next: any) => {
|
|
1034
|
-
handleSortingChange(next);
|
|
1035
|
-
};
|
|
1036
|
-
|
|
1037
|
-
const applyGlobalFilter = (next: any) => {
|
|
1038
|
-
handleGlobalFilterChange(next);
|
|
1039
|
-
};
|
|
1040
|
-
|
|
1041
|
-
const getRowIndexById = (rowsToSearch: T[], rowId: string) =>
|
|
1042
|
-
rowsToSearch.findIndex((row, index) => String(generateRowId(row, index, idKey)) === rowId);
|
|
1043
|
-
|
|
1044
|
-
const clampInsertIndex = (rowsToMutate: T[], insertIndex?: number) => {
|
|
1045
|
-
if (insertIndex === undefined) return rowsToMutate.length;
|
|
1046
|
-
return Math.max(0, Math.min(insertIndex, rowsToMutate.length));
|
|
1047
|
-
};
|
|
1048
|
-
|
|
1049
|
-
return {
|
|
1050
|
-
table: {
|
|
1051
|
-
getTable: () => table,
|
|
1052
|
-
},
|
|
1053
|
-
|
|
1054
|
-
// -------------------------------
|
|
1055
|
-
// Column Management
|
|
1056
|
-
// -------------------------------
|
|
1057
|
-
columnVisibility: {
|
|
1058
|
-
showColumn: (columnId: string) => {
|
|
1059
|
-
applyVisibility({ ...table.getState().columnVisibility, [columnId]: true });
|
|
1060
|
-
},
|
|
1061
|
-
hideColumn: (columnId: string) => {
|
|
1062
|
-
applyVisibility({ ...table.getState().columnVisibility, [columnId]: false });
|
|
1063
|
-
},
|
|
1064
|
-
toggleColumn: (columnId: string) => {
|
|
1065
|
-
const curr = table.getState().columnVisibility?.[columnId] ?? true;
|
|
1066
|
-
applyVisibility({ ...table.getState().columnVisibility, [columnId]: !curr });
|
|
1067
|
-
},
|
|
1068
|
-
showAllColumns: () => {
|
|
1069
|
-
// set all known columns true
|
|
1070
|
-
const all: Record<string, boolean> = {};
|
|
1071
|
-
table.getAllLeafColumns().forEach((c) => (all[c.id] = true));
|
|
1072
|
-
applyVisibility(all);
|
|
1073
|
-
},
|
|
1074
|
-
hideAllColumns: () => {
|
|
1075
|
-
const all: Record<string, boolean> = {};
|
|
1076
|
-
table.getAllLeafColumns().forEach((c) => (all[c.id] = false));
|
|
1077
|
-
applyVisibility(all);
|
|
1078
|
-
},
|
|
1079
|
-
resetColumnVisibility: () => {
|
|
1080
|
-
const initialVisibility = initialStateConfig.columnVisibility || {};
|
|
1081
|
-
applyVisibility(initialVisibility);
|
|
1082
|
-
},
|
|
1083
|
-
},
|
|
1084
|
-
|
|
1085
|
-
// -------------------------------
|
|
1086
|
-
// Column Ordering
|
|
1087
|
-
// -------------------------------
|
|
1088
|
-
columnOrdering: {
|
|
1089
|
-
setColumnOrder: (nextOrder: ColumnOrderState) => {
|
|
1090
|
-
applyColumnOrder(nextOrder);
|
|
1091
|
-
},
|
|
1092
|
-
moveColumn: (columnId: string, toIndex: number) => {
|
|
1093
|
-
const currentOrder =
|
|
1094
|
-
(table.getState().columnOrder?.length ? table.getState().columnOrder : buildInitialOrder()) || [];
|
|
1095
|
-
const fromIndex = currentOrder.indexOf(columnId);
|
|
1096
|
-
if (fromIndex === -1) return;
|
|
1097
|
-
|
|
1098
|
-
const next = [...currentOrder];
|
|
1099
|
-
next.splice(fromIndex, 1);
|
|
1100
|
-
next.splice(toIndex, 0, columnId);
|
|
1101
|
-
|
|
1102
|
-
applyColumnOrder(next);
|
|
1103
|
-
},
|
|
1104
|
-
resetColumnOrder: () => {
|
|
1105
|
-
applyColumnOrder(buildInitialOrder());
|
|
1106
|
-
},
|
|
1107
|
-
},
|
|
1108
|
-
|
|
1109
|
-
// -------------------------------
|
|
1110
|
-
// Column Pinning
|
|
1111
|
-
// -------------------------------
|
|
1112
|
-
columnPinning: {
|
|
1113
|
-
pinColumnLeft: (columnId: string) => {
|
|
1114
|
-
const current = table.getState().columnPinning || { left: [], right: [] };
|
|
1115
|
-
const next: ColumnPinningState = {
|
|
1116
|
-
left: [...(current.left || []).filter((id) => id !== columnId), columnId],
|
|
1117
|
-
right: (current.right || []).filter((id) => id !== columnId),
|
|
1118
|
-
};
|
|
1119
|
-
applyPinning(next);
|
|
1120
|
-
},
|
|
1121
|
-
pinColumnRight: (columnId: string) => {
|
|
1122
|
-
const current = table.getState().columnPinning || { left: [], right: [] };
|
|
1123
|
-
const next: ColumnPinningState = {
|
|
1124
|
-
left: (current.left || []).filter((id) => id !== columnId),
|
|
1125
|
-
// keep your "prepend" behavior
|
|
1126
|
-
right: [columnId, ...(current.right || []).filter((id) => id !== columnId)],
|
|
1127
|
-
};
|
|
1128
|
-
applyPinning(next);
|
|
1129
|
-
},
|
|
1130
|
-
unpinColumn: (columnId: string) => {
|
|
1131
|
-
const current = table.getState().columnPinning || { left: [], right: [] };
|
|
1132
|
-
const next: ColumnPinningState = {
|
|
1133
|
-
left: (current.left || []).filter((id) => id !== columnId),
|
|
1134
|
-
right: (current.right || []).filter((id) => id !== columnId),
|
|
1135
|
-
};
|
|
1136
|
-
applyPinning(next);
|
|
1137
|
-
},
|
|
1138
|
-
setPinning: (pinning: ColumnPinningState) => {
|
|
1139
|
-
applyPinning(pinning);
|
|
1140
|
-
},
|
|
1141
|
-
resetColumnPinning: () => {
|
|
1142
|
-
const initialPinning = initialStateConfig.columnPinning || { left: [], right: [] };
|
|
1143
|
-
applyPinning(initialPinning);
|
|
1144
|
-
},
|
|
1145
|
-
},
|
|
1146
|
-
|
|
1147
|
-
// -------------------------------
|
|
1148
|
-
// Column Resizing
|
|
1149
|
-
// -------------------------------
|
|
1150
|
-
columnResizing: {
|
|
1151
|
-
resizeColumn: (columnId: string, width: number) => {
|
|
1152
|
-
const currentSizing = table.getState().columnSizing || {};
|
|
1153
|
-
applySizing({ ...currentSizing, [columnId]: width });
|
|
1154
|
-
},
|
|
1155
|
-
autoSizeColumn: (columnId: string) => {
|
|
1156
|
-
// safe to call tanstack helper; it will feed into onColumnSizingChange if wired,
|
|
1157
|
-
// but since you're controlled, we still prefer to update through handler:
|
|
1158
|
-
const col = table.getColumn(columnId);
|
|
1159
|
-
if (!col) return;
|
|
1160
|
-
|
|
1161
|
-
col.resetSize();
|
|
1162
|
-
// after resetSize, read state and emit via handler so controlled stays synced
|
|
1163
|
-
applySizing({ ...(table.getState().columnSizing || {}) });
|
|
1164
|
-
},
|
|
1165
|
-
autoSizeAllColumns: () => {
|
|
1166
|
-
const initialSizing = initialStateConfig.columnSizing || {};
|
|
1167
|
-
applySizing(initialSizing);
|
|
1168
|
-
},
|
|
1169
|
-
resetColumnSizing: () => {
|
|
1170
|
-
const initialSizing = initialStateConfig.columnSizing || {};
|
|
1171
|
-
applySizing(initialSizing);
|
|
1172
|
-
},
|
|
1173
|
-
},
|
|
1174
|
-
|
|
1175
|
-
// -------------------------------
|
|
1176
|
-
// Filtering
|
|
1177
|
-
// -------------------------------
|
|
1178
|
-
filtering: {
|
|
1179
|
-
setGlobalFilter: (filter: string) => {
|
|
1180
|
-
applyGlobalFilter(filter);
|
|
1181
|
-
},
|
|
1182
|
-
clearGlobalFilter: () => {
|
|
1183
|
-
applyGlobalFilter("");
|
|
1184
|
-
},
|
|
1185
|
-
setColumnFilters: (filters: ColumnFilterState) => {
|
|
1186
|
-
handleColumnFilterStateChange(filters);
|
|
1187
|
-
},
|
|
1188
|
-
addColumnFilter: (columnId: string, operator: string, value: any) => {
|
|
1189
|
-
const newFilter = {
|
|
1190
|
-
id: `filter_${Date.now()}`,
|
|
1191
|
-
columnId,
|
|
1192
|
-
operator,
|
|
1193
|
-
value,
|
|
1194
|
-
};
|
|
1195
|
-
|
|
1196
|
-
const current = table.getState().columnFilter;
|
|
1197
|
-
const currentFilters = current?.filters || [];
|
|
1198
|
-
const nextFilters = [...currentFilters, newFilter];
|
|
1199
|
-
|
|
1200
|
-
handleColumnFilterStateChange({
|
|
1201
|
-
filters: nextFilters,
|
|
1202
|
-
logic: current?.logic,
|
|
1203
|
-
pendingFilters: current?.pendingFilters || [],
|
|
1204
|
-
pendingLogic: current?.pendingLogic || "AND",
|
|
1205
|
-
});
|
|
1206
|
-
|
|
1207
|
-
if (logger.isLevelEnabled("debug")) {
|
|
1208
|
-
logger.debug(`Adding column filter ${columnId} ${operator} ${value}`, nextFilters);
|
|
1209
|
-
}
|
|
1210
|
-
},
|
|
1211
|
-
removeColumnFilter: (filterId: string) => {
|
|
1212
|
-
const current = table.getState().columnFilter;
|
|
1213
|
-
const currentFilters = current?.filters || [];
|
|
1214
|
-
const nextFilters = currentFilters.filter((f: any) => f.id !== filterId);
|
|
1215
|
-
|
|
1216
|
-
handleColumnFilterStateChange({
|
|
1217
|
-
filters: nextFilters,
|
|
1218
|
-
logic: current?.logic,
|
|
1219
|
-
pendingFilters: current?.pendingFilters || [],
|
|
1220
|
-
pendingLogic: current?.pendingLogic || "AND",
|
|
1221
|
-
});
|
|
1222
|
-
|
|
1223
|
-
if (logger.isLevelEnabled("debug")) {
|
|
1224
|
-
logger.debug(`Removing column filter ${filterId}`, nextFilters);
|
|
1225
|
-
}
|
|
1226
|
-
},
|
|
1227
|
-
clearAllFilters: () => {
|
|
1228
|
-
applyGlobalFilter("");
|
|
1229
|
-
handleColumnFilterStateChange({
|
|
1230
|
-
filters: [],
|
|
1231
|
-
logic: "AND",
|
|
1232
|
-
pendingFilters: [],
|
|
1233
|
-
pendingLogic: "AND",
|
|
1234
|
-
});
|
|
1235
|
-
},
|
|
1236
|
-
resetFilters: () => {
|
|
1237
|
-
handleColumnFilterStateChange({
|
|
1238
|
-
filters: [],
|
|
1239
|
-
logic: "AND",
|
|
1240
|
-
pendingFilters: [],
|
|
1241
|
-
pendingLogic: "AND",
|
|
1242
|
-
});
|
|
1243
|
-
|
|
1244
|
-
if (logger.isLevelEnabled("debug")) {
|
|
1245
|
-
logger.debug("Resetting filters");
|
|
1246
|
-
}
|
|
1247
|
-
},
|
|
1248
|
-
},
|
|
1249
|
-
|
|
1250
|
-
// -------------------------------
|
|
1251
|
-
// Sorting
|
|
1252
|
-
// -------------------------------
|
|
1253
|
-
sorting: {
|
|
1254
|
-
setSorting: (sortingState: SortingState) => {
|
|
1255
|
-
applySorting(sortingState);
|
|
1256
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Setting sorting", sortingState);
|
|
1257
|
-
},
|
|
1258
|
-
|
|
1259
|
-
// NOTE: toggleSorting is okay, but can become "one behind" in controlled server mode.
|
|
1260
|
-
// So we implement deterministic sorting through handler.
|
|
1261
|
-
sortColumn: (columnId: string, direction: "asc" | "desc" | false) => {
|
|
1262
|
-
const current = table.getState().sorting || [];
|
|
1263
|
-
const filtered = current.filter((s: any) => s.id !== columnId);
|
|
1264
|
-
|
|
1265
|
-
if (direction === false) {
|
|
1266
|
-
applySorting(filtered);
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
applySorting([{ id: columnId, desc: direction === "desc" }, ...filtered]);
|
|
1271
|
-
},
|
|
1272
|
-
|
|
1273
|
-
clearSorting: () => {
|
|
1274
|
-
applySorting([]);
|
|
1275
|
-
},
|
|
1276
|
-
resetSorting: () => {
|
|
1277
|
-
const initialSorting = initialStateConfig.sorting || [];
|
|
1278
|
-
applySorting(initialSorting);
|
|
1279
|
-
},
|
|
1280
|
-
},
|
|
1281
|
-
|
|
1282
|
-
// -------------------------------
|
|
1283
|
-
// Pagination
|
|
1284
|
-
// -------------------------------
|
|
1285
|
-
pagination: {
|
|
1286
|
-
goToPage: (pageIndex: number) => {
|
|
1287
|
-
applyPagination((prev: any) => ({ ...prev, pageIndex }));
|
|
1288
|
-
if (logger.isLevelEnabled("debug")) logger.debug(`Going to page ${pageIndex}`);
|
|
1289
|
-
},
|
|
1290
|
-
nextPage: () => {
|
|
1291
|
-
applyPagination((prev: any) => ({ ...prev, pageIndex: (prev?.pageIndex ?? 0) + 1 }));
|
|
1292
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Next page");
|
|
1293
|
-
},
|
|
1294
|
-
previousPage: () => {
|
|
1295
|
-
applyPagination((prev: any) => ({ ...prev, pageIndex: Math.max(0, (prev?.pageIndex ?? 0) - 1) }));
|
|
1296
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Previous page");
|
|
1297
|
-
},
|
|
1298
|
-
setPageSize: (pageSize: number) => {
|
|
1299
|
-
// usually want pageIndex reset
|
|
1300
|
-
applyPagination(() => ({ pageIndex: 0, pageSize }));
|
|
1301
|
-
if (logger.isLevelEnabled("debug")) logger.debug(`Setting page size to ${pageSize}`);
|
|
1302
|
-
},
|
|
1303
|
-
goToFirstPage: () => {
|
|
1304
|
-
applyPagination((prev: any) => ({ ...prev, pageIndex: 0 }));
|
|
1305
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Going to first page");
|
|
1306
|
-
},
|
|
1307
|
-
goToLastPage: () => {
|
|
1308
|
-
// pageCount can be derived; keep safe fallback
|
|
1309
|
-
const pageCount = table.getPageCount?.() ?? 0;
|
|
1310
|
-
if (pageCount > 0) {
|
|
1311
|
-
applyPagination((prev: any) => ({ ...prev, pageIndex: pageCount - 1 }));
|
|
1312
|
-
if (logger.isLevelEnabled("debug")) logger.debug(`Going to last page ${pageCount - 1}`);
|
|
1313
|
-
}
|
|
1314
|
-
},
|
|
1315
|
-
resetPagination: () => {
|
|
1316
|
-
const initialPagination = initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 };
|
|
1317
|
-
applyPagination(initialPagination);
|
|
1318
|
-
},
|
|
1319
|
-
},
|
|
1320
|
-
|
|
1321
|
-
// -------------------------------
|
|
1322
|
-
// Selection
|
|
1323
|
-
// -------------------------------
|
|
1324
|
-
selection: {
|
|
1325
|
-
selectRow: (rowId: string) => table.selectRow?.(rowId),
|
|
1326
|
-
deselectRow: (rowId: string) => table.deselectRow?.(rowId),
|
|
1327
|
-
toggleRowSelection: (rowId: string) => table.toggleRowSelected?.(rowId),
|
|
1328
|
-
selectAll: () => table.selectAll?.(),
|
|
1329
|
-
deselectAll: () => table.deselectAll?.(),
|
|
1330
|
-
toggleSelectAll: () => table.toggleAllRowsSelected?.(),
|
|
1331
|
-
getSelectionState: () => table.getSelectionState?.() || ({ ids: [], type: "include" } as const),
|
|
1332
|
-
getSelectedRows: () => table.getSelectedRows(),
|
|
1333
|
-
getSelectedCount: () => table.getSelectedCount(),
|
|
1334
|
-
isRowSelected: (rowId: string) => table.getIsRowSelected(rowId) || false,
|
|
1335
|
-
},
|
|
1336
|
-
|
|
1337
|
-
// -------------------------------
|
|
1338
|
-
// Data Management (kept same, but ensure state changes go through handlers)
|
|
1339
|
-
// -------------------------------
|
|
1340
|
-
data: {
|
|
1341
|
-
refresh: (options?: boolean | DataRefreshOptions) => {
|
|
1342
|
-
void triggerRefresh(options, 'refresh');
|
|
1343
|
-
},
|
|
1344
|
-
|
|
1345
|
-
reload: (options: DataRefreshOptions = {}) => {
|
|
1346
|
-
void triggerRefresh(
|
|
1347
|
-
{
|
|
1348
|
-
...options,
|
|
1349
|
-
resetPagination: options.resetPagination ?? false,
|
|
1350
|
-
reason: options.reason ?? 'reload',
|
|
1351
|
-
},
|
|
1352
|
-
'reload'
|
|
1353
|
-
);
|
|
1354
|
-
},
|
|
1355
|
-
|
|
1356
|
-
resetAll: () => resetAllAndReload(),
|
|
1357
|
-
|
|
1358
|
-
getAllData: () => [...tableData],
|
|
1359
|
-
getRowData: (rowId: string) => {
|
|
1360
|
-
const rowIndex = getRowIndexById(tableData, rowId);
|
|
1361
|
-
return rowIndex === -1 ? undefined : tableData[rowIndex];
|
|
1362
|
-
},
|
|
1363
|
-
getRowByIndex: (index: number) => tableData[index],
|
|
1364
|
-
|
|
1365
|
-
updateRow: (rowId: string, updates: Partial<T>) => {
|
|
1366
|
-
applyDataMutation('updateRow', (rowsToMutate) => {
|
|
1367
|
-
const rowIndex = getRowIndexById(rowsToMutate, rowId);
|
|
1368
|
-
if (rowIndex === -1) return rowsToMutate;
|
|
1369
|
-
const nextData = [...rowsToMutate];
|
|
1370
|
-
nextData[rowIndex] = { ...nextData[rowIndex], ...updates };
|
|
1371
|
-
return nextData;
|
|
1372
|
-
}, { rowId });
|
|
1373
|
-
},
|
|
1374
|
-
|
|
1375
|
-
updateRowByIndex: (index: number, updates: Partial<T>) => {
|
|
1376
|
-
applyDataMutation('updateRowByIndex', (rowsToMutate) => {
|
|
1377
|
-
if (!rowsToMutate[index]) return rowsToMutate;
|
|
1378
|
-
const nextData = [...rowsToMutate];
|
|
1379
|
-
nextData[index] = { ...nextData[index], ...updates };
|
|
1380
|
-
return nextData;
|
|
1381
|
-
}, { index });
|
|
1382
|
-
},
|
|
1383
|
-
|
|
1384
|
-
insertRow: (newRow: T, index?: number) => {
|
|
1385
|
-
applyDataMutation('insertRow', (rowsToMutate) => {
|
|
1386
|
-
const nextData = [...rowsToMutate];
|
|
1387
|
-
nextData.splice(clampInsertIndex(nextData, index), 0, newRow);
|
|
1388
|
-
return nextData;
|
|
1389
|
-
}, { index });
|
|
1390
|
-
},
|
|
1391
|
-
|
|
1392
|
-
deleteRow: (rowId: string) => {
|
|
1393
|
-
applyDataMutation('deleteRow', (rowsToMutate) => {
|
|
1394
|
-
const rowIndex = getRowIndexById(rowsToMutate, rowId);
|
|
1395
|
-
if (rowIndex === -1) return rowsToMutate;
|
|
1396
|
-
const nextData = [...rowsToMutate];
|
|
1397
|
-
nextData.splice(rowIndex, 1);
|
|
1398
|
-
return nextData;
|
|
1399
|
-
}, { rowId });
|
|
1400
|
-
},
|
|
1401
|
-
|
|
1402
|
-
deleteRowByIndex: (index: number) => {
|
|
1403
|
-
applyDataMutation('deleteRowByIndex', (rowsToMutate) => {
|
|
1404
|
-
if (index < 0 || index >= rowsToMutate.length) return rowsToMutate;
|
|
1405
|
-
const nextData = [...rowsToMutate];
|
|
1406
|
-
nextData.splice(index, 1);
|
|
1407
|
-
return nextData;
|
|
1408
|
-
}, { index });
|
|
1409
|
-
},
|
|
1410
|
-
|
|
1411
|
-
deleteSelectedRows: () => {
|
|
1412
|
-
const currentSelection = table.getSelectionState?.() || selectionState;
|
|
1413
|
-
const selectedIds = new Set((currentSelection.ids || []).map((id) => String(id)));
|
|
1414
|
-
const loadedRowIds = tableData.map((row, index) => String(generateRowId(row, index, idKey)));
|
|
1415
|
-
const deletableRowIds = currentSelection.type === 'exclude'
|
|
1416
|
-
? loadedRowIds.filter((rowId) => !selectedIds.has(rowId))
|
|
1417
|
-
: loadedRowIds.filter((rowId) => selectedIds.has(rowId));
|
|
1418
|
-
|
|
1419
|
-
if (deletableRowIds.length === 0) return;
|
|
1420
|
-
if (
|
|
1421
|
-
currentSelection.type === 'exclude'
|
|
1422
|
-
&& table.getRowCount() > loadedRowIds.length
|
|
1423
|
-
&& logger.isLevelEnabled('info')
|
|
1424
|
-
) {
|
|
1425
|
-
logger.info('deleteSelectedRows in exclude mode removed currently loaded rows only', {
|
|
1426
|
-
removedRows: deletableRowIds.length,
|
|
1427
|
-
totalSelected: table.getSelectedCount?.(),
|
|
1428
|
-
});
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
const deletableRowIdSet = new Set(deletableRowIds);
|
|
1432
|
-
applyDataMutation(
|
|
1433
|
-
'deleteSelectedRows',
|
|
1434
|
-
(rowsToMutate) =>
|
|
1435
|
-
rowsToMutate.filter((row, index) => !deletableRowIdSet.has(String(generateRowId(row, index, idKey)))),
|
|
1436
|
-
{ rowIds: deletableRowIds }
|
|
1437
|
-
);
|
|
1438
|
-
table.deselectAll?.();
|
|
1439
|
-
},
|
|
1440
|
-
|
|
1441
|
-
replaceAllData: (newData: T[]) => {
|
|
1442
|
-
applyDataMutation('replaceAllData', () => [...newData]);
|
|
1443
|
-
},
|
|
1444
|
-
|
|
1445
|
-
updateMultipleRows: (updates: Array<{ rowId: string; data: Partial<T> }>) => {
|
|
1446
|
-
const updateMap = new Map(updates.map((update) => [update.rowId, update.data]));
|
|
1447
|
-
applyDataMutation('updateMultipleRows', (rowsToMutate) =>
|
|
1448
|
-
rowsToMutate.map((row, index) => {
|
|
1449
|
-
const currentRowId = String(generateRowId(row, index, idKey));
|
|
1450
|
-
const updateData = updateMap.get(currentRowId);
|
|
1451
|
-
return updateData ? { ...row, ...updateData } : row;
|
|
1452
|
-
})
|
|
1453
|
-
);
|
|
1454
|
-
},
|
|
1455
|
-
|
|
1456
|
-
insertMultipleRows: (newRows: T[], startIndex?: number) => {
|
|
1457
|
-
applyDataMutation('insertMultipleRows', (rowsToMutate) => {
|
|
1458
|
-
const nextData = [...rowsToMutate];
|
|
1459
|
-
nextData.splice(clampInsertIndex(nextData, startIndex), 0, ...newRows);
|
|
1460
|
-
return nextData;
|
|
1461
|
-
}, { index: startIndex });
|
|
1462
|
-
},
|
|
1463
|
-
|
|
1464
|
-
deleteMultipleRows: (rowIds: string[]) => {
|
|
1465
|
-
const idsToDelete = new Set(rowIds);
|
|
1466
|
-
applyDataMutation(
|
|
1467
|
-
'deleteMultipleRows',
|
|
1468
|
-
(rowsToMutate) =>
|
|
1469
|
-
rowsToMutate.filter((row, index) => !idsToDelete.has(String(generateRowId(row, index, idKey)))),
|
|
1470
|
-
{ rowIds }
|
|
1471
|
-
);
|
|
1472
|
-
},
|
|
1473
|
-
|
|
1474
|
-
updateField: (rowId: string, fieldName: keyof T, value: any) => {
|
|
1475
|
-
applyDataMutation('updateField', (rowsToMutate) => {
|
|
1476
|
-
const rowIndex = getRowIndexById(rowsToMutate, rowId);
|
|
1477
|
-
if (rowIndex === -1) return rowsToMutate;
|
|
1478
|
-
const nextData = [...rowsToMutate];
|
|
1479
|
-
nextData[rowIndex] = { ...nextData[rowIndex], [fieldName]: value };
|
|
1480
|
-
return nextData;
|
|
1481
|
-
}, { rowId });
|
|
1482
|
-
},
|
|
1483
|
-
|
|
1484
|
-
updateFieldByIndex: (index: number, fieldName: keyof T, value: any) => {
|
|
1485
|
-
applyDataMutation('updateFieldByIndex', (rowsToMutate) => {
|
|
1486
|
-
if (!rowsToMutate[index]) return rowsToMutate;
|
|
1487
|
-
const nextData = [...rowsToMutate];
|
|
1488
|
-
nextData[index] = { ...nextData[index], [fieldName]: value };
|
|
1489
|
-
return nextData;
|
|
1490
|
-
}, { index });
|
|
1491
|
-
},
|
|
1492
|
-
|
|
1493
|
-
findRows: (predicate: (row: T) => boolean) => tableData.filter(predicate),
|
|
1494
|
-
|
|
1495
|
-
findRowIndex: (predicate: (row: T) => boolean) => tableData.findIndex(predicate),
|
|
1496
|
-
|
|
1497
|
-
getDataCount: () => tableData.length,
|
|
1498
|
-
getFilteredDataCount: () => table.getFilteredRowModel().rows.length,
|
|
1499
|
-
},
|
|
1500
|
-
|
|
1501
|
-
// -------------------------------
|
|
1502
|
-
// Layout Management
|
|
1503
|
-
// -------------------------------
|
|
1504
|
-
layout: {
|
|
1505
|
-
resetLayout: () => {
|
|
1506
|
-
// go through handlers so controlled state updates + emit works
|
|
1507
|
-
applySizing(initialStateConfig.columnSizing || {});
|
|
1508
|
-
applyVisibility(initialStateConfig.columnVisibility || {});
|
|
1509
|
-
applySorting(initialStateConfig.sorting || []);
|
|
1510
|
-
applyGlobalFilter(initialStateConfig.globalFilter ?? "");
|
|
1511
|
-
},
|
|
1512
|
-
resetAll: () => resetAllAndReload(),
|
|
1513
|
-
saveLayout: () => ({
|
|
1514
|
-
columnVisibility: table.getState().columnVisibility,
|
|
1515
|
-
columnSizing: table.getState().columnSizing,
|
|
1516
|
-
columnOrder: table.getState().columnOrder,
|
|
1517
|
-
columnPinning: table.getState().columnPinning,
|
|
1518
|
-
sorting: table.getState().sorting,
|
|
1519
|
-
pagination: table.getState().pagination,
|
|
1520
|
-
globalFilter: table.getState().globalFilter,
|
|
1521
|
-
columnFilter: table.getState().columnFilter,
|
|
1522
|
-
}),
|
|
1523
|
-
restoreLayout: (layout: Partial<TableState>) => {
|
|
1524
|
-
if (layout.columnVisibility) applyVisibility(layout.columnVisibility as any);
|
|
1525
|
-
if (layout.columnSizing) applySizing(layout.columnSizing as any);
|
|
1526
|
-
if (layout.columnOrder) applyColumnOrder(layout.columnOrder as any);
|
|
1527
|
-
if (layout.columnPinning) applyPinning(layout.columnPinning as any);
|
|
1528
|
-
if (layout.sorting) applySorting(layout.sorting as any);
|
|
1529
|
-
if (layout.pagination && enablePagination) applyPagination(layout.pagination as any);
|
|
1530
|
-
if (layout.globalFilter !== undefined) applyGlobalFilter(layout.globalFilter);
|
|
1531
|
-
if (layout.columnFilter) handleColumnFilterStateChange(layout.columnFilter as any);
|
|
1532
|
-
},
|
|
1533
|
-
},
|
|
1534
|
-
|
|
1535
|
-
// -------------------------------
|
|
1536
|
-
// Table State
|
|
1537
|
-
// -------------------------------
|
|
1538
|
-
state: {
|
|
1539
|
-
getTableState: () => table.getState(),
|
|
1540
|
-
getCurrentFilters: () => table.getState().columnFilter,
|
|
1541
|
-
getCurrentSorting: () => table.getState().sorting,
|
|
1542
|
-
getCurrentPagination: () => table.getState().pagination,
|
|
1543
|
-
getCurrentSelection: () => table.getSelectionState?.(),
|
|
1544
|
-
},
|
|
1545
|
-
|
|
1546
|
-
// -------------------------------
|
|
1547
|
-
// Export (unchanged mostly)
|
|
1548
|
-
// -------------------------------
|
|
1549
|
-
export: {
|
|
1550
|
-
exportCSV: async (options: DataTableExportApiOptions = {}) => {
|
|
1551
|
-
const {
|
|
1552
|
-
filename = exportFilename,
|
|
1553
|
-
chunkSize = exportChunkSize,
|
|
1554
|
-
strictTotalCheck = exportStrictTotalCheck,
|
|
1555
|
-
sanitizeCSV = exportSanitizeCSV,
|
|
1556
|
-
} = options;
|
|
1557
|
-
const mode: 'client' | 'server' = dataMode === "server" && !!onServerExport ? 'server' : 'client';
|
|
1558
|
-
|
|
1559
|
-
await runExportWithPolicy({
|
|
1560
|
-
format: 'csv',
|
|
1561
|
-
filename,
|
|
1562
|
-
mode,
|
|
1563
|
-
execute: async (controller) => {
|
|
1564
|
-
const toStateChange = (state: {
|
|
1565
|
-
phase: ExportPhase;
|
|
1566
|
-
processedRows?: number;
|
|
1567
|
-
totalRows?: number;
|
|
1568
|
-
percentage?: number;
|
|
1569
|
-
message?: string;
|
|
1570
|
-
code?: string;
|
|
1571
|
-
}) => {
|
|
1572
|
-
const isFinalPhase = state.phase === 'completed' || state.phase === 'cancelled' || state.phase === 'error';
|
|
1573
|
-
handleExportStateChangeInternal({
|
|
1574
|
-
phase: state.phase,
|
|
1575
|
-
mode,
|
|
1576
|
-
format: 'csv',
|
|
1577
|
-
filename,
|
|
1578
|
-
processedRows: state.processedRows,
|
|
1579
|
-
totalRows: state.totalRows,
|
|
1580
|
-
percentage: state.percentage,
|
|
1581
|
-
message: state.message,
|
|
1582
|
-
code: state.code,
|
|
1583
|
-
startedAt: state.phase === 'starting' ? Date.now() : undefined,
|
|
1584
|
-
endedAt: isFinalPhase ? Date.now() : undefined,
|
|
1585
|
-
queueLength: queuedExportCount,
|
|
1586
|
-
});
|
|
1587
|
-
if (state.phase === 'cancelled') {
|
|
1588
|
-
onExportCancel?.();
|
|
1589
|
-
}
|
|
1590
|
-
};
|
|
1591
|
-
|
|
1592
|
-
if (mode === 'server' && onServerExport) {
|
|
1593
|
-
const currentFilters = {
|
|
1594
|
-
globalFilter: table.getState().globalFilter,
|
|
1595
|
-
columnFilter: table.getState().columnFilter,
|
|
1596
|
-
sorting: table.getState().sorting,
|
|
1597
|
-
pagination: table.getState().pagination,
|
|
1598
|
-
};
|
|
1599
|
-
|
|
1600
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Server export CSV", { currentFilters });
|
|
1601
|
-
|
|
1602
|
-
await exportServerData(table, {
|
|
1603
|
-
format: "csv",
|
|
1604
|
-
filename,
|
|
1605
|
-
fetchData: (filters: any, selection: any, signal?: AbortSignal) =>
|
|
1606
|
-
onServerExport(filters, selection, signal),
|
|
1607
|
-
currentFilters,
|
|
1608
|
-
selection: table.getSelectionState?.(),
|
|
1609
|
-
onProgress: handleExportProgressInternal,
|
|
1610
|
-
onComplete: onExportComplete,
|
|
1611
|
-
onError: onExportError,
|
|
1612
|
-
onStateChange: toStateChange,
|
|
1613
|
-
signal: controller.signal,
|
|
1614
|
-
chunkSize,
|
|
1615
|
-
strictTotalCheck,
|
|
1616
|
-
sanitizeCSV,
|
|
1617
|
-
});
|
|
1618
|
-
return;
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
await exportClientData(table, {
|
|
1622
|
-
format: "csv",
|
|
1623
|
-
filename,
|
|
1624
|
-
onProgress: handleExportProgressInternal,
|
|
1625
|
-
onComplete: onExportComplete,
|
|
1626
|
-
onError: onExportError,
|
|
1627
|
-
onStateChange: toStateChange,
|
|
1628
|
-
signal: controller.signal,
|
|
1629
|
-
sanitizeCSV,
|
|
1630
|
-
});
|
|
1631
|
-
|
|
1632
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Client export CSV", filename);
|
|
1633
|
-
}
|
|
1634
|
-
});
|
|
1635
|
-
},
|
|
1636
|
-
|
|
1637
|
-
exportExcel: async (options: DataTableExportApiOptions = {}) => {
|
|
1638
|
-
const {
|
|
1639
|
-
filename = exportFilename,
|
|
1640
|
-
chunkSize = exportChunkSize,
|
|
1641
|
-
strictTotalCheck = exportStrictTotalCheck,
|
|
1642
|
-
sanitizeCSV = exportSanitizeCSV,
|
|
1643
|
-
} = options;
|
|
1644
|
-
const mode: 'client' | 'server' = dataMode === "server" && !!onServerExport ? 'server' : 'client';
|
|
1645
|
-
|
|
1646
|
-
await runExportWithPolicy({
|
|
1647
|
-
format: 'excel',
|
|
1648
|
-
filename,
|
|
1649
|
-
mode,
|
|
1650
|
-
execute: async (controller) => {
|
|
1651
|
-
const toStateChange = (state: {
|
|
1652
|
-
phase: ExportPhase;
|
|
1653
|
-
processedRows?: number;
|
|
1654
|
-
totalRows?: number;
|
|
1655
|
-
percentage?: number;
|
|
1656
|
-
message?: string;
|
|
1657
|
-
code?: string;
|
|
1658
|
-
}) => {
|
|
1659
|
-
const isFinalPhase = state.phase === 'completed' || state.phase === 'cancelled' || state.phase === 'error';
|
|
1660
|
-
handleExportStateChangeInternal({
|
|
1661
|
-
phase: state.phase,
|
|
1662
|
-
mode,
|
|
1663
|
-
format: 'excel',
|
|
1664
|
-
filename,
|
|
1665
|
-
processedRows: state.processedRows,
|
|
1666
|
-
totalRows: state.totalRows,
|
|
1667
|
-
percentage: state.percentage,
|
|
1668
|
-
message: state.message,
|
|
1669
|
-
code: state.code,
|
|
1670
|
-
startedAt: state.phase === 'starting' ? Date.now() : undefined,
|
|
1671
|
-
endedAt: isFinalPhase ? Date.now() : undefined,
|
|
1672
|
-
queueLength: queuedExportCount,
|
|
1673
|
-
});
|
|
1674
|
-
if (state.phase === 'cancelled') {
|
|
1675
|
-
onExportCancel?.();
|
|
1676
|
-
}
|
|
1677
|
-
};
|
|
1678
|
-
|
|
1679
|
-
if (mode === 'server' && onServerExport) {
|
|
1680
|
-
const currentFilters = {
|
|
1681
|
-
globalFilter: table.getState().globalFilter,
|
|
1682
|
-
columnFilter: table.getState().columnFilter,
|
|
1683
|
-
sorting: table.getState().sorting,
|
|
1684
|
-
pagination: table.getState().pagination,
|
|
1685
|
-
};
|
|
1686
|
-
|
|
1687
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Server export Excel", { currentFilters });
|
|
1688
|
-
|
|
1689
|
-
await exportServerData(table, {
|
|
1690
|
-
format: "excel",
|
|
1691
|
-
filename,
|
|
1692
|
-
fetchData: (filters: any, selection: any, signal?: AbortSignal) =>
|
|
1693
|
-
onServerExport(filters, selection, signal),
|
|
1694
|
-
currentFilters,
|
|
1695
|
-
selection: table.getSelectionState?.(),
|
|
1696
|
-
onProgress: handleExportProgressInternal,
|
|
1697
|
-
onComplete: onExportComplete,
|
|
1698
|
-
onError: onExportError,
|
|
1699
|
-
onStateChange: toStateChange,
|
|
1700
|
-
signal: controller.signal,
|
|
1701
|
-
chunkSize,
|
|
1702
|
-
strictTotalCheck,
|
|
1703
|
-
sanitizeCSV,
|
|
1704
|
-
});
|
|
1705
|
-
return;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
await exportClientData(table, {
|
|
1709
|
-
format: "excel",
|
|
1710
|
-
filename,
|
|
1711
|
-
onProgress: handleExportProgressInternal,
|
|
1712
|
-
onComplete: onExportComplete,
|
|
1713
|
-
onError: onExportError,
|
|
1714
|
-
onStateChange: toStateChange,
|
|
1715
|
-
signal: controller.signal,
|
|
1716
|
-
sanitizeCSV,
|
|
1717
|
-
});
|
|
1718
|
-
|
|
1719
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Client export Excel", filename);
|
|
1720
|
-
}
|
|
1721
|
-
});
|
|
1722
|
-
},
|
|
1723
|
-
|
|
1724
|
-
exportServerData: async (options: {
|
|
1725
|
-
format: 'csv' | 'excel';
|
|
1726
|
-
filename?: string;
|
|
1727
|
-
fetchData?: (
|
|
1728
|
-
filters?: Partial<TableState>,
|
|
1729
|
-
selection?: SelectionState,
|
|
1730
|
-
signal?: AbortSignal
|
|
1731
|
-
) => Promise<any>;
|
|
1732
|
-
chunkSize?: number;
|
|
1733
|
-
strictTotalCheck?: boolean;
|
|
1734
|
-
sanitizeCSV?: boolean;
|
|
1735
|
-
}) => {
|
|
1736
|
-
const {
|
|
1737
|
-
format,
|
|
1738
|
-
filename = exportFilename,
|
|
1739
|
-
fetchData: fetchFn = onServerExport,
|
|
1740
|
-
chunkSize = exportChunkSize,
|
|
1741
|
-
strictTotalCheck = exportStrictTotalCheck,
|
|
1742
|
-
sanitizeCSV = exportSanitizeCSV,
|
|
1743
|
-
} = options;
|
|
1744
|
-
|
|
1745
|
-
if (!fetchFn) {
|
|
1746
|
-
onExportError?.({ message: "No server export function provided", code: "NO_SERVER_EXPORT" });
|
|
1747
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Server export data failed", "No server export function provided");
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
await runExportWithPolicy({
|
|
1752
|
-
format,
|
|
1753
|
-
filename,
|
|
1754
|
-
mode: 'server',
|
|
1755
|
-
execute: async (controller) => {
|
|
1756
|
-
const currentFilters = {
|
|
1757
|
-
globalFilter: table.getState().globalFilter,
|
|
1758
|
-
columnFilter: table.getState().columnFilter,
|
|
1759
|
-
sorting: table.getState().sorting,
|
|
1760
|
-
pagination: table.getState().pagination,
|
|
1761
|
-
};
|
|
1762
|
-
|
|
1763
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Server export data", { currentFilters });
|
|
1764
|
-
|
|
1765
|
-
await exportServerData(table, {
|
|
1766
|
-
format,
|
|
1767
|
-
filename,
|
|
1768
|
-
fetchData: (filters: any, selection: any, signal?: AbortSignal) =>
|
|
1769
|
-
fetchFn(filters, selection, signal),
|
|
1770
|
-
currentFilters,
|
|
1771
|
-
selection: table.getSelectionState?.(),
|
|
1772
|
-
onProgress: handleExportProgressInternal,
|
|
1773
|
-
onComplete: onExportComplete,
|
|
1774
|
-
onError: onExportError,
|
|
1775
|
-
onStateChange: (state) => {
|
|
1776
|
-
const isFinalPhase = state.phase === 'completed' || state.phase === 'cancelled' || state.phase === 'error';
|
|
1777
|
-
handleExportStateChangeInternal({
|
|
1778
|
-
phase: state.phase,
|
|
1779
|
-
mode: 'server',
|
|
1780
|
-
format,
|
|
1781
|
-
filename,
|
|
1782
|
-
processedRows: state.processedRows,
|
|
1783
|
-
totalRows: state.totalRows,
|
|
1784
|
-
percentage: state.percentage,
|
|
1785
|
-
message: state.message,
|
|
1786
|
-
code: state.code,
|
|
1787
|
-
startedAt: state.phase === 'starting' ? Date.now() : undefined,
|
|
1788
|
-
endedAt: isFinalPhase ? Date.now() : undefined,
|
|
1789
|
-
queueLength: queuedExportCount,
|
|
1790
|
-
});
|
|
1791
|
-
if (state.phase === 'cancelled') {
|
|
1792
|
-
onExportCancel?.();
|
|
1793
|
-
}
|
|
1794
|
-
},
|
|
1795
|
-
signal: controller.signal,
|
|
1796
|
-
chunkSize,
|
|
1797
|
-
strictTotalCheck,
|
|
1798
|
-
sanitizeCSV,
|
|
1799
|
-
});
|
|
1800
|
-
}
|
|
1801
|
-
});
|
|
1802
|
-
},
|
|
1803
|
-
|
|
1804
|
-
isExporting: () => isExporting || false,
|
|
1805
|
-
cancelExport: () => {
|
|
1806
|
-
const activeController = exportControllerRef.current;
|
|
1807
|
-
if (!activeController) {
|
|
1808
|
-
return;
|
|
1809
|
-
}
|
|
1810
|
-
activeController.abort();
|
|
1811
|
-
setExportControllerSafely((current) => (current === activeController ? null : current));
|
|
1812
|
-
if (logger.isLevelEnabled("debug")) logger.debug("Export cancelled");
|
|
1813
|
-
},
|
|
1814
|
-
},
|
|
1815
|
-
};
|
|
1816
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1817
|
-
}, [
|
|
1818
|
-
table,
|
|
1819
|
-
enhancedColumns,
|
|
1820
|
-
handleColumnOrderChange,
|
|
1821
|
-
handleColumnPinningChange,
|
|
1822
|
-
handleColumnVisibilityChange,
|
|
1823
|
-
handleColumnSizingChange,
|
|
1824
|
-
handlePaginationChange,
|
|
1825
|
-
handleSortingChange,
|
|
1826
|
-
handleGlobalFilterChange,
|
|
1827
|
-
handleColumnFilterStateChange,
|
|
1828
|
-
initialStateConfig,
|
|
1829
|
-
enablePagination,
|
|
1830
|
-
idKey,
|
|
1831
|
-
triggerRefresh,
|
|
1832
|
-
applyDataMutation,
|
|
1833
|
-
tableData,
|
|
1834
|
-
selectionState,
|
|
1835
|
-
// export
|
|
1836
|
-
exportFilename,
|
|
1837
|
-
exportChunkSize,
|
|
1838
|
-
exportStrictTotalCheck,
|
|
1839
|
-
exportSanitizeCSV,
|
|
1840
|
-
onExportComplete,
|
|
1841
|
-
onExportError,
|
|
1842
|
-
onExportCancel,
|
|
1843
|
-
onServerExport,
|
|
1844
|
-
queuedExportCount,
|
|
1845
|
-
isExporting,
|
|
1846
|
-
dataMode,
|
|
1847
|
-
handleExportProgressInternal,
|
|
1848
|
-
handleExportStateChangeInternal,
|
|
1849
|
-
runExportWithPolicy,
|
|
1850
|
-
setExportControllerSafely,
|
|
1851
|
-
logger,
|
|
1852
|
-
resetAllAndReload,
|
|
1853
|
-
]);
|
|
1854
|
-
|
|
1855
|
-
internalApiRef.current = dataTableApi;
|
|
1856
|
-
|
|
1857
|
-
useImperativeHandle(ref, () => dataTableApi, [dataTableApi]);
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
// -------------------------------
|
|
1861
|
-
// Render table rows with slot support (callback)
|
|
1862
|
-
// -------------------------------
|
|
1863
|
-
const renderTableRows = useCallback(() => {
|
|
1864
|
-
if (tableLoading) {
|
|
1865
|
-
const { component: LoadingRowComponent, props: loadingRowProps } = getSlotComponentWithProps(
|
|
1866
|
-
slots,
|
|
1867
|
-
slotProps || {},
|
|
1868
|
-
'loadingRow',
|
|
1869
|
-
LoadingRows,
|
|
1870
|
-
{}
|
|
1871
|
-
);
|
|
1872
|
-
return (
|
|
1873
|
-
<LoadingRowComponent
|
|
1874
|
-
rowCount={enablePagination ? Math.min(pagination.pageSize, skeletonRows) : skeletonRows}
|
|
1875
|
-
colSpan={table.getAllColumns().length}
|
|
1876
|
-
slots={slots}
|
|
1877
|
-
slotProps={slotProps}
|
|
1878
|
-
{...loadingRowProps}
|
|
1879
|
-
/>
|
|
1880
|
-
);
|
|
1881
|
-
}
|
|
1882
|
-
if (rows.length === 0) {
|
|
1883
|
-
const { component: EmptyRowComponent, props: emptyRowProps } = getSlotComponentWithProps(
|
|
1884
|
-
slots,
|
|
1885
|
-
slotProps || {},
|
|
1886
|
-
'emptyRow',
|
|
1887
|
-
EmptyDataRow,
|
|
1888
|
-
{}
|
|
1889
|
-
);
|
|
1890
|
-
return (
|
|
1891
|
-
<EmptyRowComponent
|
|
1892
|
-
colSpan={table.getAllColumns().length}
|
|
1893
|
-
message={emptyMessage}
|
|
1894
|
-
slots={slots}
|
|
1895
|
-
slotProps={slotProps}
|
|
1896
|
-
{...emptyRowProps}
|
|
1897
|
-
/>
|
|
1898
|
-
);
|
|
1899
|
-
}
|
|
1900
|
-
if (enableVirtualization && !enablePagination && rows.length > 0) {
|
|
1901
|
-
const virtualItems = rowVirtualizer.getVirtualItems();
|
|
1902
|
-
return (
|
|
1903
|
-
<>
|
|
1904
|
-
{virtualItems.length > 0 && (
|
|
1905
|
-
<tr>
|
|
1906
|
-
<td
|
|
1907
|
-
colSpan={table.getAllColumns().length}
|
|
1908
|
-
style={{
|
|
1909
|
-
height: `${virtualItems[0]?.start ?? 0}px`,
|
|
1910
|
-
padding: 0,
|
|
1911
|
-
border: 0,
|
|
1912
|
-
}}
|
|
1913
|
-
/>
|
|
1914
|
-
</tr>
|
|
1915
|
-
)}
|
|
1916
|
-
{virtualItems.map((virtualRow) => {
|
|
1917
|
-
const row = rows[virtualRow.index];
|
|
1918
|
-
if (!row) return null;
|
|
1919
|
-
return (
|
|
1920
|
-
<DataTableRow
|
|
1921
|
-
key={row.id}
|
|
1922
|
-
row={row}
|
|
1923
|
-
enableHover={enableHover}
|
|
1924
|
-
enableStripes={enableStripes}
|
|
1925
|
-
isOdd={virtualRow.index % 2 === 1}
|
|
1926
|
-
renderSubComponent={renderSubComponent}
|
|
1927
|
-
disableStickyHeader={enableStickyHeaderOrFooter}
|
|
1928
|
-
onRowClick={onRowClick}
|
|
1929
|
-
selectOnRowClick={selectOnRowClick}
|
|
1930
|
-
slots={slots}
|
|
1931
|
-
slotProps={slotProps}
|
|
1932
|
-
/>
|
|
1933
|
-
);
|
|
1934
|
-
})}
|
|
1935
|
-
{virtualItems.length > 0 && (
|
|
1936
|
-
<tr>
|
|
1937
|
-
<td
|
|
1938
|
-
colSpan={table.getAllColumns().length}
|
|
1939
|
-
style={{
|
|
1940
|
-
height: `${rowVirtualizer.getTotalSize() -
|
|
1941
|
-
(virtualItems[virtualItems.length - 1]?.end ?? 0)}px`,
|
|
1942
|
-
padding: 0,
|
|
1943
|
-
border: 0,
|
|
1944
|
-
}}
|
|
1945
|
-
/>
|
|
1946
|
-
</tr>
|
|
1947
|
-
)}
|
|
1948
|
-
</>
|
|
1949
|
-
);
|
|
1950
|
-
}
|
|
1951
|
-
return rows.map((row, index) => (
|
|
1952
|
-
<DataTableRow
|
|
1953
|
-
key={row.id}
|
|
1954
|
-
row={row}
|
|
1955
|
-
enableHover={enableHover}
|
|
1956
|
-
enableStripes={enableStripes}
|
|
1957
|
-
isOdd={index % 2 === 1}
|
|
1958
|
-
renderSubComponent={renderSubComponent}
|
|
1959
|
-
disableStickyHeader={enableStickyHeaderOrFooter}
|
|
1960
|
-
onRowClick={onRowClick}
|
|
1961
|
-
selectOnRowClick={selectOnRowClick}
|
|
1962
|
-
slots={slots}
|
|
1963
|
-
slotProps={slotProps}
|
|
1964
|
-
/>
|
|
1965
|
-
));
|
|
1966
|
-
}, [
|
|
1967
|
-
tableLoading,
|
|
1968
|
-
rows,
|
|
1969
|
-
enableVirtualization,
|
|
1970
|
-
enablePagination,
|
|
1971
|
-
pagination.pageSize,
|
|
1972
|
-
skeletonRows,
|
|
1973
|
-
table,
|
|
1974
|
-
slotProps,
|
|
1975
|
-
emptyMessage,
|
|
1976
|
-
rowVirtualizer,
|
|
1977
|
-
enableHover,
|
|
1978
|
-
enableStripes,
|
|
1979
|
-
renderSubComponent,
|
|
1980
|
-
enableStickyHeaderOrFooter,
|
|
1981
|
-
onRowClick,
|
|
1982
|
-
selectOnRowClick,
|
|
1983
|
-
slots,
|
|
1984
|
-
]);
|
|
1985
|
-
|
|
1986
|
-
// -------------------------------
|
|
1987
|
-
// Export cancel callback
|
|
1988
|
-
// -------------------------------
|
|
1989
|
-
const handleCancelExport = useCallback(() => {
|
|
1990
|
-
const activeController = exportControllerRef.current;
|
|
1991
|
-
if (activeController) {
|
|
1992
|
-
activeController.abort();
|
|
1993
|
-
setExportControllerSafely((current) => (current === activeController ? null : current));
|
|
1994
|
-
}
|
|
1995
|
-
}, [setExportControllerSafely]);
|
|
1996
|
-
|
|
1997
|
-
// -------------------------------
|
|
1998
|
-
// Slot components
|
|
1999
|
-
// -------------------------------
|
|
2000
|
-
const { component: RootComponent, props: rootSlotProps } = getSlotComponentWithProps(
|
|
2001
|
-
slots,
|
|
2002
|
-
slotProps || {},
|
|
2003
|
-
'root',
|
|
2004
|
-
Box,
|
|
2005
|
-
{}
|
|
2006
|
-
);
|
|
2007
|
-
const { component: ToolbarComponent, props: toolbarSlotProps } = getSlotComponentWithProps(
|
|
2008
|
-
slots,
|
|
2009
|
-
slotProps || {},
|
|
2010
|
-
'toolbar',
|
|
2011
|
-
DataTableToolbar,
|
|
2012
|
-
{}
|
|
2013
|
-
);
|
|
2014
|
-
const { component: BulkActionsComponent, props: bulkActionsSlotProps } = getSlotComponentWithProps(
|
|
2015
|
-
slots,
|
|
2016
|
-
slotProps || {},
|
|
2017
|
-
'bulkActionsToolbar',
|
|
2018
|
-
BulkActionsToolbar,
|
|
2019
|
-
{}
|
|
2020
|
-
);
|
|
2021
|
-
const { component: TableContainerComponent, props: tableContainerSlotProps } = getSlotComponentWithProps(
|
|
2022
|
-
slots,
|
|
2023
|
-
slotProps || {},
|
|
2024
|
-
'tableContainer',
|
|
2025
|
-
TableContainer,
|
|
2026
|
-
{}
|
|
2027
|
-
);
|
|
2028
|
-
const { component: TableComponent, props: tableComponentSlotProps } = getSlotComponentWithProps(
|
|
2029
|
-
slots,
|
|
2030
|
-
slotProps || {},
|
|
2031
|
-
'table',
|
|
2032
|
-
Table,
|
|
2033
|
-
{}
|
|
2034
|
-
);
|
|
2035
|
-
const { component: BodyComponent, props: bodySlotProps } = getSlotComponentWithProps(
|
|
2036
|
-
slots,
|
|
2037
|
-
slotProps || {},
|
|
2038
|
-
'body',
|
|
2039
|
-
TableBody,
|
|
2040
|
-
{}
|
|
2041
|
-
);
|
|
2042
|
-
const { component: FooterComponent, props: footerSlotProps } = getSlotComponentWithProps(
|
|
2043
|
-
slots,
|
|
2044
|
-
slotProps || {},
|
|
2045
|
-
'footer',
|
|
2046
|
-
Box,
|
|
2047
|
-
{}
|
|
2048
|
-
);
|
|
2049
|
-
const { component: PaginationComponent, props: paginationSlotProps } = getSlotComponentWithProps(
|
|
2050
|
-
slots,
|
|
2051
|
-
slotProps || {},
|
|
2052
|
-
'pagination',
|
|
2053
|
-
DataTablePagination,
|
|
2054
|
-
{}
|
|
2055
|
-
);
|
|
2056
|
-
|
|
2057
|
-
// -------------------------------
|
|
2058
|
-
// Render
|
|
2059
|
-
// -------------------------------
|
|
2060
24
|
return (
|
|
2061
|
-
<DataTableProvider
|
|
2062
|
-
|
|
2063
|
-
apiRef={internalApiRef}
|
|
2064
|
-
dataMode={dataMode}
|
|
2065
|
-
tableSize={tableSize}
|
|
2066
|
-
onTableSizeChange={(size) => {
|
|
2067
|
-
setTableSize(size);
|
|
2068
|
-
}}
|
|
2069
|
-
columnFilter={columnFilter}
|
|
2070
|
-
onChangeColumnFilter={handleColumnFilterStateChange}
|
|
2071
|
-
slots={slots}
|
|
2072
|
-
slotProps={slotProps}
|
|
2073
|
-
isExporting={isExporting}
|
|
2074
|
-
exportController={exportController}
|
|
2075
|
-
exportPhase={exportPhase}
|
|
2076
|
-
exportProgress={exportProgress}
|
|
2077
|
-
onCancelExport={handleCancelExport}
|
|
2078
|
-
exportFilename={exportFilename}
|
|
2079
|
-
onExportProgress={onExportProgress}
|
|
2080
|
-
onExportComplete={onExportComplete}
|
|
2081
|
-
onExportError={onExportError}
|
|
2082
|
-
onServerExport={onServerExport}
|
|
2083
|
-
>
|
|
2084
|
-
<RootComponent
|
|
2085
|
-
{...rootSlotProps}
|
|
2086
|
-
>
|
|
2087
|
-
{/* Toolbar */}
|
|
2088
|
-
{(enableGlobalFilter || extraFilter) ? (
|
|
2089
|
-
<ToolbarComponent
|
|
2090
|
-
extraFilter={extraFilter}
|
|
2091
|
-
enableGlobalFilter={enableGlobalFilter}
|
|
2092
|
-
enableColumnVisibility={enableColumnVisibility}
|
|
2093
|
-
enableColumnFilter={enableColumnFilter}
|
|
2094
|
-
enableExport={enableExport}
|
|
2095
|
-
enableReset={enableReset}
|
|
2096
|
-
enableTableSizeControl={enableTableSizeControl}
|
|
2097
|
-
enableColumnPinning={enableColumnPinning}
|
|
2098
|
-
enableRefresh={enableRefresh}
|
|
2099
|
-
{...toolbarSlotProps}
|
|
2100
|
-
refreshButtonProps={{
|
|
2101
|
-
loading: tableLoading, // disable while fetching
|
|
2102
|
-
showSpinnerWhileLoading: false,
|
|
2103
|
-
onRefresh: () => internalApiRef.current?.data?.refresh?.(true),
|
|
2104
|
-
...toolbarSlotProps.refreshButtonProps,
|
|
2105
|
-
}}
|
|
2106
|
-
|
|
2107
|
-
/>
|
|
2108
|
-
) : null}
|
|
2109
|
-
|
|
2110
|
-
{/* Bulk Actions Toolbar - shown when rows are selected */}
|
|
2111
|
-
{enableBulkActions && enableRowSelection && isSomeRowsSelected ? (
|
|
2112
|
-
<BulkActionsComponent
|
|
2113
|
-
selectionState={selectionState}
|
|
2114
|
-
selectedRowCount={selectedRowCount}
|
|
2115
|
-
bulkActions={bulkActions}
|
|
2116
|
-
sx={{
|
|
2117
|
-
position: 'relative',
|
|
2118
|
-
zIndex: 2,
|
|
2119
|
-
...bulkActionsSlotProps.sx,
|
|
2120
|
-
}}
|
|
2121
|
-
{...bulkActionsSlotProps}
|
|
2122
|
-
/>
|
|
2123
|
-
) : null}
|
|
2124
|
-
|
|
2125
|
-
{/* Table Container */}
|
|
2126
|
-
<TableContainerComponent
|
|
2127
|
-
component={Paper}
|
|
2128
|
-
ref={tableContainerRef}
|
|
2129
|
-
sx={{
|
|
2130
|
-
width: '100%',
|
|
2131
|
-
overflowX: 'auto',
|
|
2132
|
-
...(enableStickyHeaderOrFooter && {
|
|
2133
|
-
maxHeight: maxHeight,
|
|
2134
|
-
overflowY: 'auto',
|
|
2135
|
-
}),
|
|
2136
|
-
...(enableVirtualization && {
|
|
2137
|
-
maxHeight: maxHeight,
|
|
2138
|
-
overflowY: 'auto',
|
|
2139
|
-
}),
|
|
2140
|
-
...tableContainerSlotProps?.sx,
|
|
2141
|
-
}}
|
|
2142
|
-
{...tableContainerSlotProps}
|
|
2143
|
-
>
|
|
2144
|
-
<TableComponent
|
|
2145
|
-
size={tableSize}
|
|
2146
|
-
stickyHeader={enableStickyHeaderOrFooter}
|
|
2147
|
-
style={{
|
|
2148
|
-
...tableStyle,
|
|
2149
|
-
...tableProps?.style,
|
|
2150
|
-
}}
|
|
2151
|
-
{...mergeSlotProps(tableProps || {}, tableComponentSlotProps)}
|
|
2152
|
-
>
|
|
2153
|
-
{useFixedLayout ? (
|
|
2154
|
-
<colgroup>
|
|
2155
|
-
{visibleLeafColumns.map((column) => (
|
|
2156
|
-
<col
|
|
2157
|
-
key={column.id}
|
|
2158
|
-
style={{
|
|
2159
|
-
width: column.getSize(),
|
|
2160
|
-
minWidth: column.columnDef.minSize,
|
|
2161
|
-
maxWidth: column.columnDef.maxSize,
|
|
2162
|
-
}}
|
|
2163
|
-
/>
|
|
2164
|
-
))}
|
|
2165
|
-
</colgroup>
|
|
2166
|
-
) : null}
|
|
2167
|
-
{/* Table Headers */}
|
|
2168
|
-
<TableHeader
|
|
2169
|
-
draggable={enableColumnDragging}
|
|
2170
|
-
enableColumnResizing={enableColumnResizing}
|
|
2171
|
-
enableStickyHeader={enableStickyHeaderOrFooter}
|
|
2172
|
-
onColumnReorder={handleColumnReorder}
|
|
2173
|
-
slots={slots}
|
|
2174
|
-
slotProps={slotProps}
|
|
2175
|
-
/>
|
|
2176
|
-
|
|
2177
|
-
{/* Table Body */}
|
|
2178
|
-
<BodyComponent
|
|
2179
|
-
{...bodySlotProps}
|
|
2180
|
-
>
|
|
2181
|
-
{renderTableRows()}
|
|
2182
|
-
</BodyComponent>
|
|
2183
|
-
</TableComponent>
|
|
2184
|
-
</TableContainerComponent>
|
|
2185
|
-
|
|
2186
|
-
{/* Pagination */}
|
|
2187
|
-
{enablePagination ? (
|
|
2188
|
-
<FooterComponent
|
|
2189
|
-
sx={{
|
|
2190
|
-
...(enableStickyHeaderOrFooter && {
|
|
2191
|
-
position: 'sticky',
|
|
2192
|
-
bottom: 0,
|
|
2193
|
-
backgroundColor: 'background.paper',
|
|
2194
|
-
borderTop: '1px solid',
|
|
2195
|
-
borderColor: 'divider',
|
|
2196
|
-
zIndex: 1,
|
|
2197
|
-
}),
|
|
2198
|
-
...footerSlotProps.sx,
|
|
2199
|
-
}}
|
|
2200
|
-
{...footerSlotProps}
|
|
2201
|
-
>
|
|
2202
|
-
<PaginationComponent
|
|
2203
|
-
footerFilter={footerFilter}
|
|
2204
|
-
pagination={pagination}
|
|
2205
|
-
totalRow={tableTotalRow}
|
|
2206
|
-
{...paginationSlotProps}
|
|
2207
|
-
/>
|
|
2208
|
-
</FooterComponent>
|
|
2209
|
-
) : null}
|
|
2210
|
-
</RootComponent>
|
|
25
|
+
<DataTableProvider {...engine.providerProps}>
|
|
26
|
+
<DataTableView<T> engine={engine} {...props} />
|
|
2211
27
|
</DataTableProvider>
|
|
2212
28
|
);
|
|
2213
|
-
});
|
|
29
|
+
});
|