"use client" import { useState, useMemo, useEffect, useRef } from "react" import { useReactTable, getCoreRowModel, flexRender, type ColumnDef, type RowSelectionState, } from "@tanstack/react-table" import { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, } from "@/shared/components/ui/table" import { Checkbox } from "@/shared/components/ui/checkbox" import { DataViewProvider } from "./data-view-context" import type { DataViewProps } from "./types" import { DataViewPagination } from "./data-view-pagination" import { Skeleton } from "@/shared/components/ui/skeleton" export function DataTable({ columns, data, pagination, sorting = [], onChange, isLoading = false, onRowClick, slots, selection, }: DataViewProps) { const rowKeyStr = (selection?.rowKey as string) ?? "id" // Persisted map of id → original row data across all pages const persistedMap = useRef>(new Map()) // Current-page selection state that TanStack Table controls const [rowSelection, setRowSelection] = useState({}) // When the page/data changes, restore selection state for the new page from the map useEffect(() => { if (!selection) return const restored: RowSelectionState = {} data.forEach((row) => { const id = String((row as Record)[rowKeyStr]) if (persistedMap.current.has(id)) restored[id] = true }) setRowSelection(restored) }, [data]) // eslint-disable-line react-hooks/exhaustive-deps const selectionColumn: ColumnDef = useMemo( () => ({ id: "__select__", header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value)} aria-label="Select all" /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} aria-label="Select row" onClick={(e) => e.stopPropagation()} /> ), size: 40, enableSorting: false, }), [], ) const resolvedColumns = useMemo( () => selection ? [selectionColumn, ...(columns as ColumnDef[])] : (columns as ColumnDef[]), [selection, columns, selectionColumn], ) const table = useReactTable({ data, columns: resolvedColumns, getCoreRowModel: getCoreRowModel(), manualPagination: true, manualSorting: true, pageCount: pagination.pageCount, enableRowSelection: !!selection, getRowId: selection ? (row) => String((row as Record)[(selection.rowKey as string) ?? "id"]) : undefined, state: { sorting, pagination: { pageIndex: pagination.page - 1, pageSize: pagination.pageSize, }, rowSelection, }, onSortingChange: (updater) => { const next = typeof updater === "function" ? updater(sorting) : updater onChange?.({ type: "sorting", sorting: next }) }, onPaginationChange: (updater) => { const current = { pageIndex: pagination.page - 1, pageSize: pagination.pageSize } const next = typeof updater === "function" ? updater(current) : updater onChange?.({ type: "pagination", pagination: { page: next.pageIndex + 1, pageSize: next.pageSize, pageCount: pagination.pageCount, total: pagination.total, }, }) }, onRowSelectionChange: (updater) => { const next = typeof updater === "function" ? updater(rowSelection) : updater setRowSelection(next) if (selection) { // Sync current page into the persisted map data.forEach((row) => { const id = String((row as Record)[rowKeyStr]) if (next[id]) { persistedMap.current.set(id, row) } else { persistedMap.current.delete(id) } }) selection.onSelectionChange(Array.from(persistedMap.current.values())) } }, }) return (
{slots?.actions && (
{slots.actions}
)}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext(), )} ))} ))} {slots?.extraHeader} {isLoading ? ( Array.from({ length: pagination.pageSize }).map((_, i) => ( {columns.map((_, j) => ( ))} )) ) : table.getRowModel().rows.length ? ( table.getRowModel().rows.map((row) => ( onRowClick?.(row.original)} > {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} )) ) : ( No results. )} {slots?.extraBody} {slots?.footer && ( {slots.footer} )}
) }