Fix Visual Manager empty categories: seed roots, harden tree API.
Local Build and Deploy / deploy (push) Successful in 56s

Seed root tabs from SSR, load the full tree without CLEAR_TREE races, coerce parent ids, and tolerate catalog_items page_id type mismatches so category pages actually appear.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-17 22:02:53 +02:00
1 parent 385cefd0fa
commit 1d8efefce4
6 files changed
+285 -187

No files matched your search

+57 -31
View File
@@ -6,6 +6,8 @@ import {
deletePage,
getAncestors,
getTreeFlat,
safeItemCounts,
toInt,
} from "@/lib/services/catalog-tree";
import { rcon } from "@/lib/services/rcon";
import type { TreeNode } from "@/types/catalog";
@@ -87,8 +89,9 @@ async function getChildren(
parentId: number,
isBc: boolean,
): Promise<TreeNode[]> {
// Roots in Habbo DBs are usually parent_id = -1, sometimes 0.
const parentFilter =
parentId === -1
parentId <= 0
? { OR: [{ parentId: -1 }, { parentId: 0 }] }
: { parentId };
@@ -97,7 +100,7 @@ async function getChildren(
where: parentFilter,
orderBy: { orderNum: "asc" },
});
const ids = pages.map((p) => p.id);
const ids = pages.map((p) => toInt(p.id));
const [childCounts, itemCounts] = await Promise.all([
ids.length
? prisma.catalogPagesBc.groupBy({
@@ -105,56 +108,79 @@ async function getChildren(
where: { parentId: { in: ids } },
_count: true,
})
: [],
: Promise.resolve([]),
ids.length
? prisma.catalogItemsBc.groupBy({
by: ["pageId"],
where: { pageId: { in: ids } },
_count: true,
})
: [],
? prisma.catalogItemsBc
.groupBy({
by: ["pageId"],
where: { pageId: { in: ids } },
_count: true,
})
.catch(() => [])
: Promise.resolve([]),
]);
const childMap = Object.fromEntries(
childCounts.map((c) => [c.parentId, c._count]),
childCounts.map((c) => [toInt(c.parentId), c._count]),
);
const itemMap = Object.fromEntries(
itemCounts.map((c) => [c.pageId, c._count]),
);
return pages.map((p) =>
toTreeNode(p, childMap[p.id] ?? 0, itemMap[p.id] ?? 0),
itemCounts.map((c) => [toInt(c.pageId), c._count]),
);
return pages.map((p) => {
const id = toInt(p.id);
return toTreeNode(
{
id,
caption: p.caption,
parentId: toInt(p.parentId, -1),
orderNum: toInt(p.orderNum),
enabled: String(p.enabled ?? "0"),
visible: String(p.visible ?? "0"),
iconImage: toInt(p.iconImage),
iconColor: toInt(p.iconColor),
pageLayout: p.pageLayout,
},
childMap[id] ?? 0,
itemMap[id] ?? 0,
);
});
}
const pages = await prisma.catalogPages.findMany({
where: parentFilter,
orderBy: { orderNum: "asc" },
});
const ids = pages.map((p) => p.id);
const [childCounts, itemCounts] = await Promise.all([
const ids = pages.map((p) => toInt(p.id));
const [childCounts, itemMap] = await Promise.all([
ids.length
? prisma.catalogPages.groupBy({
by: ["parentId"],
where: { parentId: { in: ids } },
_count: true,
})
: [],
ids.length
? prisma.catalogItems.groupBy({
by: ["pageId"],
where: { pageId: { in: ids } },
_count: true,
})
: [],
: Promise.resolve([]),
safeItemCounts(ids),
]);
const childMap = Object.fromEntries(
childCounts.map((c) => [c.parentId, c._count]),
);
const itemMap = Object.fromEntries(
itemCounts.map((c) => [c.pageId, c._count]),
);
return pages.map((p) =>
toTreeNode(p, childMap[p.id] ?? 0, itemMap[p.id] ?? 0),
childCounts.map((c) => [toInt(c.parentId), c._count]),
);
return pages.map((p) => {
const id = toInt(p.id);
return toTreeNode(
{
id,
caption: p.caption,
parentId: toInt(p.parentId, -1),
orderNum: toInt(p.orderNum),
enabled: String(p.enabled ?? "0"),
visible: String(p.visible ?? "0"),
iconImage: toInt(p.iconImage),
iconColor: toInt(p.iconColor),
pageLayout: p.pageLayout,
},
childMap[id] ?? 0,
itemMap.get(id) ?? 0,
);
});
}
async function searchPages(q: string, isBc: boolean): Promise<TreeNode[]> {