'use client'; import { useState } from 'react'; import { flexRender, getCoreRowModel, useReactTable, type ColumnDef, type RowSelectionState, } from '@tanstack/react-table'; import { ArrowDown, ArrowUp, ArrowUpDown, Loader2 } from 'lucide-react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { cn } from '@/lib/utils'; export interface DataTablePagination { page: number; pageSize: number; total: number; totalPages: number; } export interface BulkAction { label: string; icon?: React.ElementType; variant?: 'default' | 'destructive'; onClick: (selectedIds: string[]) => void; } interface DataTableProps { columns: ColumnDef[]; data: TData[]; pagination?: DataTablePagination; onPaginationChange?: (page: number, pageSize: number) => void; sort?: { field: string; direction: 'asc' | 'desc' }; onSortChange?: (field: string, direction: 'asc' | 'desc') => void; rowSelection?: RowSelectionState; onRowSelectionChange?: (selection: RowSelectionState) => void; bulkActions?: BulkAction[]; emptyState?: React.ReactNode; isLoading?: boolean; getRowId?: (row: TData) => string; onRowClick?: (row: TData) => void; } export function DataTable({ columns, data, pagination, onPaginationChange, sort, onSortChange, rowSelection: externalSelection, onRowSelectionChange, bulkActions, emptyState, isLoading, getRowId, onRowClick, }: DataTableProps) { const [internalSelection, setInternalSelection] = useState({}); const rowSelectionState = externalSelection ?? internalSelection; const setRowSelection = onRowSelectionChange ?? setInternalSelection; const allColumns: ColumnDef[] = []; if (bulkActions && bulkActions.length > 0) { allColumns.push({ id: 'select', header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value)} aria-label="Select all" className="translate-y-[2px]" /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} aria-label="Select row" className="translate-y-[2px]" onClick={(e) => e.stopPropagation()} /> ), enableSorting: false, size: 40, }); } allColumns.push(...columns); const table = useReactTable({ data, columns: allColumns, getCoreRowModel: getCoreRowModel(), manualPagination: true, manualSorting: true, rowCount: pagination?.total ?? data.length, state: { rowSelection: rowSelectionState, pagination: pagination ? { pageIndex: pagination.page - 1, pageSize: pagination.pageSize } : undefined, }, onRowSelectionChange: (updater) => { const newSelection = typeof updater === 'function' ? updater(rowSelectionState) : updater; setRowSelection(newSelection); }, getRowId: getRowId as (row: TData, index: number) => string, enableRowSelection: !!bulkActions?.length, }); const selectedIds = Object.keys(rowSelectionState).filter((k) => rowSelectionState[k]); function handleSort(columnId: string) { if (!onSortChange) return; if (sort?.field === columnId) { onSortChange(columnId, sort.direction === 'asc' ? 'desc' : 'asc'); } else { onSortChange(columnId, 'asc'); } } function getSortIcon(columnId: string) { if (sort?.field !== columnId) return ; return sort.direction === 'asc' ? ( ) : ( ); } return (
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( { if ( header.column.getCanSort() && onSortChange && header.column.id !== 'select' ) { handleSort(header.column.id); } }} >
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} {header.column.getCanSort() && onSortChange && header.column.id !== 'select' && header.column.id !== 'actions' && getSortIcon(header.column.id)}
))}
))}
{isLoading ? (
Loading...
) : table.getRowModel().rows.length === 0 ? ( {emptyState ?? (
No results.
)}
) : ( table.getRowModel().rows.map((row) => ( onRowClick?.(row.original)} > {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} )) )}
{/* Pagination */} {pagination && pagination.totalPages > 1 && (
{selectedIds.length > 0 ? `${selectedIds.length} of ${pagination.total} row(s) selected` : `${pagination.total} row(s) total`}
Page {pagination.page} of {pagination.totalPages}
)} {/* Bulk actions bar */} {bulkActions && selectedIds.length > 0 && (
{selectedIds.length} selected {bulkActions.map((action) => ( ))}
)}
); }