'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, DialogTrigger, } 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 ( e.preventDefault()} > ) } 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 */}
) }