@ackplus/react-tanstack-data-table 1.1.10 → 1.1.12

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.
@@ -170,6 +170,7 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
170
170
  enableTableSizeControl = true,
171
171
  enableExport = false,
172
172
  enableReset = true,
173
+ enableRefresh = false,
173
174
 
174
175
  // Loading and empty states
175
176
  loading = false,
@@ -324,12 +325,15 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
324
325
  ...overrides,
325
326
  };
326
327
 
328
+ console.log('Fetching data', filters);
329
+
327
330
  if (logger.isLevelEnabled('info')) {
328
331
  logger.info('Requesting data', { filters });
329
332
  }
330
333
 
331
334
  try {
332
- const result = await debouncedFetch(filters, options === undefined ? 0 : options.delay || 300);
335
+ const delay = options?.delay ?? 300; // respects 0
336
+ const result = await debouncedFetch(filters, delay);
333
337
 
334
338
  if (logger.isLevelEnabled('info')) {
335
339
  logger.info('Fetch resolved', {
@@ -361,75 +365,22 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
361
365
  ]);
362
366
 
363
367
 
364
- const tableStateChange = useCallback((overrides: Partial<TableState> = {}) => {
365
- if (!onDataStateChange) {
366
- if (logger.isLevelEnabled('debug')) {
367
- logger.debug('No onDataStateChange handler registered; skipping state update notification', { overrides });
368
- }
369
- return;
370
- }
371
-
372
- const currentState: Partial<TableState> = {
373
- globalFilter,
374
- columnFilter,
375
- sorting,
376
- pagination,
377
- columnOrder,
378
- columnPinning,
379
- columnVisibility,
380
- columnSizing,
381
- ...overrides,
382
- };
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
- ]);
401
-
402
-
403
368
  const handleSelectionStateChange = useCallback((updaterOrValue) => {
404
369
  setSelectionState((prevState) => {
405
- const newSelectionState = typeof updaterOrValue === 'function'
406
- ? updaterOrValue(prevState)
407
- : updaterOrValue;
408
- setTimeout(() => {
409
- if (onSelectionChange) {
410
- onSelectionChange(newSelectionState);
411
- }
412
- if (onDataStateChange) {
413
- tableStateChange({ selectionState: newSelectionState });
414
- }
415
- }, 0);
416
- return newSelectionState;
370
+ const next =
371
+ typeof updaterOrValue === 'function' ? updaterOrValue(prevState) : updaterOrValue;
372
+ onSelectionChange?.(next);
373
+ return next;
417
374
  });
418
- }, [onSelectionChange, onDataStateChange, tableStateChange]);
375
+ }, [onSelectionChange]);
419
376
 
420
377
  const handleColumnFilterStateChange = useCallback((filterState: ColumnFilterState) => {
421
378
  if (!filterState || typeof filterState !== 'object') return;
422
379
 
423
380
  setColumnFilter(filterState);
424
-
425
- if (onColumnFiltersChange) {
426
- setTimeout(() => onColumnFiltersChange(filterState), 0);
427
- }
428
-
429
- if (onDataStateChange) {
430
- setTimeout(() => tableStateChange({ columnFilter: filterState }), 0);
431
- }
432
- }, [onColumnFiltersChange, onDataStateChange, tableStateChange]);
381
+ onColumnFiltersChange?.(filterState);
382
+ return filterState;
383
+ }, [onColumnFiltersChange]);
433
384
 
434
385
 
435
386
  const resetPageToFirst = useCallback(() => {
@@ -445,43 +396,20 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
445
396
  return newPagination;
446
397
  }, [pagination, logger, onPaginationChange]);
447
398
 
448
-
399
+
449
400
  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
-
457
- if (logger.isLevelEnabled('debug')) {
458
- logger.debug('Sorting change applied', {
459
- sorting: newSorting,
460
- serverMode: isServerMode,
461
- serverSorting: isServerSorting,
462
- });
463
- }
464
401
 
465
- if (isServerMode || isServerSorting) {
466
- const pagination = resetPageToFirst();
467
- if (logger.isLevelEnabled('debug')) {
468
- logger.debug('Sorting change triggered server fetch', { pagination, sorting: newSorting });
402
+ setSorting((prev) => {
403
+ const next = typeof updaterOrValue === 'function' ? updaterOrValue(prev) : updaterOrValue;
404
+ const cleaned = next.filter((s: any) => s?.id);
405
+ onSortingChange?.(cleaned);
406
+ const nextPagination = resetPageToFirst();
407
+ if (isServerMode || isServerSorting) {
408
+ fetchData({ sorting: cleaned, pagination: nextPagination }, { delay: 0 });
469
409
  }
470
- tableStateChange({ sorting: newSorting, pagination });
471
- fetchData({
472
- sorting: newSorting,
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]);
410
+ return cleaned;
411
+ });
412
+ }, [onSortingChange, isServerMode, isServerSorting, resetPageToFirst, fetchData]);
485
413
 
486
414
  const handleColumnOrderChange = useCallback((updatedColumnOrder: Updater<ColumnOrderState>) => {
487
415
  const newColumnOrder = typeof updatedColumnOrder === 'function'
@@ -493,144 +421,64 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
493
421
  }
494
422
  }, [onColumnDragEnd, columnOrder]);
495
423
 
496
- const handleColumnPinningChange = useCallback((updatedColumnPinning: Updater<ColumnPinningState>) => {
497
- const newColumnPinning = typeof updatedColumnPinning === 'function'
498
- ? updatedColumnPinning(columnPinning)
499
- : updatedColumnPinning;
500
- setColumnPinning(newColumnPinning);
501
- if (onColumnPinningChange) {
502
- onColumnPinningChange(newColumnPinning);
503
- }
504
- }, [onColumnPinningChange, columnPinning]);
424
+ const handleColumnPinningChange = useCallback(
425
+ (updater: Updater<ColumnPinningState>) => {
426
+ setColumnPinning((prev) => {
427
+ const next = typeof updater === "function" ? updater(prev) : updater;
428
+ // keep direct callback here (optional)
429
+ onColumnPinningChange?.(next);
430
+ return next;
431
+ });
432
+ },
433
+ [onColumnPinningChange]
434
+ );
505
435
 
506
436
  // Column visibility change handler - same pattern as column order
507
437
  const handleColumnVisibilityChange = useCallback((updater: any) => {
508
- const newVisibility = typeof updater === 'function'
509
- ? updater(columnVisibility)
510
- : updater;
511
- setColumnVisibility(newVisibility);
512
-
513
- if (onColumnVisibilityChange) {
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]);
438
+ setColumnVisibility((prev) => {
439
+ const next = typeof updater === 'function' ? updater(prev) : updater;
440
+ onColumnVisibilityChange?.(next);
441
+ return next;
442
+ });
443
+ }, [onColumnVisibilityChange]);
525
444
 
526
445
  // Column sizing change handler - same pattern as column order
527
446
  const handleColumnSizingChange = useCallback((updater: any) => {
528
- const newSizing = typeof updater === 'function'
529
- ? updater(columnSizing)
530
- : updater;
531
- setColumnSizing(newSizing);
532
-
533
- if (onColumnSizingChange) {
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]);
447
+ setColumnSizing((prev) => {
448
+ const next = typeof updater === 'function' ? updater(prev) : updater;
449
+ onColumnSizingChange?.(next);
450
+ return next;
451
+ });
452
+ }, [onColumnSizingChange]);
545
453
 
546
454
  const handlePaginationChange = useCallback((updater: any) => {
547
- const newPagination = typeof updater === 'function' ? updater(pagination) : updater;
548
- if (logger.isLevelEnabled('debug')) {
549
- logger.debug('Pagination change requested', {
550
- previous: pagination,
551
- next: newPagination,
552
- serverSide: isServerMode || isServerPagination,
553
- });
554
- }
555
-
556
- // Update pagination state
557
- setPagination(newPagination);
558
- onPaginationChange?.(newPagination);
455
+ setPagination((prev) => {
456
+ const next = typeof updater === 'function' ? updater(prev) : updater;
457
+ onPaginationChange?.(next);
458
+ if (isServerMode || isServerPagination) {
459
+ fetchData({ pagination: next }, { delay: 0 });
460
+ }
461
+ return next;
462
+ });
463
+ }, [isServerMode, isServerPagination, fetchData, onPaginationChange]);
559
464
 
560
- if (logger.isLevelEnabled('debug')) {
561
- logger.debug('Pagination state updated', newPagination);
562
- }
563
465
 
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
- ]);
591
466
 
