"use client"; import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { useUnsavedChangesGuard, UnsavedChangesDialog } from "@/components/common/UnsavedChangesGuard"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Loader2, Search, ChevronRight, ChevronDown, FolderOpen, FileText } from "lucide-react"; import { menuApi } from "@/lib/api/menu"; import { MenuItem } from "@/types/menu"; import { cn } from "@/lib/utils"; interface MenuSelectModalProps { isOpen: boolean; onClose: () => void; onConfirm: (menuObjids: number[]) => void; selectedMenuObjids?: number[]; } interface MenuTreeNode { objid: string; menuNameKor: string; menuUrl: string; level: number; children: MenuTreeNode[]; parentObjId: string; } export function MenuSelectModal({ isOpen, onClose, onConfirm, selectedMenuObjids = [] }: MenuSelectModalProps) { const [menus, setMenus] = useState([]); const [isLoading, setIsLoading] = useState(false); const [searchText, setSearchText] = useState(""); const [selectedIds, setSelectedIds] = useState>(new Set(selectedMenuObjids)); const [expandedIds, setExpandedIds] = useState>(new Set()); const initialSelectionRef = useRef(""); const hasChanges = useCallback(() => { const currentSelection = JSON.stringify(Array.from(selectedIds).sort()); return currentSelection !== initialSelectionRef.current; }, [selectedIds]); const guard = useUnsavedChangesGuard({ hasChanges, onClose, description: "변경된 선택 내용이 저장되지 않습니다. 정말 닫으시겠습니까?", }); useEffect(() => { if (isOpen) { setSelectedIds(new Set(selectedMenuObjids)); initialSelectionRef.current = JSON.stringify(Array.from(new Set(selectedMenuObjids)).sort()); } }, [isOpen, selectedMenuObjids]); useEffect(() => { if (isOpen) { fetchMenus(); } }, [isOpen]); const fetchMenus = async () => { setIsLoading(true); try { const response = await menuApi.getUserMenus(); if (response.success && response.data) { setMenus(response.data); const initialExpanded = new Set(); response.data.forEach((menu) => { const level = menu.lev || menu.LEV || 1; if (level <= 2) { initialExpanded.add(menu.objid || menu.OBJID || ""); } }); setExpandedIds(initialExpanded); } } catch (error) { console.error("메뉴 로드 오류:", error); } finally { setIsLoading(false); } }; const menuTree = useMemo(() => { const menuMap = new Map(); const rootMenus: MenuTreeNode[] = []; menus.forEach((menu) => { const objid = menu.objid || menu.OBJID || ""; const parentObjId = menu.parentObjId || menu.PARENT_OBJ_ID || ""; const menuNameKor = menu.menuNameKor || menu.MENU_NAME_KOR || menu.translated_name || menu.TRANSLATED_NAME || ""; const menuUrl = menu.menuUrl || menu.MENU_URL || ""; const level = menu.lev || menu.LEV || 1; menuMap.set(objid, { objid, menuNameKor, menuUrl, level, children: [], parentObjId }); }); menus.forEach((menu) => { const objid = menu.objid || menu.OBJID || ""; const parentObjId = menu.parentObjId || menu.PARENT_OBJ_ID || ""; const node = menuMap.get(objid); if (!node) return; const parent = menuMap.get(parentObjId); if (parent) { parent.children.push(node); } else { rootMenus.push(node); } }); const sortChildren = (nodes: MenuTreeNode[]) => { nodes.sort((a, b) => a.menuNameKor.localeCompare(b.menuNameKor, "ko")); nodes.forEach((node) => sortChildren(node.children)); }; sortChildren(rootMenus); return rootMenus; }, [menus]); const filteredTree = useMemo(() => { if (!searchText.trim()) return menuTree; const searchLower = searchText.toLowerCase(); const filterNodes = (nodes: MenuTreeNode[]): MenuTreeNode[] => { return nodes .map((node) => { const filteredChildren = filterNodes(node.children); const matches = node.menuNameKor.toLowerCase().includes(searchLower); if (matches || filteredChildren.length > 0) { return { ...node, children: filteredChildren }; } return null; }) .filter((node): node is MenuTreeNode => node !== null); }; return filterNodes(menuTree); }, [menuTree, searchText]); const toggleSelect = useCallback((objid: string) => { const numericId = Number(objid); setSelectedIds((prev) => { const next = new Set(prev); if (next.has(numericId)) { next.delete(numericId); } else { next.add(numericId); } return next; }); }, []); const toggleExpand = useCallback((objid: string) => { setExpandedIds((prev) => { const next = new Set(prev); if (next.has(objid)) { next.delete(objid); } else { next.add(objid); } return next; }); }, []); const handleConfirm = () => { onConfirm(Array.from(selectedIds)); onClose(); }; const renderMenuNode = (node: MenuTreeNode, depth: number = 0) => { const hasChildren = node.children.length > 0; const isExpanded = expandedIds.has(node.objid); const isSelected = selectedIds.has(Number(node.objid)); return (
toggleSelect(node.objid)} > {hasChildren ? ( ) : (
)} toggleSelect(node.objid)} onClick={(e) => e.stopPropagation()} /> {hasChildren ? ( ) : ( )} {node.menuNameKor}
{hasChildren && isExpanded &&
{node.children.map((child) => renderMenuNode(child, depth + 1))}
}
); }; return ( <> 사용 메뉴 선택 이 리포트를 사용할 메뉴를 선택하세요. 선택한 메뉴에서 이 리포트를 사용할 수 있습니다.
setSearchText(e.target.value)} className="pl-10" />
{selectedIds.size}개 메뉴 선택됨
{isLoading ? (
메뉴 로드 중...
) : filteredTree.length === 0 ? (
{searchText ? "검색 결과가 없습니다." : "표시할 메뉴가 없습니다."}
) : (
{filteredTree.map((node) => renderMenuNode(node))}
)}
); }