import { createColumnHelper, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, useReactTable, } from "@tanstack/react-table"; import { useMemo } from "react"; import type { AuditLog } from "src/api/backend"; import { EventFormatter, GravatarFormatter } from "src/components"; import { TableLayout } from "src/components/Table/TableLayout"; import { intl, T } from "src/locale"; interface Props { data: AuditLog[]; isFetching?: boolean; onSelectItem?: (id: number) => void; globalFilter?: string; } /** * Export filtered rows to CSV and trigger browser download. */ function exportCSV(rows: AuditLog[]) { const headers = ["ID", "Date", "User", "Object Type", "Action", "Object ID"]; const escape = (v: string) => `"${String(v ?? "").replace(/"/g, '""')}"`; const lines = [ headers.join(","), ...rows.map((r) => [ r.id, r.createdOn, r.user?.name ?? r.userId, r.objectType, r.action, r.objectId, ] .map(String) .map(escape) .join(","), ), ]; const blob = new Blob([lines.join("\r\n")], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `audit-log-${new Date().toISOString().slice(0, 10)}.csv`; a.click(); URL.revokeObjectURL(url); } export default function Table({ data, isFetching, onSelectItem, globalFilter }: Props) { const columnHelper = createColumnHelper(); const columns = useMemo( () => [ columnHelper.accessor((row: AuditLog) => row.user, { id: "user.avatar", cell: (info: any) => { const value = info.getValue(); return ; }, meta: { className: "w-1" }, }), columnHelper.accessor((row: AuditLog) => row, { id: "objectType", header: intl.formatMessage({ id: "column.event" }), cell: (info: any) => , filterFn: (row, _columnId, filterValue: string) => { const r = row.original; const haystack = [ r.user?.name ?? "", r.objectType, r.action, r.createdOn, String(r.objectId), ] .join(" ") .toLowerCase(); return haystack.includes(filterValue.toLowerCase()); }, }), columnHelper.display({ id: "id", cell: (info: any) => ( ), meta: { className: "text-end w-1" }, }), ], [columnHelper, onSelectItem], ); const tableInstance = useReactTable({ columns, data, state: { globalFilter, }, globalFilterFn: (row, _columnId, filterValue: string) => { if (!filterValue) return true; const r = row.original; const haystack = [ r.user?.name ?? "", r.objectType, r.action, r.createdOn, String(r.objectId), ] .join(" ") .toLowerCase(); return haystack.includes(filterValue.toLowerCase()); }, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), initialState: { pagination: { pageSize: 10 }, }, rowCount: data.length, meta: { isFetching }, enableSortingRemoval: false, }); const { pageIndex, pageSize } = tableInstance.getState().pagination; const filteredRows = tableInstance.getFilteredRowModel().rows.map((r) => r.original); const totalRows = filteredRows.length; const pageCount = tableInstance.getPageCount(); const from = totalRows === 0 ? 0 : pageIndex * pageSize + 1; const to = Math.min((pageIndex + 1) * pageSize, totalRows); return ( <> {(totalRows > pageSize || pageCount > 1) && (
{/* Record count */} {from}–{to} / {totalRows} {/* Prev / Page / Next */}
{pageIndex + 1} / {pageCount}
{/* Page size */}
Mỗi trang:
{/* Export */}
)} ); }