import React from 'react'; import { MagnifyingGlassIcon, ChevronUpIcon, ChevronDownIcon, ChevronUpDownIcon } from '@heroicons/react/24/outline'; export interface Column { key: string; header: string; render?: (row: T) => React.ReactNode; sortable?: boolean; className?: string; } interface Props { columns: Column[]; data: T[]; loading?: boolean; searchValue?: string; onSearchChange?: (v: string) => void; searchPlaceholder?: string; actions?: (row: T) => React.ReactNode; emptyMessage?: string; emptyAction?: React.ReactNode; headerExtra?: React.ReactNode; sortKey?: string | null; sortDir?: 'asc' | 'desc'; onSort?: (key: string) => void; /** محتوای ردیف بازشونده‌ی زیر هر ردیف؛ `null`/`undefined` یعنی بسته است. */ renderExpanded?: (row: T) => React.ReactNode; } function SkeletonRow({ cols }: { cols: number }) { return ( {Array.from({ length: cols }).map((_, i) => (
))} ); } export default function DataTable({ columns, data, loading, searchValue, onSearchChange, searchPlaceholder = 'جستجو...', actions, emptyMessage = 'هیچ موردی یافت نشد', emptyAction, headerExtra, sortKey, sortDir, onSort, renderExpanded, }: Props) { const allColumns = actions ? [...columns, { key: '__actions', header: 'اقدامات', className: 'w-[120px]' }] : columns; return (
{/* Toolbar row */} {(onSearchChange !== undefined || headerExtra) && ( // لنگرهای data-tour اینجا تعریف می‌شوند تا هر صفحه‌ای که DataTable دارد // بدون تغییر کد خودش، در تور راهنما قابل اشاره باشد.
{onSearchChange !== undefined && (
onSearchChange(e.target.value)} placeholder={searchPlaceholder} />
)} {headerExtra && <>{headerExtra}}
)}
{allColumns.map((col) => { const sortable = (col as Column).sortable && onSort; const active = sortKey === col.key; return ( ); })} {loading ? ( Array.from({ length: 6 }).map((_, i) => ( )) ) : data.length === 0 ? ( ) : ( data.map((row, rowIdx) => { const expanded = renderExpanded?.(row); return ( {columns.map((col) => ( ))} {actions && ( )} {expanded && ( )} ); }) )}
onSort!(col.key) : undefined} style={sortable ? { cursor: 'pointer', userSelect: 'none' } : undefined} > {col.header} {sortable && ( active ? (sortDir === 'desc' ? : ) : )}

{emptyMessage}

{emptyAction}
{col.render ? col.render(row) : ((row as Record)[col.key] as React.ReactNode) ?? '—'}
{actions(row)}
{expanded}
); }