import { ReactNode, memo, useState, useMemo } from 'react'; import { Text } from './Text'; import { Button } from './Button'; import { Input } from './Input'; import { Select } from './Select'; import { defaultLayoutConfig, type LayoutConfig } from '~/lib/layout-utils'; interface Column { key: keyof T | string; header: string; render?: (item: T) => ReactNode; sortable?: boolean; filterable?: boolean; filterType?: 'text' | 'select' | 'date' | 'number'; filterOptions?: { value: string; label: string }[]; className?: string; width?: string; } interface FilterState { [key: string]: string; } interface DataTableProps { data: T[]; columns: Column[]; loading?: boolean; emptyMessage?: string; className?: string; config?: Partial; onSort?: (key: string, direction: 'asc' | 'desc') => void; sortKey?: string; sortDirection?: 'asc' | 'desc'; searchable?: boolean; searchPlaceholder?: string; filterable?: boolean; pagination?: { enabled: boolean; pageSize?: number; currentPage?: number; totalItems?: number; onPageChange?: (page: number) => void; }; actions?: { label: string; render: (item: T) => ReactNode; }; } export const DataTable = memo(function DataTable>({ data, columns, loading = false, emptyMessage = "لا توجد بيانات", className = '', config = {}, onSort, sortKey, sortDirection, searchable = false, searchPlaceholder = "البحث...", filterable = false, pagination, actions, }: DataTableProps) { const layoutConfig = { ...defaultLayoutConfig, ...config }; const [searchTerm, setSearchTerm] = useState(''); const [filters, setFilters] = useState({}); const handleSort = (key: string) => { if (!onSort) return; const newDirection = sortKey === key && sortDirection === 'asc' ? 'desc' : 'asc'; onSort(key, newDirection); }; const handleFilterChange = (columnKey: string, value: string) => { setFilters(prev => ({ ...prev, [columnKey]: value, })); }; // Filter and search data const filteredData = useMemo(() => { let result = [...data]; // Apply search if (searchable && searchTerm) { result = result.filter(item => { return columns.some(column => { const value = item[column.key]; if (value == null) return false; return String(value).toLowerCase().includes(searchTerm.toLowerCase()); }); }); } // Apply column filters if (filterable) { Object.entries(filters).forEach(([columnKey, filterValue]) => { if (filterValue) { result = result.filter(item => { const value = item[columnKey]; if (value == null) return false; return String(value).toLowerCase().includes(filterValue.toLowerCase()); }); } }); } return result; }, [data, searchTerm, filters, columns, searchable, filterable]); // Paginate data const paginatedData = useMemo(() => { if (!pagination?.enabled) return filteredData; const pageSize = pagination.pageSize || 10; const currentPage = pagination.currentPage || 1; const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; return filteredData.slice(startIndex, endIndex); }, [filteredData, pagination]); const totalPages = pagination?.enabled ? Math.ceil(filteredData.length / (pagination.pageSize || 10)) : 1; if (loading) { return (
جاري التحميل...
); } if (data.length === 0) { return (
لا توجد بيانات {emptyMessage}
); } return (
{/* Search and Filters */} {(searchable || filterable) && (
{/* Search */} {searchable && (
setSearchTerm(e.target.value)} startIcon={ } />
)} {/* Column Filters */} {filterable && (
{columns .filter(column => column.filterable) .map(column => (
{column.filterType === 'select' && column.filterOptions ? ( handleFilterChange(column.key as string, e.target.value)} /> )}
))}
)}
)} {/* Table */}
{columns.map((column) => ( ))} {actions && ( )} {paginatedData.map((item, rowIndex) => { // Use item.id if available, otherwise fall back to rowIndex const rowKey = item.id ? `row-${item.id}` : `row-${rowIndex}`; return ( {columns.map((column) => ( ))} {actions && ( )} ); })}
{column.sortable && onSort ? ( ) : ( column.header )} {actions.label}
{column.render ? column.render(item) : String(item[column.key] || '') } {actions.render(item)}
{/* Pagination */} {pagination?.enabled && totalPages > 1 && (
{})} config={config} />
)} {/* Results Summary */} {(searchable || filterable || pagination?.enabled) && (
عرض {paginatedData.length} من {filteredData.length} {filteredData.length !== data.length && ` (مفلتر من ${data.length})`}
)}
); }) as >(props: DataTableProps) => JSX.Element; interface PaginationProps { currentPage: number; totalPages: number; onPageChange: (page: number) => void; className?: string; config?: Partial; } export const Pagination = memo(function Pagination({ currentPage, totalPages, onPageChange, className = '', config = {}, }: PaginationProps) { const layoutConfig = { ...defaultLayoutConfig, ...config }; if (totalPages <= 1) return null; const getPageNumbers = () => { const pages = []; const maxVisible = 5; if (totalPages <= maxVisible) { for (let i = 1; i <= totalPages; i++) { pages.push(i); } } else { if (currentPage <= 3) { for (let i = 1; i <= 4; i++) { pages.push(i); } pages.push('...'); pages.push(totalPages); } else if (currentPage >= totalPages - 2) { pages.push(1); pages.push('...'); for (let i = totalPages - 3; i <= totalPages; i++) { pages.push(i); } } else { pages.push(1); pages.push('...'); for (let i = currentPage - 1; i <= currentPage + 1; i++) { pages.push(i); } pages.push('...'); pages.push(totalPages); } } return pages; }; return (
{getPageNumbers().map((page, index) => (
{page === '...' ? ( ... ) : ( )}
))}
صفحة {currentPage} من {totalPages}
); });