Files
2026-06-03 03:08:57 +03:30

109 lines
2.8 KiB
TypeScript

"use client";
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
import { Button, EmptyState, Spinner } from "./ui";
export interface Column<T> {
key: string;
header: string;
render?: (row: T) => ReactNode;
className?: string;
}
export function DataTable<T extends { id?: number | string }>({
columns,
rows,
loading,
error,
emptyTitle = "موردی ثبت نشده است",
emptyMessage = "هنوز داده‌ای برای نمایش وجود ندارد.",
emptyIcon,
emptyAction,
onRetry,
actions,
}: {
columns: Column<T>[];
rows: T[];
loading?: boolean;
error?: string | null;
emptyTitle?: string;
emptyMessage?: string;
emptyIcon?: ReactNode;
emptyAction?: ReactNode;
onRetry?: () => void;
actions?: (row: T) => ReactNode;
}) {
if (loading) {
return (
<div className="flex justify-center py-16">
<Spinner className="h-8 w-8" />
</div>
);
}
if (error) {
return (
<EmptyState
icon="⚠️"
title="خطا در دریافت اطلاعات"
message={error}
action={
onRetry ? (
<Button variant="secondary" onClick={onRetry}>
تلاش دوباره
</Button>
) : undefined
}
/>
);
}
if (!rows.length)
return (
<EmptyState
title={emptyTitle}
message={emptyMessage}
icon={emptyIcon}
action={emptyAction}
/>
);
return (
<div className="overflow-x-auto rounded-2xl border border-border bg-surface">
<table className="w-full text-right text-sm">
<thead>
<tr className="border-b border-border bg-surface-muted text-muted">
{columns.map((c) => (
<th key={c.key} className={cn("px-4 py-3 font-medium", c.className)}>
{c.header}
</th>
))}
{actions && <th className="px-4 py-3 font-medium">عملیات</th>}
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr
key={row.id ?? i}
className="border-b border-border last:border-0 hover:bg-surface-muted/50"
>
{columns.map((c) => (
<td key={c.key} className={cn("px-4 py-3", c.className)}>
{c.render
? c.render(row)
: ((row as Record<string, unknown>)[c.key] as ReactNode) ??
"—"}
</td>
))}
{actions && (
<td className="px-4 py-3">
<div className="flex items-center gap-1">{actions(row)}</div>
</td>
)}
</tr>
))}
</tbody>
</table>
</div>
);
}