import { useState, useMemo, type ReactNode } from 'react' import { useI18n } from '../../hooks/useI18n.ts' export interface Column { key: string label: string render?: (row: T) => ReactNode sortable?: boolean align?: 'left' | 'center' | 'right' } interface DataTableProps { columns: Column[] data: T[] keyExtractor: (row: T) => string | number onRowClick?: (row: T) => void emptyMessage?: string pageSize?: number // Server-side pagination (optional) totalElements?: number currentPage?: number onPageChange?: (page: number) => void } export default function DataTable({ columns, data, keyExtractor, onRowClick, emptyMessage, pageSize = 20, totalElements, currentPage, onPageChange, }: DataTableProps) { const { t } = useI18n() const [sortKey, setSortKey] = useState(null) const [sortAsc, setSortAsc] = useState(true) const [page, setPage] = useState(0) const isServerSide = totalElements != null && currentPage != null && onPageChange != null const sorted = useMemo(() => { if (isServerSide || !sortKey) return data return [...data].sort((a, b) => { const av = (a as Record)[sortKey] const bv = (b as Record)[sortKey] if (av == null || bv == null) return 0 const cmp = av < bv ? -1 : av > bv ? 1 : 0 return sortAsc ? cmp : -cmp }) }, [data, sortKey, sortAsc, isServerSide]) const effectivePage = isServerSide ? currentPage! : page const total = isServerSide ? totalElements! : sorted.length const totalPages = Math.ceil(total / pageSize) const paged = isServerSide ? sorted : sorted.slice(effectivePage * pageSize, (effectivePage + 1) * pageSize) const handleSort = (key: string) => { if (sortKey === key) { setSortAsc(!sortAsc) } else { setSortKey(key) setSortAsc(true) } } const handlePageChange = (newPage: number) => { if (isServerSide) { onPageChange!(newPage) } else { setPage(newPage) } } return (
{columns.map(col => ( ))} {paged.length === 0 ? ( ) : ( paged.map(row => ( onRowClick(row) : undefined} style={{ cursor: onRowClick ? 'pointer' : 'default' }} > {columns.map(col => ( ))} )) )}
handleSort(col.key) : undefined} style={{ textAlign: col.align ?? 'left', cursor: col.sortable !== false ? 'pointer' : 'default' }} > {col.label} {sortKey === col.key && (sortAsc ? ' ▲' : ' ▼')}
{emptyMessage ?? t('common.noData')}
{col.render ? col.render(row) : String((row as Record)[col.key] ?? '-')}
{totalPages > 1 && (
{total}{t('common.items')} {t('common.of')} {effectivePage * pageSize + 1}-{Math.min((effectivePage + 1) * pageSize, total)}
)}
) }