import { cn } from '@/lib/utils'; import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; import { ColumnDef, flexRender, getCoreRowModel, getExpandedRowModel, getSortedRowModel, Row, SortingState, useReactTable, } from '@tanstack/react-table'; import { useState, useRef } from 'react'; import { TableVirtuoso } from 'react-virtuoso'; // Tooltip wrapper for cells that might overflow function CellWithTooltip({ children, className }: { children: React.ReactNode; className?: string }) { const [showTooltip, setShowTooltip] = useState(false); const [tooltipContent, setTooltipContent] = useState(''); const cellRef = useRef(null); const handleMouseEnter = () => { const element = cellRef.current; if (element) { // Get the text content from the element or its children const textContent = element.textContent || ''; setTooltipContent(textContent); // Check if content is overflowing by comparing scroll width to client width const isOverflowing = element.scrollWidth > element.clientWidth; if (isOverflowing && textContent.trim().length > 0) { setShowTooltip(true); } } }; const handleMouseLeave = () => { setShowTooltip(false); }; return (
{children}
{showTooltip && tooltipContent && (
{tooltipContent}
)}
); } interface DataTableProps { data: T[]; columns: ColumnDef[]; loading?: boolean; className?: string; getRowCanExpand?: (row: Row) => boolean; renderSubComponent?: (props: { row: Row }) => React.ReactElement; onRowClick?: (row: T) => void; height?: number; } export function DataTable({ data, columns, loading = false, className = '', getRowCanExpand, renderSubComponent, onRowClick, height, }: DataTableProps) { const [sorting, setSorting] = useState([]); const table = useReactTable({ data, columns, state: { sorting, }, onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getExpandedRowModel: getExpandedRowModel(), getRowCanExpand, enableColumnResizing: true, columnResizeMode: 'onChange', }); if (loading) { return (
); } const { rows } = table.getRowModel(); // For expanded rows, we need to create a flattened list const flatRows = rows.reduce }>>((acc, row) => { acc.push({ type: 'row', row }); if (row.getIsExpanded() && renderSubComponent) { acc.push({ type: 'expanded', row }); } return acc; }, []); return ( ( ), TableRow: props => { const index = props['data-index'] as number; const item = flatRows[index]; if (!item) { return null; } if (item.type === 'expanded') { return ( ); } return ( onRowClick?.(item.row.original)} > {item.row.getVisibleCells().map(cell => ( ))} ); }, }} fixedHeaderContent={() => table.getHeaderGroups().map(headerGroup => ( {headerGroup.headers.map(header => ( ))} )) } /> ); }
{renderSubComponent?.({ row: item.row })}
{(cell.column.columnDef as { disableTooltip?: boolean }).disableTooltip ? (
{flexRender(cell.column.columnDef.cell, cell.getContext())}
) : ( {flexRender(cell.column.columnDef.cell, cell.getContext())} )}
{header.isPlaceholder ? null : ( <>
{flexRender(header.column.columnDef.header, header.getContext())} {header.column.getCanSort() && (
{header.column.getIsSorted() === 'asc' ? ( ) : header.column.getIsSorted() === 'desc' ? ( ) : (
)}
)}
{/* Column resizer */} {header.column.getCanResize() && (
)} )}