"use client"; import { useCallback, useEffect, useMemo, useState, useTransition, } 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 { usePathname, useRouter, useSearchParams } from "next/navigation"; import { AuditRow } from "./auditTransforms"; import { PAGE_SIZE_OPTIONS } from "./auditConstants"; import { buildSortParam, deriveColumns, parseSortModel, toTitle, } from "./utils"; const FALLBACK_COLUMNS: GridColDef[] = [ { field: "placeholder", headerName: "Audit Data", flex: 1, sortable: false, filterable: false, }, ]; interface AuditTableClientProps { rows: AuditRow[]; total: number; pageIndex: number; pageSize: number; sortParam?: string; entityQuery?: string; } export default function AuditTableClient({ rows, total, pageIndex, pageSize, sortParam, entityQuery = "", }: AuditTableClientProps) { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const searchParamsString = searchParams.toString(); const [isNavigating, startTransition] = useTransition(); // Derive values directly from props const paginationModel = useMemo( () => ({ page: pageIndex, pageSize, }), [pageIndex, pageSize] ); const sortModel = useMemo(() => parseSortModel(sortParam), [sortParam]); const columns = useMemo( () => (rows.length ? deriveColumns(rows) : FALLBACK_COLUMNS), [rows] ); // Only entitySearchInput needs state since it's a controlled input const normalizedEntityQuery = entityQuery.trim(); const [entitySearchInput, setEntitySearchInput] = useState( normalizedEntityQuery ); // Sync entity input when query prop changes (e.g., from URL navigation) useEffect(() => { setEntitySearchInput(normalizedEntityQuery); }, [normalizedEntityQuery]); const pageTitle = useMemo( () => sortModel.length && sortModel[0].field ? `Audit Logs ยท sorted by ${toTitle(sortModel[0].field)}` : "Audit Logs", [sortModel] ); const navigateWithParams = useCallback( (updates: Record) => { const params = new URLSearchParams(searchParamsString); Object.entries(updates).forEach(([key, value]) => { if (!value) { params.delete(key); } else { params.set(key, value); } }); startTransition(() => { const queryString = params.toString(); router.push(queryString ? `${pathname}?${queryString}` : pathname); }); }, [pathname, router, searchParamsString, startTransition] ); const debouncedEntityNavigate = useMemo( () => debounce((value: string) => { navigateWithParams({ page: "1", entity: value.trim() ? value.trim() : null, }); }, 500), [navigateWithParams] ); useEffect(() => { return () => { debouncedEntityNavigate.clear(); }; }, [debouncedEntityNavigate]); const handlePaginationChange = (model: GridPaginationModel) => { navigateWithParams({ page: String(model.page + 1), limit: String(model.pageSize), }); }; const handleSortModelChange = (model: GridSortModel) => { navigateWithParams({ page: "1", sort: buildSortParam(model) ?? null, }); }; const handleEntitySearchChange = (value: string) => { setEntitySearchInput(value); debouncedEntityNavigate(value); }; const loading = isNavigating; return (
handleEntitySearchChange(e.target.value)} sx={{ width: 300, backgroundColor: "#f0f0f0" }} />

{pageTitle}

); }