"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { MenuItem, MenuState } from "@/types/menu"; import { LAYOUT_CONFIG } from "@/constants/layout"; /** * 메뉴 관련 비즈니스 로직을 관리하는 커스텀 훅 */ export const useMenu = (user: any, authLoading: boolean) => { const router = useRouter(); // 상태 관리 const [menuState, setMenuState] = useState({ menuList: [], expandedMenus: new Set(), isLoading: true, }); /** * 데이터 키를 대문자로 변환하는 함수 */ const convertToUpperCaseKeys = useCallback((data: Record[]): MenuItem[] => { return data.map((item) => { const converted: Record = {}; Object.keys(item).forEach((key) => { const upperKey = key.toUpperCase(); converted[upperKey] = item[key]; }); return converted as unknown as MenuItem; }); }, []); /** * 메뉴 트리 구조 생성 */ const buildMenuTree = useCallback((menuItems: MenuItem[]): MenuItem[] => { console.log("빌드 메뉴 트리 - 원본 메뉴 아이템들:", menuItems); const menuMap = new Map(); const rootMenus: MenuItem[] = []; // 모든 메뉴를 맵에 저장 (ID를 문자열로 변환) menuItems.forEach((menu) => { const objId = String(menu.OBJID); const parentId = String(menu.PARENT_OBJ_ID); console.log(`메뉴 처리: ${menu.MENU_NAME_KOR}, OBJID: ${objId}, PARENT_OBJ_ID: ${parentId}`); menuMap.set(objId, { ...menu, OBJID: objId, PARENT_OBJ_ID: parentId, children: [] }); }); console.log("메뉴 맵 생성 완료, 총 메뉴 수:", menuMap.size); // 부모-자식 관계 설정 menuItems.forEach((menu) => { const objId = String(menu.OBJID); const parentId = String(menu.PARENT_OBJ_ID); const menuItem = menuMap.get(objId)!; // PARENT_OBJ_ID가 특정 값이 아닌 경우 (루트가 아닌 경우) if (parentId !== "-395553955") { const parent = menuMap.get(parentId); if (parent) { parent.children = parent.children || []; parent.children.push(menuItem); console.log(`자식 메뉴 추가: ${menu.MENU_NAME_KOR} -> ${parent.MENU_NAME_KOR}`); } else { console.log(`부모 메뉴를 찾을 수 없음: ${menu.MENU_NAME_KOR}, 부모 ID: ${parentId}`); } } else { rootMenus.push(menuItem); console.log(`루트 메뉴 추가: ${menu.MENU_NAME_KOR}`); } }); console.log("루트 메뉴 개수:", rootMenus.length); console.log( "최종 루트 메뉴들:", rootMenus.map((m) => m.MENU_NAME_KOR), ); return rootMenus.sort((a, b) => (a.SEQ || 0) - (b.SEQ || 0)); }, []); /** * 메뉴 데이터 로드 */ const loadMenuData = useCallback(async () => { try { // JWT 토큰 가져오기 const token = localStorage.getItem("authToken"); if (!token) { console.error("JWT 토큰이 없습니다."); router.push("/login"); return; } // 메뉴 목록 조회 const menuResponse = await fetch(`${LAYOUT_CONFIG.API_BASE_URL}${LAYOUT_CONFIG.ENDPOINTS.USER_MENUS}`, { method: "GET", headers: { Authorization: `Bearer ${token}`, Accept: "application/json", "Content-Type": "application/json", }, }); if (menuResponse.ok) { const menuResult = await menuResponse.json(); console.log("메뉴 응답 데이터:", menuResult); if (menuResult.success && menuResult.data) { console.log("메뉴 데이터 배열:", menuResult.data); const convertedMenuData = convertToUpperCaseKeys(menuResult.data || []); console.log("변환된 메뉴 데이터:", convertedMenuData); setMenuState((prev: MenuState) => ({ ...prev, menuList: buildMenuTree(convertedMenuData), isLoading: false, })); } } else if (menuResponse.status === 401) { // 인증 실패 시 토큰 제거 및 로그인 페이지로 리다이렉트 localStorage.removeItem("authToken"); router.push("/login"); } } catch (error) { console.error("메뉴 데이터 로드 실패:", error); localStorage.removeItem("authToken"); router.push("/login"); setMenuState((prev: MenuState) => ({ ...prev, isLoading: false })); } }, [router, convertToUpperCaseKeys, buildMenuTree]); /** * 메뉴 토글 */ const toggleMenu = useCallback((menuId: string) => { setMenuState((prev: MenuState) => { const newExpanded = new Set(prev.expandedMenus); if (newExpanded.has(menuId)) { newExpanded.delete(menuId); } else { newExpanded.add(menuId); } return { ...prev, expandedMenus: newExpanded, }; }); }, []); /** * 메뉴 클릭 처리 */ const handleMenuClick = useCallback( async (menu: MenuItem) => { if (menu.children && menu.children.length > 0) { toggleMenu(String(menu.OBJID)); } else { // 메뉴 이름 저장 (엑셀 다운로드 파일명에 사용) const menuName = menu.MENU_NAME_KOR || menu.menuNameKor || menu.TRANSLATED_NAME || "메뉴"; if (typeof window !== "undefined") { localStorage.setItem("currentMenuName", menuName); } // 먼저 할당된 화면이 있는지 확인 (URL 유무와 관계없이) try { const menuObjid = menu.OBJID || menu.objid; if (menuObjid) { const { menuScreenApi } = await import("@/lib/api/screen"); const assignedScreens = await menuScreenApi.getScreensByMenu(parseInt(menuObjid.toString())); if (assignedScreens.length > 0) { // 할당된 화면이 있으면 첫 번째 화면으로 이동 const firstScreen = assignedScreens[0]; router.push(`/screens/${firstScreen.screenId}`); return; } } } catch (error) { console.warn("할당된 화면 조회 실패:", error); } // 할당된 화면이 없고 URL이 있으면 기존 URL로 이동 if (menu.MENU_URL) { router.push(menu.MENU_URL); } else { // URL도 없고 할당된 화면도 없으면 경고 메시지 console.warn("메뉴에 URL이나 할당된 화면이 없습니다:", menu); const { toast } = await import("sonner"); toast.warning("이 메뉴에는 연결된 페이지나 화면이 없습니다."); } } }, [toggleMenu, router], ); // 사용자 정보가 있고 로딩이 완료되면 메뉴 데이터 로드 useEffect(() => { if (user && !authLoading) { loadMenuData(); } }, [user, authLoading, loadMenuData]); return { menuList: menuState.menuList, expandedMenus: menuState.expandedMenus, isMenuLoading: menuState.isLoading, handleMenuClick, toggleMenu, refreshMenus: loadMenuData, // 메뉴 새로고침 함수 추가 }; };