"use client"; import { useTranslations } from "next-intl"; import { useEffect, useRef, useState } from "react"; import { applyCatalogIntegrityRepairAction, inspectCatalogIntegrityAction, previewCatalogIntegrityRepairAction, } from "@/actions/catalog-integrity"; import Link from "@/components/link"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import type { IntegrityCatalog, inspectCatalogIntegrity, } from "@/lib/services/catalog-integrity"; import type { CatalogParentRepairPreview } from "@/lib/services/catalog-repair"; type Report = ReturnType; export function CatalogIntegrityPanel({ canRepair }: { canRepair: boolean }) { const t = useTranslations("pages.admin.catalog.integrity"); const [catalog, setCatalog] = useState("normal"); const [report, setReport] = useState(null); const [preview, setPreview] = useState( null, ); const [busy, setBusy] = useState(false); const [error, setError] = useState<"failed" | "stalePreview" | null>(null); const [applied, setApplied] = useState(null); const [refresh, setRefresh] = useState(0); const request = useRef(0); const applying = useRef(false); // biome-ignore lint/correctness/useExhaustiveDependencies: A requested refresh must start a new read-only scan. useEffect(() => { const version = ++request.current; setBusy(true); setReport(null); setPreview(null); setError(null); void inspectCatalogIntegrityAction(catalog) .then((result) => { if (version !== request.current) return; if (result.ok && result.data) setReport(result.data); else setError("failed"); }) .catch(() => { if (version === request.current) setError("failed"); }) .finally(() => { if (version === request.current) setBusy(false); }); return () => { request.current++; }; }, [catalog, refresh]); async function prepare() { setBusy(true); setPreview(null); setError(null); setApplied(null); try { const result = await previewCatalogIntegrityRepairAction(catalog); if (result.ok && result.data) setPreview(result.data); else setError("failed"); } catch { setError("failed"); } finally { setBusy(false); } } async function apply() { if (!preview || applying.current) return; applying.current = true; setBusy(true); setError(null); try { const result = await applyCatalogIntegrityRepairAction( preview.catalog, preview.fingerprint, ); setPreview(null); if (result.ok && result.data) { setApplied(result.data.applied); setRefresh((value) => value + 1); } else if (!result.ok) setError(result.error === "stalePreview" ? "stalePreview" : "failed"); } catch { setError("failed"); setPreview(null); } finally { applying.current = false; setBusy(false); } } return ( {t("title")} {t("description")}
{busy &&

{t("working")}

} {error && (

{t(error)}

)} {applied !== null && (

{t("applied", { count: applied })}

)} {report && ( <> {!report.iconsAvailable && (

{t("iconsUnavailable")}

)}

{report.total === 0 ? t("healthy") : t("total", { count: report.total, shown: report.issues.length, })}

{Object.entries(report.counts).map(([code, count]) => ( {t(`issues.${code}`)}: {count} ))}
{report.issues.length > 0 && (
    {report.issues.map((issue) => (
  • {t(`issues.${issue.code}`)} ยท #{issue.id} {issue.name} {t("open")}

    {t("reference", { value: issue.detail })}

  • ))}
)} )} {canRepair && (

{t("repairHint")}

{preview && ( <>

{t("planned", { count: preview.changes.length })}

    {preview.changes.map((change) => (
  • #{change.id} {change.caption}:{" "} {t("parentChange", { from: change.from })}
  • ))}
)}
)}
); }