From 683fee3bd9a9afc87401451a7dc6799fb0f1c5a0 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 11 Sep 2026 23:25:53 +0200 Subject: [PATCH] fix: resolve layout type errors in organize-imports-dialog --- src/app/api/admin/import/organize/route.ts | 52 ++++++++- .../admin/studio/organize-imports-dialog.tsx | 103 ++++++++++-------- 2 files changed, 106 insertions(+), 49 deletions(-) diff --git a/src/app/api/admin/import/organize/route.ts b/src/app/api/admin/import/organize/route.ts index 0a2383dd..da6f08fe 100644 --- a/src/app/api/admin/import/organize/route.ts +++ b/src/app/api/admin/import/organize/route.ts @@ -1,4 +1,8 @@ import { sql } from "drizzle-orm"; +import { + groupSuggestedCategories, + type SugFurniEntry, +} from "@/features/catalog/domain/organize"; import { getImportedCategoryPageIds, getImportedRootCaption, @@ -193,9 +197,55 @@ export const GET = withAdmin( ); }); + // 5) Group on the server so the client only ever sees compact, ready-made + // categories instead of a giant raw item array. + type SugFurniEntryExtra = SugFurniEntry & { + alreadyPlaced?: boolean; + catalogItemId?: number; + spriteId?: number; + sourcePageCaption?: string; + itemId?: number; + }; + // Map to the extended shape so groupSuggestedCategories accepts it, + // then we can use the extra fields in the returned groups. + const sugItems = items.map((it) => ({ + id: it.itemId, + itemName: it.itemName, + publicName: it.publicName, + type: it.type, + spriteId: it.spriteId, + interactionType: it.interactionType, + alreadyPlaced: it.alreadyPlaced, + catalogItemId: it.catalogItemId, + })) as SugFurniEntryExtra[]; + const groups = groupSuggestedCategories(sugItems).map((g) => { + const moved = g.items.filter((i) => i.alreadyPlaced).length; + return { + name: g.name, + icon: g.icon, + layout: g.layout?.layout ?? null, + total: g.items.length, + moved, + added: g.items.length - moved, + thumbs: g.items.slice(0, 12).map((i) => ({ + itemName: i.itemName, + publicName: i.publicName, + spriteId: i.spriteId, + alreadyPlaced: i.alreadyPlaced, + sourcePageCaption: i.sourcePageCaption, + })), + items: g.items.map((i) => ({ + itemId: i.itemId, + catalogItemId: i.catalogItemId, + alreadyPlaced: i.alreadyPlaced, + })), + }; + }); + const importRootPageId = pageIds[0] ?? null; return apiOk({ - items, + groups, + groupCount: groups.length, total: items.length, importRootPageId, importRootCaption: await getImportedRootCaption(), diff --git a/src/components/admin/studio/organize-imports-dialog.tsx b/src/components/admin/studio/organize-imports-dialog.tsx index 4748ba29..54a70f8e 100644 --- a/src/components/admin/studio/organize-imports-dialog.tsx +++ b/src/components/admin/studio/organize-imports-dialog.tsx @@ -34,23 +34,15 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { groupSuggestedCategories } from "@/features/catalog/domain/organize"; import { CATALOG_LAYOUTS, type CatalogLayout } from "@/lib/catalog-layouts"; +// import { CATALOG_LAYOUTS } from "@/lib/catalog-layouts"; import { getLocalIconUrl } from "@/lib/furni/classname"; import { cn } from "@/lib/utils"; -interface OrganizeItem { - id: number; - itemId: number; - itemName: string; - publicName: string; - type: string; - spriteId: number; - interactionType: string; - catalogItemId: number | null; - sourcePageId: number | null; - sourcePageCaption: string | null; - alreadyPlaced: boolean; +interface OrganizeGroupOverride { + caption?: string; + icon?: number; + layout?: CatalogLayout; } interface RootPage { @@ -58,10 +50,25 @@ interface RootPage { caption: string; } -interface OrganizeGroupOverride { - caption?: string; - icon?: number; - layout?: CatalogLayout; +interface OrganizeImportGroup { + name: string; + icon: number; + layout: CatalogLayout | null; + total: number; + moved: number; + added: number; + thumbs: { + itemName: string; + publicName: string; + spriteId: number; + alreadyPlaced: boolean; + sourcePageCaption: string | null; + }[]; + items: { + itemId: number; + catalogItemId: number | null; + alreadyPlaced: boolean; + }[]; } interface OrganizeImportsDialogProps { @@ -78,7 +85,7 @@ export function OrganizeImportsDialog({ const [loading, setLoading] = useState(false); const [loadError, setLoadError] = useState(false); - const [items, setItems] = useState([]); + const [groups, setGroups] = useState([]); const [rootPages, setRootPages] = useState([]); const [importRootPageId, setImportRootPageId] = useState(null); const [importRootCaption, setImportRootCaption] = useState( @@ -122,7 +129,7 @@ export function OrganizeImportsDialog({ signal: controller.signal, }).then((r) => r.json()), ]); - setItems(Array.isArray(data?.items) ? data.items : []); + setGroups(Array.isArray(data?.groups) ? data.groups : []); setImportRootPageId(data?.importRootPageId ?? null); setImportRootCaption(data?.importRootCaption ?? null); const pages = Array.isArray(tree?.pages) ? tree.pages : []; @@ -135,7 +142,7 @@ export function OrganizeImportsDialog({ } catch (error) { console.error("[OrganizeImports] load failed:", error); setLoadError(true); - setItems([]); + setGroups([]); } finally { clearTimeout(timeout); setLoading(false); @@ -144,7 +151,7 @@ export function OrganizeImportsDialog({ useEffect(() => { if (!open) return; - setItems([]); + setGroups([]); setApproved({}); setOverrides({}); setParentId(-1); @@ -198,8 +205,6 @@ export function OrganizeImportsDialog({ return () => ac.abort(); }, [moveMode, destDebounced]); - const groups = useMemo(() => groupSuggestedCategories(items), [items]); - const allApproved = groups.length > 0 && groups.every((g) => approved[g.name]); const totalSelected = useMemo( @@ -243,11 +248,11 @@ export function OrganizeImportsDialog({ caption: moveMode ? (destinationCaption ?? "Imported") : (o.caption ?? g.name).trim(), - pageLayout: o.layout ?? g.layout?.layout ?? "default_3x3", + pageLayout: o.layout ?? g.layout ?? "default_3x3", iconImage: o.icon ?? g.icon, destinationPageId: moveMode ? destinationPageId : null, items: g.items.map((item) => ({ - itemId: item.id, + itemId: item.itemId, catalogItemId: item.catalogItemId, credits: freeMode ? 0 : credits, points: freeMode ? 0 : points, @@ -304,7 +309,7 @@ export function OrganizeImportsDialog({
{/* Price (new offers only) */} - {!loading && items.length > 0 && ( + {!loading && groups.length > 0 && (
{t("price")} @@ -377,7 +382,7 @@ export function OrganizeImportsDialog({ )} {/* Mode: create new pages vs move into an existing page */} - {!loading && items.length > 0 && ( + {!loading && groups.length > 0 && (
{t("mode")} @@ -408,7 +413,7 @@ export function OrganizeImportsDialog({ )} {/* Destination page (move mode) */} - {moveMode && !loading && items.length > 0 && ( + {moveMode && !loading && groups.length > 0 && (
{t("destination")} {destinationPageId != null ? ( @@ -482,7 +487,7 @@ export function OrganizeImportsDialog({ )} {/* Parent */} - {!moveMode && !loading && items.length > 0 && ( + {!moveMode && !loading && groups.length > 0 && (
{t("parent")}