payment-backoffice/app/dashboard/audits/AuditTableClient.tsx
Mitchell Magro abf15a7a7d s
2025-12-22 10:32:37 +01:00

202 lines
5.1 KiB
TypeScript

"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<GridPaginationModel>(
() => ({
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<string>(
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<string, string | null | undefined>) => {
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 (
<div className="audits-page">
<Box sx={{ display: "flex", gap: 2, mt: 5 }}>
<TextField
label="Search by Entity"
variant="outlined"
size="small"
value={entitySearchInput}
onChange={e => handleEntitySearchChange(e.target.value)}
sx={{ width: 300, backgroundColor: "#f0f0f0" }}
/>
</Box>
<h1 className="page-title">{pageTitle}</h1>
<div className="table-container">
<div className="scroll-wrapper">
<div
className="table-inner"
style={{ minWidth: `${columns.length * 200}px` }}
>
<DataGrid
rows={rows}
columns={columns.length ? columns : FALLBACK_COLUMNS}
loading={loading}
paginationMode="server"
sortingMode="server"
paginationModel={paginationModel}
onPaginationModelChange={handlePaginationChange}
rowCount={total}
sortModel={sortModel}
onSortModelChange={handleSortModelChange}
pageSizeOptions={[...PAGE_SIZE_OPTIONS]}
disableRowSelectionOnClick
sx={{
border: 0,
minHeight: 500,
"& .MuiDataGrid-cell": {
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
},
}}
/>
</div>
</div>
</div>
</div>
);
}