'use client'; 'use no memo'; // fixes rerendering with react compiler, v9 of tanstack table will fix this import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Skeleton } from '@/components/ui/skeleton'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { type ColumnFiltersState, type RowData, type SortingState, type VisibilityState, flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table'; import { MoreVertical } from 'lucide-react'; import { type ComponentType, useMemo, useState } from 'react'; import { DataTablePagination, type DataTablePaginationOptions } from './data-table-pagination'; import { DataTableToolbar, type DataTableToolbarOptions } from './data-table-toolbar'; /** * Props for the DataTable component. * @template TData The type of data in the table. * @template TValue The type of values in the table cells. */ export interface DataTableRowAction { label: string; component: (row: TData) => React.ReactNode; } interface DataTableProps { /** The column definitions for the table. */ columns: Parameters>[0]['columns']; /** The data to be displayed in the table. */ data: TData[]; isLoading?: boolean; icons?: Record>; name: string; rowActions?: DataTableRowAction[]; toolbar?: DataTableToolbarOptions; pagination?: DataTablePaginationOptions; initialSorting?: SortingState; } declare module '@tanstack/table-core' { // biome-ignore lint/correctness/noUnusedVariables: required for table meta interface TableMeta { name: string; icons?: Record>; } } declare module '@tanstack/react-table' { // biome-ignore lint/correctness/noUnusedVariables: required for table meta interface ColumnMeta { enableCsvExport?: boolean; } } /** /** * A reusable data table component with sorting, filtering, and pagination. * @template TData The type of data in the table. * @template TValue The type of values in the table cells. * @param props The component props. * @returns The rendered DataTable component. */ export function DataTable({ columns, data, isLoading, icons, name, toolbar, pagination, rowActions, initialSorting, }: DataTableProps) { const [rowSelection, setRowSelection] = useState({}); const [sorting, setSorting] = useState(initialSorting ?? []); const [columnFilters, setColumnFilters] = useState([]); const [columnVisibility, setColumnVisibility] = useState({}); const [globalFilter, setGlobalFilter] = useState(''); const memoizedColumns = useMemo(() => { if (!rowActions?.length) { return columns; } return [ ...columns, { id: 'actions', cell: ({ row }) => , }, ]; }, [columns, rowActions]); const memoizedData = useMemo(() => data, [data]); const table = useReactTable({ data: memoizedData, columns: memoizedColumns, enableRowSelection: true, enableGlobalFilter: true, enableColumnFilters: true, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), globalFilterFn: 'includesString', state: { sorting, columnFilters, columnVisibility, rowSelection, globalFilter, }, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, onSortingChange: setSorting, onRowSelectionChange: setRowSelection, onGlobalFilterChange: setGlobalFilter, meta: { name, icons, }, }); const renderTableBody = () => { if (isLoading) { return Array.from({ length: 5 }).map((_, index) => ( {columns.map((_column, cellIndex) => ( ))} )); } if (table.getRowModel().rows?.length) { return table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} )); } return ( No results ); }; return (
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} ); })} ))} {renderTableBody()}
{table.getRowModel().rows?.length > 0 && }
); } function DataTableRowActions({ row, actions, }: { row: TData; actions: DataTableRowAction[]; }) { return ( {actions?.map((action, index) => ( {action.component(row)} ))} ); }