Fix Visual Manager opacity and load categories like CatalogTree.
Local Build and Deploy / deploy (push) Successful in 55s

Use an opaque admin-canvas portal and lazy parentId fetches instead of mode=full, which fails on large catalogs. Search no longer loads the entire tree.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-17 22:09:01 +02:00
1 parent 1d8efefce4
commit 33a8a19820
4 files changed
+191 -46

No files matched your search

+72 -6
View File
@@ -184,13 +184,79 @@ async function getChildren(
} }
async function searchPages(q: string, isBc: boolean): Promise<TreeNode[]> { async function searchPages(q: string, isBc: boolean): Promise<TreeNode[]> {
const flat = isBc ? await getBcTreeFlat() : await getTreeFlat(); const needle = q.trim();
const needle = q.toLowerCase(); const idExact = Number.parseInt(needle, 10);
return flat.filter( const idFilter =
(p) => Number.isFinite(idExact) && String(idExact) === needle
p.caption.toLowerCase().includes(needle) || ? [{ id: idExact }]
String(p.id) === needle, : [];
if (isBc) {
const pages = await prisma.catalogPagesBc.findMany({
where: {
OR: [{ caption: { contains: needle } }, ...idFilter],
},
orderBy: { orderNum: "asc" },
take: 50,
});
return pages.map((p) =>
toTreeNode(
{
id: toInt(p.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,
},
0,
0,
),
);
}
const pages = await prisma.catalogPages.findMany({
where: {
OR: [{ caption: { contains: needle } }, ...idFilter],
},
orderBy: { orderNum: "asc" },
take: 50,
});
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,
})
: Promise.resolve([]),
safeItemCounts(ids),
]);
const childMap = Object.fromEntries(
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,
);
});
} }
export const GET = withAdmin( export const GET = withAdmin(
@@ -59,13 +59,14 @@ export function CatalogManagerDialog({
</Button> </Button>
{mounted && open {mounted && open
? createPortal( ? createPortal(
<div className="fixed inset-0 z-50"> <div className="fixed inset-0 z-[200]">
<div className="absolute inset-0 bg-black/50" /> <div className="absolute inset-0 bg-black/70" />
<div <div
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="visual-catalog-manager-title" aria-labelledby="visual-catalog-manager-title"
className="fixed top-1/2 left-1/2 z-50 flex h-[95vh] max-h-[95vh] w-[98vw] max-w-[98vw] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-lg border bg-card text-card-foreground shadow-lg" className="fixed top-[2.5vh] left-[1vw] z-[201] flex h-[95vh] max-h-[95vh] w-[98vw] max-w-[98vw] flex-col overflow-hidden rounded-lg border border-[var(--admin-border)] bg-[var(--admin-canvas)] text-[var(--admin-text)] shadow-2xl"
style={{ backgroundColor: "var(--admin-canvas)" }}
> >
<CatalogManagerProvider> <CatalogManagerProvider>
<CatalogManagerInner <CatalogManagerInner
@@ -212,7 +213,10 @@ function CatalogManagerInner({
return ( return (
<TooltipProvider delayDuration={300}> <TooltipProvider delayDuration={300}>
<div className="flex shrink-0 items-center justify-between gap-2 border-b px-4 py-2.5"> <div
className="flex shrink-0 items-center justify-between gap-2 border-b border-[var(--admin-border)] px-4 py-2.5"
style={{ backgroundColor: "var(--admin-surface)" }}
>
<h2 <h2
id="visual-catalog-manager-title" id="visual-catalog-manager-title"
className="flex items-center gap-2 text-base font-semibold leading-none" className="flex items-center gap-2 text-base font-semibold leading-none"
@@ -243,7 +247,10 @@ function CatalogManagerInner({
loading={state.loadingRoot} loading={state.loadingRoot}
/> />
<div className="flex min-h-0 flex-1 overflow-hidden"> <div
className="flex min-h-0 flex-1 overflow-hidden"
style={{ backgroundColor: "var(--admin-canvas)" }}
>
<SortableTree <SortableTree
activeTabId={state.activeTabId} activeTabId={state.activeTabId}
selectedPageId={state.selectedPageId} selectedPageId={state.selectedPageId}
@@ -251,10 +258,15 @@ function CatalogManagerInner({
onTreeMutation={loadRootPages} onTreeMutation={loadRootPages}
nodeUpdate={null} nodeUpdate={null}
/> />
<InlineEditor <div
pageId={state.selectedPageId} className="flex min-h-0 min-w-0 flex-1 flex-col"
onSaved={handleEditorSaved} style={{ backgroundColor: "var(--admin-canvas)" }}
/> >
<InlineEditor
pageId={state.selectedPageId}
onSaved={handleEditorSaved}
/>
</div>
</div> </div>
<StatsBar /> <StatsBar />
@@ -128,7 +128,10 @@ export function RootTabs({
: null; : null;
return ( return (
<div className="flex items-center gap-1 border-b bg-muted/30 px-2 py-1.5 overflow-x-auto shrink-0"> <div
className="flex shrink-0 items-center gap-1 overflow-x-auto border-b border-[var(--admin-border)] px-2 py-1.5"
style={{ backgroundColor: "var(--admin-surface)" }}
>
{confirmDialog} {confirmDialog}
{loading && ( {loading && (
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground mx-2" /> <Loader2 className="h-4 w-4 animate-spin text-muted-foreground mx-2" />
File diff suppressed because it is too large. Load diff