TTMT.ManageWebGUI/src/template/audit-list-template.tsx
2026-03-25 12:33:09 +07:00

242 lines
6.9 KiB
TypeScript

import {
type ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import {
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
} from "@/components/ui/table";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { type Audits } from "@/types/audit";
import { AuditFilterBar } from "@/components/filters/audit-filter-bar";
import { AuditDetailDialog } from "@/components/dialogs/audit-detail-dialog";
interface AuditListTemplateProps {
// data
items: Audits[];
total: number;
columns: ColumnDef<Audits>[];
isLoading: boolean;
isFetching: boolean;
// pagination
pageNumber: number;
pageSize: number;
pageCount: number;
onPreviousPage: () => void;
onNextPage: () => void;
canPreviousPage: boolean;
canNextPage: boolean;
// filter
username: string | null;
action: string | null;
from: string | null;
to: string | null;
onUsernameChange: (v: string | null) => void;
onActionChange: (v: string | null) => void;
onFromChange: (v: string | null) => void;
onToChange: (v: string | null) => void;
onSearch: () => void;
onReset: () => void;
// detail dialog
selectedAudit: Audits | null;
onRowClick: (audit: Audits) => void;
onDialogClose: () => void;
}
export function AuditListTemplate({
items,
total,
columns,
isLoading,
isFetching,
pageNumber,
pageSize,
pageCount,
onPreviousPage,
onNextPage,
canPreviousPage,
canNextPage,
username,
action,
from,
to,
onUsernameChange,
onActionChange,
onFromChange,
onToChange,
onSearch,
onReset,
selectedAudit,
onRowClick,
onDialogClose,
}: AuditListTemplateProps) {
const table = useReactTable({
data: items,
columns,
state: {
pagination: { pageIndex: Math.max(0, pageNumber - 1), pageSize },
},
pageCount,
manualPagination: true,
onPaginationChange: (updater) => {
const next =
typeof updater === "function"
? updater({ pageIndex: Math.max(0, pageNumber - 1), pageSize })
: updater;
const newPage = (next.pageIndex ?? 0) + 1;
if (newPage > pageNumber) onNextPage();
else if (newPage < pageNumber) onPreviousPage();
},
getCoreRowModel: getCoreRowModel(),
});
return (
<div className="w-full px-4 md:px-6 space-y-4">
<div>
<h1 className="text-2xl md:text-3xl font-bold">Nhật hoạt đng</h1>
<p className="text-muted-foreground mt-1 text-sm">
Xem nhật audit hệ thống
</p>
</div>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Danh sách audit</CardTitle>
<CardDescription className="text-xs">
Lọc theo người dùng, loại, hành đng khoảng thời gian. Nhấn vào
dòng đ xem chi tiết.
</CardDescription>
</CardHeader>
<CardContent>
<AuditFilterBar
username={username}
action={action}
from={from}
to={to}
isLoading={isLoading}
isFetching={isFetching}
onUsernameChange={onUsernameChange}
onActionChange={onActionChange}
onFromChange={onFromChange}
onToChange={onToChange}
onSearch={onSearch}
onReset={onReset}
/>
<div className="rounded-md border overflow-x-auto">
<Table className="min-w-[640px] w-full">
<TableHeader className="sticky top-0 bg-background z-10">
{table.getHeaderGroups().map((hg) => (
<TableRow key={hg.id}>
{hg.headers.map((header) => (
<TableHead
key={header.id}
className="text-xs font-semibold whitespace-nowrap"
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{isLoading || isFetching ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="text-center py-10 text-muted-foreground text-sm"
>
Đang tải...
</TableCell>
</TableRow>
) : table.getRowModel().rows.length === 0 ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="text-center py-10 text-muted-foreground text-sm"
>
Không dữ liệu
</TableCell>
</TableRow>
) : (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
className="hover:bg-muted/40 cursor-pointer"
onClick={() => onRowClick(row.original)}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className="py-2.5 align-middle">
{cell.column.columnDef.cell
? flexRender(
cell.column.columnDef.cell,
cell.getContext()
)
: String(cell.getValue() ?? "")}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-between mt-4">
<span className="text-xs text-muted-foreground">
Hiển thị {items.length} / {total} mục
</span>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
disabled={!canPreviousPage || isFetching}
onClick={onPreviousPage}
>
Trước
</Button>
<span className="text-sm tabular-nums">
{pageNumber} / {pageCount}
</span>
<Button
variant="outline"
size="sm"
disabled={!canNextPage || isFetching}
onClick={onNextPage}
>
Sau
</Button>
</div>
</div>
</CardContent>
</Card>
<AuditDetailDialog
audit={selectedAudit}
open={!!selectedAudit}
onClose={onDialogClose}
/>
</div>
);
}