feat(catalog): repair missing furniture components with verified status
CI / check (push) Successful in 1m33s
CI / deploy (push) Successful in 1m22s
CI / e2e (push) Successful in 24s

This commit is contained in:
Simo committed 2026-09-05 18:12:34 +02:00
1 parent 3c24df9b1d
commit 570f3dde44
13 files changed
+438 -164

No files matched your search

@@ -5,6 +5,7 @@ import { adminFetch } from "@/lib/admin-fetch";
import {
compareFurniture,
type FurnitureInspection,
furnitureHealth,
} from "@/lib/furni/studio-inspection";
import type { FurniItem } from "./studio-types";
@@ -172,7 +173,15 @@ export function FurnitureComparison({
</div>
);
}
export function FurnitureInspector({ item }: { item: FurniItem }) {
export function FurnitureInspector({
item,
onRepair,
busy = false,
}: {
item: FurniItem;
onRepair?: () => Promise<unknown>;
busy?: boolean;
}) {
const inspection = useFurnitureInspection([item.classname]);
if (inspection.loading)
return (
@@ -192,8 +201,54 @@ export function FurnitureInspector({ item }: { item: FurniItem }) {
const local = inspection.items.find(
(entry) => entry.classname === item.classname,
);
return local ? (
<FurnitureComparison item={item} local={local} />
const health = local ? furnitureHealth(local) : null;
return local && health ? (
<div className="space-y-3">
<section className="rounded-md border border-[var(--admin-border)] p-3 space-y-2">
<strong>
{
{
complete: "Complete",
incomplete: "Needs repair",
new: "Not imported",
conflict: "Local conflict",
unknown: "Not verified",
}[health.state]
}
</strong>
{health.missing.length > 0 && (
<p className="text-sm">Missing: {health.missing.join(", ")}</p>
)}
{health.state === "conflict" && (
<p className="text-sm">
Duplicate records or mismatched IDs/types require review before
repair.
</p>
)}
{health.state === "unknown" && (
<p className="text-sm">
Some local data could not be checked. Retry verification before
repair.
</p>
)}
{health.state === "incomplete" && onRepair && (
<>
<p className="text-sm text-[var(--admin-text-muted)]">
Repair adds missing parts and preserves existing IDs, settings,
furnidata and catalog offers. Missing offers use the automatic
category and price shown below.
</p>
<Button disabled={busy} onClick={() => void onRepair()}>
{busy ? "Repair queued or running…" : "Repair furni"}
</Button>
</>
)}
<Button variant="outline" disabled={busy} onClick={inspection.retry}>
Recheck local state
</Button>
</section>
<FurnitureComparison item={item} local={local} />
</div>
) : (
<p>Local inspection unavailable.</p>
);
@@ -46,7 +46,12 @@ export function useFurnitureJobs(onComplete: () => void) {
}, [refresh]);
const submit = async (
items: ImportJobItem[],
options: { sourceId?: string; translate: boolean; langs?: string[] },
options: {
sourceId?: string;
translate: boolean;
langs?: string[];
mode?: "repair";
},
) => {
if (submitting.current) return false;
submitting.current = true;
@@ -144,6 +149,7 @@ export function FurnitureJobHistory({
onClick={() =>
void state.submit(failed, {
sourceId: job.sourceId,
mode: job.mode,
translate: job.translate,
langs: job.langs,
})
+19 -11
View File
@@ -2082,8 +2082,26 @@ export function StudioClient({
</div>
<FurnitureInspector
key={`${detail.classname}-${detail.alreadyImported}`}
key={`${detail.classname}-${detail.alreadyImported}-${jobs.jobs
.filter((j) => j.state === "completed")
.map((j) => j.id)
.join(",")}`}
item={detail}
busy={
jobs.busy ||
jobs.jobs.some(
(j) =>
(j.state === "queued" || j.state === "running") &&
j.items.some((i) => i.classname === detail.classname),
)
}
onRepair={() =>
jobs.submit([detail], {
sourceId: activeSource || undefined,
translate: false,
mode: "repair",
})
}
/>
{/* Auto-catalog preview */}
{detailPreview && (
@@ -2137,16 +2155,6 @@ export function StudioClient({
</Button>
) : (
<div className="space-y-2">
<Button
variant="outline"
className="w-full gap-1.5"
disabled={importingId !== null}
onClick={() =>
setReview({ items: [detail], single: true })
}
>
Complete missing data
</Button>
{!detail.nitroExists && (
<>
<p className="rounded-md border border-[var(--admin-warning)]/30 bg-[var(--admin-warning)]/10 p-2 text-[0.65rem] text-[var(--admin-warning)]">