Studio: add clone-all-per-hotel, per-item status detail, retry, ETA, and auto-translate
CI / runtime-diagnostics (push) Skipped
CI / check (push) Successful in 36s
CI / release (push) Skipped
CI / deploy (push) Successful in 1m14s

- Add 'Clone all missing' button for clone sources with confirmation dialog
- Show per-item status badges: nitro, furnidata, catalog entry
- Add status filters: missing furnidata, missing catalog entry
- Add ETA and percentage to batch progress bar
- Add retry button for failed items after batch import
- Auto-translate all languages after clone-all completes
- Fix statusFilter 'all' bug that incorrectly filtered imported items
- Bulk-load FurnitureData + catalog references for O(1) per-item checks
This commit is contained in:
openhands committed 2026-09-01 17:29:12 +02:00
1 parent 1610aa1008
commit 1374bd332f
4 files changed
+580 -47

No files matched your search

+73 -1
View File
@@ -11,6 +11,7 @@ import { getSource } from "@/lib/services/clone-sources";
import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs";
import {
appendFurniEntriesBulk,
readFurniData,
rebuildCatalogOfferIds,
reconcileFurniDataWithItemsBase,
verifyAndFixSpriteIds,
@@ -311,6 +312,38 @@ export const GET = withAdmin(
});
const sourceIconBase = source.iconBaseUrl?.trim().replace(/\/+$/, "");
// Bulk-load FurnitureData and catalog references for status checks
const furniDataSet = new Set<string>();
const catalogIdSet = new Set<number>();
try {
const fd = (await readFurniData()) as {
roomitemtypes?: { furnitype?: Array<{ classname?: string }> };
wallitemtypes?: { furnitype?: Array<{ classname?: string }> };
};
for (const e of [
...(fd.roomitemtypes?.furnitype ?? []),
...(fd.wallitemtypes?.furnitype ?? []),
]) {
if (e.classname) furniDataSet.add(e.classname);
}
} catch {
/* best effort */
}
try {
const rows = await db.execute(sql`SELECT item_ids FROM catalog_items`);
for (const row of (
rows as unknown as [Array<{ item_ids: string | null }>, unknown]
)[0]) {
for (const part of String(row.item_ids ?? "").split(/[;,]/)) {
const n = Number(part.trim());
if (Number.isFinite(n) && n > 0) catalogIdSet.add(n);
}
}
} catch {
/* best effort */
}
const enriched = items.map((item) => {
const imported = item.present;
let nitroExists = false;
@@ -336,6 +369,8 @@ export const GET = withAdmin(
category: String(item.category ?? ""),
alreadyImported: imported,
nitroExists,
hasFurnidata: imported ? furniDataSet.has(item.classname) : undefined,
hasCatalogEntry: imported ? catalogIdSet.has(item.id) : undefined,
iconUrl: sourceIconBase
? `${sourceIconBase}/${encodeURIComponent(item.classname)}_icon.png`
: undefined,
@@ -364,6 +399,37 @@ export const GET = withAdmin(
allImported.map((e) => e.itemName).filter((n): n is string => !!n),
);
// Bulk-load FurnitureData and catalog references for status checks
const furniDataSet = new Set<string>();
const catalogIdSet = new Set<number>();
try {
const fd = (await readFurniData()) as {
roomitemtypes?: { furnitype?: Array<{ classname?: string }> };
wallitemtypes?: { furnitype?: Array<{ classname?: string }> };
};
for (const e of [
...(fd.roomitemtypes?.furnitype ?? []),
...(fd.wallitemtypes?.furnitype ?? []),
]) {
if (e.classname) furniDataSet.add(e.classname);
}
} catch {
/* best effort */
}
try {
const rows = await db.execute(sql`SELECT item_ids FROM catalog_items`);
for (const row of (
rows as unknown as [Array<{ item_ids: string | null }>, unknown]
)[0]) {
for (const part of String(row.item_ids ?? "").split(/[;,]/)) {
const n = Number(part.trim());
if (Number.isFinite(n) && n > 0) catalogIdSet.add(n);
}
}
} catch {
/* best effort */
}
const result = await listFurniture({
search: search || undefined,
page,
@@ -386,7 +452,13 @@ export const GET = withAdmin(
);
nitroExists = existsSync(/*turbopackIgnore: true*/ p);
}
return { ...item, alreadyImported: imported, nitroExists };
return {
...item,
alreadyImported: imported,
nitroExists,
hasFurnidata: imported ? furniDataSet.has(item.classname) : undefined,
hasCatalogEntry: imported ? catalogIdSet.has(item.id) : undefined,
};
});
return apiOk({ items: enriched, meta: result.meta });