"use client"; import { AlertCircle, CheckCircle2, ClipboardList, ExternalLink, Loader2, SearchX, } from "lucide-react"; import { useCallback, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { adminFetch } from "@/lib/admin-fetch"; interface AuditIssue { type: string; severity: "error" | "warning" | "info"; classname?: string; itemId?: number; catalogId?: number; message: string; sourceName?: string; } interface MissingFromSource { sourceName: string; sourceId: string; classname: string; itemName: string; } interface AuditResult { issues: AuditIssue[]; missingFromSources: MissingFromSource[]; summary: { totalItemsBase: number; totalCatalogItems: number; missingCatalog: number; orphanedCatalog: number; missingNitro: number; missingIcon: number; duplicates: number; missingFromSourcesTotal: number; }; } const ICON_COLORS: Record = { error: "text-[var(--admin-error)]", warning: "text-[var(--admin-warning)]", info: "text-[var(--admin-text-muted)]", }; const TYPE_LABELS: Record = { missing_catalog: "No catalog entry", orphaned_catalog: "Orphaned catalog reference", missing_nitro: "Missing .nitro file", missing_icon: "Missing icon file", duplicate_classname: "Duplicate classname", source_fetch_failed: "Source fetch failed", sources_list_failed: "Sources list failed", }; export function AuditClient() { const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [progress, setProgress] = useState(""); const [activeTab, setActiveTab] = useState<"issues" | "sources">("issues"); const runAudit = useCallback(async () => { setLoading(true); setResult(null); setProgress("Starting audit…"); try { const res = await adminFetch("/api/admin/import/audit", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}), }); if (!res.ok) { const data = await res.json().catch(() => ({})); toast.error(data.error || "Audit failed"); setProgress(""); setLoading(false); return; } const data = await res.json(); setResult(data); toast.success("Audit complete"); } catch { toast.error("Audit request failed"); } finally { setLoading(false); setProgress(""); } }, []); const hasIssues = result && result.issues.length > 0; const errors = result?.issues.filter((i) => i.severity === "error") ?? []; const warnings = result?.issues.filter((i) => i.severity === "warning") ?? []; return (
{!result && ( )} {result && ( <> {/* Summary cards */}
{result.summary.totalItemsBase}
items_base entries
{result.summary.totalCatalogItems}
catalog_items entries
0 ? "border-[var(--admin-error-border)] bg-[var(--admin-error-subtle)]" : "bg-card"}`} >
0 ? "text-[var(--admin-error)]" : ""}`} > {errors.length}
Errors
0 ? "border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)]" : "bg-card"}`} >
0 ? "text-[var(--admin-warning)]" : ""}`} > {warnings.length}
Warnings
{/* Quick stats */}
{result.summary.missingCatalog > 0 ? ( ) : ( )} {result.summary.missingCatalog} items without catalog entry
{result.summary.orphanedCatalog > 0 ? ( ) : ( )} {result.summary.orphanedCatalog} orphaned catalog references
{result.summary.missingNitro > 0 ? ( ) : ( )} {result.summary.missingNitro} items without .nitro
{result.summary.missingIcon > 0 ? ( ) : ( )} {result.summary.missingIcon} items without icon
{result.summary.duplicates > 0 ? ( ) : ( )} {result.summary.duplicates} duplicate classnames
{result.summary.missingFromSourcesTotal > 0 ? ( ) : ( )} {result.summary.missingFromSourcesTotal} items missing from clone sources
{/* Tabs */} {hasIssues || result.missingFromSources.length > 0 ? (
) : null} {/* Issues list */} {activeTab === "issues" && hasIssues && (
{result.issues.map((issue) => (
{TYPE_LABELS[issue.type] || issue.type} {issue.classname && ( {issue.classname} )} {issue.sourceName && ( source: {issue.sourceName} )}

{issue.message}

))}
)} {/* Missing from sources list */} {activeTab === "sources" && result.missingFromSources.length > 0 && (
{result.missingFromSources.map((item) => (
{item.classname} — {item.itemName} in {item.sourceName}
))}
)} {activeTab === "issues" && !hasIssues && result.missingFromSources.length === 0 && (

No issues found

Your catalog is clean!

)} )} {!loading && !result && (

Run a full audit to check for missing catalog entries, broken references, missing files, and items available from clone sources.

)}
); }