import { type ReactNode, useMemo, useState } from 'react' export interface Column { header: string accessor: (row: T) => ReactNode className?: string sortKey?: (row: T) => string | number } type SortDir = 'asc' | 'desc' interface AdminTableProps { columns: Column[] data: T[] isLoading?: boolean emptyMessage?: string onRowClick?: (row: T) => void keyFn: (row: T) => string | number } export function AdminTable({ columns, data, isLoading, emptyMessage = 'No data found.', onRowClick, keyFn, }: AdminTableProps) { const [sortCol, setSortCol] = useState(null) const [sortDir, setSortDir] = useState('asc') const handleSort = (header: string) => { if (sortCol === header) { if (sortDir === 'asc') { setSortDir('desc') } else { // Third click: clear sort setSortCol(null) setSortDir('asc') } } else { setSortCol(header) setSortDir('asc') } } const sortedData = useMemo(() => { if (!sortCol) return data const col = columns.find((c) => c.header === sortCol) if (!col?.sortKey) return data const key = col.sortKey const sorted = [...data].sort((a, b) => { const va = key(a) const vb = key(b) if (va < vb) return -1 if (va > vb) return 1 return 0 }) return sortDir === 'desc' ? sorted.reverse() : sorted }, [data, sortCol, sortDir, columns]) if (isLoading) { return (
{columns.map((col) => ( ))} {Array.from({ length: 5 }).map((_, i) => ( {columns.map((col) => ( ))} ))}
{col.header}
) } if (data.length === 0) { return (
{emptyMessage}
) } return (
{columns.map((col) => { const sortable = !!col.sortKey const active = sortCol === col.header return ( ) })} {sortedData.map((row) => ( onRowClick(row) : undefined} className={onRowClick ? 'cursor-pointer hover:bg-surface-2' : ''} > {columns.map((col) => ( ))} ))}
handleSort(col.header) : undefined} className={`px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider ${col.className ?? ''} ${sortable ? 'cursor-pointer select-none hover:text-text-primary' : ''}`} > {col.header} {sortable && active && ( {sortDir === 'asc' ? '\u2191' : '\u2193'} )}
{col.accessor(row)}
) }