"use client"; import { useState, useEffect, useCallback, useTransition } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { RefreshCw, Plus, Loader2 } from "lucide-react"; import { DomainTabs } from "./domain-tabs"; import { MenuTree } from "./menu-tree"; import { EditNodeDialog } from "./edit-node-dialog"; import { AddNodeDialog } from "./add-node-dialog"; import { MoveToDialog } from "./move-to-dialog"; import { UnassignedMenusPanel } from "./unassigned-menus-panel"; import { getMenuTreeForAdmin, createMenuGroup, createGroup, createTopLevelMenu, updateNode, moveNodeUp, moveNodeDown, moveNodeToParent, getAvailableParents, assignMenuToGroup, activateAsTopLevelMenu, syncDiscoveredMenus, } from "../service"; import type { MenuDomain, MenuTreeNode, MenuTreeAdminResult, UpdateNodeInput, CreateMenuGroupInput, CreateGroupInput, CreateTopLevelMenuInput, } from "../types"; interface MenuTreeManagerProps { initialDomain?: MenuDomain; } export function MenuTreeManager({ initialDomain = "evcp" }: MenuTreeManagerProps) { const [domain, setDomain] = useState(initialDomain); const [data, setData] = useState(null); const [isInitialLoading, setIsInitialLoading] = useState(true); const [isPending, startTransition] = useTransition(); // Dialog states const [editDialogOpen, setEditDialogOpen] = useState(false); const [editingNode, setEditingNode] = useState(null); const [addDialogOpen, setAddDialogOpen] = useState(false); const [addDialogType, setAddDialogType] = useState<"menu_group" | "group" | "top_level_menu">("menu_group"); const [addGroupParentId, setAddGroupParentId] = useState(undefined); // Move dialog state const [moveDialogOpen, setMoveDialogOpen] = useState(false); const [movingNode, setMovingNode] = useState(null); const [availableParents, setAvailableParents] = useState<{ id: number | null; title: string; depth: number }[]>([]); // Tree expansion state const [expandedIds, setExpandedIds] = useState>(new Set()); // Load data using server action const loadData = useCallback(async (isRefresh = false) => { if (!isRefresh) { setIsInitialLoading(true); } try { const result = await getMenuTreeForAdmin(domain); setData(result); } catch (error) { console.error("Error loading menu tree:", error); toast.error("Failed to load menu tree"); } finally { setIsInitialLoading(false); } }, [domain]); useEffect(() => { setExpandedIds(new Set()); loadData(); }, [loadData]); const handleSync = async () => { startTransition(async () => { try { const result = await syncDiscoveredMenus(domain); toast.success(`Sync complete: ${result.added} menus added`); loadData(true); } catch (error) { console.error("Error syncing menus:", error); toast.error("Failed to sync menus"); } }); }; const handleEdit = (node: MenuTreeNode) => { setEditingNode(node); setEditDialogOpen(true); }; const handleSaveEdit = async (nodeId: number, input: UpdateNodeInput) => { startTransition(async () => { try { await updateNode(nodeId, input); toast.success("Saved successfully"); loadData(true); } catch (error) { console.error("Error updating node:", error); toast.error("Failed to save"); } }); }; // Move up (within same parent) const handleMoveUp = async (nodeId: number) => { startTransition(async () => { try { await moveNodeUp(nodeId); loadData(true); } catch (error) { console.error("Error moving node up:", error); toast.error("Failed to move"); } }); }; // Move down (within same parent) const handleMoveDown = async (nodeId: number) => { startTransition(async () => { try { await moveNodeDown(nodeId); loadData(true); } catch (error) { console.error("Error moving node down:", error); toast.error("Failed to move"); } }); }; // Open move to dialog const handleOpenMoveDialog = async (node: MenuTreeNode) => { setMovingNode(node); try { const parents = await getAvailableParents(node.id, domain, node.nodeType); setAvailableParents(parents); setMoveDialogOpen(true); } catch (error) { console.error("Error loading available parents:", error); toast.error("Failed to load move options"); } }; // Execute move to different parent const handleMoveTo = async (newParentId: number | null) => { if (!movingNode) return; startTransition(async () => { try { await moveNodeToParent(movingNode.id, newParentId); toast.success("Moved successfully"); setMoveDialogOpen(false); setMovingNode(null); loadData(true); } catch (error) { console.error("Error moving node:", error); toast.error("Failed to move"); } }); }; const handleAddMenuGroup = () => { setAddDialogType("menu_group"); setAddGroupParentId(undefined); setAddDialogOpen(true); }; const handleAddGroup = (parentId: number) => { setAddDialogType("group"); setAddGroupParentId(parentId); setAddDialogOpen(true); }; const handleAddTopLevelMenu = () => { setAddDialogType("top_level_menu"); setAddGroupParentId(undefined); setAddDialogOpen(true); }; const handleSaveAdd = async ( input: CreateMenuGroupInput | CreateGroupInput | CreateTopLevelMenuInput ) => { startTransition(async () => { try { if (addDialogType === "menu_group") { await createMenuGroup(domain, input as CreateMenuGroupInput); } else if (addDialogType === "group") { await createGroup(domain, input as CreateGroupInput); } else if (addDialogType === "top_level_menu") { await createTopLevelMenu(domain, input as CreateTopLevelMenuInput); } toast.success("Created successfully"); loadData(true); } catch (error) { console.error("Error creating node:", error); toast.error("Failed to create"); } }); }; const handleAssign = async (menuId: number, groupId: number) => { startTransition(async () => { try { await assignMenuToGroup(menuId, groupId); toast.success("Assigned successfully"); loadData(true); } catch (error) { console.error("Error assigning menu:", error); toast.error("Failed to assign"); } }); }; const handleActivateAsTopLevel = async (menuId: number) => { startTransition(async () => { try { await activateAsTopLevelMenu(menuId); toast.success("Activated as top-level menu"); loadData(true); } catch (error) { console.error("Error activating as top level:", error); toast.error("Failed to activate"); } }); }; // Build list of available groups for assignment const getAvailableGroups = () => { if (!data) return []; const groups: { id: number; title: string; parentTitle?: string }[] = []; for (const node of data.tree) { if (node.nodeType !== 'menu_group') continue; groups.push({ id: node.id, title: node.titleKo }); if (node.children) { for (const child of node.children) { if (child.nodeType === "group") { groups.push({ id: child.id, title: child.titleKo, parentTitle: node.titleKo, }); } } } } return groups; }; if (isInitialLoading) { return (
); } return (
{/* Header */}
{/* [jh] I've commented this button.. */} {/* */}
{/* Main Content */}
{/* Menu Tree */}
{domain === "evcp" ? "EVCP" : "Partners"} Menu Structure Use arrow buttons to reorder, or click Move To to change parent. {data?.tree && data.tree.length > 0 ? ( ) : (

No menus. Add one using the buttons above.

)}
{/* Unassigned Menus */}
{/* Dialogs */}
); }