import { Box, Button, Card, CardBody, Flex, FormControl, FormLabel, Input, Popover, PopoverArrow, PopoverBody, PopoverCloseButton, PopoverContent, PopoverTrigger, Spacer, Table, TableCaption, TableContainer, Tbody, Td, Th, Thead, Tr, useDisclosure, } from "@chakra-ui/react"; import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { Filter } from "lucide-react"; import { ChangeEvent, ReactNode } from "react"; import { useDebouncedCallback } from "use-debounce"; export type DataTableColumnDef = ColumnDef & { filterable?: boolean; }; // TODO: stricter type export type FilterItem = { id: string; value: string; }; export type DataTableProps = { data: T[]; columns: DataTableColumnDef[]; caption?: string; filterValues?: FilterItem[]; onNextClick?: () => void; onPreviousClick?: () => void; onFilterChange?: (items: FilterItem[]) => void; disableNext?: boolean; disablePrevious?: boolean; }; export const DataTable = ({ data, columns, caption, filterValues = [], onNextClick, onPreviousClick, onFilterChange, disableNext, disablePrevious, }: DataTableProps) => { const { getHeaderGroups, getRowModel } = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), }); const handleFilterChange = (value: FilterItem) => { const idx = filterValues.findIndex((oldValue) => oldValue.id === value.id); let newValues: FilterItem[] = []; if (idx === -1) { newValues = [...filterValues, value]; } else { newValues = filterValues.map((oldValue) => (oldValue.id === value.id ? value : oldValue)); } onFilterChange(newValues); }; return ( {caption} {getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} ))} {getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( ))} ))}
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} {(header.column.columnDef as DataTableColumnDef).filterable && ( value.id === header.id)?.value ?? ""} onChange={(value) => handleFilterChange({ id: header.id, value })} label={flexRender(header.column.columnDef.header, header.getContext())} > )}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
); }; const FilterModal = ({ label, onChange, value, }: { label: ReactNode; onChange: (val: string) => void; value: string; }) => { const { isOpen, onOpen, onClose } = useDisclosure(); const handleInputChange = useDebouncedCallback((e: ChangeEvent) => { onChange(e.target.value); }, 500); return ( {label} ); };