"use client"; /** * DataTable — Shared UI primitive (T-29) * * Configurable data table with sticky header, row click, * and optional loading/empty states. Extracts the shared * table rendering pattern from RequestLoggerV2 and ProxyLogger. * * Usage: * {row[column.key]}} * onRowClick={(row) => openDetail(row)} * selectedId={selectedLog?.id} * loading={isLoading} * emptyIcon="📋" * emptyMessage="No logs found" * /> */ interface DataTableColumn { key: string; label: string; maxWidth?: string; } interface DataTableRow { id?: string | number; [key: string]: unknown; } interface DataTableProps { columns?: DataTableColumn[]; data?: DataTableRow[]; renderCell: (row: DataTableRow, column: DataTableColumn) => React.ReactNode; renderHeader?: (column: DataTableColumn) => React.ReactNode; onRowClick?: (row: DataTableRow) => void; selectedId?: string | number; loading?: boolean; maxHeight?: string; emptyIcon?: string; emptyMessage?: string; } export default function DataTable({ columns = [], data = [], renderCell, renderHeader, onRowClick, selectedId, loading = false, maxHeight = "calc(100vh - 320px)", emptyIcon = "📭", emptyMessage = "No data found", }: DataTableProps) { if (loading) { return (
Loading...
); } if (data.length === 0) { return (
{emptyIcon} {emptyMessage}
); } return (
{columns.map((col) => ( ))} {data.map((row, idx) => ( onRowClick?.(row)} style={{ cursor: onRowClick ? "pointer" : "default", background: row.id === selectedId ? "rgba(99,102,241,0.1)" : idx % 2 === 0 ? "transparent" : "rgba(255,255,255,0.02)", transition: "background 0.15s", }} onMouseEnter={(e) => { if (row.id !== selectedId) { e.currentTarget.style.background = "rgba(255,255,255,0.04)"; } }} onMouseLeave={(e) => { if (row.id !== selectedId) { e.currentTarget.style.background = idx % 2 === 0 ? "transparent" : "rgba(255,255,255,0.02)"; } }} > {columns.map((col) => ( ))} ))}
{renderHeader ? renderHeader(col) : col.label}
{renderCell(row, col)}
); }