"use client"; import { useEffect, useMemo, useState } from "react"; import { DataGrid, GridColDef, GridPaginationModel, GridSortModel, } from "@mui/x-data-grid"; import TextField from "@mui/material/TextField"; import { Box, debounce } from "@mui/material"; import useSWR from "swr"; import { getAudits } from "@/app/services/audits"; import { AuditQueryResult, AuditRow, DEFAULT_PAGE_SIZE, } from "./auditTransforms"; const FALLBACK_COLUMNS: GridColDef[] = [ { field: "placeholder", headerName: "Audit Data", flex: 1, sortable: false, filterable: false, }, ]; const toTitle = (field: string) => field .replace(/_/g, " ") .replace(/-/g, " ") .replace(/([a-z])([A-Z])/g, "$1 $2") .replace(/\s+/g, " ") .trim() .replace(/^\w/g, char => char.toUpperCase()); const deriveColumns = (rows: AuditRow[]): GridColDef[] => { if (!rows.length) return []; return Object.keys(rows[0]).map(field => ({ field, headerName: toTitle(field), flex: field === "id" ? 0 : 1, minWidth: field === "id" ? 140 : 200, sortable: true, })); }; interface AuditTableClientProps { initialData: AuditQueryResult; } const ENTITY_PREFIX = "LIKE/"; const buildSortParam = (sortModel: GridSortModel) => sortModel.length && sortModel[0].field && sortModel[0].sort ? `${sortModel[0].field}:${sortModel[0].sort}` : undefined; const buildEntityParam = (entitySearch: string) => entitySearch.trim() ? `${ENTITY_PREFIX}${entitySearch.trim()}` : undefined; export default function AuditTableClient({ initialData, }: AuditTableClientProps) { const [paginationModel, setPaginationModel] = useState({ page: initialData.pageIndex ?? 0, pageSize: DEFAULT_PAGE_SIZE, }); const [sortModel, setSortModel] = useState([]); const [entitySearch, setEntitySearch] = useState(""); const [entitySearchInput, setEntitySearchInput] = useState(""); const [columns, setColumns] = useState( initialData.rows.length ? deriveColumns(initialData.rows) : FALLBACK_COLUMNS ); const debouncedSetEntitySearch = useMemo( () => debounce((value: string) => { setEntitySearch(value); setPaginationModel(prev => ({ ...prev, page: 0 })); }, 500), [] ); useEffect(() => { return () => { debouncedSetEntitySearch.clear(); }; }, [debouncedSetEntitySearch]); const sortParam = useMemo(() => buildSortParam(sortModel), [sortModel]); const entityParam = useMemo( () => buildEntityParam(entitySearch), [entitySearch] ); const { data, error, isLoading, isValidating } = useSWR( [ "audits", paginationModel.page, paginationModel.pageSize, sortParam ?? "", entityParam ?? "", ], () => getAudits({ limit: paginationModel.pageSize, page: paginationModel.page + 1, sort: sortParam, entity: entityParam, }), { keepPreviousData: true, revalidateOnFocus: true, revalidateOnReconnect: true, fallbackData: initialData, } ); const rows = data?.rows ?? initialData.rows; const rowCount = data?.total ?? initialData.total ?? rows.length; const loading = isLoading || (isValidating && !rows.length); const pageTitle = useMemo( () => sortModel.length && sortModel[0].field ? `Audit Logs ยท sorted by ${toTitle(sortModel[0].field)}` : "Audit Logs", [sortModel] ); useEffect(() => { setColumns(prev => rows.length ? deriveColumns(rows) : prev.length ? prev : FALLBACK_COLUMNS ); }, [rows]); const handlePaginationChange = (model: GridPaginationModel) => { setPaginationModel(model); }; const handleSortModelChange = (model: GridSortModel) => { setSortModel(model); setPaginationModel(prev => ({ ...prev, page: 0 })); }; const handleEntitySearchChange = (value: string) => { setEntitySearchInput(value); debouncedSetEntitySearch(value); }; const errorMessage = error instanceof Error ? error.message : error ? "Failed to load audits" : null; return (
handleEntitySearchChange(e.target.value)} sx={{ width: 300, backgroundColor: "#f0f0f0" }} />

{pageTitle}

{errorMessage && (
{errorMessage}
)}
); }