feat(catalog): add a Studio button to fix missing furnidata entries
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 28s
CI / tests-integration (push) Successful in 1m33s
CI / tests-unit (push) Successful in 1m38s
CI / tests-ui (push) Successful in 2m26s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m1s
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 28s
CI / tests-integration (push) Successful in 1m33s
CI / tests-unit (push) Successful in 1m38s
CI / tests-ui (push) Successful in 2m26s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m1s
"Missing furnidata" was only a filter in the Studio status dropdown, so
imported items whose classname was absent from FurnitureData.json could
be found but not fixed from that screen. Only the Catalog Audit page could
repair them, and only globally.
Adds the same shape of quick action that "no nitro" already had:
- studio-client.tsx: a "N no furnidata" shortcut next to the "N no nitro"
button that sets the missingFurnidata status filter, and a bulk "Add
missing furnidata (N)" button for the selected rows. Both only appear
when there is something to act on. Rows that come back repaired flip
to hasFurnidata: true so the badges and counts update in place; rows
the server reported in errors keep their state.
- resync/route.ts: accepts an optional { classnames: string[] } body to
target exactly the selected rows. classnames are resolved through the
same normalized local index the listing uses to decide hasFurnidata, so
the rows written are the rows flagged as missing. The upsert is already
idempotent, and RCON updateCatalog + updateItems run afterwards so the
emulator picks the new entries up.
Also clears the Studio furnidata cache after a write, which this route
never did: without it the listing kept serving a stale hasFurnidata for
up to the 30s cache TTL, so a repair looked like it had done nothing.
PERMS is now imported from permission-slugs (identical re-export) so the
route no longer pulls next-auth into tests.
- studio-filters.test.ts: pins the missingFurnidata branch, in particular
that an unchecked item (hasFurnidata undefined) is not treated as missing.
The existing ?days / ?missing / ?broken / ?all modes are unchanged; the
body is only consulted when it carries a classnames array.
This commit is contained in:
1 parent
4be7eaed59
commit
2f7e557d5e
4 files changed
+384
-11
No files matched your search
@@ -8,6 +8,7 @@ import {
|
||||
Check,
|
||||
CircleAlert,
|
||||
CloudDownload,
|
||||
FileJson2,
|
||||
Globe,
|
||||
Languages,
|
||||
Layers,
|
||||
@@ -1145,6 +1146,7 @@ export function StudioClient({
|
||||
|
||||
const [bulkDeleting, setBulkDeleting] = useState(false);
|
||||
const [bulkRegenning, setBulkRegenning] = useState(false);
|
||||
const [bulkFurnidata, setBulkFurnidata] = useState(false);
|
||||
|
||||
async function bulkDeleteSelected() {
|
||||
if (selectedImported.length === 0) return;
|
||||
@@ -1241,6 +1243,46 @@ export function StudioClient({
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkAddFurnidata() {
|
||||
if (selectedMissingFurnidata.length === 0) return;
|
||||
setBulkFurnidata(true);
|
||||
const wanted = selectedMissingFurnidata.map((i) => i.classname);
|
||||
try {
|
||||
const res = await adminFetch("/api/admin/import/furni/resync", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ classnames: wanted }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
const failedClassnames = new Set<string>(
|
||||
((data.errors ?? []) as Array<{ classname: string }>).map(
|
||||
(e) => e.classname,
|
||||
),
|
||||
);
|
||||
const fixedClassnames = new Set(
|
||||
wanted.filter((cn) => !failedClassnames.has(cn)),
|
||||
);
|
||||
setItems((prev) =>
|
||||
prev.map((i) =>
|
||||
fixedClassnames.has(i.classname) ? { ...i, hasFurnidata: true } : i,
|
||||
),
|
||||
);
|
||||
const fixed = Number(data.resynced ?? fixedClassnames.size);
|
||||
const failed = Number(data.failed ?? 0);
|
||||
toast.success(
|
||||
`Furnidata added: ${fixed}${failed > 0 ? `, failed: ${failed}` : ""}`,
|
||||
);
|
||||
} else {
|
||||
toast.error(data.error || "Adding furnidata failed");
|
||||
}
|
||||
} catch {
|
||||
toast.error("Network error");
|
||||
} finally {
|
||||
setBulkFurnidata(false);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedNonImported = useMemo(
|
||||
() =>
|
||||
filteredItems.filter(
|
||||
@@ -1255,6 +1297,13 @@ export function StudioClient({
|
||||
[filteredItems],
|
||||
);
|
||||
|
||||
const missingFurnidataCount = useMemo(
|
||||
() =>
|
||||
filteredItems.filter((i) => i.alreadyImported && i.hasFurnidata === false)
|
||||
.length,
|
||||
[filteredItems],
|
||||
);
|
||||
|
||||
const selectedImported = useMemo(
|
||||
() =>
|
||||
filteredItems.filter(
|
||||
@@ -1268,6 +1317,11 @@ export function StudioClient({
|
||||
[selectedImported],
|
||||
);
|
||||
|
||||
const selectedMissingFurnidata = useMemo(
|
||||
() => selectedImported.filter((i) => i.hasFurnidata === false),
|
||||
[selectedImported],
|
||||
);
|
||||
|
||||
const notImportedCount = useMemo(
|
||||
() =>
|
||||
activeSource && cloneStats
|
||||
@@ -1556,6 +1610,21 @@ export function StudioClient({
|
||||
{missingNitroCount} no nitro
|
||||
</button>
|
||||
)}
|
||||
{missingFurnidataCount > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setStatusFilter("missingFurnidata")}
|
||||
className={cn(
|
||||
"flex h-8 items-center gap-1.5 rounded-md border px-2.5 text-xs font-medium transition-colors",
|
||||
statusFilter === "missingFurnidata"
|
||||
? "border-[var(--admin-warning)] bg-[var(--admin-warning)]/10 text-[var(--admin-warning)]"
|
||||
: "border-[var(--admin-border)] text-[var(--admin-warning)] hover:bg-[var(--admin-warning)]/10",
|
||||
)}
|
||||
>
|
||||
<FileJson2 size={12} />
|
||||
{missingFurnidataCount} no furnidata
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="flex h-8 items-center overflow-hidden rounded-md border border-[var(--admin-border)]">
|
||||
<button
|
||||
@@ -1848,6 +1917,23 @@ export function StudioClient({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{selectedMissingFurnidata.length > 0 && !batchProgress && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="gap-1.5"
|
||||
disabled={bulkFurnidata}
|
||||
onClick={bulkAddFurnidata}
|
||||
>
|
||||
{bulkFurnidata ? (
|
||||
<Loader2 size={14} className="animate-spin" />
|
||||
) : (
|
||||
<FileJson2 size={14} />
|
||||
)}
|
||||
Add missing furnidata ({selectedMissingFurnidata.length})
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{selectedImported.length > 0 && !batchProgress && (
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
Reference in new issue
Block a user