import React, { useMemo, useCallback } from 'react'; import { clsx } from 'clsx'; import { ResponsiveTable, Pagination, SearchBar, Checkbox, Button } from '@/components/ui'; import { MoreVertical } from 'lucide-react'; import { DropdownMenu } from '@/components/ui'; export interface DataTableColumn { key: string; header: string; render: (item: T, index: number) => React.ReactNode; width?: string | number; minWidth?: string | number; sortable?: boolean; align?: 'left' | 'center' | 'right'; } export interface DataTableAction { label: string; icon?: React.ReactNode; onClick: (item: T) => void; variant?: 'default' | 'destructive'; disabled?: (item: T) => boolean; } export interface DataTableProps { data: T[]; columns: DataTableColumn[]; getRowId: (item: T) => string; isLoading?: boolean; // Pagination pagination?: { currentPage: number; totalPages: number; pageSize: number; totalItems: number; onPageChange: (page: number) => void; onPageSizeChange?: (size: number) => void; }; // Sorting sorting?: { column: string | null; direction: 'asc' | 'desc' | null; onSort: (column: string, direction: 'asc' | 'desc') => void; }; // Search search?: { value: string; onChange: (value: string) => void; placeholder?: string; }; // Selection selection?: { selectedRows: Set; onSelectionChange: (selected: Set) => void; }; // Actions actions?: DataTableAction[]; bulkActions?: Array<{ label: string; icon?: React.ReactNode; onClick: (selectedIds: string[]) => void; variant?: 'default' | 'destructive'; }>; // Empty state emptyMessage?: string; emptyDescription?: string; emptyAction?: React.ReactNode; // Mobile card renderer renderMobileCard?: (item: T, index: number) => React.ReactNode; className?: string; } /** * Enhanced DataTable component with built-in pagination, sorting, filtering, and bulk actions */ export function DataTable({ data, columns, getRowId, isLoading = false, pagination, sorting, search, selection, actions, bulkActions, emptyMessage = 'No data available', emptyDescription, emptyAction, renderMobileCard, className, }: DataTableProps) { const selectedCount = selection?.selectedRows.size || 0; const hasSelection = selectedCount > 0; const handleSelectAll = (checked: boolean) => { if (!selection) return; if (checked) { const allIds = new Set(data.map(getRowId)); selection.onSelectionChange(allIds); } else { selection.onSelectionChange(new Set()); } }; const handleSelectRow = useCallback( (id: string, checked: boolean) => { if (!selection) return; const newSelection = new Set(selection.selectedRows); if (checked) { newSelection.add(id); } else { newSelection.delete(id); } selection.onSelectionChange(newSelection); }, [selection] ); const allSelected = data.length > 0 && data.every((item) => selection?.selectedRows.has(getRowId(item))); const someSelected = data.some((item) => selection?.selectedRows.has(getRowId(item))); // Add selection column if selection is enabled const tableColumns = useMemo(() => { if (!selection) return columns; return [ { key: '__select__', header: '', render: (_: T, index: number) => { const id = getRowId(data[index]); const isSelected = selection.selectedRows.has(id); return ( handleSelectRow(id, e.target.checked)} onClick={(e) => e.stopPropagation()} /> ); }, width: 40, sortable: false, align: 'center' as const, }, ...columns, ]; }, [columns, selection, data, getRowId, handleSelectRow]); // Add actions column if actions are provided const finalColumns = useMemo(() => { if (!actions || actions.length === 0) return tableColumns; return [ ...tableColumns, { key: '__actions__', header: 'Actions', render: (item: T) => { const enabledActions = actions.filter( (action) => !action.disabled || !action.disabled(item) ); if (enabledActions.length === 0) return null; return ( } items={enabledActions.map((action) => ({ label: action.label, value: action.label, icon: action.icon, onClick: () => action.onClick(item), disabled: action.disabled?.(item), }))} align="right" /> ); }, width: 80, sortable: false, align: 'right' as const, }, ]; }, [tableColumns, actions]); const handleSort = (key: string) => { if (!sorting) return; const newDirection = sorting.column === key && sorting.direction === 'asc' ? 'desc' : 'asc'; sorting.onSort(key, newDirection); }; return (
{/* Toolbar */}
{/* Search */} {search && (
)} {/* Bulk Actions */} {hasSelection && bulkActions && bulkActions.length > 0 && (
{selectedCount} selected {bulkActions.map((action, index) => ( ))}
)}
{/* Select All Checkbox (if selection enabled) */} {selection && data.length > 0 && (
{ if (el) { el.indeterminate = someSelected && !allSelected; } }} onChange={(e) => handleSelectAll(e.target.checked)} /> Select all ({data.length} items)
)} {/* Table */} { const item = data.find((d) => getRowId(d) === id); if (item && selection) { const isSelected = selection.selectedRows.has(id); handleSelectRow(id, !isSelected); } }} getRowId={getRowId} renderMobileCard={renderMobileCard} isLoading={isLoading} /> {/* Pagination */} {pagination && (
{pagination.onPageSizeChange && (
Items per page:
)}
)}
); }