"use client"; import { LayoutGrid } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { createCatalogPage } from "@/actions/catalog"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { TooltipProvider } from "@/components/ui/tooltip"; import type { RootTab } from "@/types/catalog"; import { BreadcrumbBar } from "./breadcrumb-bar"; import { InlineEditor, type PageUpdateData } from "./inline-editor"; import { RootTabs } from "./root-tabs"; import { SortableTree } from "./sortable-tree"; import { StatsBar } from "./stats-bar"; import { CatalogManagerProvider, useCatalogManager } from "./use-catalog-store"; export function CatalogManagerDialog() { const [open, setOpen] = useState(false); return ( {open ? ( ) : null} ); } function CatalogManagerInner({ open, // biome-ignore lint/correctness/noUnusedFunctionParameters: part of the dialog public API onOpenChange, }: { open: boolean; onOpenChange: (v: boolean) => void; }) { const { state, dispatch } = useCatalogManager(); // Load root pages when dialog opens const loadRootPages = useCallback(async () => { dispatch({ type: "SET_LOADING_ROOT", loading: true }); try { const res = await fetch("/api/admin/catalog/tree?parentId=-1"); if (!res.ok) throw new Error(); const data = await res.json(); const pages: RootTab[] = (data.pages || []).map( (p: Record) => ({ id: p.id as number, caption: p.caption as string, iconImage: p.iconImage as number, enabled: String(p.enabled), orderNum: (p.orderNum as number) || 0, }), ); dispatch({ type: "SET_ROOT_PAGES", pages }); // Auto-select first tab if none or missing if ( pages.length > 0 && (!state.activeTabId || !pages.find((p) => p.id === state.activeTabId)) ) { dispatch({ type: "SET_ACTIVE_TAB", id: pages[0].id }); } } catch { toast.error("Failed to load catalog root pages"); dispatch({ type: "SET_LOADING_ROOT", loading: false }); } }, [dispatch, state.activeTabId]); // biome-ignore lint/correctness/useExhaustiveDependencies: explicitly chosen here, see surrounding code useEffect(() => { if (open) loadRootPages(); }, [open]); // eslint-disable-line react-hooks/exhaustive-deps // Handle dialog close: preserve tree state, clear selection useEffect(() => { if (!open) dispatch({ type: "ON_DIALOG_CLOSE" }); }, [open, dispatch]); const handleTabClick = (id: number) => { if (state.editorDirty && !confirm("You have unsaved changes. Discard?")) return; dispatch({ type: "SET_ACTIVE_TAB", id }); dispatch({ type: "CLEAR_TREE" }); }; const handleCreateRoot = async () => { const caption = prompt("Root page name:"); if (!caption?.trim()) return; try { const result = await createCatalogPage({ parentId: -1, caption: caption.trim(), }); if (!result.ok) throw new Error(result.error); await loadRootPages(); toast.success("Root page created"); } catch { toast.error("Failed to create root page"); } }; const handleSelectPage = (id: number) => { if (state.editorDirty && !confirm("You have unsaved changes. Discard?")) return; dispatch({ type: "SELECT_PAGE", id: id > 0 ? id : null }); }; const handleEditTab = (id: number) => { // First ensure this tab is active, then select it in the editor if (state.activeTabId !== id) { dispatch({ type: "SET_ACTIVE_TAB", id }); dispatch({ type: "CLEAR_TREE" }); } if (state.editorDirty && !confirm("You have unsaved changes. Discard?")) return; dispatch({ type: "SELECT_PAGE", id }); }; const handleEditorSaved = (updated: PageUpdateData) => { dispatch({ type: "UPDATE_NODE", update: { ...updated } }); dispatch({ type: "SET_DIRTY", dirty: false }); // Update root tabs if root page const rootIdx = state.rootPages.findIndex((p) => p.id === updated.id); if (rootIdx !== -1) { const pages = [...state.rootPages]; pages[rootIdx] = { ...pages[rootIdx], caption: updated.caption, enabled: updated.enabled, }; dispatch({ type: "SET_ROOT_PAGES", pages }); } }; return ( {/* Header */} Visual Catalog Manager {/* Breadcrumb */} {/* Root tabs */} {/* Main content: tree + editor */}
{/* Stats bar */}
); }