109 lines
2.8 KiB
TypeScript
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>
|
|
);
|
|
}
|