"use client"; import { AlertTriangle, CheckCircle2, RefreshCw, Wrench } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { applyAlignIdsAction, fixCatalogOffersAction, fixEmulatorIssuesAction, fixEverythingAction, fixSpriteIdsAction, getFurniHealthAction, previewAlignIdsAction, reconcileIdsAction, removeDuplicateItemsBaseAction, } from "@/actions/furni-maintenance"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; interface Health { itemsBaseTotal: number; spriteIdMismatch: number; catalogOfferMismatch: number; duplicateClassnames: number; idMismatchVsFurnidata: number; catalogDuplicates: number; catalogBcDuplicates: number; catalogPageDuplicates: number; emulatorIssues: number; } export function CatalogMaintenancePanel() { const [health, setHealth] = useState(null); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(null); const [dedupePages, setDedupePages] = useState(true); const [alignPreview, setAlignPreview] = useState<{ moves: number; inserted: number; skipped: string[]; } | null>(null); const refresh = useCallback(async () => { setLoading(true); const res = await getFurniHealthAction(); if (res.ok && res.data?.health) setHealth(res.data.health as Health); else if (!res.ok) toast.error(res.error); setAlignPreview(null); setLoading(false); }, []); useEffect(() => { void refresh(); }, [refresh]); async function run( key: string, fn: () => Promise<{ ok: boolean; error?: string; data?: unknown }>, success: string, ) { setBusy(key); try { const res = await fn(); if (res.ok) { toast.success(success); await refresh(); } else { toast.error(res.error ?? "Failed"); } } catch (e) { toast.error(String(e)); } finally { setBusy(null); } } function Stat({ label, value, hint, }: { label: string; value: number; hint?: string; }) { const ok = value === 0; return (
{label}
{hint}
{ok ? : value}
); } return (

Catalog maintenance

Force the furniture tables to 100% consistency with FurnitureData.json. Every action runs inside a database transaction and rolls back on error.

Current health {health ? `${health.itemsBaseTotal} items in items_base` : "Loading…"} {health && ( <> )}
Align items_base IDs to FurnitureData Moves every items_base.id to the FurnitureData sprite id (source of truth). Rewrites all referencing tables. Preview first. {alignPreview && (
Planned ID moves: {alignPreview.moves}
New items to insert: {alignPreview.inserted}
{alignPreview.skipped.length > 0 && (
Skipped (id occupied): {alignPreview.skipped.join(", ")}
)}
)}
Fix everything Runs sprite fix, offer fix, reconcile, dedup and ID alignment in order. Heavy operation — make a backup first.
setDedupePages(v === true)} />
); }