import { ReactNode, useState, useMemo } from 'react' import { ChevronUpIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, } from '@heroicons/react/20/solid' import clsx from 'clsx' // Types export interface Column { key: string header: string width?: string sortable?: boolean render?: (item: T, index: number) => ReactNode align?: 'left' | 'center' | 'right' } export interface TableProps { data: T[] columns: Column[] keyExtractor: (item: T) => string onRowClick?: (item: T) => void selectedKey?: string emptyMessage?: string isLoading?: boolean loadingRows?: number // Sorting sortable?: boolean defaultSortKey?: string defaultSortOrder?: 'asc' | 'desc' onSort?: (key: string, order: 'asc' | 'desc') => void // Pagination pagination?: boolean pageSize?: number currentPage?: number totalItems?: number onPageChange?: (page: number) => void // Styling compact?: boolean striped?: boolean hoverable?: boolean stickyHeader?: boolean } export default function Table({ data, columns, keyExtractor, onRowClick, selectedKey, emptyMessage = 'No hay datos disponibles', isLoading = false, loadingRows = 5, sortable = true, defaultSortKey, defaultSortOrder = 'asc', onSort, pagination = false, pageSize = 10, currentPage: controlledPage, totalItems, onPageChange, compact = false, striped = false, hoverable = true, stickyHeader = false, }: TableProps) { const [sortKey, setSortKey] = useState(defaultSortKey) const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>(defaultSortOrder) const [internalPage, setInternalPage] = useState(1) const currentPage = controlledPage ?? internalPage const setCurrentPage = onPageChange ?? setInternalPage // Handle sorting const handleSort = (key: string) => { const newOrder = sortKey === key && sortOrder === 'asc' ? 'desc' : 'asc' setSortKey(key) setSortOrder(newOrder) onSort?.(key, newOrder) } // Sort data locally if no external handler const sortedData = useMemo(() => { if (!sortKey || onSort) return data return [...data].sort((a, b) => { const aVal = (a as Record)[sortKey] const bVal = (b as Record)[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 sortOrder === 'asc' ? comparison : -comparison }) }, [data, sortKey, sortOrder, onSort]) // Pagination const total = totalItems ?? sortedData.length const totalPages = Math.ceil(total / pageSize) const paginatedData = useMemo(() => { if (!pagination || onPageChange) return sortedData const start = (currentPage - 1) * pageSize return sortedData.slice(start, start + pageSize) }, [sortedData, pagination, currentPage, pageSize, onPageChange]) // Cell padding based on compact mode const cellPadding = compact ? 'px-3 py-2' : 'px-4 py-3' return (
{/* Header */} {columns.map((column) => ( ))} {/* Body */} {isLoading ? ( // Loading skeleton Array.from({ length: loadingRows }).map((_, i) => ( {columns.map((column) => ( ))} )) ) : paginatedData.length === 0 ? ( // Empty state ) : ( // Data rows paginatedData.map((item, index) => { const key = keyExtractor(item) const isSelected = selectedKey === key return ( onRowClick?.(item)} className={clsx( 'transition-colors duration-100', striped && index % 2 === 1 && 'bg-slate-800/30', hoverable && 'hover:bg-slate-700/30', onRowClick && 'cursor-pointer', isSelected && 'bg-accent-500/10 hover:bg-accent-500/20' )} > {columns.map((column) => ( ))} ) }) )}
column.sortable !== false && sortable && handleSort(column.key) } >
{column.header} {column.sortable !== false && sortable && ( )}
{emptyMessage}
{column.render ? column.render(item, index) : String((item as Record)[column.key] ?? '-')}
{/* Pagination */} {pagination && totalPages > 1 && (

Mostrando {(currentPage - 1) * pageSize + 1} -{' '} {Math.min(currentPage * pageSize, total)} de {total}

{/* Page numbers */}
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => { let pageNum: number if (totalPages <= 5) { pageNum = i + 1 } else if (currentPage <= 3) { pageNum = i + 1 } else if (currentPage >= totalPages - 2) { pageNum = totalPages - 4 + i } else { pageNum = currentPage - 2 + i } return ( ) })}
)}
) }