@ackplus/react-tanstack-data-table 1.1.11 → 1.1.13
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 +143 -11
- package/dist/lib/components/droupdown/menu-dropdown.d.ts.map +1 -1
- package/dist/lib/components/droupdown/menu-dropdown.js +8 -1
- package/dist/lib/components/filters/filter-value-input.js +2 -2
- package/dist/lib/components/pagination/data-table-pagination.d.ts.map +1 -1
- package/dist/lib/components/pagination/data-table-pagination.js +10 -1
- package/dist/lib/components/toolbar/data-table-toolbar.d.ts.map +1 -1
- package/dist/lib/components/toolbar/data-table-toolbar.js +5 -2
- package/dist/lib/components/toolbar/table-export-control.d.ts.map +1 -1
- package/dist/lib/components/toolbar/table-export-control.js +46 -12
- package/dist/lib/components/toolbar/table-refresh-control.d.ts +15 -0
- package/dist/lib/components/toolbar/table-refresh-control.d.ts.map +1 -0
- package/dist/lib/components/toolbar/table-refresh-control.js +61 -0
- package/dist/lib/contexts/data-table-context.d.ts +7 -10
- package/dist/lib/contexts/data-table-context.d.ts.map +1 -1
- package/dist/lib/contexts/data-table-context.js +5 -1
- package/dist/lib/data-table.d.ts.map +1 -1
- package/dist/lib/data-table.js +1110 -946
- package/dist/lib/features/column-filter.feature.js +38 -21
- package/dist/lib/features/selection.feature.d.ts.map +1 -1
- package/dist/lib/features/selection.feature.js +11 -3
- package/dist/lib/types/column.types.d.ts +19 -0
- package/dist/lib/types/column.types.d.ts.map +1 -1
- package/dist/lib/types/data-table-api.d.ts +25 -18
- package/dist/lib/types/data-table-api.d.ts.map +1 -1
- package/dist/lib/types/data-table.types.d.ts +37 -10
- package/dist/lib/types/data-table.types.d.ts.map +1 -1
- package/dist/lib/types/export.types.d.ts +57 -13
- package/dist/lib/types/export.types.d.ts.map +1 -1
- package/dist/lib/types/slots.types.d.ts +12 -1
- package/dist/lib/types/slots.types.d.ts.map +1 -1
- package/dist/lib/types/table.types.d.ts +1 -3
- package/dist/lib/types/table.types.d.ts.map +1 -1
- package/dist/lib/utils/debounced-fetch.utils.d.ts +8 -4
- package/dist/lib/utils/debounced-fetch.utils.d.ts.map +1 -1
- package/dist/lib/utils/debounced-fetch.utils.js +63 -14
- package/dist/lib/utils/export-utils.d.ts +14 -4
- package/dist/lib/utils/export-utils.d.ts.map +1 -1
- package/dist/lib/utils/export-utils.js +362 -66
- package/dist/lib/utils/slot-helpers.d.ts +1 -1
- package/dist/lib/utils/slot-helpers.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/lib/components/droupdown/menu-dropdown.tsx +9 -3
- package/src/lib/components/filters/filter-value-input.tsx +2 -2
- package/src/lib/components/pagination/data-table-pagination.tsx +14 -2
- package/src/lib/components/toolbar/data-table-toolbar.tsx +15 -1
- package/src/lib/components/toolbar/table-export-control.tsx +65 -9
- package/src/lib/components/toolbar/table-refresh-control.tsx +58 -0
- package/src/lib/contexts/data-table-context.tsx +16 -2
- package/src/lib/data-table.tsx +1282 -932
- package/src/lib/features/column-filter.feature.ts +40 -19
- package/src/lib/features/selection.feature.ts +11 -5
- package/src/lib/types/column.types.ts +20 -1
- package/src/lib/types/data-table-api.ts +37 -15
- package/src/lib/types/data-table.types.ts +59 -3
- package/src/lib/types/export.types.ts +79 -10
- package/src/lib/types/slots.types.ts +11 -1
- package/src/lib/types/table.types.ts +1 -3
- package/src/lib/utils/debounced-fetch.utils.ts +90 -18
- package/src/lib/utils/export-utils.ts +496 -69
- package/src/lib/utils/slot-helpers.tsx +1 -1
package/src/lib/data-table.tsx
CHANGED
|
@@ -41,9 +41,16 @@ import { TableHeader } from './components/headers';
|
|
|
41
41
|
import { DataTablePagination } from './components/pagination';
|
|
42
42
|
import { DataTableRow, LoadingRows, EmptyDataRow } from './components/rows';
|
|
43
43
|
import { DataTableToolbar, BulkActionsToolbar } from './components/toolbar';
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
import {
|
|
45
|
+
DataFetchMeta,
|
|
46
|
+
DataMutationAction,
|
|
47
|
+
DataMutationContext,
|
|
48
|
+
DataRefreshContext,
|
|
49
|
+
DataRefreshOptions,
|
|
50
|
+
DataTableProps,
|
|
51
|
+
} from './types/data-table.types';
|
|
52
|
+
import { ColumnFilterState, ExportPhase, ExportProgressPayload, ExportStateChange, TableFiltersForFetch, TableState } from './types';
|
|
53
|
+
import { DataTableApi, DataTableExportApiOptions } from './types/data-table-api';
|
|
47
54
|
import {
|
|
48
55
|
createExpandingColumn,
|
|
49
56
|
createSelectionColumn,
|
|
@@ -92,6 +99,8 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
92
99
|
dataMode = 'client',
|
|
93
100
|
initialLoadData = true,
|
|
94
101
|
onFetchData,
|
|
102
|
+
onRefreshData,
|
|
103
|
+
onDataChange,
|
|
95
104
|
onDataStateChange,
|
|
96
105
|
|
|
97
106
|
// Selection props
|
|
@@ -145,11 +154,16 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
145
154
|
sortingMode = 'client',
|
|
146
155
|
onSortingChange,
|
|
147
156
|
exportFilename = 'export',
|
|
157
|
+
exportConcurrency = 'cancelAndRestart',
|
|
158
|
+
exportChunkSize = 1000,
|
|
159
|
+
exportStrictTotalCheck = false,
|
|
160
|
+
exportSanitizeCSV = true,
|
|
148
161
|
onExportProgress,
|
|
149
162
|
onExportComplete,
|
|
150
163
|
onExportError,
|
|
151
164
|
onServerExport,
|
|
152
165
|
onExportCancel,
|
|
166
|
+
onExportStateChange,
|
|
153
167
|
|
|
154
168
|
// Styling props
|
|
155
169
|
enableHover = true,
|
|
@@ -170,6 +184,7 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
170
184
|
enableTableSizeControl = true,
|
|
171
185
|
enableExport = false,
|
|
172
186
|
enableReset = true,
|
|
187
|
+
enableRefresh = false,
|
|
173
188
|
|
|
174
189
|
// Loading and empty states
|
|
175
190
|
loading = false,
|
|
@@ -251,16 +266,32 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
251
266
|
const [serverData, setServerData] = useState<T[] | null>(null);
|
|
252
267
|
const [serverTotal, setServerTotal] = useState(0);
|
|
253
268
|
const [exportController, setExportController] = useState<AbortController | null>(null);
|
|
269
|
+
const [exportProgress, setExportProgress] = useState<ExportProgressPayload>({});
|
|
270
|
+
const [exportPhase, setExportPhase] = useState<ExportPhase | null>(null);
|
|
271
|
+
const [queuedExportCount, setQueuedExportCount] = useState(0);
|
|
254
272
|
|
|
255
273
|
// -------------------------------
|
|
256
274
|
// Ref hooks (grouped together)
|
|
257
275
|
// -------------------------------
|
|
258
276
|
const tableContainerRef = useRef<HTMLDivElement>(null);
|
|
259
277
|
const internalApiRef = useRef<DataTableApi<T>>(null);
|
|
278
|
+
const exportControllerRef = useRef<AbortController | null>(null);
|
|
279
|
+
const exportQueueRef = useRef<Promise<void>>(Promise.resolve());
|
|
280
|
+
|
|
281
|
+
const isExternallyControlledData = useMemo(
|
|
282
|
+
() => !onFetchData && (!!onDataChange || !!onRefreshData),
|
|
283
|
+
[onFetchData, onDataChange, onRefreshData]
|
|
284
|
+
);
|
|
260
285
|
|
|
261
286
|
const { debouncedFetch, isLoading: fetchLoading } = useDebouncedFetch(onFetchData);
|
|
262
|
-
const tableData = useMemo(() =>
|
|
263
|
-
|
|
287
|
+
const tableData = useMemo(() => {
|
|
288
|
+
if (isExternallyControlledData) return data;
|
|
289
|
+
return serverData !== null ? serverData : data;
|
|
290
|
+
}, [isExternallyControlledData, serverData, data]);
|
|
291
|
+
const tableTotalRow = useMemo(
|
|
292
|
+
() => (isExternallyControlledData ? (totalRow || data.length) : (serverData !== null ? serverTotal : totalRow || data.length)),
|
|
293
|
+
[isExternallyControlledData, serverData, serverTotal, totalRow, data]
|
|
294
|
+
);
|
|
264
295
|
const tableLoading = useMemo(() => onFetchData ? (loading || fetchLoading) : loading, [onFetchData, loading, fetchLoading]);
|
|
265
296
|
|
|
266
297
|
|
|
@@ -308,7 +339,10 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
308
339
|
// -------------------------------
|
|
309
340
|
// Callback hooks (grouped together)
|
|
310
341
|
// -------------------------------
|
|
311
|
-
const fetchData = useCallback(async (
|
|
342
|
+
const fetchData = useCallback(async (
|
|
343
|
+
overrides: Partial<TableState> = {},
|
|
344
|
+
options?: { delay?: number; meta?: DataFetchMeta }
|
|
345
|
+
) => {
|
|
312
346
|
if (!onFetchData) {
|
|
313
347
|
if (logger.isLevelEnabled('debug')) {
|
|
314
348
|
logger.debug('onFetchData not provided, skipping fetch', { overrides, columnFilter, sorting, pagination });
|
|
@@ -316,7 +350,7 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
316
350
|
return;
|
|
317
351
|
}
|
|
318
352
|
|
|
319
|
-
const filters: TableFiltersForFetch = {
|
|
353
|
+
const filters: Partial<TableFiltersForFetch> = {
|
|
320
354
|
globalFilter,
|
|
321
355
|
pagination,
|
|
322
356
|
columnFilter,
|
|
@@ -325,11 +359,19 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
325
359
|
};
|
|
326
360
|
|
|
327
361
|
if (logger.isLevelEnabled('info')) {
|
|
328
|
-
logger.info('Requesting data', {
|
|
362
|
+
logger.info('Requesting data', {
|
|
363
|
+
filters,
|
|
364
|
+
reason: options?.meta?.reason,
|
|
365
|
+
force: options?.meta?.force,
|
|
366
|
+
});
|
|
329
367
|
}
|
|
330
368
|
|
|
331
369
|
try {
|
|
332
|
-
const
|
|
370
|
+
const delay = options?.delay ?? 300; // respects 0
|
|
371
|
+
const result = await debouncedFetch(filters, {
|
|
372
|
+
debounceDelay: delay,
|
|
373
|
+
meta: options?.meta,
|
|
374
|
+
});
|
|
333
375
|
|
|
334
376
|
if (logger.isLevelEnabled('info')) {
|
|
335
377
|
logger.info('Fetch resolved', {
|
|
@@ -338,7 +380,7 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
338
380
|
});
|
|
339
381
|
}
|
|
340
382
|
|
|
341
|
-
if (result
|
|
383
|
+
if (result && Array.isArray(result.data) && result.total !== undefined) {
|
|
342
384
|
setServerData(result.data);
|
|
343
385
|
setServerTotal(result.total);
|
|
344
386
|
} else if (logger.isLevelEnabled('warn')) {
|
|
@@ -360,76 +402,42 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
360
402
|
logger,
|
|
361
403
|
]);
|
|
362
404
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
405
|
+
const normalizeRefreshOptions = useCallback((
|
|
406
|
+
options?: boolean | DataRefreshOptions,
|
|
407
|
+
fallbackReason: string = 'refresh'
|
|
408
|
+
) => {
|
|
409
|
+
if (typeof options === 'boolean') {
|
|
410
|
+
return {
|
|
411
|
+
resetPagination: options,
|
|
412
|
+
force: false,
|
|
413
|
+
reason: fallbackReason,
|
|
414
|
+
};
|
|
370
415
|
}
|
|
371
416
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
pagination,
|
|
377
|
-
columnOrder,
|
|
378
|
-
columnPinning,
|
|
379
|
-
columnVisibility,
|
|
380
|
-
columnSizing,
|
|
381
|
-
...overrides,
|
|
417
|
+
return {
|
|
418
|
+
resetPagination: options?.resetPagination ?? false,
|
|
419
|
+
force: options?.force ?? false,
|
|
420
|
+
reason: options?.reason ?? fallbackReason,
|
|
382
421
|
};
|
|
383
|
-
|
|
384
|
-
if (logger.isLevelEnabled('debug')) {
|
|
385
|
-
logger.debug('Emitting tableStateChange', currentState);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
onDataStateChange?.(currentState);
|
|
389
|
-
}, [
|
|
390
|
-
onDataStateChange,
|
|
391
|
-
globalFilter,
|
|
392
|
-
columnFilter,
|
|
393
|
-
sorting,
|
|
394
|
-
pagination,
|
|
395
|
-
columnOrder,
|
|
396
|
-
columnPinning,
|
|
397
|
-
columnVisibility,
|
|
398
|
-
columnSizing,
|
|
399
|
-
logger,
|
|
400
|
-
]);
|
|
422
|
+
}, []);
|
|
401
423
|
|
|
402
424
|
|
|
403
425
|
const handleSelectionStateChange = useCallback((updaterOrValue) => {
|
|
404
426
|
setSelectionState((prevState) => {
|
|
405
|
-
const
|
|
406
|
-
? updaterOrValue(prevState)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (onSelectionChange) {
|
|
410
|
-
onSelectionChange(newSelectionState);
|
|
411
|
-
}
|
|
412
|
-
if (onDataStateChange) {
|
|
413
|
-
tableStateChange({ selectionState: newSelectionState });
|
|
414
|
-
}
|
|
415
|
-
}, 0);
|
|
416
|
-
return newSelectionState;
|
|
427
|
+
const next =
|
|
428
|
+
typeof updaterOrValue === 'function' ? updaterOrValue(prevState) : updaterOrValue;
|
|
429
|
+
onSelectionChange?.(next);
|
|
430
|
+
return next;
|
|
417
431
|
});
|
|
418
|
-
}, [onSelectionChange
|
|
432
|
+
}, [onSelectionChange]);
|
|
419
433
|
|
|
420
434
|
const handleColumnFilterStateChange = useCallback((filterState: ColumnFilterState) => {
|
|
421
435
|
if (!filterState || typeof filterState !== 'object') return;
|
|
422
436
|
|
|
423
437
|
setColumnFilter(filterState);
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (onDataStateChange) {
|
|
430
|
-
setTimeout(() => tableStateChange({ columnFilter: filterState }), 0);
|
|
431
|
-
}
|
|
432
|
-
}, [onColumnFiltersChange, onDataStateChange, tableStateChange]);
|
|
438
|
+
onColumnFiltersChange?.(filterState);
|
|
439
|
+
return filterState;
|
|
440
|
+
}, [onColumnFiltersChange]);
|
|
433
441
|
|
|
434
442
|
|
|
435
443
|
const resetPageToFirst = useCallback(() => {
|
|
@@ -445,43 +453,20 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
445
453
|
return newPagination;
|
|
446
454
|
}, [pagination, logger, onPaginationChange]);
|
|
447
455
|
|
|
448
|
-
|
|
449
|
-
const handleSortingChange = useCallback((updaterOrValue: any) => {
|
|
450
|
-
let newSorting = typeof updaterOrValue === 'function'
|
|
451
|
-
? updaterOrValue(sorting)
|
|
452
|
-
: updaterOrValue;
|
|
453
|
-
newSorting = newSorting.filter((sort: any) => sort.id);
|
|
454
|
-
setSorting(newSorting);
|
|
455
|
-
onSortingChange?.(newSorting);
|
|
456
456
|
|
|
457
|
-
|
|
458
|
-
logger.debug('Sorting change applied', {
|
|
459
|
-
sorting: newSorting,
|
|
460
|
-
serverMode: isServerMode,
|
|
461
|
-
serverSorting: isServerSorting,
|
|
462
|
-
});
|
|
463
|
-
}
|
|
457
|
+
const handleSortingChange = useCallback((updaterOrValue: any) => {
|
|
464
458
|
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
459
|
+
setSorting((prev) => {
|
|
460
|
+
const next = typeof updaterOrValue === 'function' ? updaterOrValue(prev) : updaterOrValue;
|
|
461
|
+
const cleaned = next.filter((s: any) => s?.id);
|
|
462
|
+
onSortingChange?.(cleaned);
|
|
463
|
+
const nextPagination = resetPageToFirst();
|
|
464
|
+
if (isServerMode || isServerSorting) {
|
|
465
|
+
fetchData({ sorting: cleaned, pagination: nextPagination }, { delay: 0 });
|
|
469
466
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
pagination,
|
|
474
|
-
});
|
|
475
|
-
} else if (onDataStateChange) {
|
|
476
|
-
const pagination = resetPageToFirst();
|
|
477
|
-
setTimeout(() => {
|
|
478
|
-
if (logger.isLevelEnabled('debug')) {
|
|
479
|
-
logger.debug('Sorting change notified client state change', { pagination, sorting: newSorting });
|
|
480
|
-
}
|
|
481
|
-
tableStateChange({ sorting: newSorting, pagination });
|
|
482
|
-
}, 0);
|
|
483
|
-
}
|
|
484
|
-
}, [sorting, onSortingChange, logger, isServerMode, isServerSorting, onDataStateChange, resetPageToFirst, tableStateChange, fetchData]);
|
|
467
|
+
return cleaned;
|
|
468
|
+
});
|
|
469
|
+
}, [onSortingChange, isServerMode, isServerSorting, resetPageToFirst, fetchData]);
|
|
485
470
|
|
|
486
471
|
const handleColumnOrderChange = useCallback((updatedColumnOrder: Updater<ColumnOrderState>) => {
|
|
487
472
|
const newColumnOrder = typeof updatedColumnOrder === 'function'
|
|
@@ -493,144 +478,64 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
493
478
|
}
|
|
494
479
|
}, [onColumnDragEnd, columnOrder]);
|
|
495
480
|
|
|
496
|
-
const handleColumnPinningChange = useCallback(
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
481
|
+
const handleColumnPinningChange = useCallback(
|
|
482
|
+
(updater: Updater<ColumnPinningState>) => {
|
|
483
|
+
setColumnPinning((prev) => {
|
|
484
|
+
const next = typeof updater === "function" ? updater(prev) : updater;
|
|
485
|
+
// keep direct callback here (optional)
|
|
486
|
+
onColumnPinningChange?.(next);
|
|
487
|
+
return next;
|
|
488
|
+
});
|
|
489
|
+
},
|
|
490
|
+
[onColumnPinningChange]
|
|
491
|
+
);
|
|
505
492
|
|
|
506
493
|
// Column visibility change handler - same pattern as column order
|
|
507
494
|
const handleColumnVisibilityChange = useCallback((updater: any) => {
|
|
508
|
-
|
|
509
|
-
? updater(
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
setTimeout(() => {
|
|
515
|
-
onColumnVisibilityChange(newVisibility);
|
|
516
|
-
}, 0);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
if (onDataStateChange) {
|
|
520
|
-
setTimeout(() => {
|
|
521
|
-
tableStateChange({ columnVisibility: newVisibility });
|
|
522
|
-
}, 0);
|
|
523
|
-
}
|
|
524
|
-
}, [onColumnVisibilityChange, onDataStateChange, tableStateChange, columnVisibility]);
|
|
495
|
+
setColumnVisibility((prev) => {
|
|
496
|
+
const next = typeof updater === 'function' ? updater(prev) : updater;
|
|
497
|
+
onColumnVisibilityChange?.(next);
|
|
498
|
+
return next;
|
|
499
|
+
});
|
|
500
|
+
}, [onColumnVisibilityChange]);
|
|
525
501
|
|
|
526
502
|
// Column sizing change handler - same pattern as column order
|
|
527
503
|
const handleColumnSizingChange = useCallback((updater: any) => {
|
|
528
|
-
|
|
529
|
-
? updater(
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
setTimeout(() => {
|
|
535
|
-
onColumnSizingChange(newSizing);
|
|
536
|
-
}, 0);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if (onDataStateChange) {
|
|
540
|
-
setTimeout(() => {
|
|
541
|
-
tableStateChange({ columnSizing: newSizing });
|
|
542
|
-
}, 0);
|
|
543
|
-
}
|
|
544
|
-
}, [onColumnSizingChange, onDataStateChange, tableStateChange, columnSizing]);
|
|
504
|
+
setColumnSizing((prev) => {
|
|
505
|
+
const next = typeof updater === 'function' ? updater(prev) : updater;
|
|
506
|
+
onColumnSizingChange?.(next);
|
|
507
|
+
return next;
|
|
508
|
+
});
|
|
509
|
+
}, [onColumnSizingChange]);
|
|
545
510
|
|
|
546
511
|
const handlePaginationChange = useCallback((updater: any) => {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
next:
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Update pagination state
|
|
557
|
-
setPagination(newPagination);
|
|
558
|
-
onPaginationChange?.(newPagination);
|
|
559
|
-
|
|
560
|
-
if (logger.isLevelEnabled('debug')) {
|
|
561
|
-
logger.debug('Pagination state updated', newPagination);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// Notify state change and fetch data if needed
|
|
565
|
-
if (isServerMode || isServerPagination) {
|
|
566
|
-
setTimeout(() => {
|
|
567
|
-
if (logger.isLevelEnabled('debug')) {
|
|
568
|
-
logger.debug('Notifying server-side pagination change', newPagination);
|
|
569
|
-
}
|
|
570
|
-
tableStateChange({ pagination: newPagination });
|
|
571
|
-
fetchData({ pagination: newPagination });
|
|
572
|
-
}, 0);
|
|
573
|
-
} else if (onDataStateChange) {
|
|
574
|
-
setTimeout(() => {
|
|
575
|
-
if (logger.isLevelEnabled('debug')) {
|
|
576
|
-
logger.debug('Notifying client-side pagination change', newPagination);
|
|
577
|
-
}
|
|
578
|
-
tableStateChange({ pagination: newPagination });
|
|
579
|
-
}, 0);
|
|
580
|
-
}
|
|
581
|
-
}, [
|
|
582
|
-
pagination,
|
|
583
|
-
isServerMode,
|
|
584
|
-
isServerPagination,
|
|
585
|
-
onDataStateChange,
|
|
586
|
-
fetchData,
|
|
587
|
-
tableStateChange,
|
|
588
|
-
logger,
|
|
589
|
-
onPaginationChange,
|
|
590
|
-
]);
|
|
512
|
+
setPagination((prev) => {
|
|
513
|
+
const next = typeof updater === 'function' ? updater(prev) : updater;
|
|
514
|
+
onPaginationChange?.(next);
|
|
515
|
+
if (isServerMode || isServerPagination) {
|
|
516
|
+
fetchData({ pagination: next }, { delay: 0 });
|
|
517
|
+
}
|
|
518
|
+
return next;
|
|
519
|
+
});
|
|
520
|
+
}, [isServerMode, isServerPagination, fetchData, onPaginationChange]);
|
|
591
521
|
|
|
592
522
|
|
|
593
523
|
|
|
594
524
|
const handleGlobalFilterChange = useCallback((updaterOrValue: any) => {
|
|
595
|
-
|
|
596
|
-
? updaterOrValue(
|
|
597
|
-
: updaterOrValue;
|
|
598
|
-
setGlobalFilter(newFilter);
|
|
525
|
+
setGlobalFilter((prev) => {
|
|
526
|
+
const next = typeof updaterOrValue === 'function' ? updaterOrValue(prev) : updaterOrValue;
|
|
599
527
|
|
|
600
|
-
|
|
601
|
-
logger.debug('Global filter change applied', {
|
|
602
|
-
value: newFilter,
|
|
603
|
-
serverMode: isServerMode,
|
|
604
|
-
serverFiltering: isServerFiltering,
|
|
605
|
-
});
|
|
606
|
-
}
|
|
528
|
+
onGlobalFilterChange?.(next);
|
|
607
529
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
tableStateChange({ globalFilter: newFilter, pagination });
|
|
618
|
-
fetchData({ globalFilter: newFilter, pagination });
|
|
619
|
-
}, 0);
|
|
620
|
-
} else if (onDataStateChange) {
|
|
621
|
-
const pagination = resetPageToFirst();
|
|
622
|
-
setTimeout(() => {
|
|
623
|
-
if (logger.isLevelEnabled('debug')) {
|
|
624
|
-
logger.debug('Global filter change notifying client listeners', {
|
|
625
|
-
pagination,
|
|
626
|
-
value: newFilter,
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
tableStateChange({ globalFilter: newFilter, pagination });
|
|
630
|
-
}, 0);
|
|
631
|
-
}
|
|
632
|
-
onGlobalFilterChange?.(newFilter);
|
|
633
|
-
}, [globalFilter, logger, isServerMode, isServerFiltering, onDataStateChange, onGlobalFilterChange, resetPageToFirst, tableStateChange, fetchData]);
|
|
530
|
+
if (isServerMode || isServerFiltering) {
|
|
531
|
+
const nextPagination = { pageIndex: 0, pageSize: pagination.pageSize };
|
|
532
|
+
setPagination(nextPagination);
|
|
533
|
+
fetchData({ globalFilter: next, pagination: nextPagination }, { delay: 0 });
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return next;
|
|
537
|
+
});
|
|
538
|
+
}, [isServerMode, isServerFiltering, onGlobalFilterChange, fetchData, pagination.pageSize]);
|
|
634
539
|
|
|
635
540
|
const onColumnFilterChangeHandler = useCallback((updater: any) => {
|
|
636
541
|
const currentState = columnFilter;
|
|
@@ -648,24 +553,15 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
648
553
|
|
|
649
554
|
const onColumnFilterApplyHandler = useCallback((appliedState: ColumnFilterState) => {
|
|
650
555
|
const pagination = resetPageToFirst();
|
|
651
|
-
|
|
652
556
|
if (isServerFiltering) {
|
|
653
|
-
tableStateChange({
|
|
654
|
-
columnFilter: appliedState,
|
|
655
|
-
pagination,
|
|
656
|
-
});
|
|
657
557
|
fetchData({
|
|
658
558
|
columnFilter: appliedState,
|
|
659
559
|
pagination,
|
|
660
560
|
});
|
|
661
|
-
} else if (onDataStateChange) {
|
|
662
|
-
setTimeout(() => tableStateChange({ columnFilter: appliedState, pagination }), 0);
|
|
663
561
|
}
|
|
664
562
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}, 0);
|
|
668
|
-
}, [resetPageToFirst, isServerFiltering, onDataStateChange, tableStateChange, fetchData, onColumnFiltersChange]);
|
|
563
|
+
onColumnFiltersChange?.(appliedState);
|
|
564
|
+
}, [resetPageToFirst, isServerFiltering, fetchData, onColumnFiltersChange]);
|
|
669
565
|
|
|
670
566
|
// -------------------------------
|
|
671
567
|
// Table creation (after callbacks/memo)
|
|
@@ -795,12 +691,21 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
795
691
|
// -------------------------------
|
|
796
692
|
// Effects (after callbacks)
|
|
797
693
|
// -------------------------------
|
|
694
|
+
useEffect(() => {
|
|
695
|
+
if (!isExternallyControlledData || serverData === null) return;
|
|
696
|
+
setServerData(null);
|
|
697
|
+
setServerTotal(0);
|
|
698
|
+
}, [isExternallyControlledData, serverData]);
|
|
699
|
+
|
|
798
700
|
useEffect(() => {
|
|
799
701
|
if (initialLoadData && onFetchData) {
|
|
800
702
|
if (logger.isLevelEnabled('info')) {
|
|
801
703
|
logger.info('Initial data load triggered', { initialLoadData });
|
|
802
704
|
}
|
|
803
|
-
fetchData({}
|
|
705
|
+
fetchData({}, {
|
|
706
|
+
delay: 0,
|
|
707
|
+
meta: { reason: 'initial' },
|
|
708
|
+
});
|
|
804
709
|
} else if (logger.isLevelEnabled('debug')) {
|
|
805
710
|
logger.debug('Skipping initial data load', {
|
|
806
711
|
initialLoadData,
|
|
@@ -825,501 +730,852 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
825
730
|
}, [enableColumnDragging, enhancedColumns, columnOrder.length]);
|
|
826
731
|
|
|
827
732
|
|
|
828
|
-
const
|
|
829
|
-
table: {
|
|
830
|
-
getTable: () => table,
|
|
831
|
-
},
|
|
832
|
-
// Column Management
|
|
833
|
-
columnVisibility: {
|
|
834
|
-
showColumn: (columnId: string) => {
|
|
835
|
-
table.getColumn(columnId)?.toggleVisibility(true);
|
|
836
|
-
},
|
|
837
|
-
hideColumn: (columnId: string) => {
|
|
838
|
-
table.getColumn(columnId)?.toggleVisibility(false);
|
|
839
|
-
},
|
|
840
|
-
toggleColumn: (columnId: string) => {
|
|
841
|
-
table.getColumn(columnId)?.toggleVisibility();
|
|
842
|
-
},
|
|
843
|
-
showAllColumns: () => {
|
|
844
|
-
table.toggleAllColumnsVisible(true);
|
|
845
|
-
},
|
|
846
|
-
hideAllColumns: () => {
|
|
847
|
-
table.toggleAllColumnsVisible(false);
|
|
848
|
-
},
|
|
849
|
-
resetColumnVisibility: () => {
|
|
850
|
-
const initialVisibility = initialStateConfig.columnVisibility || {};
|
|
851
|
-
table.setColumnVisibility(initialVisibility);
|
|
852
|
-
// Manually trigger handler to ensure callbacks are called
|
|
853
|
-
handleColumnVisibilityChange(initialVisibility);
|
|
854
|
-
},
|
|
855
|
-
},
|
|
733
|
+
const lastSentRef = useRef<string>("");
|
|
856
734
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
setColumnOrder: (columnOrder: ColumnOrderState) => {
|
|
860
|
-
table.setColumnOrder(columnOrder);
|
|
861
|
-
},
|
|
862
|
-
moveColumn: (columnId: string, toIndex: number) => {
|
|
863
|
-
const currentOrder = table.getState().columnOrder || [];
|
|
864
|
-
const currentIndex = currentOrder.indexOf(columnId);
|
|
865
|
-
if (currentIndex === -1) return;
|
|
735
|
+
const emitTableState = useCallback(() => {
|
|
736
|
+
if (!onDataStateChange) return;
|
|
866
737
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
newOrder.splice(toIndex, 0, columnId);
|
|
738
|
+
const live = table.getState();
|
|
739
|
+
const liveColumnFilter = live.columnFilter;
|
|
870
740
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
table.setColumnOrder(initialOrder);
|
|
883
|
-
// Manually trigger handler to ensure callbacks are called
|
|
884
|
-
handleColumnOrderChange(initialOrder);
|
|
885
|
-
},
|
|
886
|
-
},
|
|
741
|
+
// only keep what you persist/store
|
|
742
|
+
const payload = {
|
|
743
|
+
sorting: live.sorting,
|
|
744
|
+
pagination: live.pagination,
|
|
745
|
+
globalFilter: live.globalFilter,
|
|
746
|
+
columnFilter: liveColumnFilter,
|
|
747
|
+
columnVisibility: live.columnVisibility,
|
|
748
|
+
columnSizing: live.columnSizing,
|
|
749
|
+
columnOrder: live.columnOrder,
|
|
750
|
+
columnPinning: live.columnPinning,
|
|
751
|
+
};
|
|
887
752
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
pinColumnLeft: (columnId: string) => {
|
|
891
|
-
const currentPinning = table.getState().columnPinning;
|
|
892
|
-
const newPinning = { ...currentPinning };
|
|
753
|
+
const key = JSON.stringify(payload);
|
|
754
|
+
if (key === lastSentRef.current) return;
|
|
893
755
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
newPinning.left = [...(newPinning.left || []).filter(id => id !== columnId), columnId];
|
|
756
|
+
lastSentRef.current = key;
|
|
757
|
+
onDataStateChange(payload);
|
|
758
|
+
}, [onDataStateChange, table]);
|
|
898
759
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
760
|
+
useEffect(() => {
|
|
761
|
+
emitTableState();
|
|
762
|
+
}, [
|
|
763
|
+
emitTableState,
|
|
764
|
+
sorting,
|
|
765
|
+
pagination,
|
|
766
|
+
globalFilter,
|
|
767
|
+
columnFilter,
|
|
768
|
+
columnVisibility,
|
|
769
|
+
columnSizing,
|
|
770
|
+
columnOrder,
|
|
771
|
+
columnPinning,
|
|
772
|
+
]);
|
|
904
773
|
|
|
905
|
-
// Remove from left if exists
|
|
906
|
-
newPinning.left = (newPinning.left || []).filter(id => id !== columnId);
|
|
907
|
-
// Add to right if not exists - prepend to beginning (appears rightmost to leftmost)
|
|
908
|
-
// First column pinned appears rightmost, second appears to its left, etc.
|
|
909
|
-
newPinning.right = [columnId, ...(newPinning.right || []).filter(id => id !== columnId)];
|
|
910
774
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
const newPinning = {
|
|
916
|
-
left: (currentPinning.left || []).filter(id => id !== columnId),
|
|
917
|
-
right: (currentPinning.right || []).filter(id => id !== columnId),
|
|
918
|
-
};
|
|
775
|
+
const getResetState = useCallback((): Partial<TableState> => {
|
|
776
|
+
const resetSorting = initialStateConfig.sorting || [];
|
|
777
|
+
const resetGlobalFilter = initialStateConfig.globalFilter ?? '';
|
|
778
|
+
const resetColumnFilter = initialStateConfig.columnFilter;
|
|
919
779
|
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
table.setColumnPinning(pinning);
|
|
924
|
-
},
|
|
925
|
-
resetColumnPinning: () => {
|
|
926
|
-
const initialPinning = initialStateConfig.columnPinning || { left: [], right: [] };
|
|
927
|
-
table.setColumnPinning(initialPinning);
|
|
928
|
-
// Manually trigger handler to ensure callbacks are called
|
|
929
|
-
handleColumnPinningChange(initialPinning);
|
|
930
|
-
},
|
|
931
|
-
},
|
|
780
|
+
const resetPagination = enablePagination
|
|
781
|
+
? (initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 })
|
|
782
|
+
: undefined;
|
|
932
783
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
[columnId]: width,
|
|
941
|
-
});
|
|
942
|
-
},
|
|
943
|
-
autoSizeColumn: (columnId: string) => {
|
|
944
|
-
// TanStack doesn't have built-in auto-size, so reset to default
|
|
945
|
-
table.getColumn(columnId)?.resetSize();
|
|
946
|
-
},
|
|
947
|
-
autoSizeAllColumns: () => {
|
|
948
|
-
const initialSizing = initialStateConfig.columnSizing || {};
|
|
949
|
-
table.setColumnSizing(initialSizing);
|
|
950
|
-
// Manually trigger handler to ensure callbacks are called
|
|
951
|
-
handleColumnSizingChange(initialSizing);
|
|
952
|
-
},
|
|
953
|
-
resetColumnSizing: () => {
|
|
954
|
-
const initialSizing = initialStateConfig.columnSizing || {};
|
|
955
|
-
table.setColumnSizing(initialSizing);
|
|
956
|
-
// Manually trigger handler to ensure callbacks are called
|
|
957
|
-
handleColumnSizingChange(initialSizing);
|
|
958
|
-
},
|
|
959
|
-
},
|
|
784
|
+
return {
|
|
785
|
+
sorting: resetSorting,
|
|
786
|
+
globalFilter: resetGlobalFilter,
|
|
787
|
+
columnFilter: resetColumnFilter,
|
|
788
|
+
...(resetPagination ? { pagination: resetPagination } : {}),
|
|
789
|
+
};
|
|
790
|
+
}, [initialStateConfig, enablePagination]);
|
|
960
791
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
},
|
|
969
|
-
setColumnFilters: (filters: ColumnFilterState) => {
|
|
970
|
-
handleColumnFilterStateChange(filters);
|
|
971
|
-
},
|
|
972
|
-
addColumnFilter: (columnId: string, operator: string, value: any) => {
|
|
973
|
-
const newFilter = {
|
|
974
|
-
id: `filter_${Date.now()}`,
|
|
975
|
-
columnId,
|
|
976
|
-
operator,
|
|
977
|
-
value,
|
|
978
|
-
};
|
|
979
|
-
const columnFilter = table.getState().columnFilter;
|
|
980
|
-
|
|
981
|
-
const currentFilters = columnFilter.filters || [];
|
|
982
|
-
const newFilters = [...currentFilters, newFilter];
|
|
983
|
-
handleColumnFilterStateChange({
|
|
984
|
-
filters: newFilters,
|
|
985
|
-
logic: columnFilter.logic,
|
|
986
|
-
pendingFilters: columnFilter.pendingFilters || [],
|
|
987
|
-
pendingLogic: columnFilter.pendingLogic || 'AND',
|
|
988
|
-
});
|
|
989
|
-
if (logger.isLevelEnabled('debug')) {
|
|
990
|
-
logger.debug(`Adding column filter ${columnId} ${operator} ${value}`, newFilters);
|
|
991
|
-
}
|
|
992
|
-
},
|
|
993
|
-
removeColumnFilter: (filterId: string) => {
|
|
994
|
-
const columnFilter = table.getState().columnFilter;
|
|
995
|
-
const currentFilters = columnFilter.filters || [];
|
|
996
|
-
const newFilters = currentFilters.filter((f: any) => f.id !== filterId);
|
|
997
|
-
handleColumnFilterStateChange({
|
|
998
|
-
filters: newFilters,
|
|
999
|
-
logic: columnFilter.logic,
|
|
1000
|
-
pendingFilters: columnFilter.pendingFilters || [],
|
|
1001
|
-
pendingLogic: columnFilter.pendingLogic || 'AND',
|
|
1002
|
-
});
|
|
1003
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1004
|
-
logger.debug(`Removing column filter ${filterId}`, newFilters);
|
|
1005
|
-
}
|
|
1006
|
-
},
|
|
1007
|
-
clearAllFilters: () => {
|
|
1008
|
-
table.setGlobalFilter('');
|
|
1009
|
-
handleColumnFilterStateChange({
|
|
1010
|
-
filters: [],
|
|
1011
|
-
logic: 'AND',
|
|
1012
|
-
pendingFilters: [],
|
|
1013
|
-
pendingLogic: 'AND',
|
|
1014
|
-
});
|
|
1015
|
-
},
|
|
1016
|
-
resetFilters: () => {
|
|
1017
|
-
handleColumnFilterStateChange({
|
|
1018
|
-
filters: [],
|
|
1019
|
-
logic: 'AND',
|
|
1020
|
-
pendingFilters: [],
|
|
1021
|
-
pendingLogic: 'AND',
|
|
1022
|
-
});
|
|
1023
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1024
|
-
logger.debug('Resetting filters');
|
|
1025
|
-
}
|
|
1026
|
-
},
|
|
1027
|
-
},
|
|
792
|
+
const applyDataMutation = useCallback((
|
|
793
|
+
action: DataMutationAction,
|
|
794
|
+
updater: (rows: T[]) => T[],
|
|
795
|
+
details: Partial<Omit<DataMutationContext<T>, 'action' | 'previousData' | 'nextData'>> = {}
|
|
796
|
+
) => {
|
|
797
|
+
const previousData = [...tableData];
|
|
798
|
+
const nextData = updater(previousData);
|
|
1028
799
|
|
|
1029
|
-
|
|
1030
|
-
sorting: {
|
|
1031
|
-
setSorting: (sortingState: SortingState) => {
|
|
1032
|
-
table.setSorting(sortingState);
|
|
1033
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1034
|
-
logger.debug(`Setting sorting`, sortingState);
|
|
1035
|
-
}
|
|
1036
|
-
},
|
|
1037
|
-
sortColumn: (columnId: string, direction: 'asc' | 'desc' | false) => {
|
|
1038
|
-
const column = table.getColumn(columnId);
|
|
1039
|
-
if (!column) return;
|
|
1040
|
-
|
|
1041
|
-
if (direction === false) {
|
|
1042
|
-
column.clearSorting();
|
|
1043
|
-
} else {
|
|
1044
|
-
column.toggleSorting(direction === 'desc');
|
|
1045
|
-
}
|
|
1046
|
-
},
|
|
1047
|
-
clearSorting: () => {
|
|
1048
|
-
table.setSorting([]);
|
|
1049
|
-
// Manually trigger handler to ensure callbacks are called
|
|
1050
|
-
handleSortingChange([]);
|
|
1051
|
-
},
|
|
1052
|
-
resetSorting: () => {
|
|
1053
|
-
const initialSorting = initialStateConfig.sorting || [];
|
|
1054
|
-
table.setSorting(initialSorting);
|
|
1055
|
-
// Manually trigger handler to ensure callbacks are called
|
|
1056
|
-
handleSortingChange(initialSorting);
|
|
1057
|
-
},
|
|
1058
|
-
},
|
|
800
|
+
if (nextData === previousData) return nextData;
|
|
1059
801
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
802
|
+
const nextTotal = Math.max(0, tableTotalRow + (nextData.length - previousData.length));
|
|
803
|
+
|
|
804
|
+
if (!isExternallyControlledData) {
|
|
805
|
+
setServerData(nextData);
|
|
806
|
+
setServerTotal(nextTotal);
|
|
807
|
+
}
|
|
808
|
+
onDataChange?.(nextData, {
|
|
809
|
+
action,
|
|
810
|
+
previousData,
|
|
811
|
+
nextData,
|
|
812
|
+
totalRow: nextTotal,
|
|
813
|
+
...details,
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
if (logger.isLevelEnabled('debug')) {
|
|
817
|
+
logger.debug('Applied data mutation', {
|
|
818
|
+
action,
|
|
819
|
+
previousCount: previousData.length,
|
|
820
|
+
nextCount: nextData.length,
|
|
821
|
+
totalRow: nextTotal,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return nextData;
|
|
826
|
+
}, [isExternallyControlledData, logger, onDataChange, tableData, tableTotalRow]);
|
|
827
|
+
|
|
828
|
+
const buildRefreshContext = useCallback((
|
|
829
|
+
options: ReturnType<typeof normalizeRefreshOptions>,
|
|
830
|
+
paginationOverride?: { pageIndex: number; pageSize: number }
|
|
831
|
+
): DataRefreshContext => {
|
|
832
|
+
const state = table.getState();
|
|
833
|
+
const nextPagination = paginationOverride || state.pagination || pagination;
|
|
834
|
+
|
|
835
|
+
return {
|
|
836
|
+
filters: {
|
|
837
|
+
globalFilter,
|
|
838
|
+
pagination: nextPagination,
|
|
839
|
+
columnFilter,
|
|
840
|
+
sorting,
|
|
841
|
+
},
|
|
842
|
+
state: {
|
|
843
|
+
sorting,
|
|
844
|
+
pagination: nextPagination,
|
|
845
|
+
globalFilter,
|
|
846
|
+
columnFilter,
|
|
847
|
+
columnVisibility: state.columnVisibility,
|
|
848
|
+
columnSizing: state.columnSizing,
|
|
849
|
+
columnOrder: state.columnOrder,
|
|
850
|
+
columnPinning: state.columnPinning,
|
|
851
|
+
},
|
|
852
|
+
options,
|
|
853
|
+
};
|
|
854
|
+
}, [table, pagination, globalFilter, columnFilter, sorting]);
|
|
855
|
+
|
|
856
|
+
const triggerRefresh = useCallback(async (
|
|
857
|
+
options?: boolean | DataRefreshOptions,
|
|
858
|
+
fallbackReason: string = 'refresh'
|
|
859
|
+
) => {
|
|
860
|
+
const normalizedOptions = normalizeRefreshOptions(options, fallbackReason);
|
|
861
|
+
const nextPagination = enablePagination
|
|
862
|
+
? {
|
|
863
|
+
pageIndex: normalizedOptions.resetPagination ? 0 : pagination.pageIndex,
|
|
864
|
+
pageSize: pagination.pageSize,
|
|
865
|
+
}
|
|
866
|
+
: undefined;
|
|
867
|
+
|
|
868
|
+
const shouldUpdatePagination = !!nextPagination
|
|
869
|
+
&& (nextPagination.pageIndex !== pagination.pageIndex || nextPagination.pageSize !== pagination.pageSize);
|
|
870
|
+
|
|
871
|
+
if (nextPagination && shouldUpdatePagination) {
|
|
872
|
+
setPagination(nextPagination);
|
|
873
|
+
onPaginationChange?.(nextPagination);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const refreshContext = buildRefreshContext(normalizedOptions, nextPagination);
|
|
877
|
+
|
|
878
|
+
if (onRefreshData) {
|
|
879
|
+
await onRefreshData(refreshContext);
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (onFetchData) {
|
|
884
|
+
await fetchData(
|
|
885
|
+
nextPagination ? { pagination: nextPagination } : {},
|
|
886
|
+
{
|
|
887
|
+
delay: 0,
|
|
888
|
+
meta: {
|
|
889
|
+
reason: normalizedOptions.reason,
|
|
890
|
+
force: normalizedOptions.force,
|
|
891
|
+
},
|
|
1078
892
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
893
|
+
);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (logger.isLevelEnabled('debug')) {
|
|
898
|
+
logger.debug('Refresh skipped because no refresh handler is configured', refreshContext);
|
|
899
|
+
}
|
|
900
|
+
}, [
|
|
901
|
+
normalizeRefreshOptions,
|
|
902
|
+
enablePagination,
|
|
903
|
+
pagination,
|
|
904
|
+
onPaginationChange,
|
|
905
|
+
buildRefreshContext,
|
|
906
|
+
onRefreshData,
|
|
907
|
+
onFetchData,
|
|
908
|
+
fetchData,
|
|
909
|
+
logger,
|
|
910
|
+
]);
|
|
911
|
+
|
|
912
|
+
const resetAllAndReload = useCallback(() => {
|
|
913
|
+
const resetState = getResetState();
|
|
914
|
+
|
|
915
|
+
setSorting(resetState.sorting || []);
|
|
916
|
+
setGlobalFilter(resetState.globalFilter ?? '');
|
|
917
|
+
setColumnFilter(resetState.columnFilter as any);
|
|
918
|
+
|
|
919
|
+
if (resetState.pagination) {
|
|
920
|
+
setPagination(resetState.pagination);
|
|
921
|
+
onPaginationChange?.(resetState.pagination);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
setSelectionState(initialSelectionState);
|
|
925
|
+
setExpanded({});
|
|
926
|
+
|
|
927
|
+
// layout state
|
|
928
|
+
setColumnVisibility(initialStateConfig.columnVisibility || {});
|
|
929
|
+
setColumnSizing(initialStateConfig.columnSizing || {});
|
|
930
|
+
setColumnOrder(initialStateConfig.columnOrder || []);
|
|
931
|
+
setColumnPinning(initialStateConfig.columnPinning || { left: [], right: [] });
|
|
932
|
+
|
|
933
|
+
const resetOptions = normalizeRefreshOptions({
|
|
934
|
+
resetPagination: true,
|
|
935
|
+
force: true,
|
|
936
|
+
reason: 'reset',
|
|
937
|
+
}, 'reset');
|
|
938
|
+
|
|
939
|
+
const refreshContext = buildRefreshContext(resetOptions, resetState.pagination);
|
|
940
|
+
|
|
941
|
+
if (onRefreshData) {
|
|
942
|
+
void onRefreshData(refreshContext);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (onFetchData) {
|
|
947
|
+
void fetchData(resetState, {
|
|
948
|
+
delay: 0,
|
|
949
|
+
meta: {
|
|
950
|
+
reason: resetOptions.reason,
|
|
951
|
+
force: resetOptions.force,
|
|
952
|
+
},
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
}, [
|
|
956
|
+
getResetState,
|
|
957
|
+
initialSelectionState,
|
|
958
|
+
initialStateConfig,
|
|
959
|
+
onPaginationChange,
|
|
960
|
+
normalizeRefreshOptions,
|
|
961
|
+
buildRefreshContext,
|
|
962
|
+
onRefreshData,
|
|
963
|
+
onFetchData,
|
|
964
|
+
fetchData,
|
|
965
|
+
]);
|
|
966
|
+
|
|
967
|
+
const setExportControllerSafely = useCallback((
|
|
968
|
+
value: AbortController | null | ((current: AbortController | null) => AbortController | null)
|
|
969
|
+
) => {
|
|
970
|
+
setExportController((current) => {
|
|
971
|
+
const next = typeof value === 'function' ? (value as any)(current) : value;
|
|
972
|
+
exportControllerRef.current = next;
|
|
973
|
+
return next;
|
|
974
|
+
});
|
|
975
|
+
}, []);
|
|
976
|
+
|
|
977
|
+
const handleExportProgressInternal = useCallback((progress: ExportProgressPayload) => {
|
|
978
|
+
setExportProgress(progress || {});
|
|
979
|
+
onExportProgress?.(progress);
|
|
980
|
+
}, [onExportProgress]);
|
|
981
|
+
|
|
982
|
+
const handleExportStateChangeInternal = useCallback((state: ExportStateChange) => {
|
|
983
|
+
setExportPhase(state.phase);
|
|
984
|
+
if (
|
|
985
|
+
state.processedRows !== undefined
|
|
986
|
+
|| state.totalRows !== undefined
|
|
987
|
+
|| state.percentage !== undefined
|
|
988
|
+
) {
|
|
989
|
+
setExportProgress({
|
|
990
|
+
processedRows: state.processedRows,
|
|
991
|
+
totalRows: state.totalRows,
|
|
992
|
+
percentage: state.percentage,
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
onExportStateChange?.(state);
|
|
996
|
+
}, [onExportStateChange]);
|
|
997
|
+
|
|
998
|
+
const runExportWithPolicy = useCallback(
|
|
999
|
+
async (
|
|
1000
|
+
options: {
|
|
1001
|
+
format: 'csv' | 'excel';
|
|
1002
|
+
filename: string;
|
|
1003
|
+
mode: 'client' | 'server';
|
|
1004
|
+
execute: (controller: AbortController) => Promise<void>;
|
|
1005
|
+
}
|
|
1006
|
+
) => {
|
|
1007
|
+
const { format, filename, mode, execute } = options;
|
|
1008
|
+
|
|
1009
|
+
const startExecution = async () => {
|
|
1010
|
+
const controller = new AbortController();
|
|
1011
|
+
setExportProgress({});
|
|
1012
|
+
setExportControllerSafely(controller);
|
|
1013
|
+
try {
|
|
1014
|
+
await execute(controller);
|
|
1015
|
+
} finally {
|
|
1016
|
+
setExportControllerSafely((current) => (current === controller ? null : current));
|
|
1084
1017
|
}
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
if (exportConcurrency === 'queue') {
|
|
1021
|
+
setQueuedExportCount((prev) => prev + 1);
|
|
1022
|
+
const runQueued = async (): Promise<void> => {
|
|
1023
|
+
setQueuedExportCount((prev) => Math.max(0, prev - 1));
|
|
1024
|
+
await startExecution();
|
|
1025
|
+
};
|
|
1026
|
+
const queuedPromise = exportQueueRef.current
|
|
1027
|
+
.catch(() => undefined)
|
|
1028
|
+
.then(runQueued);
|
|
1029
|
+
exportQueueRef.current = queuedPromise;
|
|
1030
|
+
return queuedPromise;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
const activeController = exportControllerRef.current;
|
|
1034
|
+
if (activeController) {
|
|
1035
|
+
if (exportConcurrency === 'ignoreIfRunning') {
|
|
1036
|
+
handleExportStateChangeInternal({
|
|
1037
|
+
phase: 'error',
|
|
1038
|
+
mode,
|
|
1039
|
+
format,
|
|
1040
|
+
filename,
|
|
1041
|
+
message: 'An export is already running',
|
|
1042
|
+
code: 'EXPORT_IN_PROGRESS',
|
|
1043
|
+
endedAt: Date.now(),
|
|
1044
|
+
});
|
|
1045
|
+
onExportError?.({
|
|
1046
|
+
message: 'An export is already running',
|
|
1047
|
+
code: 'EXPORT_IN_PROGRESS',
|
|
1048
|
+
});
|
|
1049
|
+
return;
|
|
1090
1050
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
if (pageCount > 0) {
|
|
1095
|
-
table.setPageIndex(pageCount - 1);
|
|
1096
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1097
|
-
logger.debug(`Going to last page ${pageCount - 1}`);
|
|
1098
|
-
}
|
|
1051
|
+
|
|
1052
|
+
if (exportConcurrency === 'cancelAndRestart') {
|
|
1053
|
+
activeController.abort();
|
|
1099
1054
|
}
|
|
1100
|
-
}
|
|
1101
|
-
resetPagination: () => {
|
|
1102
|
-
const initialPagination = initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 };
|
|
1103
|
-
table.setPagination(initialPagination);
|
|
1104
|
-
// Manually trigger handler to ensure callbacks are called
|
|
1105
|
-
handlePaginationChange(initialPagination);
|
|
1106
|
-
},
|
|
1107
|
-
},
|
|
1055
|
+
}
|
|
1108
1056
|
|
|
1109
|
-
|
|
1110
|
-
selection: {
|
|
1111
|
-
selectRow: (rowId: string) => table.selectRow?.(rowId),
|
|
1112
|
-
deselectRow: (rowId: string) => table.deselectRow?.(rowId),
|
|
1113
|
-
toggleRowSelection: (rowId: string) => table.toggleRowSelected?.(rowId),
|
|
1114
|
-
selectAll: () => table.selectAll?.(),
|
|
1115
|
-
deselectAll: () => table.deselectAll?.(),
|
|
1116
|
-
toggleSelectAll: () => table.toggleAllRowsSelected?.(),
|
|
1117
|
-
getSelectionState: () => table.getSelectionState?.() || { ids: [], type: 'include' as const },
|
|
1118
|
-
getSelectedRows: () => table.getSelectedRows(),
|
|
1119
|
-
getSelectedCount: () => table.getSelectedCount(),
|
|
1120
|
-
isRowSelected: (rowId) => table.getIsRowSelected(rowId) || false,
|
|
1057
|
+
await startExecution();
|
|
1121
1058
|
},
|
|
1059
|
+
[
|
|
1060
|
+
exportConcurrency,
|
|
1061
|
+
handleExportStateChangeInternal,
|
|
1062
|
+
onExportError,
|
|
1063
|
+
setExportControllerSafely,
|
|
1064
|
+
]
|
|
1065
|
+
);
|
|
1122
1066
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
setPagination(pagination);
|
|
1133
|
-
onDataStateChange?.({ ...allState, pagination });
|
|
1134
|
-
fetchData?.({ pagination });
|
|
1135
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1136
|
-
logger.debug('Refreshing data using Ref', { pagination, allState });
|
|
1137
|
-
}
|
|
1138
|
-
},
|
|
1139
|
-
reload: () => {
|
|
1140
|
-
const allState = table.getState();
|
|
1067
|
+
const dataTableApi = useMemo(() => {
|
|
1068
|
+
// helpers (avoid repeating boilerplate)
|
|
1069
|
+
const buildInitialOrder = () =>
|
|
1070
|
+
enhancedColumns.map((col, index) => {
|
|
1071
|
+
if ((col as any).id) return (col as any).id as string;
|
|
1072
|
+
const anyCol = col as any;
|
|
1073
|
+
if (anyCol.accessorKey && typeof anyCol.accessorKey === "string") return anyCol.accessorKey;
|
|
1074
|
+
return `column_${index}`;
|
|
1075
|
+
});
|
|
1141
1076
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1147
|
-
},
|
|
1148
|
-
// Data CRUD operations
|
|
1149
|
-
getAllData: () => {
|
|
1150
|
-
return table.getRowModel().rows?.map(row => row.original) || [];
|
|
1151
|
-
},
|
|
1152
|
-
getRowData: (rowId: string) => {
|
|
1153
|
-
return table.getRowModel().rows?.find(row => String(row.original[idKey]) === rowId)?.original;
|
|
1154
|
-
},
|
|
1155
|
-
getRowByIndex: (index: number) => {
|
|
1156
|
-
return table.getRowModel().rows?.[index]?.original;
|
|
1157
|
-
},
|
|
1158
|
-
updateRow: (rowId: string, updates: Partial<T>) => {
|
|
1159
|
-
const newData = table.getRowModel().rows?.map(row => String(row.original[idKey]) === rowId
|
|
1160
|
-
? {
|
|
1161
|
-
...row.original,
|
|
1162
|
-
...updates,
|
|
1163
|
-
}
|
|
1164
|
-
: row.original);
|
|
1165
|
-
setServerData?.(newData || []);
|
|
1166
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1167
|
-
logger.debug(`Updating row ${rowId}`, updates);
|
|
1168
|
-
}
|
|
1169
|
-
},
|
|
1170
|
-
updateRowByIndex: (index: number, updates: Partial<T>) => {
|
|
1171
|
-
const newData = table.getRowModel().rows?.map(row => row.original);
|
|
1172
|
-
if (newData?.[index]) {
|
|
1173
|
-
newData[index] = {
|
|
1174
|
-
...newData[index]!,
|
|
1175
|
-
...updates,
|
|
1176
|
-
};
|
|
1177
|
-
setServerData(newData);
|
|
1178
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1179
|
-
logger.debug(`Updating row by index ${index}`, updates);
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
},
|
|
1183
|
-
insertRow: (newRow: T, index?: number) => {
|
|
1184
|
-
const newData = table.getRowModel().rows?.map(row => row.original) || [];
|
|
1185
|
-
if (index !== undefined) {
|
|
1186
|
-
newData.splice(index, 0, newRow);
|
|
1187
|
-
} else {
|
|
1188
|
-
newData.push(newRow);
|
|
1189
|
-
}
|
|
1190
|
-
setServerData(newData || []);
|
|
1191
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1192
|
-
logger.debug(`Inserting row`, newRow);
|
|
1193
|
-
}
|
|
1194
|
-
},
|
|
1195
|
-
deleteRow: (rowId: string) => {
|
|
1196
|
-
const newData = (table.getRowModel().rows || [])?.filter(row => String(row.original[idKey]) !== rowId);
|
|
1197
|
-
setServerData?.(newData?.map(row => row.original) || []);
|
|
1198
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1199
|
-
logger.debug(`Deleting row ${rowId}`);
|
|
1200
|
-
}
|
|
1201
|
-
},
|
|
1202
|
-
deleteRowByIndex: (index: number) => {
|
|
1203
|
-
const newData = (table.getRowModel().rows || [])?.map(row => row.original);
|
|
1204
|
-
newData.splice(index, 1);
|
|
1205
|
-
setServerData(newData);
|
|
1206
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1207
|
-
logger.debug(`Deleting row by index ${index}`);
|
|
1208
|
-
}
|
|
1209
|
-
},
|
|
1210
|
-
deleteSelectedRows: () => {
|
|
1211
|
-
const selectedRows = table.getSelectedRows?.() || [];
|
|
1212
|
-
if (selectedRows.length === 0) return;
|
|
1077
|
+
const applyColumnOrder = (next: ColumnOrderState) => {
|
|
1078
|
+
// handleColumnOrderChange supports both Updater<ColumnOrderState> and array in your impl
|
|
1079
|
+
handleColumnOrderChange(next as any);
|
|
1080
|
+
};
|
|
1213
1081
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1082
|
+
const applyPinning = (next: ColumnPinningState) => {
|
|
1083
|
+
handleColumnPinningChange(next as any);
|
|
1084
|
+
};
|
|
1216
1085
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1086
|
+
const applyVisibility = (next: Record<string, boolean>) => {
|
|
1087
|
+
handleColumnVisibilityChange(next as any);
|
|
1088
|
+
};
|
|
1219
1089
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
},
|
|
1224
|
-
replaceAllData: (newData: T[]) => {
|
|
1225
|
-
setServerData?.(newData);
|
|
1226
|
-
},
|
|
1090
|
+
const applySizing = (next: Record<string, number>) => {
|
|
1091
|
+
handleColumnSizingChange(next as any);
|
|
1092
|
+
};
|
|
1227
1093
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const newData = (table.getRowModel().rows || [])?.map(row => {
|
|
1232
|
-
const rowId = String(row.original[idKey]);
|
|
1233
|
-
const updateData = updateMap.get(rowId);
|
|
1234
|
-
return updateData ? {
|
|
1235
|
-
...row.original,
|
|
1236
|
-
...updateData,
|
|
1237
|
-
} : row.original;
|
|
1238
|
-
});
|
|
1239
|
-
setServerData(newData || []);
|
|
1240
|
-
},
|
|
1241
|
-
insertMultipleRows: (newRows: T[], startIndex?: number) => {
|
|
1242
|
-
const newData = (table.getRowModel().rows || [])?.map(row => row.original);
|
|
1243
|
-
if (startIndex !== undefined) {
|
|
1244
|
-
newData.splice(startIndex, 0, ...newRows);
|
|
1245
|
-
} else {
|
|
1246
|
-
newData.push(...newRows);
|
|
1247
|
-
}
|
|
1248
|
-
setServerData?.(newData);
|
|
1249
|
-
},
|
|
1250
|
-
deleteMultipleRows: (rowIds: string[]) => {
|
|
1251
|
-
const idsToDelete = new Set(rowIds);
|
|
1252
|
-
const newData = (table.getRowModel().rows || [])?.filter(row => !idsToDelete.has(String(row.original[idKey])))?.map(row => row.original);
|
|
1253
|
-
setServerData(newData);
|
|
1254
|
-
},
|
|
1094
|
+
const applyPagination = (next: any) => {
|
|
1095
|
+
handlePaginationChange(next);
|
|
1096
|
+
};
|
|
1255
1097
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1098
|
+
const applySorting = (next: any) => {
|
|
1099
|
+
handleSortingChange(next);
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
const applyGlobalFilter = (next: any) => {
|
|
1103
|
+
handleGlobalFilterChange(next);
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
const getRowIndexById = (rowsToSearch: T[], rowId: string) =>
|
|
1107
|
+
rowsToSearch.findIndex((row, index) => String(generateRowId(row, index, idKey)) === rowId);
|
|
1108
|
+
|
|
1109
|
+
const clampInsertIndex = (rowsToMutate: T[], insertIndex?: number) => {
|
|
1110
|
+
if (insertIndex === undefined) return rowsToMutate.length;
|
|
1111
|
+
return Math.max(0, Math.min(insertIndex, rowsToMutate.length));
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
return {
|
|
1115
|
+
table: {
|
|
1116
|
+
getTable: () => table,
|
|
1117
|
+
},
|
|
1118
|
+
|
|
1119
|
+
// -------------------------------
|
|
1120
|
+
// Column Management
|
|
1121
|
+
// -------------------------------
|
|
1122
|
+
columnVisibility: {
|
|
1123
|
+
showColumn: (columnId: string) => {
|
|
1124
|
+
applyVisibility({ ...table.getState().columnVisibility, [columnId]: true });
|
|
1125
|
+
},
|
|
1126
|
+
hideColumn: (columnId: string) => {
|
|
1127
|
+
applyVisibility({ ...table.getState().columnVisibility, [columnId]: false });
|
|
1128
|
+
},
|
|
1129
|
+
toggleColumn: (columnId: string) => {
|
|
1130
|
+
const curr = table.getState().columnVisibility?.[columnId] ?? true;
|
|
1131
|
+
applyVisibility({ ...table.getState().columnVisibility, [columnId]: !curr });
|
|
1132
|
+
},
|
|
1133
|
+
showAllColumns: () => {
|
|
1134
|
+
// set all known columns true
|
|
1135
|
+
const all: Record<string, boolean> = {};
|
|
1136
|
+
table.getAllLeafColumns().forEach((c) => (all[c.id] = true));
|
|
1137
|
+
applyVisibility(all);
|
|
1138
|
+
},
|
|
1139
|
+
hideAllColumns: () => {
|
|
1140
|
+
const all: Record<string, boolean> = {};
|
|
1141
|
+
table.getAllLeafColumns().forEach((c) => (all[c.id] = false));
|
|
1142
|
+
applyVisibility(all);
|
|
1143
|
+
},
|
|
1144
|
+
resetColumnVisibility: () => {
|
|
1145
|
+
const initialVisibility = initialStateConfig.columnVisibility || {};
|
|
1146
|
+
applyVisibility(initialVisibility);
|
|
1147
|
+
},
|
|
1148
|
+
},
|
|
1149
|
+
|
|
1150
|
+
// -------------------------------
|
|
1151
|
+
// Column Ordering
|
|
1152
|
+
// -------------------------------
|
|
1153
|
+
columnOrdering: {
|
|
1154
|
+
setColumnOrder: (nextOrder: ColumnOrderState) => {
|
|
1155
|
+
applyColumnOrder(nextOrder);
|
|
1156
|
+
},
|
|
1157
|
+
moveColumn: (columnId: string, toIndex: number) => {
|
|
1158
|
+
const currentOrder =
|
|
1159
|
+
(table.getState().columnOrder?.length ? table.getState().columnOrder : buildInitialOrder()) || [];
|
|
1160
|
+
const fromIndex = currentOrder.indexOf(columnId);
|
|
1161
|
+
if (fromIndex === -1) return;
|
|
1162
|
+
|
|
1163
|
+
const next = [...currentOrder];
|
|
1164
|
+
next.splice(fromIndex, 1);
|
|
1165
|
+
next.splice(toIndex, 0, columnId);
|
|
1166
|
+
|
|
1167
|
+
applyColumnOrder(next);
|
|
1168
|
+
},
|
|
1169
|
+
resetColumnOrder: () => {
|
|
1170
|
+
applyColumnOrder(buildInitialOrder());
|
|
1171
|
+
},
|
|
1172
|
+
},
|
|
1173
|
+
|
|
1174
|
+
// -------------------------------
|
|
1175
|
+
// Column Pinning
|
|
1176
|
+
// -------------------------------
|
|
1177
|
+
columnPinning: {
|
|
1178
|
+
pinColumnLeft: (columnId: string) => {
|
|
1179
|
+
const current = table.getState().columnPinning || { left: [], right: [] };
|
|
1180
|
+
const next: ColumnPinningState = {
|
|
1181
|
+
left: [...(current.left || []).filter((id) => id !== columnId), columnId],
|
|
1182
|
+
right: (current.right || []).filter((id) => id !== columnId),
|
|
1183
|
+
};
|
|
1184
|
+
applyPinning(next);
|
|
1185
|
+
},
|
|
1186
|
+
pinColumnRight: (columnId: string) => {
|
|
1187
|
+
const current = table.getState().columnPinning || { left: [], right: [] };
|
|
1188
|
+
const next: ColumnPinningState = {
|
|
1189
|
+
left: (current.left || []).filter((id) => id !== columnId),
|
|
1190
|
+
// keep your "prepend" behavior
|
|
1191
|
+
right: [columnId, ...(current.right || []).filter((id) => id !== columnId)],
|
|
1192
|
+
};
|
|
1193
|
+
applyPinning(next);
|
|
1194
|
+
},
|
|
1195
|
+
unpinColumn: (columnId: string) => {
|
|
1196
|
+
const current = table.getState().columnPinning || { left: [], right: [] };
|
|
1197
|
+
const next: ColumnPinningState = {
|
|
1198
|
+
left: (current.left || []).filter((id) => id !== columnId),
|
|
1199
|
+
right: (current.right || []).filter((id) => id !== columnId),
|
|
1200
|
+
};
|
|
1201
|
+
applyPinning(next);
|
|
1202
|
+
},
|
|
1203
|
+
setPinning: (pinning: ColumnPinningState) => {
|
|
1204
|
+
applyPinning(pinning);
|
|
1205
|
+
},
|
|
1206
|
+
resetColumnPinning: () => {
|
|
1207
|
+
const initialPinning = initialStateConfig.columnPinning || { left: [], right: [] };
|
|
1208
|
+
applyPinning(initialPinning);
|
|
1209
|
+
},
|
|
1210
|
+
},
|
|
1211
|
+
|
|
1212
|
+
// -------------------------------
|
|
1213
|
+
// Column Resizing
|
|
1214
|
+
// -------------------------------
|
|
1215
|
+
columnResizing: {
|
|
1216
|
+
resizeColumn: (columnId: string, width: number) => {
|
|
1217
|
+
const currentSizing = table.getState().columnSizing || {};
|
|
1218
|
+
applySizing({ ...currentSizing, [columnId]: width });
|
|
1219
|
+
},
|
|
1220
|
+
autoSizeColumn: (columnId: string) => {
|
|
1221
|
+
// safe to call tanstack helper; it will feed into onColumnSizingChange if wired,
|
|
1222
|
+
// but since you're controlled, we still prefer to update through handler:
|
|
1223
|
+
const col = table.getColumn(columnId);
|
|
1224
|
+
if (!col) return;
|
|
1225
|
+
|
|
1226
|
+
col.resetSize();
|
|
1227
|
+
// after resetSize, read state and emit via handler so controlled stays synced
|
|
1228
|
+
applySizing({ ...(table.getState().columnSizing || {}) });
|
|
1229
|
+
},
|
|
1230
|
+
autoSizeAllColumns: () => {
|
|
1231
|
+
const initialSizing = initialStateConfig.columnSizing || {};
|
|
1232
|
+
applySizing(initialSizing);
|
|
1233
|
+
},
|
|
1234
|
+
resetColumnSizing: () => {
|
|
1235
|
+
const initialSizing = initialStateConfig.columnSizing || {};
|
|
1236
|
+
applySizing(initialSizing);
|
|
1237
|
+
},
|
|
1238
|
+
},
|
|
1239
|
+
|
|
1240
|
+
// -------------------------------
|
|
1241
|
+
// Filtering
|
|
1242
|
+
// -------------------------------
|
|
1243
|
+
filtering: {
|
|
1244
|
+
setGlobalFilter: (filter: string) => {
|
|
1245
|
+
applyGlobalFilter(filter);
|
|
1246
|
+
},
|
|
1247
|
+
clearGlobalFilter: () => {
|
|
1248
|
+
applyGlobalFilter("");
|
|
1249
|
+
},
|
|
1250
|
+
setColumnFilters: (filters: ColumnFilterState) => {
|
|
1251
|
+
handleColumnFilterStateChange(filters);
|
|
1252
|
+
},
|
|
1253
|
+
addColumnFilter: (columnId: string, operator: string, value: any) => {
|
|
1254
|
+
const newFilter = {
|
|
1255
|
+
id: `filter_${Date.now()}`,
|
|
1256
|
+
columnId,
|
|
1257
|
+
operator,
|
|
1258
|
+
value,
|
|
1272
1259
|
};
|
|
1273
|
-
setServerData?.(newData);
|
|
1274
|
-
}
|
|
1275
|
-
},
|
|
1276
1260
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
},
|
|
1281
|
-
findRowIndex: (predicate: (row: T) => boolean) => {
|
|
1282
|
-
return (table.getRowModel().rows || [])?.findIndex(row => predicate(row.original));
|
|
1283
|
-
},
|
|
1284
|
-
getDataCount: () => {
|
|
1285
|
-
return (table.getRowModel().rows || [])?.length || 0;
|
|
1286
|
-
},
|
|
1287
|
-
getFilteredDataCount: () => {
|
|
1288
|
-
return table.getFilteredRowModel().rows.length;
|
|
1289
|
-
},
|
|
1290
|
-
},
|
|
1261
|
+
const current = table.getState().columnFilter;
|
|
1262
|
+
const currentFilters = current?.filters || [];
|
|
1263
|
+
const nextFilters = [...currentFilters, newFilter];
|
|
1291
1264
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
table.resetGlobalFilter();
|
|
1299
|
-
},
|
|
1300
|
-
resetAll: () => {
|
|
1301
|
-
// Reset everything to initial state
|
|
1302
|
-
table.resetColumnSizing();
|
|
1303
|
-
table.resetColumnVisibility();
|
|
1304
|
-
table.resetSorting();
|
|
1305
|
-
table.resetGlobalFilter();
|
|
1306
|
-
table.resetColumnOrder();
|
|
1307
|
-
table.resetExpanded();
|
|
1308
|
-
handleSelectionStateChange(initialSelectionState);
|
|
1309
|
-
table.resetColumnPinning();
|
|
1310
|
-
|
|
1311
|
-
handleColumnFilterStateChange(initialStateConfig.columnFilter || { filters: [], logic: 'AND', pendingFilters: [], pendingLogic: 'AND' });
|
|
1312
|
-
|
|
1313
|
-
if (enablePagination) {
|
|
1314
|
-
table.setPagination(initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 });
|
|
1315
|
-
}
|
|
1265
|
+
handleColumnFilterStateChange({
|
|
1266
|
+
filters: nextFilters,
|
|
1267
|
+
logic: current?.logic,
|
|
1268
|
+
pendingFilters: current?.pendingFilters || [],
|
|
1269
|
+
pendingLogic: current?.pendingLogic || "AND",
|
|
1270
|
+
});
|
|
1316
1271
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1272
|
+
if (logger.isLevelEnabled("debug")) {
|
|
1273
|
+
logger.debug(`Adding column filter ${columnId} ${operator} ${value}`, nextFilters);
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
removeColumnFilter: (filterId: string) => {
|
|
1277
|
+
const current = table.getState().columnFilter;
|
|
1278
|
+
const currentFilters = current?.filters || [];
|
|
1279
|
+
const nextFilters = currentFilters.filter((f: any) => f.id !== filterId);
|
|
1280
|
+
|
|
1281
|
+
handleColumnFilterStateChange({
|
|
1282
|
+
filters: nextFilters,
|
|
1283
|
+
logic: current?.logic,
|
|
1284
|
+
pendingFilters: current?.pendingFilters || [],
|
|
1285
|
+
pendingLogic: current?.pendingLogic || "AND",
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
if (logger.isLevelEnabled("debug")) {
|
|
1289
|
+
logger.debug(`Removing column filter ${filterId}`, nextFilters);
|
|
1290
|
+
}
|
|
1291
|
+
},
|
|
1292
|
+
clearAllFilters: () => {
|
|
1293
|
+
applyGlobalFilter("");
|
|
1294
|
+
handleColumnFilterStateChange({
|
|
1295
|
+
filters: [],
|
|
1296
|
+
logic: "AND",
|
|
1297
|
+
pendingFilters: [],
|
|
1298
|
+
pendingLogic: "AND",
|
|
1299
|
+
});
|
|
1300
|
+
},
|
|
1301
|
+
resetFilters: () => {
|
|
1302
|
+
handleColumnFilterStateChange({
|
|
1303
|
+
filters: [],
|
|
1304
|
+
logic: "AND",
|
|
1305
|
+
pendingFilters: [],
|
|
1306
|
+
pendingLogic: "AND",
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
if (logger.isLevelEnabled("debug")) {
|
|
1310
|
+
logger.debug("Resetting filters");
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
},
|
|
1314
|
+
|
|
1315
|
+
// -------------------------------
|
|
1316
|
+
// Sorting
|
|
1317
|
+
// -------------------------------
|
|
1318
|
+
sorting: {
|
|
1319
|
+
setSorting: (sortingState: SortingState) => {
|
|
1320
|
+
applySorting(sortingState);
|
|
1321
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Setting sorting", sortingState);
|
|
1322
|
+
},
|
|
1323
|
+
|
|
1324
|
+
// NOTE: toggleSorting is okay, but can become "one behind" in controlled server mode.
|
|
1325
|
+
// So we implement deterministic sorting through handler.
|
|
1326
|
+
sortColumn: (columnId: string, direction: "asc" | "desc" | false) => {
|
|
1327
|
+
const current = table.getState().sorting || [];
|
|
1328
|
+
const filtered = current.filter((s: any) => s.id !== columnId);
|
|
1329
|
+
|
|
1330
|
+
if (direction === false) {
|
|
1331
|
+
applySorting(filtered);
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
applySorting([{ id: columnId, desc: direction === "desc" }, ...filtered]);
|
|
1336
|
+
},
|
|
1337
|
+
|
|
1338
|
+
clearSorting: () => {
|
|
1339
|
+
applySorting([]);
|
|
1340
|
+
},
|
|
1341
|
+
resetSorting: () => {
|
|
1342
|
+
const initialSorting = initialStateConfig.sorting || [];
|
|
1343
|
+
applySorting(initialSorting);
|
|
1344
|
+
},
|
|
1345
|
+
},
|
|
1346
|
+
|
|
1347
|
+
// -------------------------------
|
|
1348
|
+
// Pagination
|
|
1349
|
+
// -------------------------------
|
|
1350
|
+
pagination: {
|
|
1351
|
+
goToPage: (pageIndex: number) => {
|
|
1352
|
+
applyPagination((prev: any) => ({ ...prev, pageIndex }));
|
|
1353
|
+
if (logger.isLevelEnabled("debug")) logger.debug(`Going to page ${pageIndex}`);
|
|
1354
|
+
},
|
|
1355
|
+
nextPage: () => {
|
|
1356
|
+
applyPagination((prev: any) => ({ ...prev, pageIndex: (prev?.pageIndex ?? 0) + 1 }));
|
|
1357
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Next page");
|
|
1358
|
+
},
|
|
1359
|
+
previousPage: () => {
|
|
1360
|
+
applyPagination((prev: any) => ({ ...prev, pageIndex: Math.max(0, (prev?.pageIndex ?? 0) - 1) }));
|
|
1361
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Previous page");
|
|
1362
|
+
},
|
|
1363
|
+
setPageSize: (pageSize: number) => {
|
|
1364
|
+
// usually want pageIndex reset
|
|
1365
|
+
applyPagination(() => ({ pageIndex: 0, pageSize }));
|
|
1366
|
+
if (logger.isLevelEnabled("debug")) logger.debug(`Setting page size to ${pageSize}`);
|
|
1367
|
+
},
|
|
1368
|
+
goToFirstPage: () => {
|
|
1369
|
+
applyPagination((prev: any) => ({ ...prev, pageIndex: 0 }));
|
|
1370
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Going to first page");
|
|
1371
|
+
},
|
|
1372
|
+
goToLastPage: () => {
|
|
1373
|
+
// pageCount can be derived; keep safe fallback
|
|
1374
|
+
const pageCount = table.getPageCount?.() ?? 0;
|
|
1375
|
+
if (pageCount > 0) {
|
|
1376
|
+
applyPagination((prev: any) => ({ ...prev, pageIndex: pageCount - 1 }));
|
|
1377
|
+
if (logger.isLevelEnabled("debug")) logger.debug(`Going to last page ${pageCount - 1}`);
|
|
1378
|
+
}
|
|
1379
|
+
},
|
|
1380
|
+
resetPagination: () => {
|
|
1381
|
+
const initialPagination = initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 };
|
|
1382
|
+
applyPagination(initialPagination);
|
|
1383
|
+
},
|
|
1384
|
+
},
|
|
1385
|
+
|
|
1386
|
+
// -------------------------------
|
|
1387
|
+
// Selection
|
|
1388
|
+
// -------------------------------
|
|
1389
|
+
selection: {
|
|
1390
|
+
selectRow: (rowId: string) => table.selectRow?.(rowId),
|
|
1391
|
+
deselectRow: (rowId: string) => table.deselectRow?.(rowId),
|
|
1392
|
+
toggleRowSelection: (rowId: string) => table.toggleRowSelected?.(rowId),
|
|
1393
|
+
selectAll: () => table.selectAll?.(),
|
|
1394
|
+
deselectAll: () => table.deselectAll?.(),
|
|
1395
|
+
toggleSelectAll: () => table.toggleAllRowsSelected?.(),
|
|
1396
|
+
getSelectionState: () => table.getSelectionState?.() || ({ ids: [], type: "include" } as const),
|
|
1397
|
+
getSelectedRows: () => table.getSelectedRows(),
|
|
1398
|
+
getSelectedCount: () => table.getSelectedCount(),
|
|
1399
|
+
isRowSelected: (rowId: string) => table.getIsRowSelected(rowId) || false,
|
|
1400
|
+
},
|
|
1401
|
+
|
|
1402
|
+
// -------------------------------
|
|
1403
|
+
// Data Management (kept same, but ensure state changes go through handlers)
|
|
1404
|
+
// -------------------------------
|
|
1405
|
+
data: {
|
|
1406
|
+
refresh: (options?: boolean | DataRefreshOptions) => {
|
|
1407
|
+
void triggerRefresh(options, 'refresh');
|
|
1408
|
+
},
|
|
1409
|
+
|
|
1410
|
+
reload: (options: DataRefreshOptions = {}) => {
|
|
1411
|
+
void triggerRefresh(
|
|
1412
|
+
{
|
|
1413
|
+
...options,
|
|
1414
|
+
resetPagination: options.resetPagination ?? false,
|
|
1415
|
+
reason: options.reason ?? 'reload',
|
|
1416
|
+
},
|
|
1417
|
+
'reload'
|
|
1418
|
+
);
|
|
1419
|
+
},
|
|
1420
|
+
|
|
1421
|
+
resetAll: () => resetAllAndReload(),
|
|
1422
|
+
|
|
1423
|
+
getAllData: () => [...tableData],
|
|
1424
|
+
getRowData: (rowId: string) => {
|
|
1425
|
+
const rowIndex = getRowIndexById(tableData, rowId);
|
|
1426
|
+
return rowIndex === -1 ? undefined : tableData[rowIndex];
|
|
1427
|
+
},
|
|
1428
|
+
getRowByIndex: (index: number) => tableData[index],
|
|
1429
|
+
|
|
1430
|
+
updateRow: (rowId: string, updates: Partial<T>) => {
|
|
1431
|
+
applyDataMutation('updateRow', (rowsToMutate) => {
|
|
1432
|
+
const rowIndex = getRowIndexById(rowsToMutate, rowId);
|
|
1433
|
+
if (rowIndex === -1) return rowsToMutate;
|
|
1434
|
+
const nextData = [...rowsToMutate];
|
|
1435
|
+
nextData[rowIndex] = { ...nextData[rowIndex], ...updates };
|
|
1436
|
+
return nextData;
|
|
1437
|
+
}, { rowId });
|
|
1438
|
+
},
|
|
1439
|
+
|
|
1440
|
+
updateRowByIndex: (index: number, updates: Partial<T>) => {
|
|
1441
|
+
applyDataMutation('updateRowByIndex', (rowsToMutate) => {
|
|
1442
|
+
if (!rowsToMutate[index]) return rowsToMutate;
|
|
1443
|
+
const nextData = [...rowsToMutate];
|
|
1444
|
+
nextData[index] = { ...nextData[index], ...updates };
|
|
1445
|
+
return nextData;
|
|
1446
|
+
}, { index });
|
|
1447
|
+
},
|
|
1448
|
+
|
|
1449
|
+
insertRow: (newRow: T, index?: number) => {
|
|
1450
|
+
applyDataMutation('insertRow', (rowsToMutate) => {
|
|
1451
|
+
const nextData = [...rowsToMutate];
|
|
1452
|
+
nextData.splice(clampInsertIndex(nextData, index), 0, newRow);
|
|
1453
|
+
return nextData;
|
|
1454
|
+
}, { index });
|
|
1455
|
+
},
|
|
1456
|
+
|
|
1457
|
+
deleteRow: (rowId: string) => {
|
|
1458
|
+
applyDataMutation('deleteRow', (rowsToMutate) => {
|
|
1459
|
+
const rowIndex = getRowIndexById(rowsToMutate, rowId);
|
|
1460
|
+
if (rowIndex === -1) return rowsToMutate;
|
|
1461
|
+
const nextData = [...rowsToMutate];
|
|
1462
|
+
nextData.splice(rowIndex, 1);
|
|
1463
|
+
return nextData;
|
|
1464
|
+
}, { rowId });
|
|
1465
|
+
},
|
|
1466
|
+
|
|
1467
|
+
deleteRowByIndex: (index: number) => {
|
|
1468
|
+
applyDataMutation('deleteRowByIndex', (rowsToMutate) => {
|
|
1469
|
+
if (index < 0 || index >= rowsToMutate.length) return rowsToMutate;
|
|
1470
|
+
const nextData = [...rowsToMutate];
|
|
1471
|
+
nextData.splice(index, 1);
|
|
1472
|
+
return nextData;
|
|
1473
|
+
}, { index });
|
|
1474
|
+
},
|
|
1475
|
+
|
|
1476
|
+
deleteSelectedRows: () => {
|
|
1477
|
+
const currentSelection = table.getSelectionState?.() || selectionState;
|
|
1478
|
+
const selectedIds = new Set((currentSelection.ids || []).map((id) => String(id)));
|
|
1479
|
+
const loadedRowIds = tableData.map((row, index) => String(generateRowId(row, index, idKey)));
|
|
1480
|
+
const deletableRowIds = currentSelection.type === 'exclude'
|
|
1481
|
+
? loadedRowIds.filter((rowId) => !selectedIds.has(rowId))
|
|
1482
|
+
: loadedRowIds.filter((rowId) => selectedIds.has(rowId));
|
|
1483
|
+
|
|
1484
|
+
if (deletableRowIds.length === 0) return;
|
|
1485
|
+
if (
|
|
1486
|
+
currentSelection.type === 'exclude'
|
|
1487
|
+
&& table.getRowCount() > loadedRowIds.length
|
|
1488
|
+
&& logger.isLevelEnabled('info')
|
|
1489
|
+
) {
|
|
1490
|
+
logger.info('deleteSelectedRows in exclude mode removed currently loaded rows only', {
|
|
1491
|
+
removedRows: deletableRowIds.length,
|
|
1492
|
+
totalSelected: table.getSelectedCount?.(),
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
const deletableRowIdSet = new Set(deletableRowIds);
|
|
1497
|
+
applyDataMutation(
|
|
1498
|
+
'deleteSelectedRows',
|
|
1499
|
+
(rowsToMutate) =>
|
|
1500
|
+
rowsToMutate.filter((row, index) => !deletableRowIdSet.has(String(generateRowId(row, index, idKey)))),
|
|
1501
|
+
{ rowIds: deletableRowIds }
|
|
1502
|
+
);
|
|
1503
|
+
table.deselectAll?.();
|
|
1504
|
+
},
|
|
1505
|
+
|
|
1506
|
+
replaceAllData: (newData: T[]) => {
|
|
1507
|
+
applyDataMutation('replaceAllData', () => [...newData]);
|
|
1508
|
+
},
|
|
1509
|
+
|
|
1510
|
+
updateMultipleRows: (updates: Array<{ rowId: string; data: Partial<T> }>) => {
|
|
1511
|
+
const updateMap = new Map(updates.map((update) => [update.rowId, update.data]));
|
|
1512
|
+
applyDataMutation('updateMultipleRows', (rowsToMutate) =>
|
|
1513
|
+
rowsToMutate.map((row, index) => {
|
|
1514
|
+
const currentRowId = String(generateRowId(row, index, idKey));
|
|
1515
|
+
const updateData = updateMap.get(currentRowId);
|
|
1516
|
+
return updateData ? { ...row, ...updateData } : row;
|
|
1517
|
+
})
|
|
1518
|
+
);
|
|
1519
|
+
},
|
|
1520
|
+
|
|
1521
|
+
insertMultipleRows: (newRows: T[], startIndex?: number) => {
|
|
1522
|
+
applyDataMutation('insertMultipleRows', (rowsToMutate) => {
|
|
1523
|
+
const nextData = [...rowsToMutate];
|
|
1524
|
+
nextData.splice(clampInsertIndex(nextData, startIndex), 0, ...newRows);
|
|
1525
|
+
return nextData;
|
|
1526
|
+
}, { index: startIndex });
|
|
1527
|
+
},
|
|
1528
|
+
|
|
1529
|
+
deleteMultipleRows: (rowIds: string[]) => {
|
|
1530
|
+
const idsToDelete = new Set(rowIds);
|
|
1531
|
+
applyDataMutation(
|
|
1532
|
+
'deleteMultipleRows',
|
|
1533
|
+
(rowsToMutate) =>
|
|
1534
|
+
rowsToMutate.filter((row, index) => !idsToDelete.has(String(generateRowId(row, index, idKey)))),
|
|
1535
|
+
{ rowIds }
|
|
1536
|
+
);
|
|
1537
|
+
},
|
|
1538
|
+
|
|
1539
|
+
updateField: (rowId: string, fieldName: keyof T, value: any) => {
|
|
1540
|
+
applyDataMutation('updateField', (rowsToMutate) => {
|
|
1541
|
+
const rowIndex = getRowIndexById(rowsToMutate, rowId);
|
|
1542
|
+
if (rowIndex === -1) return rowsToMutate;
|
|
1543
|
+
const nextData = [...rowsToMutate];
|
|
1544
|
+
nextData[rowIndex] = { ...nextData[rowIndex], [fieldName]: value };
|
|
1545
|
+
return nextData;
|
|
1546
|
+
}, { rowId });
|
|
1547
|
+
},
|
|
1548
|
+
|
|
1549
|
+
updateFieldByIndex: (index: number, fieldName: keyof T, value: any) => {
|
|
1550
|
+
applyDataMutation('updateFieldByIndex', (rowsToMutate) => {
|
|
1551
|
+
if (!rowsToMutate[index]) return rowsToMutate;
|
|
1552
|
+
const nextData = [...rowsToMutate];
|
|
1553
|
+
nextData[index] = { ...nextData[index], [fieldName]: value };
|
|
1554
|
+
return nextData;
|
|
1555
|
+
}, { index });
|
|
1556
|
+
},
|
|
1557
|
+
|
|
1558
|
+
findRows: (predicate: (row: T) => boolean) => tableData.filter(predicate),
|
|
1559
|
+
|
|
1560
|
+
findRowIndex: (predicate: (row: T) => boolean) => tableData.findIndex(predicate),
|
|
1561
|
+
|
|
1562
|
+
getDataCount: () => tableData.length,
|
|
1563
|
+
getFilteredDataCount: () => table.getFilteredRowModel().rows.length,
|
|
1564
|
+
},
|
|
1565
|
+
|
|
1566
|
+
// -------------------------------
|
|
1567
|
+
// Layout Management
|
|
1568
|
+
// -------------------------------
|
|
1569
|
+
layout: {
|
|
1570
|
+
resetLayout: () => {
|
|
1571
|
+
// go through handlers so controlled state updates + emit works
|
|
1572
|
+
applySizing(initialStateConfig.columnSizing || {});
|
|
1573
|
+
applyVisibility(initialStateConfig.columnVisibility || {});
|
|
1574
|
+
applySorting(initialStateConfig.sorting || []);
|
|
1575
|
+
applyGlobalFilter(initialStateConfig.globalFilter ?? "");
|
|
1576
|
+
},
|
|
1577
|
+
resetAll: () => resetAllAndReload(),
|
|
1578
|
+
saveLayout: () => ({
|
|
1323
1579
|
columnVisibility: table.getState().columnVisibility,
|
|
1324
1580
|
columnSizing: table.getState().columnSizing,
|
|
1325
1581
|
columnOrder: table.getState().columnOrder,
|
|
@@ -1328,251 +1584,337 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
1328
1584
|
pagination: table.getState().pagination,
|
|
1329
1585
|
globalFilter: table.getState().globalFilter,
|
|
1330
1586
|
columnFilter: table.getState().columnFilter,
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1587
|
+
}),
|
|
1588
|
+
restoreLayout: (layout: Partial<TableState>) => {
|
|
1589
|
+
if (layout.columnVisibility) applyVisibility(layout.columnVisibility as any);
|
|
1590
|
+
if (layout.columnSizing) applySizing(layout.columnSizing as any);
|
|
1591
|
+
if (layout.columnOrder) applyColumnOrder(layout.columnOrder as any);
|
|
1592
|
+
if (layout.columnPinning) applyPinning(layout.columnPinning as any);
|
|
1593
|
+
if (layout.sorting) applySorting(layout.sorting as any);
|
|
1594
|
+
if (layout.pagination && enablePagination) applyPagination(layout.pagination as any);
|
|
1595
|
+
if (layout.globalFilter !== undefined) applyGlobalFilter(layout.globalFilter);
|
|
1596
|
+
if (layout.columnFilter) handleColumnFilterStateChange(layout.columnFilter as any);
|
|
1597
|
+
},
|
|
1598
|
+
},
|
|
1599
|
+
|
|
1600
|
+
// -------------------------------
|
|
1601
|
+
// Table State
|
|
1602
|
+
// -------------------------------
|
|
1603
|
+
state: {
|
|
1604
|
+
getTableState: () => table.getState(),
|
|
1605
|
+
getCurrentFilters: () => table.getState().columnFilter,
|
|
1606
|
+
getCurrentSorting: () => table.getState().sorting,
|
|
1607
|
+
getCurrentPagination: () => table.getState().pagination,
|
|
1608
|
+
getCurrentSelection: () => table.getSelectionState?.(),
|
|
1609
|
+
},
|
|
1610
|
+
|
|
1611
|
+
// -------------------------------
|
|
1612
|
+
// Export (unchanged mostly)
|
|
1613
|
+
// -------------------------------
|
|
1614
|
+
export: {
|
|
1615
|
+
exportCSV: async (options: DataTableExportApiOptions = {}) => {
|
|
1616
|
+
const {
|
|
1617
|
+
filename = exportFilename,
|
|
1618
|
+
chunkSize = exportChunkSize,
|
|
1619
|
+
strictTotalCheck = exportStrictTotalCheck,
|
|
1620
|
+
sanitizeCSV = exportSanitizeCSV,
|
|
1621
|
+
} = options;
|
|
1622
|
+
const mode: 'client' | 'server' = dataMode === "server" && !!onServerExport ? 'server' : 'client';
|
|
1623
|
+
|
|
1624
|
+
await runExportWithPolicy({
|
|
1625
|
+
format: 'csv',
|
|
1626
|
+
filename,
|
|
1627
|
+
mode,
|
|
1628
|
+
execute: async (controller) => {
|
|
1629
|
+
const toStateChange = (state: {
|
|
1630
|
+
phase: ExportPhase;
|
|
1631
|
+
processedRows?: number;
|
|
1632
|
+
totalRows?: number;
|
|
1633
|
+
percentage?: number;
|
|
1634
|
+
message?: string;
|
|
1635
|
+
code?: string;
|
|
1636
|
+
}) => {
|
|
1637
|
+
const isFinalPhase = state.phase === 'completed' || state.phase === 'cancelled' || state.phase === 'error';
|
|
1638
|
+
handleExportStateChangeInternal({
|
|
1639
|
+
phase: state.phase,
|
|
1640
|
+
mode,
|
|
1641
|
+
format: 'csv',
|
|
1642
|
+
filename,
|
|
1643
|
+
processedRows: state.processedRows,
|
|
1644
|
+
totalRows: state.totalRows,
|
|
1645
|
+
percentage: state.percentage,
|
|
1646
|
+
message: state.message,
|
|
1647
|
+
code: state.code,
|
|
1648
|
+
startedAt: state.phase === 'starting' ? Date.now() : undefined,
|
|
1649
|
+
endedAt: isFinalPhase ? Date.now() : undefined,
|
|
1650
|
+
queueLength: queuedExportCount,
|
|
1651
|
+
});
|
|
1652
|
+
if (state.phase === 'cancelled') {
|
|
1653
|
+
onExportCancel?.();
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1657
|
+
if (mode === 'server' && onServerExport) {
|
|
1658
|
+
const currentFilters = {
|
|
1659
|
+
globalFilter: table.getState().globalFilter,
|
|
1660
|
+
columnFilter: table.getState().columnFilter,
|
|
1661
|
+
sorting: table.getState().sorting,
|
|
1662
|
+
pagination: table.getState().pagination,
|
|
1663
|
+
};
|
|
1664
|
+
|
|
1665
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Server export CSV", { currentFilters });
|
|
1666
|
+
|
|
1667
|
+
await exportServerData(table, {
|
|
1668
|
+
format: "csv",
|
|
1669
|
+
filename,
|
|
1670
|
+
fetchData: (filters: any, selection: any, signal?: AbortSignal) =>
|
|
1671
|
+
onServerExport(filters, selection, signal),
|
|
1672
|
+
currentFilters,
|
|
1673
|
+
selection: table.getSelectionState?.(),
|
|
1674
|
+
onProgress: handleExportProgressInternal,
|
|
1675
|
+
onComplete: onExportComplete,
|
|
1676
|
+
onError: onExportError,
|
|
1677
|
+
onStateChange: toStateChange,
|
|
1678
|
+
signal: controller.signal,
|
|
1679
|
+
chunkSize,
|
|
1680
|
+
strictTotalCheck,
|
|
1681
|
+
sanitizeCSV,
|
|
1682
|
+
});
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
await exportClientData(table, {
|
|
1687
|
+
format: "csv",
|
|
1688
|
+
filename,
|
|
1689
|
+
onProgress: handleExportProgressInternal,
|
|
1690
|
+
onComplete: onExportComplete,
|
|
1691
|
+
onError: onExportError,
|
|
1692
|
+
onStateChange: toStateChange,
|
|
1693
|
+
signal: controller.signal,
|
|
1694
|
+
sanitizeCSV,
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Client export CSV", filename);
|
|
1423
1698
|
}
|
|
1424
|
-
}
|
|
1425
|
-
} catch (error: any) {
|
|
1426
|
-
onExportError?.({
|
|
1427
|
-
message: error.message || 'Export failed',
|
|
1428
|
-
code: 'EXPORT_ERROR',
|
|
1429
1699
|
});
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1700
|
+
},
|
|
1701
|
+
|
|
1702
|
+
exportExcel: async (options: DataTableExportApiOptions = {}) => {
|
|
1703
|
+
const {
|
|
1704
|
+
filename = exportFilename,
|
|
1705
|
+
chunkSize = exportChunkSize,
|
|
1706
|
+
strictTotalCheck = exportStrictTotalCheck,
|
|
1707
|
+
sanitizeCSV = exportSanitizeCSV,
|
|
1708
|
+
} = options;
|
|
1709
|
+
const mode: 'client' | 'server' = dataMode === "server" && !!onServerExport ? 'server' : 'client';
|
|
1710
|
+
|
|
1711
|
+
await runExportWithPolicy({
|
|
1712
|
+
format: 'excel',
|
|
1713
|
+
filename,
|
|
1714
|
+
mode,
|
|
1715
|
+
execute: async (controller) => {
|
|
1716
|
+
const toStateChange = (state: {
|
|
1717
|
+
phase: ExportPhase;
|
|
1718
|
+
processedRows?: number;
|
|
1719
|
+
totalRows?: number;
|
|
1720
|
+
percentage?: number;
|
|
1721
|
+
message?: string;
|
|
1722
|
+
code?: string;
|
|
1723
|
+
}) => {
|
|
1724
|
+
const isFinalPhase = state.phase === 'completed' || state.phase === 'cancelled' || state.phase === 'error';
|
|
1725
|
+
handleExportStateChangeInternal({
|
|
1726
|
+
phase: state.phase,
|
|
1727
|
+
mode,
|
|
1728
|
+
format: 'excel',
|
|
1729
|
+
filename,
|
|
1730
|
+
processedRows: state.processedRows,
|
|
1731
|
+
totalRows: state.totalRows,
|
|
1732
|
+
percentage: state.percentage,
|
|
1733
|
+
message: state.message,
|
|
1734
|
+
code: state.code,
|
|
1735
|
+
startedAt: state.phase === 'starting' ? Date.now() : undefined,
|
|
1736
|
+
endedAt: isFinalPhase ? Date.now() : undefined,
|
|
1737
|
+
queueLength: queuedExportCount,
|
|
1738
|
+
});
|
|
1739
|
+
if (state.phase === 'cancelled') {
|
|
1740
|
+
onExportCancel?.();
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1744
|
+
if (mode === 'server' && onServerExport) {
|
|
1745
|
+
const currentFilters = {
|
|
1746
|
+
globalFilter: table.getState().globalFilter,
|
|
1747
|
+
columnFilter: table.getState().columnFilter,
|
|
1748
|
+
sorting: table.getState().sorting,
|
|
1749
|
+
pagination: table.getState().pagination,
|
|
1750
|
+
};
|
|
1751
|
+
|
|
1752
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Server export Excel", { currentFilters });
|
|
1753
|
+
|
|
1754
|
+
await exportServerData(table, {
|
|
1755
|
+
format: "excel",
|
|
1756
|
+
filename,
|
|
1757
|
+
fetchData: (filters: any, selection: any, signal?: AbortSignal) =>
|
|
1758
|
+
onServerExport(filters, selection, signal),
|
|
1759
|
+
currentFilters,
|
|
1760
|
+
selection: table.getSelectionState?.(),
|
|
1761
|
+
onProgress: handleExportProgressInternal,
|
|
1762
|
+
onComplete: onExportComplete,
|
|
1763
|
+
onError: onExportError,
|
|
1764
|
+
onStateChange: toStateChange,
|
|
1765
|
+
signal: controller.signal,
|
|
1766
|
+
chunkSize,
|
|
1767
|
+
strictTotalCheck,
|
|
1768
|
+
sanitizeCSV,
|
|
1769
|
+
});
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
await exportClientData(table, {
|
|
1774
|
+
format: "excel",
|
|
1775
|
+
filename,
|
|
1776
|
+
onProgress: handleExportProgressInternal,
|
|
1777
|
+
onComplete: onExportComplete,
|
|
1778
|
+
onError: onExportError,
|
|
1779
|
+
onStateChange: toStateChange,
|
|
1780
|
+
signal: controller.signal,
|
|
1781
|
+
sanitizeCSV,
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1784
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Client export Excel", filename);
|
|
1475
1785
|
}
|
|
1476
|
-
}
|
|
1477
|
-
} catch (error: any) {
|
|
1478
|
-
onExportError?.({
|
|
1479
|
-
message: error.message || 'Export failed',
|
|
1480
|
-
code: 'EXPORT_ERROR',
|
|
1481
|
-
});
|
|
1482
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1483
|
-
logger.debug('Server export Excel failed', error);
|
|
1484
|
-
}
|
|
1485
|
-
} finally {
|
|
1486
|
-
setExportController?.(null);
|
|
1487
|
-
}
|
|
1488
|
-
},
|
|
1489
|
-
exportServerData: async (options) => {
|
|
1490
|
-
const {
|
|
1491
|
-
format,
|
|
1492
|
-
filename = exportFilename,
|
|
1493
|
-
fetchData = onServerExport,
|
|
1494
|
-
} = options;
|
|
1495
|
-
|
|
1496
|
-
if (!fetchData) {
|
|
1497
|
-
onExportError?.({
|
|
1498
|
-
message: 'No server export function provided',
|
|
1499
|
-
code: 'NO_SERVER_EXPORT',
|
|
1500
1786
|
});
|
|
1501
|
-
|
|
1502
|
-
|
|
1787
|
+
},
|
|
1788
|
+
|
|
1789
|
+
exportServerData: async (options: {
|
|
1790
|
+
format: 'csv' | 'excel';
|
|
1791
|
+
filename?: string;
|
|
1792
|
+
fetchData?: (
|
|
1793
|
+
filters?: Partial<TableState>,
|
|
1794
|
+
selection?: SelectionState,
|
|
1795
|
+
signal?: AbortSignal
|
|
1796
|
+
) => Promise<any>;
|
|
1797
|
+
chunkSize?: number;
|
|
1798
|
+
strictTotalCheck?: boolean;
|
|
1799
|
+
sanitizeCSV?: boolean;
|
|
1800
|
+
}) => {
|
|
1801
|
+
const {
|
|
1802
|
+
format,
|
|
1803
|
+
filename = exportFilename,
|
|
1804
|
+
fetchData: fetchFn = onServerExport,
|
|
1805
|
+
chunkSize = exportChunkSize,
|
|
1806
|
+
strictTotalCheck = exportStrictTotalCheck,
|
|
1807
|
+
sanitizeCSV = exportSanitizeCSV,
|
|
1808
|
+
} = options;
|
|
1809
|
+
|
|
1810
|
+
if (!fetchFn) {
|
|
1811
|
+
onExportError?.({ message: "No server export function provided", code: "NO_SERVER_EXPORT" });
|
|
1812
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Server export data failed", "No server export function provided");
|
|
1813
|
+
return;
|
|
1503
1814
|
}
|
|
1504
|
-
return;
|
|
1505
|
-
}
|
|
1506
1815
|
|
|
1507
|
-
|
|
1508
|
-
// Create abort controller for this export
|
|
1509
|
-
const controller = new AbortController();
|
|
1510
|
-
setExportController?.(controller);
|
|
1511
|
-
|
|
1512
|
-
const currentFilters = {
|
|
1513
|
-
globalFilter: table.getState().globalFilter,
|
|
1514
|
-
columnFilter: table.getState().columnFilter,
|
|
1515
|
-
sorting: table.getState().sorting,
|
|
1516
|
-
pagination: table.getState().pagination,
|
|
1517
|
-
};
|
|
1518
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1519
|
-
logger.debug('Server export data', { currentFilters });
|
|
1520
|
-
}
|
|
1521
|
-
await exportServerData(table, {
|
|
1816
|
+
await runExportWithPolicy({
|
|
1522
1817
|
format,
|
|
1523
1818
|
filename,
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1819
|
+
mode: 'server',
|
|
1820
|
+
execute: async (controller) => {
|
|
1821
|
+
const currentFilters = {
|
|
1822
|
+
globalFilter: table.getState().globalFilter,
|
|
1823
|
+
columnFilter: table.getState().columnFilter,
|
|
1824
|
+
sorting: table.getState().sorting,
|
|
1825
|
+
pagination: table.getState().pagination,
|
|
1826
|
+
};
|
|
1827
|
+
|
|
1828
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Server export data", { currentFilters });
|
|
1829
|
+
|
|
1830
|
+
await exportServerData(table, {
|
|
1831
|
+
format,
|
|
1832
|
+
filename,
|
|
1833
|
+
fetchData: (filters: any, selection: any, signal?: AbortSignal) =>
|
|
1834
|
+
fetchFn(filters, selection, signal),
|
|
1835
|
+
currentFilters,
|
|
1836
|
+
selection: table.getSelectionState?.(),
|
|
1837
|
+
onProgress: handleExportProgressInternal,
|
|
1838
|
+
onComplete: onExportComplete,
|
|
1839
|
+
onError: onExportError,
|
|
1840
|
+
onStateChange: (state) => {
|
|
1841
|
+
const isFinalPhase = state.phase === 'completed' || state.phase === 'cancelled' || state.phase === 'error';
|
|
1842
|
+
handleExportStateChangeInternal({
|
|
1843
|
+
phase: state.phase,
|
|
1844
|
+
mode: 'server',
|
|
1845
|
+
format,
|
|
1846
|
+
filename,
|
|
1847
|
+
processedRows: state.processedRows,
|
|
1848
|
+
totalRows: state.totalRows,
|
|
1849
|
+
percentage: state.percentage,
|
|
1850
|
+
message: state.message,
|
|
1851
|
+
code: state.code,
|
|
1852
|
+
startedAt: state.phase === 'starting' ? Date.now() : undefined,
|
|
1853
|
+
endedAt: isFinalPhase ? Date.now() : undefined,
|
|
1854
|
+
queueLength: queuedExportCount,
|
|
1855
|
+
});
|
|
1856
|
+
if (state.phase === 'cancelled') {
|
|
1857
|
+
onExportCancel?.();
|
|
1858
|
+
}
|
|
1859
|
+
},
|
|
1860
|
+
signal: controller.signal,
|
|
1861
|
+
chunkSize,
|
|
1862
|
+
strictTotalCheck,
|
|
1863
|
+
sanitizeCSV,
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1535
1866
|
});
|
|
1536
|
-
|
|
1537
|
-
|
|
1867
|
+
},
|
|
1868
|
+
|
|
1869
|
+
isExporting: () => isExporting || false,
|
|
1870
|
+
cancelExport: () => {
|
|
1871
|
+
const activeController = exportControllerRef.current;
|
|
1872
|
+
if (!activeController) {
|
|
1873
|
+
return;
|
|
1538
1874
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1875
|
+
activeController.abort();
|
|
1876
|
+
setExportControllerSafely((current) => (current === activeController ? null : current));
|
|
1877
|
+
if (logger.isLevelEnabled("debug")) logger.debug("Export cancelled");
|
|
1878
|
+
},
|
|
1542
1879
|
},
|
|
1543
|
-
|
|
1544
|
-
isExporting: () => isExporting || false,
|
|
1545
|
-
cancelExport: () => {
|
|
1546
|
-
exportController?.abort();
|
|
1547
|
-
setExportController?.(null);
|
|
1548
|
-
if (logger.isLevelEnabled('debug')) {
|
|
1549
|
-
logger.debug('Export cancelled');
|
|
1550
|
-
}
|
|
1551
|
-
},
|
|
1552
|
-
},
|
|
1880
|
+
};
|
|
1553
1881
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1554
|
-
}
|
|
1882
|
+
}, [
|
|
1555
1883
|
table,
|
|
1556
1884
|
enhancedColumns,
|
|
1885
|
+
handleColumnOrderChange,
|
|
1886
|
+
handleColumnPinningChange,
|
|
1887
|
+
handleColumnVisibilityChange,
|
|
1888
|
+
handleColumnSizingChange,
|
|
1889
|
+
handlePaginationChange,
|
|
1890
|
+
handleSortingChange,
|
|
1891
|
+
handleGlobalFilterChange,
|
|
1557
1892
|
handleColumnFilterStateChange,
|
|
1558
|
-
|
|
1559
|
-
onDataStateChange,
|
|
1560
|
-
fetchData,
|
|
1561
|
-
enableColumnPinning,
|
|
1893
|
+
initialStateConfig,
|
|
1562
1894
|
enablePagination,
|
|
1563
|
-
|
|
1895
|
+
idKey,
|
|
1896
|
+
triggerRefresh,
|
|
1897
|
+
applyDataMutation,
|
|
1898
|
+
tableData,
|
|
1899
|
+
selectionState,
|
|
1900
|
+
// export
|
|
1564
1901
|
exportFilename,
|
|
1565
|
-
|
|
1902
|
+
exportChunkSize,
|
|
1903
|
+
exportStrictTotalCheck,
|
|
1904
|
+
exportSanitizeCSV,
|
|
1566
1905
|
onExportComplete,
|
|
1567
1906
|
onExportError,
|
|
1907
|
+
onExportCancel,
|
|
1568
1908
|
onServerExport,
|
|
1569
|
-
|
|
1570
|
-
setExportController,
|
|
1909
|
+
queuedExportCount,
|
|
1571
1910
|
isExporting,
|
|
1572
1911
|
dataMode,
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1912
|
+
handleExportProgressInternal,
|
|
1913
|
+
handleExportStateChangeInternal,
|
|
1914
|
+
runExportWithPolicy,
|
|
1915
|
+
setExportControllerSafely,
|
|
1916
|
+
logger,
|
|
1917
|
+
resetAllAndReload,
|
|
1576
1918
|
]);
|
|
1577
1919
|
|
|
1578
1920
|
internalApiRef.current = dataTableApi;
|
|
@@ -1710,14 +2052,12 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
1710
2052
|
// Export cancel callback
|
|
1711
2053
|
// -------------------------------
|
|
1712
2054
|
const handleCancelExport = useCallback(() => {
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
onExportCancel();
|
|
1718
|
-
}
|
|
2055
|
+
const activeController = exportControllerRef.current;
|
|
2056
|
+
if (activeController) {
|
|
2057
|
+
activeController.abort();
|
|
2058
|
+
setExportControllerSafely((current) => (current === activeController ? null : current));
|
|
1719
2059
|
}
|
|
1720
|
-
}, [
|
|
2060
|
+
}, [setExportControllerSafely]);
|
|
1721
2061
|
|
|
1722
2062
|
// -------------------------------
|
|
1723
2063
|
// Slot components
|
|
@@ -1797,6 +2137,8 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
1797
2137
|
slotProps={slotProps}
|
|
1798
2138
|
isExporting={isExporting}
|
|
1799
2139
|
exportController={exportController}
|
|
2140
|
+
exportPhase={exportPhase}
|
|
2141
|
+
exportProgress={exportProgress}
|
|
1800
2142
|
onCancelExport={handleCancelExport}
|
|
1801
2143
|
exportFilename={exportFilename}
|
|
1802
2144
|
onExportProgress={onExportProgress}
|
|
@@ -1818,7 +2160,15 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
|
|
|
1818
2160
|
enableReset={enableReset}
|
|
1819
2161
|
enableTableSizeControl={enableTableSizeControl}
|
|
1820
2162
|
enableColumnPinning={enableColumnPinning}
|
|
2163
|
+
enableRefresh={enableRefresh}
|
|
1821
2164
|
{...toolbarSlotProps}
|
|
2165
|
+
refreshButtonProps={{
|
|
2166
|
+
loading: tableLoading, // disable while fetching
|
|
2167
|
+
showSpinnerWhileLoading: false,
|
|
2168
|
+
onRefresh: () => internalApiRef.current?.data?.refresh?.(true),
|
|
2169
|
+
...toolbarSlotProps.refreshButtonProps,
|
|
2170
|
+
}}
|
|
2171
|
+
|
|
1822
2172
|
/>
|
|
1823
2173
|
) : null}
|
|
1824
2174
|
|