import type { SortKey } from 'hooks/useDataControl'; import { useState, useEffect } from 'react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { ArrowUpDown } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { useDataControl, type UseDataControlReturn } from 'hooks/useDataControl'; import { PaginationBar } from './PaginationBar'; import { cn } from 'helpers/cn'; import { useTranslation } from '@/components/locales'; export const DataTable = (props: DataTableProps) => { const { t } = useTranslation(); const [columns, setColumns] = useState[]>([]); const { dataController, uniqueKey, noDataCTA, noDataMessage } = props; const theUniqueKey = uniqueKey || 'id'; const { rawData, data, pageSize, sortBy, sortedBy, setCustomSort } = dataController; useEffect(() => { if (props.columns) { setColumns(props.columns); } else if (!!rawData && rawData.length > 0) { const cols: Column[] = Object.keys(rawData[0] as object).map((key) => ({ field: key as keyof T, label: toLabel(key), sortKey: key as SortKey, })); setColumns(cols); } else { setColumns([]); } }, [props.columns, rawData]); useEffect(() => { if (typeof props.pageSize === 'number') { dataController.setPageSize(props.pageSize); } }, [props.pageSize]); useEffect(() => { if (!!props.customSort) { setCustomSort(() => props.customSort!); } }, [props.customSort]); useEffect(() => { if (props.searchKeys) { const propsSearchKeys = JSON.stringify(props.searchKeys); const controllerSearchKeys = JSON.stringify(dataController.searchKeys); if (propsSearchKeys !== controllerSearchKeys) { dataController.setSearchKeys(props.searchKeys); } } else if (columns.length > 0 && !dataController.searchKeys) { const colFields = columns.map((col) => col.field).filter((f): f is keyof T => !!f); dataController.setSearchKeys(colFields); } }, [props.searchKeys, columns.length]); if (!rawData) return null; if (rawData.length === 0) { return (
{noDataMessage || t('dataTable.empty')}
{noDataCTA && <>{noDataCTA}}
); } return (
{columns.map((column: Column, idx: number) => { const { label = '', sortKey, field, condition = true } = column; const { tooltip, headerClassName, labelMobile } = column; const renderColumn = typeof condition === 'function' ? condition() : condition; return ( renderColumn && (
sortBy(sortKey as Extract | undefined)} > {String(label || field || '')} {tooltip ? ⓘ : ''} {String(labelMobile || label || field || '')} {tooltip ? ⓘ : ''} {sortKey && }
{tooltip && (

{tooltip}

)}
) ); })}
{data?.map((item: T, idx: number) => { return ( {columns.map((column, idx) => { const value = column.field !== undefined ? item[column.field] : undefined; const { format, condition = true, cellClassName } = column; const renderColumn = typeof condition === 'function' ? condition() : condition; return ( renderColumn && ( <> {format ? column.field !== undefined ? format({ value: item[column.field], item, data, idx, }) : format({ value: undefined as unknown as T[keyof T], item, data, idx, }) : value} ) ); })} ); })}
{!!pageSize && pageSize < rawData?.length && ( } /> )}
); }; export { useDataControl }; export type { UseDataControlReturn }; type DataTableProps = { dataController: UseDataControlReturn; columns?: Column[]; uniqueKey?: keyof T; searchKeys?: (keyof T)[]; pageSize?: number; noDataCTA?: React.ReactNode; customSort?: (a: T, b: T, originalSort: (a: T, b: T) => number) => number; noDataMessage?: string; }; export type DataTableFormatArgs = { value: T[keyof T]; item: T; data: T[]; idx: number; }; export type Column = { field?: keyof T; label?: string; labelMobile?: string; headerClassName?: string; cellClassName?: string; format?: (args: DataTableFormatArgs) => React.ReactNode; condition?: boolean | (() => boolean); sortKey?: SortKey; tooltip?: string; }; function toLabel(key: string): string { return key .replace(/([a-z0-9])([A-Z])/g, '$1 $2') // insert space before capitals .replace(/_/g, ' ') // replace underscores with spaces .replace(/\s+/g, ' ') // collapse multiple spaces .trim() .replace(/\w\S*/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()); }