import React, { useEffect, useRef, useState } from "react"; import { Box, Button, Chip, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, } from "@mui/material"; import { api, getApiErrorMessage } from "../api"; import { useToast } from "../toast"; import { useI18n } from "../i18n/I18nProvider"; import { useViewResource } from "../hooks/useViewResource"; import ViewStateNotice from "../components/ViewStateNotice"; import { PageHeader, PageSection } from "../layout/PageLayout"; import { checkSessionCookie, getSessionContext, isSessionInvalidated } from "../sessionBoundary"; type AuditItem = { id: number; type: string; oldValue?: string | null; newValue?: string | null; note?: string | null; at: string; jobApplicationId: number; jobTitle?: string | null; companyName?: string | null; ownerUserId?: string | null; ownerEmail?: string | null; ownerUserName?: string | null; }; function canUndo(type: string) { return ["StatusChanged", "FollowUpSet", "ResponseUpdated", "Deleted", "Restored"].includes(type); } export default function AdminAuditPage() { const { toast } = useToast(); const { language, t } = useI18n(); const [busyId, setBusyId] = useState(null); const body=useRef(null); const owner=useRef({ mounted: true, generation: 0, busy: false }); const currentTranslation=useRef(t); const fallback=useRef(t("adminAuditLoadFailed")); useEffect(() => { currentTranslation.current=t; }, [t]); useEffect(() => { const lifetime=owner.current; lifetime.mounted=true; lifetime.generation+=1; return () => { lifetime.mounted=false; lifetime.generation+=1; }; }, []); const auditResource = useViewResource( async () => { const r = await api.get("/admin/audit?take=200"); return r.data ?? []; }, { initialData: [], errorMessage: fallback.current }, ); const mutate = async (e: AuditItem, action: "undo" | "restore") => { if (action === "undo" ? !canUndo(e.type) : e.type !== "Deleted") return; checkSessionCookie(); const lifetime=owner.current; if (!lifetime.mounted || lifetime.busy || isSessionInvalidated()) return; lifetime.busy=true; const generation=lifetime.generation; const context=getSessionContext(); const active=() => { checkSessionCookie(); return lifetime.mounted && lifetime.generation===generation && !isSessionInvalidated() && getSessionContext()===context; }; if (body.current?.contains(document.activeElement)) body.current.focus(); setBusyId(e.id); try { const res=await api.post<{ ok: boolean; message?: string }>(action === "undo" ? `/admin/audit/${e.id}/undo` : `/jobapplications/${e.jobApplicationId}/restore`, {}); if (!active()) return; toast(action === "undo" && res.data?.message || currentTranslation.current(action === "undo" ? "adminAuditUndone" : "adminAuditRestored"), "success"); await auditResource.reload(); } catch (error) { if (active()) toast(getApiErrorMessage(error, currentTranslation.current(action === "undo" ? "adminAuditUndoFailed" : "adminAuditRestoreFailed")), "error"); } finally { lifetime.busy=false; if (active()) setBusyId(null); } }; const rows = auditResource.data; const error=auditResource.error?.message===fallback.current ? { ...auditResource.error, message: t("adminAuditLoadFailed") } : auditResource.error; return ( { body.current?.focus(); await auditResource.reload(); }} /> {!auditResource.error ? {t("adminAuditAt")} {t("adminAuditType")} {t("adminAuditJob")} {t("adminAuditUser")} {t("adminAuditDetails")} {t("adminAuditActions")} {auditResource.loading ? ( {t("loading")} ) : rows.length === 0 ? ( {t("adminAuditNoEvents")} ) : ( rows.map((e) => { const jobLabel = e.jobTitle || t("adminAuditJobFallback", { id: e.jobApplicationId }); const ownerLabel = e.ownerEmail || e.ownerUserName || e.ownerUserId || "-"; const details = e.oldValue || e.newValue ? `${e.oldValue ?? ""} -> ${e.newValue ?? ""}` : ""; const disabled = busyId !== null; const showRestore = e.type === "Deleted"; return ( {e.at ? new Date(e.at).toLocaleString(language === "nb" ? "nb-NO" : "en-GB") : ""} {e.companyName ? `${e.companyName} - ` : ""} {jobLabel} jobId={e.jobApplicationId} {ownerLabel} {details} {e.note ? `${details ? "\n" : ""}${e.note}` : ""} {canUndo(e.type) ? ( ) : null} {showRestore ? ( ) : null} ); }) )}
: null}
); }