467
+ const handleGlobalFilterChange = useCallback((updaterOrValue: any) => {
468
+ setGlobalFilter((prev) => {
469
+ const next = typeof updaterOrValue === 'function' ? updaterOrValue(prev) : updaterOrValue;
592
470
 
471
+ onGlobalFilterChange?.(next);
593
472
 
594
- const handleGlobalFilterChange = useCallback((updaterOrValue: any) => {
595
- const newFilter = typeof updaterOrValue === 'function'
596
- ? updaterOrValue(globalFilter)
597
- : updaterOrValue;
598
- setGlobalFilter(newFilter);
599
-
600
- if (logger.isLevelEnabled('debug')) {
601
- logger.debug('Global filter change applied', {
602
- value: newFilter,
603
- serverMode: isServerMode,
604
- serverFiltering: isServerFiltering,
605
- });
606
- }
473
+ if (isServerMode || isServerFiltering) {
474
+ const nextPagination = { pageIndex: 0, pageSize: pagination.pageSize };
475
+ setPagination(nextPagination);
476
+ fetchData({ globalFilter: next, pagination: nextPagination }, { delay: 0 });
477
+ }
607
478
 
608
- if (isServerMode || isServerFiltering) {
609
- const pagination = resetPageToFirst();
610
- setTimeout(() => {
611
- if (logger.isLevelEnabled('debug')) {
612
- logger.debug('Global filter change triggering server fetch', {
613
- pagination,
614
- value: newFilter,
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]);
479
+ return next;
480
+ });
481
+ }, [isServerMode, isServerFiltering, onGlobalFilterChange, fetchData, pagination.pageSize]);
634
482
 
635
483
  const onColumnFilterChangeHandler = useCallback((updater: any) => {
636
484
  const currentState = columnFilter;
@@ -648,24 +496,15 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
648
496
 
649
497
  const onColumnFilterApplyHandler = useCallback((appliedState: ColumnFilterState) => {
650
498
  const pagination = resetPageToFirst();
651
-
652
499
  if (isServerFiltering) {
653
- tableStateChange({
654
- columnFilter: appliedState,
655
- pagination,
656
- });
657
500
  fetchData({
658
501
  columnFilter: appliedState,
659
502
  pagination,
660
503
  });
661
- } else if (onDataStateChange) {
662
- setTimeout(() => tableStateChange({ columnFilter: appliedState, pagination }), 0);
663
504
  }
664
505
 
665
- setTimeout(() => {
666
- onColumnFiltersChange?.(appliedState);
667
- }, 0);
668
- }, [resetPageToFirst, isServerFiltering, onDataStateChange, tableStateChange, fetchData, onColumnFiltersChange]);
506
+ onColumnFiltersChange?.(appliedState);
507
+ }, [resetPageToFirst, isServerFiltering, fetchData, onColumnFiltersChange]);
669
508
 
670
509
  // -------------------------------
671
510
  // Table creation (after callbacks/memo)
@@ -713,7 +552,8 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
713
552
  getCoreRowModel: getCoreRowModel(),
714
553
  ...(enableSorting ? { getSortedRowModel: getSortedRowModel() } : {}),
715
554
  ...(enableColumnFilter || enableGlobalFilter ? { getFilteredRowModel: getCombinedFilteredRowModel<T>() } : {}),
716
- ...(enablePagination ? { getPaginationRowModel: getPaginationRowModel() } : {}),
555
+ // Only use getPaginationRowModel for client-side pagination
556
+ ...(enablePagination && !isServerPagination ? { getPaginationRowModel: getPaginationRowModel() } : {}),
717
557
  // Sorting
718
558
  enableSorting: enableSorting,
719
559
  manualSorting: isServerSorting,
@@ -760,11 +600,7 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
760
600
  // -------------------------------
761
601
  // Note: globalFilter is needed in dependencies to trigger recalculation when filter changes
762
602
  // The table object is stable, so we need to depend on the filter state directly
763
- const rows = useMemo(() => {
764
- const rowModel = table.getRowModel();
765
- return rowModel?.rows || [];
766
- // eslint-disable-next-line react-hooks/exhaustive-deps
767
- }, [table, tableData, globalFilter, enableGlobalFilter, enableColumnFilter, enablePagination]);
603
+ const rows = table.getRowModel().rows;
768
604
  const rowVirtualizer = useVirtualizer({
769
605
  count: rows.length,
770
606
  getScrollElement: () => tableContainerRef.current,
@@ -828,501 +664,573 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
828
664
  }, [enableColumnDragging, enhancedColumns, columnOrder.length]);
829
665
 
830
666
 
831
- const dataTableApi = useMemo(() => ({
832
- table: {
833
- getTable: () => table,
834
- },
835
- // Column Management
836
- columnVisibility: {
837
- showColumn: (columnId: string) => {
838
- table.getColumn(columnId)?.toggleVisibility(true);
839
- },
840
- hideColumn: (columnId: string) => {
841
- table.getColumn(columnId)?.toggleVisibility(false);
842
- },
843
- toggleColumn: (columnId: string) => {
844
- table.getColumn(columnId)?.toggleVisibility();
845
- },
846
- showAllColumns: () => {
847
- table.toggleAllColumnsVisible(true);
848
- },
849
- hideAllColumns: () => {
850
- table.toggleAllColumnsVisible(false);
851
- },
852
- resetColumnVisibility: () => {
853
- const initialVisibility = initialStateConfig.columnVisibility || {};
854
- table.setColumnVisibility(initialVisibility);
855
- // Manually trigger handler to ensure callbacks are called
856
- handleColumnVisibilityChange(initialVisibility);
857
- },
858
- },
667
+ const lastSentRef = useRef<string>("");
859
668
 
860
- // Column Ordering
861
- columnOrdering: {
862
- setColumnOrder: (columnOrder: ColumnOrderState) => {
863
- table.setColumnOrder(columnOrder);
864
- },
865
- moveColumn: (columnId: string, toIndex: number) => {
866
- const currentOrder = table.getState().columnOrder || [];
867
- const currentIndex = currentOrder.indexOf(columnId);
868
- if (currentIndex === -1) return;
669
+ const emitTableState = useCallback(() => {
670
+ if (!onDataStateChange) return;
869
671
 
870
- const newOrder = [...currentOrder];
871
- newOrder.splice(currentIndex, 1);
872
- newOrder.splice(toIndex, 0, columnId);
672
+ const live = table.getState();
873
673
 
874
- table.setColumnOrder(newOrder);
875
- },
876
- resetColumnOrder: () => {
877
- const initialOrder = enhancedColumns.map((col, index) => {
878
- if (col.id) return col.id;
879
- const anyCol = col as any;
880
- if (anyCol.accessorKey && typeof anyCol.accessorKey === 'string') {
881
- return anyCol.accessorKey;
882
- }
883
- return `column_${index}`;
884
- });
885
- table.setColumnOrder(initialOrder);
886
- // Manually trigger handler to ensure callbacks are called
887
- handleColumnOrderChange(initialOrder);
888
- },
889
- },
674
+ // only keep what you persist/store
675
+ const payload = {
676
+ sorting: live.sorting,
677
+ pagination: live.pagination,
678
+ globalFilter: live.globalFilter,
679
+ columnFilter: live.columnFilter,
680
+ columnVisibility: live.columnVisibility,
681
+ columnSizing: live.columnSizing,
682
+ columnOrder: live.columnOrder,
683
+ columnPinning: live.columnPinning,
684
+ };
890
685
 
891
- // Column Pinning
892
- columnPinning: {
893
- pinColumnLeft: (columnId: string) => {
894
- const currentPinning = table.getState().columnPinning;
895
- const newPinning = { ...currentPinning };
686
+ const key = JSON.stringify(payload);
687
+ if (key === lastSentRef.current) return;
896
688
 
897
- // Remove from right if exists
898
- newPinning.right = (newPinning.right || []).filter(id => id !== columnId);
899
- // Add to left if not exists
900
- newPinning.left = [...(newPinning.left || []).filter(id => id !== columnId), columnId];
689
+ lastSentRef.current = key;
690
+ onDataStateChange(payload);
691
+ }, [onDataStateChange, table]);
901
692
 
902
- table.setColumnPinning(newPinning);
903
- },
904
- pinColumnRight: (columnId: string) => {
905
- const currentPinning = table.getState().columnPinning;
906
- const newPinning = { ...currentPinning };
693
+ useEffect(() => {
694
+ emitTableState();
695
+ }, [
696
+ emitTableState,
697
+ sorting,
698
+ pagination,
699
+ globalFilter,
700
+ columnFilter,
701
+ columnVisibility,
702
+ columnSizing,
703
+ columnOrder,
704
+ columnPinning,
705
+ ]);
907
706
 
908
- // Remove from left if exists
909
- newPinning.left = (newPinning.left || []).filter(id => id !== columnId);
910
- // Add to right if not exists - prepend to beginning (appears rightmost to leftmost)
911
- // First column pinned appears rightmost, second appears to its left, etc.
912
- newPinning.right = [columnId, ...(newPinning.right || []).filter(id => id !== columnId)];
913
707
 
914
- table.setColumnPinning(newPinning);
915
- },
916
- unpinColumn: (columnId: string) => {
917
- const currentPinning = table.getState().columnPinning;
918
- const newPinning = {
919
- left: (currentPinning.left || []).filter(id => id !== columnId),
920
- right: (currentPinning.right || []).filter(id => id !== columnId),
921
- };
922
-
923
- table.setColumnPinning(newPinning);
924
- },
925
- setPinning: (pinning: ColumnPinningState) => {
926
- table.setColumnPinning(pinning);
927
- },
928
- resetColumnPinning: () => {
929
- const initialPinning = initialStateConfig.columnPinning || { left: [], right: [] };
930
- table.setColumnPinning(initialPinning);
931
- // Manually trigger handler to ensure callbacks are called
932
- handleColumnPinningChange(initialPinning);
933
- },
934
- },
708
+ const getResetState = useCallback((): Partial<TableState> => {
709
+ const resetSorting = initialStateConfig.sorting || [];
710
+ const resetGlobalFilter = initialStateConfig.globalFilter ?? '';
711
+ const resetColumnFilter =
712
+ initialStateConfig.columnFilter || { filters: [], logic: 'AND', pendingFilters: [], pendingLogic: 'AND' };
935
713
 
936
- // Column Resizing
937
- columnResizing: {
938
- resizeColumn: (columnId: string, width: number) => {
939
- // Use table's setColumnSizing method
940
- const currentSizing = table.getState().columnSizing;
941
- table.setColumnSizing({
942
- ...currentSizing,
943
- [columnId]: width,
944
- });
945
- },
946
- autoSizeColumn: (columnId: string) => {
947
- // TanStack doesn't have built-in auto-size, so reset to default
948
- table.getColumn(columnId)?.resetSize();
949
- },
950
- autoSizeAllColumns: () => {
951
- const initialSizing = initialStateConfig.columnSizing || {};
952
- table.setColumnSizing(initialSizing);
953
- // Manually trigger handler to ensure callbacks are called
954
- handleColumnSizingChange(initialSizing);
955
- },
956
- resetColumnSizing: () => {
957
- const initialSizing = initialStateConfig.columnSizing || {};
958
- table.setColumnSizing(initialSizing);
959
- // Manually trigger handler to ensure callbacks are called
960
- handleColumnSizingChange(initialSizing);
961
- },
962
- },
714
+ const resetPagination = enablePagination
715
+ ? (initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 })
716
+ : undefined;
963
717
 
964
- // Filtering
965
- filtering: {
966
- setGlobalFilter: (filter: string) => {
967
- table.setGlobalFilter(filter);
968
- },
969
- clearGlobalFilter: () => {
970
- table.setGlobalFilter('');
971
- },
972
- setColumnFilters: (filters: ColumnFilterState) => {
973
- handleColumnFilterStateChange(filters);
974
- },
975
- addColumnFilter: (columnId: string, operator: string, value: any) => {
976
- const newFilter = {
977
- id: `filter_${Date.now()}`,
978
- columnId,
979
- operator,
980
- value,
981
- };
982
- const columnFilter = table.getState().columnFilter;
983
-
984
- const currentFilters = columnFilter.filters || [];
985
- const newFilters = [...currentFilters, newFilter];
986
- handleColumnFilterStateChange({
987
- filters: newFilters,
988
- logic: columnFilter.logic,
989
- pendingFilters: columnFilter.pendingFilters || [],
990
- pendingLogic: columnFilter.pendingLogic || 'AND',
991
- });
992
- if (logger.isLevelEnabled('debug')) {
993
- logger.debug(`Adding column filter ${columnId} ${operator} ${value}`, newFilters);
994
- }
995
- },
996
- removeColumnFilter: (filterId: string) => {
997
- const columnFilter = table.getState().columnFilter;
998
- const currentFilters = columnFilter.filters || [];
999
- const newFilters = currentFilters.filter((f: any) => f.id !== filterId);
1000
- handleColumnFilterStateChange({
1001
- filters: newFilters,
1002
- logic: columnFilter.logic,
1003
- pendingFilters: columnFilter.pendingFilters || [],
1004
- pendingLogic: columnFilter.pendingLogic || 'AND',
1005
- });
1006
- if (logger.isLevelEnabled('debug')) {
1007
- logger.debug(`Removing column filter ${filterId}`, newFilters);
1008
- }
1009
- },
1010
- clearAllFilters: () => {
1011
- table.setGlobalFilter('');
1012
- handleColumnFilterStateChange({
1013
- filters: [],
1014
- logic: 'AND',
1015
- pendingFilters: [],
1016
- pendingLogic: 'AND',
1017
- });
1018
- },
1019
- resetFilters: () => {
1020
- handleColumnFilterStateChange({
1021
- filters: [],
1022
- logic: 'AND',
1023
- pendingFilters: [],
1024
- pendingLogic: 'AND',
1025
- });
1026
- if (logger.isLevelEnabled('debug')) {
1027
- logger.debug('Resetting filters');
1028
- }
1029
- },
1030
- },
718
+ return {
719
+ sorting: resetSorting,
720
+ globalFilter: resetGlobalFilter,
721
+ columnFilter: resetColumnFilter,
722
+ ...(resetPagination ? { pagination: resetPagination } : {}),
723
+ };
724
+ }, [initialStateConfig, enablePagination]);
1031
725
 
1032
- // Sorting
1033
- sorting: {
1034
- setSorting: (sortingState: SortingState) => {
1035
- table.setSorting(sortingState);
1036
- if (logger.isLevelEnabled('debug')) {
1037
- logger.debug(`Setting sorting`, sortingState);
1038
- }
1039
- },
1040
- sortColumn: (columnId: string, direction: 'asc' | 'desc' | false) => {
1041
- const column = table.getColumn(columnId);
1042
- if (!column) return;
1043
-
1044
- if (direction === false) {
1045
- column.clearSorting();
1046
- } else {
1047
- column.toggleSorting(direction === 'desc');
1048
- }
1049
- },
1050
- clearSorting: () => {
1051
- table.setSorting([]);
1052
- // Manually trigger handler to ensure callbacks are called
1053
- handleSortingChange([]);
1054
- },
1055
- resetSorting: () => {
1056
- const initialSorting = initialStateConfig.sorting || [];
1057
- table.setSorting(initialSorting);
1058
- // Manually trigger handler to ensure callbacks are called
1059
- handleSortingChange(initialSorting);
1060
- },
1061
- },
726
+ const resetAllAndReload = useCallback(() => {
727
+ const resetState = getResetState();
1062
728
 
1063
- // Pagination
1064
- pagination: {
1065
- goToPage: (pageIndex: number) => {
1066
- table.setPageIndex(pageIndex);
1067
- if (logger.isLevelEnabled('debug')) {
1068
- logger.debug(`Going to page ${pageIndex}`);
1069
- }
1070
- },
1071
- nextPage: () => {
1072
- table.nextPage();
1073
- if (logger.isLevelEnabled('debug')) {
1074
- logger.debug('Next page');
1075
- }
1076
- },
1077
- previousPage: () => {
1078
- table.previousPage();
1079
- if (logger.isLevelEnabled('debug')) {
1080
- logger.debug('Previous page');
1081
- }
1082
- },
1083
- setPageSize: (pageSize: number) => {
1084
- table.setPageSize(pageSize);
1085
- if (logger.isLevelEnabled('debug')) {
1086
- logger.debug(`Setting page size to ${pageSize}`);
1087
- }
1088
- },
1089
- goToFirstPage: () => {
1090
- table.setPageIndex(0);
1091
- if (logger.isLevelEnabled('debug')) {
1092
- logger.debug('Going to first page');
1093
- }
1094
- },
1095
- goToLastPage: () => {
1096
- const pageCount = table.getPageCount();
1097
- if (pageCount > 0) {
1098
- table.setPageIndex(pageCount - 1);
1099
- if (logger.isLevelEnabled('debug')) {
1100
- logger.debug(`Going to last page ${pageCount - 1}`);
729
+ setSorting(resetState.sorting || []);
730
+ setGlobalFilter(resetState.globalFilter ?? '');
731
+ setColumnFilter(resetState.columnFilter as any);
732
+
733
+ if (resetState.pagination) setPagination(resetState.pagination);
734
+
735
+ setSelectionState(initialSelectionState);
736
+ setExpanded({});
737
+
738
+ // layout state
739
+ setColumnVisibility(initialStateConfig.columnVisibility || {});
740
+ setColumnSizing(initialStateConfig.columnSizing || {});
741
+ setColumnOrder(initialStateConfig.columnOrder || []);
742
+ setColumnPinning(initialStateConfig.columnPinning || { left: [], right: [] });
743
+
744
+ if (onFetchData) fetchData(resetState, { delay: 0 });
745
+ }, [getResetState, initialSelectionState, initialStateConfig, onFetchData, fetchData]);
746
+
747
+ const dataTableApi = useMemo(() => {
748
+ // helpers (avoid repeating boilerplate)
749
+ const buildInitialOrder = () =>
750
+ enhancedColumns.map((col, index) => {
751
+ if ((col as any).id) return (col as any).id as string;
752
+ const anyCol = col as any;
753
+ if (anyCol.accessorKey && typeof anyCol.accessorKey === "string") return anyCol.accessorKey;
754
+ return `column_${index}`;
755
+ });
756
+
757
+ const applyColumnOrder = (next: ColumnOrderState) => {
758
+ // handleColumnOrderChange supports both Updater<ColumnOrderState> and array in your impl
759
+ handleColumnOrderChange(next as any);
760
+ };
761
+
762
+ const applyPinning = (next: ColumnPinningState) => {
763
+ handleColumnPinningChange(next as any);
764
+ };
765
+
766
+ const applyVisibility = (next: Record<string, boolean>) => {
767
+ handleColumnVisibilityChange(next as any);
768
+ };
769
+
770
+ const applySizing = (next: Record<string, number>) => {
771
+ handleColumnSizingChange(next as any);
772
+ };
773
+
774
+ const applyPagination = (next: any) => {
775
+ handlePaginationChange(next);
776
+ };
777
+
778
+ const applySorting = (next: any) => {
779
+ handleSortingChange(next);
780
+ };
781
+
782
+ const applyGlobalFilter = (next: any) => {
783
+ handleGlobalFilterChange(next);
784
+ };
785
+
786
+ return {
787
+ table: {
788
+ getTable: () => table,
789
+ },
790
+
791
+ // -------------------------------
792
+ // Column Management
793
+ // -------------------------------
794
+ columnVisibility: {
795
+ showColumn: (columnId: string) => {
796
+ applyVisibility({ ...table.getState().columnVisibility, [columnId]: true });
797
+ },
798
+ hideColumn: (columnId: string) => {
799
+ applyVisibility({ ...table.getState().columnVisibility, [columnId]: false });
800
+ },
801
+ toggleColumn: (columnId: string) => {
802
+ const curr = table.getState().columnVisibility?.[columnId] ?? true;
803
+ applyVisibility({ ...table.getState().columnVisibility, [columnId]: !curr });
804
+ },
805
+ showAllColumns: () => {
806
+ // set all known columns true
807
+ const all: Record<string, boolean> = {};
808
+ table.getAllLeafColumns().forEach((c) => (all[c.id] = true));
809
+ applyVisibility(all);
810
+ },
811
+ hideAllColumns: () => {
812
+ const all: Record<string, boolean> = {};
813
+ table.getAllLeafColumns().forEach((c) => (all[c.id] = false));
814
+ applyVisibility(all);
815
+ },
816
+ resetColumnVisibility: () => {
817
+ const initialVisibility = initialStateConfig.columnVisibility || {};
818
+ applyVisibility(initialVisibility);
819
+ },
820
+ },
821
+
822
+ // -------------------------------
823
+ // Column Ordering
824
+ // -------------------------------
825
+ columnOrdering: {
826
+ setColumnOrder: (nextOrder: ColumnOrderState) => {
827
+ applyColumnOrder(nextOrder);
828
+ },
829
+ moveColumn: (columnId: string, toIndex: number) => {
830
+ const currentOrder =
831
+ (table.getState().columnOrder?.length ? table.getState().columnOrder : buildInitialOrder()) || [];
832
+ const fromIndex = currentOrder.indexOf(columnId);
833
+ if (fromIndex === -1) return;
834
+
835
+ const next = [...currentOrder];
836
+ next.splice(fromIndex, 1);
837
+ next.splice(toIndex, 0, columnId);
838
+
839
+ applyColumnOrder(next);
840
+ },
841
+ resetColumnOrder: () => {
842
+ applyColumnOrder(buildInitialOrder());
843
+ },
844
+ },
845
+
846
+ // -------------------------------
847
+ // Column Pinning
848
+ // -------------------------------
849
+ columnPinning: {
850
+ pinColumnLeft: (columnId: string) => {
851
+ const current = table.getState().columnPinning || { left: [], right: [] };
852
+ const next: ColumnPinningState = {
853
+ left: [...(current.left || []).filter((id) => id !== columnId), columnId],
854
+ right: (current.right || []).filter((id) => id !== columnId),
855
+ };
856
+ applyPinning(next);
857
+ },
858
+ pinColumnRight: (columnId: string) => {
859
+ const current = table.getState().columnPinning || { left: [], right: [] };
860
+ const next: ColumnPinningState = {
861
+ left: (current.left || []).filter((id) => id !== columnId),
862
+ // keep your "prepend" behavior
863
+ right: [columnId, ...(current.right || []).filter((id) => id !== columnId)],
864
+ };
865
+ applyPinning(next);
866
+ },
867
+ unpinColumn: (columnId: string) => {
868
+ const current = table.getState().columnPinning || { left: [], right: [] };
869
+ const next: ColumnPinningState = {
870
+ left: (current.left || []).filter((id) => id !== columnId),
871
+ right: (current.right || []).filter((id) => id !== columnId),
872
+ };
873
+ applyPinning(next);
874
+ },
875
+ setPinning: (pinning: ColumnPinningState) => {
876
+ applyPinning(pinning);
877
+ },
878
+ resetColumnPinning: () => {
879
+ const initialPinning = initialStateConfig.columnPinning || { left: [], right: [] };
880
+ applyPinning(initialPinning);
881
+ },
882
+ },
883
+
884
+ // -------------------------------
885
+ // Column Resizing
886
+ // -------------------------------
887
+ columnResizing: {
888
+ resizeColumn: (columnId: string, width: number) => {
889
+ const currentSizing = table.getState().columnSizing || {};
890
+ applySizing({ ...currentSizing, [columnId]: width });
891
+ },
892
+ autoSizeColumn: (columnId: string) => {
893
+ // safe to call tanstack helper; it will feed into onColumnSizingChange if wired,
894
+ // but since you're controlled, we still prefer to update through handler:
895
+ const col = table.getColumn(columnId);
896
+ if (!col) return;
897
+
898
+ col.resetSize();
899
+ // after resetSize, read state and emit via handler so controlled stays synced
900
+ applySizing({ ...(table.getState().columnSizing || {}) });
901
+ },
902
+ autoSizeAllColumns: () => {
903
+ const initialSizing = initialStateConfig.columnSizing || {};
904
+ applySizing(initialSizing);
905
+ },
906
+ resetColumnSizing: () => {
907
+ const initialSizing = initialStateConfig.columnSizing || {};
908
+ applySizing(initialSizing);
909
+ },
910
+ },
911
+
912
+ // -------------------------------
913
+ // Filtering
914
+ // -------------------------------
915
+ filtering: {
916
+ setGlobalFilter: (filter: string) => {
917
+ applyGlobalFilter(filter);
918
+ },
919
+ clearGlobalFilter: () => {
920
+ applyGlobalFilter("");
921
+ },
922
+ setColumnFilters: (filters: ColumnFilterState) => {
923
+ handleColumnFilterStateChange(filters);
924
+ },
925
+ addColumnFilter: (columnId: string, operator: string, value: any) => {
926
+ const newFilter = {
927
+ id: `filter_${Date.now()}`,
928
+ columnId,
929
+ operator,
930
+ value,
931
+ };
932
+
933
+ const current = table.getState().columnFilter;
934
+ const currentFilters = current?.filters || [];
935
+ const nextFilters = [...currentFilters, newFilter];
936
+
937
+ handleColumnFilterStateChange({
938
+ filters: nextFilters,
939
+ logic: current?.logic,
940
+ pendingFilters: current?.pendingFilters || [],
941
+ pendingLogic: current?.pendingLogic || "AND",
942
+ });
943
+
944
+ if (logger.isLevelEnabled("debug")) {
945
+ logger.debug(`Adding column filter ${columnId} ${operator} ${value}`, nextFilters);
1101
946
  }
1102
- }
1103
- },
1104
- resetPagination: () => {
1105
- const initialPagination = initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 };
1106
- table.setPagination(initialPagination);
1107
- // Manually trigger handler to ensure callbacks are called
1108
- handlePaginationChange(initialPagination);
1109
- },
1110
- },
947
+ },
948
+ removeColumnFilter: (filterId: string) => {
949
+ const current = table.getState().columnFilter;
950
+ const currentFilters = current?.filters || [];
951
+ const nextFilters = currentFilters.filter((f: any) => f.id !== filterId);
952
+
953
+ handleColumnFilterStateChange({
954
+ filters: nextFilters,
955
+ logic: current?.logic,
956
+ pendingFilters: current?.pendingFilters || [],
957
+ pendingLogic: current?.pendingLogic || "AND",
958
+ });
1111
959
 
1112
- // Access via table methods: table.selectRow(), table.getIsRowSelected(), etc.
1113
- selection: {
1114
- selectRow: (rowId: string) => table.selectRow?.(rowId),
1115
- deselectRow: (rowId: string) => table.deselectRow?.(rowId),
1116
- toggleRowSelection: (rowId: string) => table.toggleRowSelected?.(rowId),
1117
- selectAll: () => table.selectAll?.(),
1118
- deselectAll: () => table.deselectAll?.(),
1119
- toggleSelectAll: () => table.toggleAllRowsSelected?.(),
1120
- getSelectionState: () => table.getSelectionState?.() || { ids: [], type: 'include' as const },
1121
- getSelectedRows: () => table.getSelectedRows(),
1122
- getSelectedCount: () => table.getSelectedCount(),
1123
- isRowSelected: (rowId) => table.getIsRowSelected(rowId) || false,
1124
- },
960
+ if (logger.isLevelEnabled("debug")) {
961
+ logger.debug(`Removing column filter ${filterId}`, nextFilters);
962
+ }
963
+ },
964
+ clearAllFilters: () => {
965
+ applyGlobalFilter("");
966
+ handleColumnFilterStateChange({
967
+ filters: [],
968
+ logic: "AND",
969
+ pendingFilters: [],
970
+ pendingLogic: "AND",
971
+ });
972
+ },
973
+ resetFilters: () => {
974
+ handleColumnFilterStateChange({
975
+ filters: [],
976
+ logic: "AND",
977
+ pendingFilters: [],
978
+ pendingLogic: "AND",
979
+ });
1125
980
 
1126
- // Data Management
1127
- data: {
1128
- refresh: (resetPagination = false) => {
1129
- const filters = table.getState();
1130
- const pagination = {
1131
- pageIndex: resetPagination ? 0 : initialStateConfig.pagination?.pageIndex || 0,
1132
- pageSize: filters.pagination?.pageSize || initialStateConfig.pagination?.pageSize || 10,
1133
- };
1134
- const allState = table.getState();
1135
- setPagination(pagination);
1136
- onDataStateChange?.({ ...allState, pagination });
1137
- fetchData?.({ pagination });
1138
- if (logger.isLevelEnabled('debug')) {
1139
- logger.debug('Refreshing data using Ref', { pagination, allState });
1140
- }
1141
- },
1142
- reload: () => {
1143
- const allState = table.getState();
981
+ if (logger.isLevelEnabled("debug")) {
982
+ logger.debug("Resetting filters");
983
+ }
984
+ },
985
+ },
986
+
987
+ // -------------------------------
988
+ // Sorting
989
+ // -------------------------------
990
+ sorting: {
991
+ setSorting: (sortingState: SortingState) => {
992
+ applySorting(sortingState);
993
+ if (logger.isLevelEnabled("debug")) logger.debug("Setting sorting", sortingState);
994
+ },
995
+
996
+ // NOTE: toggleSorting is okay, but can become "one behind" in controlled server mode.
997
+ // So we implement deterministic sorting through handler.
998
+ sortColumn: (columnId: string, direction: "asc" | "desc" | false) => {
999
+ const current = table.getState().sorting || [];
1000
+ const filtered = current.filter((s: any) => s.id !== columnId);
1001
+
1002
+ if (direction === false) {
1003
+ applySorting(filtered);
1004
+ return;
1005
+ }
1144
1006
 
1145
- onDataStateChange?.(allState);
1146
- fetchData?.({});
1147
- if (logger.isLevelEnabled('debug')) {
1148
- logger.info('Reloading data', allState);
1149
- }
1150
- },
1151
- // Data CRUD operations
1152
- getAllData: () => {
1153
- return table.getRowModel().rows?.map(row => row.original) || [];
1154
- },
1155
- getRowData: (rowId: string) => {
1156
- return table.getRowModel().rows?.find(row => String(row.original[idKey]) === rowId)?.original;
1157
- },
1158
- getRowByIndex: (index: number) => {
1159
- return table.getRowModel().rows?.[index]?.original;
1160
- },
1161
- updateRow: (rowId: string, updates: Partial<T>) => {
1162
- const newData = table.getRowModel().rows?.map(row => String(row.original[idKey]) === rowId
1163
- ? {
1164
- ...row.original,
1165
- ...updates,
1007
+ applySorting([{ id: columnId, desc: direction === "desc" }, ...filtered]);
1008
+ },
1009
+
1010
+ clearSorting: () => {
1011
+ applySorting([]);
1012
+ },
1013
+ resetSorting: () => {
1014
+ const initialSorting = initialStateConfig.sorting || [];
1015
+ applySorting(initialSorting);
1016
+ },
1017
+ },
1018
+
1019
+ // -------------------------------
1020
+ // Pagination
1021
+ // -------------------------------
1022
+ pagination: {
1023
+ goToPage: (pageIndex: number) => {
1024
+ applyPagination((prev: any) => ({ ...prev, pageIndex }));
1025
+ if (logger.isLevelEnabled("debug")) logger.debug(`Going to page ${pageIndex}`);
1026
+ },
1027
+ nextPage: () => {
1028
+ applyPagination((prev: any) => ({ ...prev, pageIndex: (prev?.pageIndex ?? 0) + 1 }));
1029
+ if (logger.isLevelEnabled("debug")) logger.debug("Next page");
1030
+ },
1031
+ previousPage: () => {
1032
+ applyPagination((prev: any) => ({ ...prev, pageIndex: Math.max(0, (prev?.pageIndex ?? 0) - 1) }));
1033
+ if (logger.isLevelEnabled("debug")) logger.debug("Previous page");
1034
+ },
1035
+ setPageSize: (pageSize: number) => {
1036
+ // usually want pageIndex reset
1037
+ applyPagination(() => ({ pageIndex: 0, pageSize }));
1038
+ if (logger.isLevelEnabled("debug")) logger.debug(`Setting page size to ${pageSize}`);
1039
+ },
1040
+ goToFirstPage: () => {
1041
+ applyPagination((prev: any) => ({ ...prev, pageIndex: 0 }));
1042
+ if (logger.isLevelEnabled("debug")) logger.debug("Going to first page");
1043
+ },
1044
+ goToLastPage: () => {
1045
+ // pageCount can be derived; keep safe fallback
1046
+ const pageCount = table.getPageCount?.() ?? 0;
1047
+ if (pageCount > 0) {
1048
+ applyPagination((prev: any) => ({ ...prev, pageIndex: pageCount - 1 }));
1049
+ if (logger.isLevelEnabled("debug")) logger.debug(`Going to last page ${pageCount - 1}`);
1166
1050
  }
1167
- : row.original);
1168
- setServerData?.(newData || []);
1169
- if (logger.isLevelEnabled('debug')) {
1170
- logger.debug(`Updating row ${rowId}`, updates);
1171
- }
1172
- },
1173
- updateRowByIndex: (index: number, updates: Partial<T>) => {
1174
- const newData = table.getRowModel().rows?.map(row => row.original);
1175
- if (newData?.[index]) {
1176
- newData[index] = {
1177
- ...newData[index]!,
1178
- ...updates,
1051
+ },
1052
+ resetPagination: () => {
1053
+ const initialPagination = initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 };
1054
+ applyPagination(initialPagination);
1055
+ },
1056
+ },
1057
+
1058
+ // -------------------------------
1059
+ // Selection
1060
+ // -------------------------------
1061
+ selection: {
1062
+ selectRow: (rowId: string) => table.selectRow?.(rowId),
1063
+ deselectRow: (rowId: string) => table.deselectRow?.(rowId),
1064
+ toggleRowSelection: (rowId: string) => table.toggleRowSelected?.(rowId),
1065
+ selectAll: () => table.selectAll?.(),
1066
+ deselectAll: () => table.deselectAll?.(),
1067
+ toggleSelectAll: () => table.toggleAllRowsSelected?.(),
1068
+ getSelectionState: () => table.getSelectionState?.() || ({ ids: [], type: "include" } as const),
1069
+ getSelectedRows: () => table.getSelectedRows(),
1070
+ getSelectedCount: () => table.getSelectedCount(),
1071
+ isRowSelected: (rowId: string) => table.getIsRowSelected(rowId) || false,
1072
+ },
1073
+
1074
+ // -------------------------------
1075
+ // Data Management (kept same, but ensure state changes go through handlers)
1076
+ // -------------------------------
1077
+ data: {
1078
+ refresh: (resetPagination = false) => {
1079
+ const allState = table.getState();
1080
+ const current = allState.pagination;
1081
+
1082
+ const nextPagination = {
1083
+ pageIndex: resetPagination ? 0 : current?.pageIndex ?? 0,
1084
+ pageSize: current?.pageSize ?? initialStateConfig.pagination?.pageSize ?? 10,
1179
1085
  };
1180
- setServerData(newData);
1181
- if (logger.isLevelEnabled('debug')) {
1182
- logger.debug(`Updating row by index ${index}`, updates);
1086
+
1087
+ // must go through handler so server fetch triggers
1088
+ applyPagination(nextPagination);
1089
+
1090
+ // emit persisted state (your emitTableState effect will also do it)
1091
+ onDataStateChange?.({ ...allState, pagination: nextPagination });
1092
+
1093
+ fetchData?.({ pagination: nextPagination });
1094
+
1095
+ if (logger.isLevelEnabled("debug")) {
1096
+ logger.debug("Refreshing data", { nextPagination, allState });
1183
1097
  }
1184
- }
1185
- },
1186
- insertRow: (newRow: T, index?: number) => {
1187
- const newData = table.getRowModel().rows?.map(row => row.original) || [];
1188
- if (index !== undefined) {
1189
- newData.splice(index, 0, newRow);
1190
- } else {
1191
- newData.push(newRow);
1192
- }
1193
- setServerData(newData || []);
1194
- if (logger.isLevelEnabled('debug')) {
1195
- logger.debug(`Inserting row`, newRow);
1196
- }
1197
- },
1198
- deleteRow: (rowId: string) => {
1199
- const newData = (table.getRowModel().rows || [])?.filter(row => String(row.original[idKey]) !== rowId);
1200
- setServerData?.(newData?.map(row => row.original) || []);
1201
- if (logger.isLevelEnabled('debug')) {
1202
- logger.debug(`Deleting row ${rowId}`);
1203
- }
1204
- },
1205
- deleteRowByIndex: (index: number) => {
1206
- const newData = (table.getRowModel().rows || [])?.map(row => row.original);
1207
- newData.splice(index, 1);
1208
- setServerData(newData);
1209
- if (logger.isLevelEnabled('debug')) {
1210
- logger.debug(`Deleting row by index ${index}`);
1211
- }
1212
- },
1213
- deleteSelectedRows: () => {
1214
- const selectedRows = table.getSelectedRows?.() || [];
1215
- if (selectedRows.length === 0) return;
1098
+ },
1099
+
1100
+ reload: () => {
1101
+ const allState = table.getState();
1102
+ onDataStateChange?.(allState);
1103
+ fetchData?.();
1104
+ if (logger.isLevelEnabled("debug")) {
1105
+ logger.info("Reloading data", allState);
1106
+ }
1107
+ },
1108
+
1109
+ resetAll: () => resetAllAndReload({ resetLayout: true }),
1110
+
1111
+ getAllData: () => table.getRowModel().rows?.map((row) => row.original) || [],
1112
+ getRowData: (rowId: string) =>
1113
+ table.getRowModel().rows?.find((row) => String(row.original[idKey]) === rowId)?.original,
1114
+ getRowByIndex: (index: number) => table.getRowModel().rows?.[index]?.original,
1115
+
1116
+ updateRow: (rowId: string, updates: Partial<T>) => {
1117
+ const newData = table.getRowModel().rows?.map((row) =>
1118
+ String(row.original[idKey]) === rowId ? { ...row.original, ...updates } : row.original
1119
+ );
1120
+ setServerData?.(newData || []);
1121
+ if (logger.isLevelEnabled("debug")) logger.debug(`Updating row ${rowId}`, updates);
1122
+ },
1123
+
1124
+ updateRowByIndex: (index: number, updates: Partial<T>) => {
1125
+ const newData = table.getRowModel().rows?.map((row) => row.original) || [];
1126
+ if (newData[index]) {
1127
+ newData[index] = { ...newData[index]!, ...updates };
1128
+ setServerData(newData);
1129
+ if (logger.isLevelEnabled("debug")) logger.debug(`Updating row by index ${index}`, updates);
1130
+ }
1131
+ },
1132
+
1133
+ insertRow: (newRow: T, index?: number) => {
1134
+ const newData = table.getRowModel().rows?.map((row) => row.original) || [];
1135
+ if (index !== undefined) newData.splice(index, 0, newRow);
1136
+ else newData.push(newRow);
1137
+ setServerData(newData || []);
1138
+ if (logger.isLevelEnabled("debug")) logger.debug("Inserting row", newRow);
1139
+ },
1140
+
1141
+ deleteRow: (rowId: string) => {
1142
+ const newData = (table.getRowModel().rows || []).filter((row) => String(row.original[idKey]) !== rowId);
1143
+ setServerData?.(newData.map((r) => r.original) || []);
1144
+ if (logger.isLevelEnabled("debug")) logger.debug(`Deleting row ${rowId}`);
1145
+ },
1146
+
1147
+ deleteRowByIndex: (index: number) => {
1148
+ const newData = (table.getRowModel().rows || []).map((row) => row.original);
1149
+ newData.splice(index, 1);
1150
+ setServerData(newData);
1151
+ if (logger.isLevelEnabled("debug")) logger.debug(`Deleting row by index ${index}`);
1152
+ },
1216
1153
 
1217
- const selectedIds = new Set(selectedRows.map(row => String(row.original[idKey])));
1218
- const newData = (table.getRowModel().rows || [])?.filter(row => !selectedIds.has(String(row.original[idKey])));
1154
+ deleteSelectedRows: () => {
1155
+ const selectedRows = table.getSelectedRows?.() || [];
1156
+ if (selectedRows.length === 0) return;
1219
1157
 
1220
- setServerData(newData?.map(row => row.original) || []);
1221
- table.deselectAll?.();
1158
+ const selectedIds = new Set(selectedRows.map((row) => String(row.original[idKey])));
1159
+ const newData = (table.getRowModel().rows || [])
1160
+ .filter((row) => !selectedIds.has(String(row.original[idKey])))
1161
+ .map((row) => row.original);
1222
1162
 
1223
- if (logger.isLevelEnabled('debug')) {
1224
- logger.debug('Deleting selected rows');
1225
- }
1226
- },
1227
- replaceAllData: (newData: T[]) => {
1228
- setServerData?.(newData);
1229
- },
1163
+ setServerData(newData);
1164
+ table.deselectAll?.();
1230
1165
 
1231
- // Bulk operations
1232
- updateMultipleRows: (updates: Array<{ rowId: string; data: Partial<T> }>) => {
1233
- const updateMap = new Map(updates.map(u => [u.rowId, u.data]));
1234
- const newData = (table.getRowModel().rows || [])?.map(row => {
1235
- const rowId = String(row.original[idKey]);
1236
- const updateData = updateMap.get(rowId);
1237
- return updateData ? {
1238
- ...row.original,
1239
- ...updateData,
1240
- } : row.original;
1241
- });
1242
- setServerData(newData || []);
1243
- },
1244
- insertMultipleRows: (newRows: T[], startIndex?: number) => {
1245
- const newData = (table.getRowModel().rows || [])?.map(row => row.original);
1246
- if (startIndex !== undefined) {
1247
- newData.splice(startIndex, 0, ...newRows);
1248
- } else {
1249
- newData.push(...newRows);
1250
- }
1251
- setServerData?.(newData);
1252
- },
1253
- deleteMultipleRows: (rowIds: string[]) => {
1254
- const idsToDelete = new Set(rowIds);
1255
- const newData = (table.getRowModel().rows || [])?.filter(row => !idsToDelete.has(String(row.original[idKey])))?.map(row => row.original);
1256
- setServerData(newData);
1257
- },
1166
+ if (logger.isLevelEnabled("debug")) logger.debug("Deleting selected rows");
1167
+ },
1258
1168
 
1259
- // Field-specific updates
1260
- updateField: (rowId: string, fieldName: keyof T, value: any) => {
1261
- const newData = (table.getRowModel().rows || [])?.map(row => String(row.original[idKey]) === rowId
1262
- ? {
1263
- ...row.original,
1264
- [fieldName]: value,
1265
- }
1266
- : row.original);
1267
- setServerData?.(newData);
1268
- },
1269
- updateFieldByIndex: (index: number, fieldName: keyof T, value: any) => {
1270
- const newData = (table.getRowModel().rows || [])?.map(row => row.original);
1271
- if (newData[index]) {
1272
- newData[index] = {
1273
- ...newData[index],
1274
- [fieldName]: value,
1275
- };
1169
+ replaceAllData: (newData: T[]) => setServerData?.(newData),
1170
+
1171
+ updateMultipleRows: (updates: Array<{ rowId: string; data: Partial<T> }>) => {
1172
+ const updateMap = new Map(updates.map((u) => [u.rowId, u.data]));
1173
+ const newData = (table.getRowModel().rows || []).map((row) => {
1174
+ const rowId = String(row.original[idKey]);
1175
+ const updateData = updateMap.get(rowId);
1176
+ return updateData ? { ...row.original, ...updateData } : row.original;
1177
+ });
1178
+ setServerData(newData || []);
1179
+ },
1180
+
1181
+ insertMultipleRows: (newRows: T[], startIndex?: number) => {
1182
+ const newData = (table.getRowModel().rows || []).map((row) => row.original);
1183
+ if (startIndex !== undefined) newData.splice(startIndex, 0, ...newRows);
1184
+ else newData.push(...newRows);
1276
1185
  setServerData?.(newData);
1277
- }
1278
- },
1186
+ },
1279
1187
 
1280
- // Data queries
1281
- findRows: (predicate: (row: T) => boolean) => {
1282
- return (table.getRowModel().rows || [])?.filter(row => predicate(row.original))?.map(row => row.original);
1283
- },
1284
- findRowIndex: (predicate: (row: T) => boolean) => {
1285
- return (table.getRowModel().rows || [])?.findIndex(row => predicate(row.original));
1286
- },
1287
- getDataCount: () => {
1288
- return (table.getRowModel().rows || [])?.length || 0;
1289
- },
1290
- getFilteredDataCount: () => {
1291
- return table.getFilteredRowModel().rows.length;
1292
- },
1293
- },
1188
+ deleteMultipleRows: (rowIds: string[]) => {
1189
+ const idsToDelete = new Set(rowIds);
1190
+ const newData = (table.getRowModel().rows || [])
1191
+ .filter((row) => !idsToDelete.has(String(row.original[idKey])))
1192
+ .map((row) => row.original);
1193
+ setServerData(newData);
1194
+ },
1294
1195
 
1295
- // Layout Management
1296
- layout: {
1297
- resetLayout: () => {
1298
- table.resetColumnSizing();
1299
- table.resetColumnVisibility();
1300
- table.resetSorting();
1301
- table.resetGlobalFilter();
1302
- },
1303
- resetAll: () => {
1304
- // Reset everything to initial state
1305
- table.resetColumnSizing();
1306
- table.resetColumnVisibility();
1307
- table.resetSorting();
1308
- table.resetGlobalFilter();
1309
- table.resetColumnOrder();
1310
- table.resetExpanded();
1311
- handleSelectionStateChange(initialSelectionState);
1312
- table.resetColumnPinning();
1313
-
1314
- handleColumnFilterStateChange(initialStateConfig.columnFilter || { filters: [], logic: 'AND', pendingFilters: [], pendingLogic: 'AND' });
1315
-
1316
- if (enablePagination) {
1317
- table.setPagination(initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 });
1318
- }
1196
+ updateField: (rowId: string, fieldName: keyof T, value: any) => {
1197
+ const newData = (table.getRowModel().rows || []).map((row) =>
1198
+ String(row.original[idKey]) === rowId ? { ...row.original, [fieldName]: value } : row.original
1199
+ );
1200
+ setServerData?.(newData);
1201
+ },
1319
1202
 
1320
- if (enableColumnPinning) {
1321
- table.setColumnPinning(initialStateConfig.columnPinning || { left: [], right: [] });
1322
- }
1323
- },
1324
- saveLayout: () => {
1325
- return {
1203
+ updateFieldByIndex: (index: number, fieldName: keyof T, value: any) => {
1204
+ const newData = (table.getRowModel().rows || []).map((row) => row.original);
1205
+ if (newData[index]) {
1206
+ newData[index] = { ...newData[index], [fieldName]: value };
1207
+ setServerData?.(newData);
1208
+ }
1209
+ },
1210
+
1211
+ findRows: (predicate: (row: T) => boolean) =>
1212
+ (table.getRowModel().rows || []).filter((row) => predicate(row.original)).map((row) => row.original),
1213
+
1214
+ findRowIndex: (predicate: (row: T) => boolean) =>
1215
+ (table.getRowModel().rows || []).findIndex((row) => predicate(row.original)),
1216
+
1217
+ getDataCount: () => (table.getRowModel().rows || []).length || 0,
1218
+ getFilteredDataCount: () => table.getFilteredRowModel().rows.length,
1219
+ },
1220
+
1221
+ // -------------------------------
1222
+ // Layout Management
1223
+ // -------------------------------
1224
+ layout: {
1225
+ resetLayout: () => {
1226
+ // go through handlers so controlled state updates + emit works
1227
+ applySizing(initialStateConfig.columnSizing || {});
1228
+ applyVisibility(initialStateConfig.columnVisibility || {});
1229
+ applySorting(initialStateConfig.sorting || []);
1230
+ applyGlobalFilter(initialStateConfig.globalFilter ?? "");
1231
+ },
1232
+ resetAll: () => resetAllAndReload({ resetLayout: true }),
1233
+ saveLayout: () => ({
1326
1234
  columnVisibility: table.getState().columnVisibility,
1327
1235
  columnSizing: table.getState().columnSizing,
1328
1236
  columnOrder: table.getState().columnOrder,
@@ -1331,119 +1239,138 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
1331
1239
  pagination: table.getState().pagination,
1332
1240
  globalFilter: table.getState().globalFilter,
1333
1241
  columnFilter: table.getState().columnFilter,
1334
- };
1335
- },
1336
- restoreLayout: (layout: Partial<TableState>) => {
1337
- if (layout.columnVisibility) {
1338
- table.setColumnVisibility(layout.columnVisibility);
1339
- }
1340
- if (layout.columnSizing) {
1341
- table.setColumnSizing(layout.columnSizing);
1342
- }
1343
- if (layout.columnOrder) {
1344
- table.setColumnOrder(layout.columnOrder);
1345
- }
1346
- if (layout.columnPinning) {
1347
- table.setColumnPinning(layout.columnPinning);
1348
- }
1349
- if (layout.sorting) {
1350
- table.setSorting(layout.sorting);
1351
- }
1352
- if (layout.pagination && enablePagination) {
1353
- table.setPagination(layout.pagination);
1354
- }
1355
- if (layout.globalFilter !== undefined) {
1356
- table.setGlobalFilter(layout.globalFilter);
1357
- }
1358
- if (layout.columnFilter) {
1359
- handleColumnFilterStateChange(layout.columnFilter);
1360
- }
1361
- },
1362
- },
1363
-
1364
- // Table State
1365
- state: {
1366
- getTableState: () => {
1367
- return table.getState();
1368
- },
1369
- getCurrentFilters: () => {
1370
- return table.getState().columnFilter;
1371
- },
1372
- getCurrentSorting: () => {
1373
- return table.getState().sorting;
1374
- },
1375
- getCurrentPagination: () => {
1376
- return table.getState().pagination;
1377
- },
1378
- // Backward compatibility: expose the raw selection array expected by older consumers
1379
- getCurrentSelection: () => {
1380
- return table.getSelectionState?.();
1381
- },
1382
- },
1383
-
1384
- // Simplified Export
1385
- export: {
1386
- exportCSV: async (options: any = {}) => {
1387
- const { filename = exportFilename, } = options;
1388
-
1389
- try {
1390
- // Create abort controller for this export
1391
- const controller = new AbortController();
1392
- setExportController?.(controller);
1393
-
1394
- if (dataMode === 'server' && onServerExport) {
1395
- // Server export with selection data
1396
- const currentFilters = {
1397
- globalFilter: table.getState().globalFilter,
1398
- columnFilter: table.getState().columnFilter,
1399
- sorting: table.getState().sorting,
1400
- pagination: table.getState().pagination,
1401
- };
1402
- if (logger.isLevelEnabled('debug')) {
1403
- logger.debug('Server export CSV', { currentFilters });
1242
+ }),
1243
+ restoreLayout: (layout: Partial<TableState>) => {
1244
+ if (layout.columnVisibility) applyVisibility(layout.columnVisibility as any);
1245
+ if (layout.columnSizing) applySizing(layout.columnSizing as any);
1246
+ if (layout.columnOrder) applyColumnOrder(layout.columnOrder as any);
1247
+ if (layout.columnPinning) applyPinning(layout.columnPinning as any);
1248
+ if (layout.sorting) applySorting(layout.sorting as any);
1249
+ if (layout.pagination && enablePagination) applyPagination(layout.pagination as any);
1250
+ if (layout.globalFilter !== undefined) applyGlobalFilter(layout.globalFilter);
1251
+ if (layout.columnFilter) handleColumnFilterStateChange(layout.columnFilter as any);
1252
+ },
1253
+ },
1254
+
1255
+ // -------------------------------
1256
+ // Table State
1257
+ // -------------------------------
1258
+ state: {
1259
+ getTableState: () => table.getState(),
1260
+ getCurrentFilters: () => table.getState().columnFilter,
1261
+ getCurrentSorting: () => table.getState().sorting,
1262
+ getCurrentPagination: () => table.getState().pagination,
1263
+ getCurrentSelection: () => table.getSelectionState?.(),
1264
+ },
1265
+
1266
+ // -------------------------------
1267
+ // Export (unchanged mostly)
1268
+ // -------------------------------
1269
+ export: {
1270
+ exportCSV: async (options: any = {}) => {
1271
+ const { filename = exportFilename } = options;
1272
+
1273
+ try {
1274
+ const controller = new AbortController();
1275
+ setExportController?.(controller);
1276
+
1277
+ if (dataMode === "server" && onServerExport) {
1278
+ const currentFilters = {
1279
+ globalFilter: table.getState().globalFilter,
1280
+ columnFilter: table.getState().columnFilter,
1281
+ sorting: table.getState().sorting,
1282
+ pagination: table.getState().pagination,
1283
+ };
1284
+
1285
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export CSV", { currentFilters });
1286
+
1287
+ await exportServerData(table, {
1288
+ format: "csv",
1289
+ filename,
1290
+ fetchData: (filters: any, selection: any) => onServerExport(filters, selection),
1291
+ currentFilters,
1292
+ selection: table.getSelectionState?.(),
1293
+ onProgress: onExportProgress,
1294
+ onComplete: onExportComplete,
1295
+ onError: onExportError,
1296
+ });
1297
+ } else {
1298
+ await exportClientData(table, {
1299
+ format: "csv",
1300
+ filename,
1301
+ onProgress: onExportProgress,
1302
+ onComplete: onExportComplete,
1303
+ onError: onExportError,
1304
+ });
1305
+
1306
+ if (logger.isLevelEnabled("debug")) logger.debug("Client export CSV", filename);
1404
1307
  }
1405
- await exportServerData(table, {
1406
- format: 'csv',
1407
- filename,
1408
- fetchData: (filters, selection) => onServerExport(filters, selection),
1409
- currentFilters,
1410
- selection: table.getSelectionState?.(),
1411
- onProgress: onExportProgress,
1412
- onComplete: onExportComplete,
1413
- onError: onExportError,
1414
- });
1415
- } else {
1416
- // Client export - auto-detect selected rows if not specified
1417
- await exportClientData(table, {
1418
- format: 'csv',
1419
- filename,
1420
- onProgress: onExportProgress,
1421
- onComplete: onExportComplete,
1422
- onError: onExportError,
1423
- });
1424
- if (logger.isLevelEnabled('debug')) {
1425
- logger.debug('Client export CSV', filename);
1308
+ } catch (error: any) {
1309
+ onExportError?.({ message: error.message || "Export failed", code: "EXPORT_ERROR" });
1310
+ } finally {
1311
+ setExportController?.(null);
1312
+ }
1313
+ },
1314
+
1315
+ exportExcel: async (options: any = {}) => {
1316
+ const { filename = exportFilename } = options;
1317
+
1318
+ try {
1319
+ const controller = new AbortController();
1320
+ setExportController?.(controller);
1321
+
1322
+ if (dataMode === "server" && onServerExport) {
1323
+ const currentFilters = {
1324
+ globalFilter: table.getState().globalFilter,
1325
+ columnFilter: table.getState().columnFilter,
1326
+ sorting: table.getState().sorting,
1327
+ pagination: table.getState().pagination,
1328
+ };
1329
+
1330
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export Excel", { currentFilters });
1331
+
1332
+ await exportServerData(table, {
1333
+ format: "excel",
1334
+ filename,
1335
+ fetchData: (filters: any, selection: any) => onServerExport(filters, selection),
1336
+ currentFilters,
1337
+ selection: table.getSelectionState?.(),
1338
+ onProgress: onExportProgress,
1339
+ onComplete: onExportComplete,
1340
+ onError: onExportError,
1341
+ });
1342
+ } else {
1343
+ await exportClientData(table, {
1344
+ format: "excel",
1345
+ filename,
1346
+ onProgress: onExportProgress,
1347
+ onComplete: onExportComplete,
1348
+ onError: onExportError,
1349
+ });
1350
+
1351
+ if (logger.isLevelEnabled("debug")) logger.debug("Client export Excel", filename);
1426
1352
  }
1353
+ } catch (error: any) {
1354
+ onExportError?.({ message: error.message || "Export failed", code: "EXPORT_ERROR" });
1355
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export Excel failed", error);
1356
+ } finally {
1357
+ setExportController?.(null);
1427
1358
  }
1428
- } catch (error: any) {
1429
- onExportError?.({
1430
- message: error.message || 'Export failed',
1431
- code: 'EXPORT_ERROR',
1432
- });
1433
- } finally {
1434
- setExportController?.(null);
1435
- }
1436
- },
1437
- exportExcel: async (options: any = {}) => {
1438
- const { filename = exportFilename } = options;
1359
+ },
1360
+
1361
+ exportServerData: async (options: any) => {
1362
+ const { format, filename = exportFilename, fetchData: fetchFn = onServerExport } = options;
1439
1363
 
1440
- try {
1441
- // Create abort controller for this export
1442
- const controller = new AbortController();
1443
- setExportController?.(controller);
1364
+ if (!fetchFn) {
1365
+ onExportError?.({ message: "No server export function provided", code: "NO_SERVER_EXPORT" });
1366
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export data failed", "No server export function provided");
1367
+ return;
1368
+ }
1369
+
1370
+ try {
1371
+ const controller = new AbortController();
1372
+ setExportController?.(controller);
1444
1373
 
1445
- if (dataMode === 'server' && onServerExport) {
1446
- // Server export with selection data
1447
1374
  const currentFilters = {
1448
1375
  globalFilter: table.getState().globalFilter,
1449
1376
  columnFilter: table.getState().columnFilter,
@@ -1451,131 +1378,62 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
1451
1378
  pagination: table.getState().pagination,
1452
1379
  };
1453
1380
 
1454
- if (logger.isLevelEnabled('debug')) {
1455
- logger.debug('Server export Excel', { currentFilters });
1456
- }
1381
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export data", { currentFilters });
1382
+
1457
1383
  await exportServerData(table, {
1458
- format: 'excel',
1384
+ format,
1459
1385
  filename,
1460
- fetchData: (filters, selection) => onServerExport(filters, selection),
1386
+ fetchData: (filters: any, selection: any) => fetchFn(filters, selection),
1461
1387
  currentFilters,
1462
1388
  selection: table.getSelectionState?.(),
1463
1389
  onProgress: onExportProgress,
1464
1390
  onComplete: onExportComplete,
1465
1391
  onError: onExportError,
1466
1392
  });
1467
- } else {
1468
- // Client export - auto-detect selected rows if not specified
1469
- await exportClientData(table, {
1470
- format: 'excel',
1471
- filename,
1472
- onProgress: onExportProgress,
1473
- onComplete: onExportComplete,
1474
- onError: onExportError,
1475
- });
1476
- if (logger.isLevelEnabled('debug')) {
1477
- logger.debug('Client export Excel', filename);
1478
- }
1479
- }
1480
- } catch (error: any) {
1481
- onExportError?.({
1482
- message: error.message || 'Export failed',
1483
- code: 'EXPORT_ERROR',
1484
- });
1485
- if (logger.isLevelEnabled('debug')) {
1486
- logger.debug('Server export Excel failed', error);
1393
+ } catch (error: any) {
1394
+ onExportError?.({ message: error.message || "Export failed", code: "EXPORT_ERROR" });
1395
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export data failed", error);
1396
+ } finally {
1397
+ setExportController?.(null);
1487
1398
  }
1488
- } finally {
1489
- setExportController?.(null);
1490
- }
1491
- },
1492
- exportServerData: async (options) => {
1493
- const {
1494
- format,
1495
- filename = exportFilename,
1496
- fetchData = onServerExport,
1497
- } = options;
1498
-
1499
- if (!fetchData) {
1500
- onExportError?.({
1501
- message: 'No server export function provided',
1502
- code: 'NO_SERVER_EXPORT',
1503
- });
1504
- if (logger.isLevelEnabled('debug')) {
1505
- logger.debug('Server export data failed', 'No server export function provided');
1506
- }
1507
- return;
1508
- }
1509
-
1510
- try {
1511
- // Create abort controller for this export
1512
- const controller = new AbortController();
1513
- setExportController?.(controller);
1399
+ },
1514
1400
 
1515
- const currentFilters = {
1516
- globalFilter: table.getState().globalFilter,
1517
- columnFilter: table.getState().columnFilter,
1518
- sorting: table.getState().sorting,
1519
- pagination: table.getState().pagination,
1520
- };
1521
- if (logger.isLevelEnabled('debug')) {
1522
- logger.debug('Server export data', { currentFilters });
1523
- }
1524
- await exportServerData(table, {
1525
- format,
1526
- filename,
1527
- fetchData: (filters, selection) => fetchData(filters, selection),
1528
- currentFilters,
1529
- selection: table.getSelectionState?.(),
1530
- onProgress: onExportProgress,
1531
- onComplete: onExportComplete,
1532
- onError: onExportError,
1533
- });
1534
- } catch (error: any) {
1535
- onExportError?.({
1536
- message: error.message || 'Export failed',
1537
- code: 'EXPORT_ERROR',
1538
- });
1539
- if (logger.isLevelEnabled('debug')) {
1540
- logger.debug('Server export data failed', error);
1541
- }
1542
- } finally {
1401
+ isExporting: () => isExporting || false,
1402
+ cancelExport: () => {
1403
+ exportController?.abort();
1543
1404
  setExportController?.(null);
1544
- }
1405
+ if (logger.isLevelEnabled("debug")) logger.debug("Export cancelled");
1406
+ },
1545
1407
  },
1546
- // Export state
1547
- isExporting: () => isExporting || false,
1548
- cancelExport: () => {
1549
- exportController?.abort();
1550
- setExportController?.(null);
1551
- if (logger.isLevelEnabled('debug')) {
1552
- logger.debug('Export cancelled');
1553
- }
1554
- },
1555
- },
1408
+ };
1556
1409
  // eslint-disable-next-line react-hooks/exhaustive-deps
1557
- }), [
1410
+ }, [
1558
1411
  table,
1559
1412
  enhancedColumns,
1413
+ handleColumnOrderChange,
1414
+ handleColumnPinningChange,
1415
+ handleColumnVisibilityChange,
1416
+ handleColumnSizingChange,
1417
+ handlePaginationChange,
1418
+ handleSortingChange,
1419
+ handleGlobalFilterChange,
1560
1420
  handleColumnFilterStateChange,
1421
+ initialStateConfig,
1422
+ enablePagination,
1561
1423
  idKey,
1562
1424
  onDataStateChange,
1563
1425
  fetchData,
1564
- enableColumnPinning,
1565
- enablePagination,
1566
- // Export dependencies
1426
+ // export
1567
1427
  exportFilename,
1568
1428
  onExportProgress,
1569
1429
  onExportComplete,
1570
1430
  onExportError,
1571
1431
  onServerExport,
1572
1432
  exportController,
1573
- setExportController,
1574
1433
  isExporting,
1575
1434
  dataMode,
1576
- selectMode,
1577
- onSelectionChange,
1578
- // Note: custom selection removed from dependency array
1435
+ logger,
1436
+ resetAllAndReload,
1579
1437
  ]);
1580
1438
 
1581
1439
  internalApiRef.current = dataTableApi;
@@ -1821,7 +1679,15 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
1821
1679
  enableReset={enableReset}
1822
1680
  enableTableSizeControl={enableTableSizeControl}
1823
1681
  enableColumnPinning={enableColumnPinning}
1682
+ enableRefresh={enableRefresh}
1824
1683
  {...toolbarSlotProps}
1684
+ refreshButtonProps={{
1685
+ loading: tableLoading, // disable while fetching
1686
+ showSpinnerWhileLoading: false,
1687
+ onRefresh: () => internalApiRef.current?.data?.refresh?.(true),
1688
+ ...toolbarSlotProps.refreshButtonProps,
1689
+ }}
1690
+
1825
1691
  />
1826
1692
  ) : null}
1827
1693