feat: initial
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user