import { useState } from 'react'; import { ChevronUp, ChevronDown, ChevronsUpDown, ChevronLeft, ChevronRight } from 'lucide-react'; import { cn } from '../../utils/helpers'; import { Skeleton } from './LoadingSkeleton'; import { EmptyState } from './EmptyState'; // Simplified column definition export interface ColumnDef { id: string; header: string; accessorKey?: keyof T; accessorFn?: (row: T) => unknown; cell?: (info: { row: T; getValue: () => unknown }) => React.ReactNode; sortable?: boolean; width?: string; } interface DataTableProps { columns: ColumnDef[]; data: T[]; isLoading?: boolean; emptyMessage?: string; emptyDescription?: string; pageSize?: number; showPagination?: boolean; enableSorting?: boolean; className?: string; striped?: boolean; compact?: boolean; onRowClick?: (row: T) => void; getRowId?: (row: T) => string; } type SortDirection = 'asc' | 'desc' | null; interface SortState { columnId: string | null; direction: SortDirection; } export function DataTable({ columns, data, isLoading = false, emptyMessage = 'Нет данных', emptyDescription, pageSize = 10, showPagination = true, enableSorting = true, className, striped = false, compact = false, onRowClick, getRowId, }: DataTableProps) { const [sort, setSort] = useState({ columnId: null, direction: null }); const [currentPage, setCurrentPage] = useState(0); // Get value from row const getValue = (row: T, column: ColumnDef): unknown => { if (column.accessorFn) return column.accessorFn(row); if (column.accessorKey) return row[column.accessorKey]; return null; }; // Sort data const sortedData = [...data].sort((a, b) => { if (!sort.columnId || !sort.direction) return 0; const column = columns.find((c) => c.id === sort.columnId); if (!column) return 0; const aVal = getValue(a, column); const bVal = getValue(b, column); 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 sort.direction === 'asc' ? comparison : -comparison; }); // Paginate data const totalPages = Math.ceil(sortedData.length / pageSize); const paginatedData = showPagination ? sortedData.slice(currentPage * pageSize, (currentPage + 1) * pageSize) : sortedData; // Toggle sort const toggleSort = (columnId: string) => { if (!enableSorting) return; setSort((prev) => { if (prev.columnId !== columnId) { return { columnId, direction: 'asc' }; } if (prev.direction === 'asc') { return { columnId, direction: 'desc' }; } return { columnId: null, direction: null }; }); }; if (isLoading) { return (
{columns.map((col) => ( ))} {Array.from({ length: 5 }).map((_, i) => ( {columns.map((col) => ( ))} ))}
); } if (data.length === 0) { return ( ); } return (
{columns.map((column) => ( ))} {paginatedData.map((row, index) => ( onRowClick?.(row)} > {columns.map((column) => { const value = getValue(row, column); return ( ); })} ))}
column.sortable !== false && toggleSort(column.id)} > {column.header} {column.sortable !== false && enableSorting && ( {sort.columnId === column.id && sort.direction === 'asc' ? ( ) : sort.columnId === column.id && sort.direction === 'desc' ? ( ) : ( )} )}
{column.cell ? column.cell({ row, getValue: () => value }) : String(value ?? '')}
{showPagination && totalPages > 1 && (
Страница {currentPage + 1} из {totalPages}
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => { let pageNum: number; if (totalPages <= 5) { pageNum = i; } else if (currentPage < 3) { pageNum = i; } else if (currentPage > totalPages - 4) { pageNum = totalPages - 5 + i; } else { pageNum = currentPage - 2 + i; } return ( ); })}
)}
); } export default DataTable;