/** * Data Table Component * Sortable and filterable data table for analytics */ import { useState, useMemo } from 'react'; interface Column { key: string; header: string; sortable?: boolean; render?: (value: any, row: any) => React.ReactNode; width?: string; align?: 'left' | 'center' | 'right'; } interface DataTableProps { data: any[]; columns: Column[]; title?: string; pageSize?: number; showSearch?: boolean; searchPlaceholder?: string; } type SortDirection = 'asc' | 'desc' | null; export default function DataTable({ data, columns, title, pageSize = 10, showSearch = true, searchPlaceholder = 'Search...', }: DataTableProps) { const [sortKey, setSortKey] = useState(null); const [sortDir, setSortDir] = useState(null); const [search, setSearch] = useState(''); const [page, setPage] = useState(0); const filteredData = useMemo(() => { if (!search) return data; const searchLower = search.toLowerCase(); return data.filter((row) => columns.some((col) => { const value = row[col.key]; return String(value).toLowerCase().includes(searchLower); }) ); }, [data, columns, search]); const sortedData = useMemo(() => { if (!sortKey || !sortDir) return filteredData; return [...filteredData].sort((a, b) => { const aVal = a[sortKey]; const bVal = b[sortKey]; if (aVal === bVal) return 0; if (aVal === null || aVal === undefined) return 1; if (bVal === null || bVal === undefined) return -1; const comparison = aVal < bVal ? -1 : 1; return sortDir === 'asc' ? comparison : -comparison; }); }, [filteredData, sortKey, sortDir]); const paginatedData = useMemo(() => { const start = page * pageSize; return sortedData.slice(start, start + pageSize); }, [sortedData, page, pageSize]); const totalPages = Math.ceil(sortedData.length / pageSize); const handleSort = (key: string) => { if (sortKey === key) { if (sortDir === 'asc') setSortDir('desc'); else if (sortDir === 'desc') { setSortKey(null); setSortDir(null); } } else { setSortKey(key); setSortDir('asc'); } }; const getSortIcon = (key: string) => { if (sortKey !== key) { return ( ); } if (sortDir === 'asc') { return ( ); } return ( ); }; const alignClasses = { left: 'text-left', center: 'text-center', right: 'text-right', }; return (
{/* Header */}
{title &&

{title}

} {showSearch && (
{ setSearch(e.target.value); setPage(0); }} className="pl-10 pr-4 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500" />
)}
{/* Table */}
{columns.map((col) => ( ))} {paginatedData.length === 0 ? ( ) : ( paginatedData.map((row, rowIndex) => ( {columns.map((col) => ( ))} )) )}
col.sortable !== false && handleSort(col.key)} >
{col.header} {col.sortable !== false && getSortIcon(col.key)}
No data available
{col.render ? col.render(row[col.key], row) : row[col.key]}
{/* Pagination */} {totalPages > 1 && (
Showing {page * pageSize + 1} to {Math.min((page + 1) * pageSize, sortedData.length)} of{' '} {sortedData.length} results
)}
); }