'use client'; import React, { useState, useMemo } from 'react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Input } from '@/components/ui/input'; import { ChevronDown, MoreHorizontal, Search } from 'lucide-react'; export interface DataTableColumn { key: keyof T; label: string; sortable?: boolean; filterable?: boolean; render?: (value: any, row: T) => React.ReactNode; width?: string; } export interface DataTableAction { label: string; onClick: (row: T) => void; variant?: 'default' | 'destructive'; } export interface DataTableProps { columns: DataTableColumn[]; data: T[]; actions?: DataTableAction[]; onRowClick?: (row: T) => void; searchableFields?: (keyof T)[]; isLoading?: boolean; rowKey?: keyof T; } export function DataTable>({ columns, data, actions, onRowClick, searchableFields = [], isLoading = false, rowKey = 'id' as keyof T, }: DataTableProps) { const [searchTerm, setSearchTerm] = useState(''); const [sortConfig, setSortConfig] = useState<{ key: keyof T; direction: 'asc' | 'desc'; } | null>(null); // Filter data based on search const filteredData = useMemo(() => { if (!searchTerm || searchableFields.length === 0) return data; return data.filter((row) => searchableFields.some((field) => { const value = row[field]; return value?.toString().toLowerCase().includes(searchTerm.toLowerCase()); }) ); }, [data, searchTerm, searchableFields]); // Sort data const sortedData = useMemo(() => { if (!sortConfig) return filteredData; return [...filteredData].sort((a, b) => { const aVal = a[sortConfig.key]; const bVal = b[sortConfig.key]; if (aVal < bVal) return sortConfig.direction === 'asc' ? -1 : 1; if (aVal > bVal) return sortConfig.direction === 'asc' ? 1 : -1; return 0; }); }, [filteredData, sortConfig]); const handleSort = (key: keyof T) => { setSortConfig((prev) => prev?.key === key && prev.direction === 'asc' ? { key, direction: 'desc' } : { key, direction: 'asc' } ); }; if (isLoading) { return
Loading...
; } return (
{searchableFields.length > 0 && (
setSearchTerm(e.target.value)} className="pl-10" />
)}
{columns.map((col) => (
{col.label} {col.sortable && ( )}
))} {actions && }
{sortedData.map((row, idx) => ( onRowClick?.(row)} className="cursor-pointer hover:bg-gray-50" > {columns.map((col) => ( {col.render ? col.render(row[col.key], row) : row[col.key]} ))} {actions && ( e.stopPropagation()}> {actions.map((action) => ( { e.stopPropagation(); action.onClick(row); }} className={ action.variant === 'destructive' ? 'text-red-600' : '' } > {action.label} ))} )} ))}
{sortedData.length === 0 && (
No data found
)}
); }