import { Fragment, useRef } from 'react'; import { ColumnDef, flexRender, Row, Table as TableType, } from '@tanstack/react-table'; import { useVirtualizer, VirtualItem, Virtualizer, } from '@tanstack/react-virtual'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; interface ColumnMeta { isVirtual?: boolean; cellClassName?: string; cellStyle?: React.CSSProperties; } interface DataTableProps { table: TableType; columns: ColumnDef[]; emptyMessage?: string; renderRow?: ( row: Row, virtualRow: VirtualItem, rowVirtualizer: Virtualizer, ) => React.ReactNode; renderDateHeader?: ( date: string, colSpan: number, ) => React.ReactNode; getRowDate?: (row: TData) => string; maxHeight?: number; /** Static content pinned as the first row of the table body, spanning all columns. */ topRow?: React.ReactNode; } export function DataTable({ table, columns, emptyMessage = 'No results found.', renderRow, renderDateHeader, getRowDate, maxHeight, topRow, }: DataTableProps) { const tableContainerRef = useRef(null); const rows = table.getRowModel().rows; const rowVirtualizer = useVirtualizer({ count: rows.length, getScrollElement: () => tableContainerRef.current, estimateSize: () => 72, overscan: 12, }); const virtualRows = rowVirtualizer.getVirtualItems(); const totalSize = rowVirtualizer.getTotalSize(); const paddingTop = virtualRows.length > 0 ? virtualRows[0].start : 0; const paddingBottom = virtualRows.length > 0 ? totalSize - virtualRows[virtualRows.length - 1].end : 0; const visibleColumnCount = table.getVisibleLeafColumns().length || columns.length || 1; const showDateHeaders = renderDateHeader && getRowDate && table.getState().columnVisibility.transaction_date !== false; return (
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { const meta = header.column.columnDef .meta as ColumnMeta | undefined; if (meta?.isVirtual) { return null; } return ( {header.isPlaceholder ? null : flexRender( header.column.columnDef .header, header.getContext(), )} ); })} ))} {topRow && ( {topRow} )} {rows.length ? ( <> {paddingTop > 0 && ( )} {virtualRows.map((virtualRow) => { const row = rows[virtualRow.index]; const prevRow = virtualRow.index > 0 ? rows[virtualRow.index - 1] : null; const currentDate = showDateHeaders && getRowDate ? getRowDate(row.original) : null; const prevDate = showDateHeaders && getRowDate && prevRow ? getRowDate(prevRow.original) : null; const showDateHeader = showDateHeaders && currentDate && currentDate !== prevDate; if (renderRow) { return ( {showDateHeader && renderDateHeader && renderDateHeader( currentDate, visibleColumnCount, )} {renderRow( row, virtualRow, rowVirtualizer, )} ); } return ( {showDateHeader && renderDateHeader && renderDateHeader( currentDate, visibleColumnCount, )} {row .getVisibleCells() .map((cell) => { const meta = cell.column .columnDef.meta as | ColumnMeta | undefined; if (meta?.isVirtual) { return null; } return ( {flexRender( cell.column .columnDef .cell, cell.getContext(), )} ); })} ); })} {paddingBottom > 0 && ( )} ) : ( {emptyMessage} )}
); }