import { ColumnDef, ColumnFiltersState, flexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, SortingState, useReactTable, VisibilityState, } from '@tanstack/react-table'; import clsx from 'clsx'; import { ArrowUpDown } from 'lucide-react'; import { KeyboardEvent, useEffect, useRef, useState } from 'react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { defaultColumns } from '@/lib/constants'; import { TableData, useTableStore } from '@/store/tableStore'; import AddColumn from './AddColumn'; import TableCellInput from './TableCellInput'; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, } from './ui/context-menu'; interface DataTableProps { data: TableData; } export function DataTable({ data }: DataTableProps) { const tableRef = useRef(null); const { addRow, deleteRows, deleteColumn } = useTableStore(); const [sorting, setSorting] = useState([]); const [focusedCell, setFocusedCell] = useState<{ rowIndex: number; columnIndex: number; } | null>(null); const [rowSelection, setRowSelection] = useState({}); const [columnFilters, setColumnFilters] = useState([]); const [columnVisibility, setColumnVisibility] = useState({}); const columns: ColumnDef[] = defaultColumns.concat( data.columns.map((column) => ({ accessorKey: column.name, header: ({ column: curColumn }) => { return ( ); }, cell: ({ row, column: curColumn, getValue }) => { const initialValue = getValue() as string; return ( ); }, })) ); const table = useReactTable({ data: data.rows, columns, onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), onRowSelectionChange: setRowSelection, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, state: { sorting, rowSelection, columnFilters, columnVisibility, }, }); useEffect(() => { if (focusedCell && tableRef.current) { const cell = tableRef.current.querySelector( `[data-row="${focusedCell.rowIndex}"][data-col="${focusedCell.columnIndex}"]` ) as HTMLElement; if (cell) { cell.focus(); } } }, [focusedCell]); const handleKeyDown = (e: KeyboardEvent) => { if (!focusedCell) return; const { rowIndex, columnIndex } = focusedCell; const visibleColumns = table.getVisibleLeafColumns(); const rows = table.getRowModel().rows; switch (e.key) { case 'ArrowUp': if (rowIndex > 0) setFocusedCell({ rowIndex: rowIndex - 1, columnIndex }); break; case 'ArrowDown': if (rowIndex < rows.length - 1) setFocusedCell({ rowIndex: rowIndex + 1, columnIndex }); break; case 'ArrowLeft': if (columnIndex > 0) setFocusedCell({ rowIndex, columnIndex: columnIndex - 1 }); break; case 'ArrowRight': if (columnIndex < visibleColumns.length - 1) setFocusedCell({ rowIndex, columnIndex: columnIndex + 1 }); break; } }; const handleAddRow = () => { const newRow = data.columns.reduce( (acc, col) => { acc[col.name] = ''; return acc; }, {} as Record ); addRow(data.id, newRow); }; const handleRemoveSelectedRows = () => { const selectedRowIds = Object.keys(rowSelection); deleteRows( data.id, selectedRowIds.map((id) => parseInt(id, 10)) ); setRowSelection({}); }; const handleHideColumn = (columnId: string) => { setColumnVisibility((prev) => ({ ...prev, [columnId]: false, })); }; const handleDeleteColumn = (columnId: string) => { deleteColumn(data.id, columnId); }; return (
{table .getAllColumns() .filter((column) => column.getCanHide()) .map((column) => { return ( column.toggleVisibility(!!value) } > {column.id} ); })}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} handleHideColumn(header.column.id)} > Hide handleDeleteColumn(header.column.id)} > Delete
header.column.resetSize(), onMouseDown: header.getResizeHandler(), onTouchStart: header.getResizeHandler(), className: `resizer ${ table.options.columnResizeDirection } ${header.column.getIsResizing() ? 'isResizing' : ''}`, style: { transform: header.column.getIsResizing() ? `translateX(${ (table.options.columnResizeDirection === 'rtl' ? -1 : 1) * (table.getState().columnSizingInfo .deltaOffset ?? 0) }px)` : '', }, }} /> ); })} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row, rowIndex) => ( {row.getVisibleCells().map((cell, columnIndex) => ( { if (e.target === e.currentTarget) { setFocusedCell({ rowIndex, columnIndex }); } }} style={{ width: cell.column.getSize() }} className={clsx('border', { 'px-1 py-0': columnIndex !== 0, })} > {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} )) ) : ( No results. )}
); }