"use client"; import React, { useState, useEffect } from "react"; import { MenuItem, MenuFormData, menuApi, LangKey } from "@/lib/api/menu"; import { companyAPI } from "@/lib/api/company"; import { screenApi } from "@/lib/api/screen"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { toast } from "sonner"; import { ChevronDown, Search } from "lucide-react"; import { MENU_MANAGEMENT_KEYS } from "@/lib/utils/multilang"; import { ScreenDefinition } from "@/types/screen"; interface Company { company_code: string; company_name: string; status: string; } interface MenuFormModalProps { isOpen: boolean; onClose: () => void; onSuccess: () => void; menuId?: string; parentId?: string; menuType?: string; level?: number; parentCompanyCode?: string; // 다국어 텍스트 props 추가 uiTexts: Record; } export const MenuFormModal: React.FC = ({ isOpen, onClose, onSuccess, menuId, parentId, menuType, level, parentCompanyCode, uiTexts, }) => { // console.log("🎯 MenuFormModal 렌더링 - Props:", { // isOpen, // menuId, // parentId, // menuType, // level, // parentCompanyCode, // }); // 다국어 텍스트 가져오기 함수 const getText = (key: string, fallback?: string): string => { return uiTexts[key] || fallback || key; }; // console.log("🔍 MenuFormModal 컴포넌트 마운트됨"); const [formData, setFormData] = useState({ parentObjId: parentId || "0", menuNameKor: "", menuUrl: "", menuDesc: "", seq: 1, menuType: "1", status: "ACTIVE", companyCode: parentCompanyCode || "none", langKey: "", }); // 화면 할당 관련 상태 const [urlType, setUrlType] = useState<"direct" | "screen">("screen"); // URL 직접 입력 or 화면 할당 (기본값: 화면 할당) const [selectedScreen, setSelectedScreen] = useState(null); const [screens, setScreens] = useState([]); const [screenSearchText, setScreenSearchText] = useState(""); const [isScreenDropdownOpen, setIsScreenDropdownOpen] = useState(false); const [loading, setLoading] = useState(false); const [isEdit, setIsEdit] = useState(false); const [companies, setCompanies] = useState([]); const [langKeys, setLangKeys] = useState([]); const [isLangKeyDropdownOpen, setIsLangKeyDropdownOpen] = useState(false); const [langKeySearchText, setLangKeySearchText] = useState(""); // 화면 목록 로드 const loadScreens = async () => { try { const response = await screenApi.getScreens({ size: 1000 }); // 모든 화면 가져오기 // console.log("🔍 화면 목록 로드 디버깅:", { // totalScreens: response.data.length, // firstScreen: response.data[0], // firstScreenFields: response.data[0] ? Object.keys(response.data[0]) : [], // firstScreenValues: response.data[0] ? Object.values(response.data[0]) : [], // allScreenIds: response.data // .map((s) => ({ // screenId: s.screenId, // legacyId: s.id, // name: s.screenName, // code: s.screenCode, // })) // .slice(0, 5), // 처음 5개만 출력 // }); setScreens(response.data); console.log("✅ 화면 목록 로드 완료:", response.data.length); } catch (error) { console.error("❌ 화면 목록 로드 실패:", error); toast.error("화면 목록을 불러오는데 실패했습니다."); } }; // 화면 선택 시 URL 자동 설정 const handleScreenSelect = (screen: ScreenDefinition) => { // console.log("🖥️ 화면 선택 디버깅:", { // screen, // screenId: screen.screenId, // screenIdType: typeof screen.screenId, // legacyId: screen.id, // allFields: Object.keys(screen), // screenValues: Object.values(screen), // }); // ScreenDefinition에서는 screenId 필드를 사용 const actualScreenId = screen.screenId || screen.id; if (!actualScreenId) { console.error("❌ 화면 ID를 찾을 수 없습니다:", screen); toast.error("화면 ID를 찾을 수 없습니다. 다른 화면을 선택해주세요."); return; } setSelectedScreen(screen); setIsScreenDropdownOpen(false); // 실제 라우팅 패턴에 맞게 URL 생성: /screens/[screenId] (복수형) // 관리자 메뉴인 경우 mode=admin 파라미터 추가 let screenUrl = `/screens/${actualScreenId}`; // 현재 메뉴 타입이 관리자인지 확인 (0 또는 "admin") const isAdminMenu = menuType === "0" || menuType === "admin" || formData.menuType === "0"; if (isAdminMenu) { screenUrl += "?mode=admin"; } setFormData((prev) => ({ ...prev, menuUrl: screenUrl, })); // console.log("🖥️ 화면 선택 완료:", { // screenId: screen.screenId, // legacyId: screen.id, // actualScreenId, // screenName: screen.screenName, // menuType: menuType, // formDataMenuType: formData.menuType, // isAdminMenu, // generatedUrl: screenUrl, // }); }; // URL 타입 변경 시 처리 const handleUrlTypeChange = (type: "direct" | "screen") => { // console.log("🔄 URL 타입 변경:", { // from: urlType, // to: type, // currentSelectedScreen: selectedScreen?.screenName, // currentUrl: formData.menuUrl, // }); setUrlType(type); if (type === "direct") { // 직접 입력 모드로 변경 시 선택된 화면 초기화 setSelectedScreen(null); // URL 필드도 초기화 (사용자가 직접 입력할 수 있도록) setFormData((prev) => ({ ...prev, menuUrl: "", })); } else { // 화면 할당 모드로 변경 시 // 기존에 선택된 화면이 있고, 해당 화면의 URL이 있다면 유지 if (selectedScreen) { console.log("📋 기존 선택된 화면 유지:", selectedScreen.screenName); // 현재 선택된 화면으로 URL 재생성 const actualScreenId = selectedScreen.screenId || selectedScreen.id; let screenUrl = `/screens/${actualScreenId}`; // 관리자 메뉴인 경우 mode=admin 파라미터 추가 const isAdminMenu = menuType === "0" || menuType === "admin" || formData.menuType === "0"; if (isAdminMenu) { screenUrl += "?mode=admin"; } setFormData((prev) => ({ ...prev, menuUrl: screenUrl, })); } else { // 선택된 화면이 없으면 URL만 초기화 setFormData((prev) => ({ ...prev, menuUrl: "", })); } } }; // loadMenuData 함수를 먼저 정의 const loadMenuData = async () => { console.log("loadMenuData 호출됨 - menuId:", menuId); if (!menuId) { console.log("menuId가 없어서 loadMenuData 종료"); return; } try { setLoading(true); console.log("API 호출 시작 - menuId:", menuId); // console.log("API URL:", `/admin/menus/${menuId}`); const response = await menuApi.getMenuInfo(menuId); console.log("메뉴 정보 조회 응답:", response); console.log("응답 success:", response.success); console.log("응답 data:", response.data); console.log("응답 message:", response.message); console.log("응답 errorCode:", response.errorCode); if (response.success && response.data) { const menu = response.data; console.log("메뉴 데이터:", menu); console.log("메뉴 데이터 키들:", Object.keys(menu)); // 대문자 키와 소문자 키 모두 처리 const menuType = menu.menu_type || menu.MENU_TYPE || "1"; const status = menu.status || menu.STATUS || "active"; const companyCode = menu.company_code || menu.COMPANY_CODE || ""; const langKey = menu.lang_key || menu.LANG_KEY || ""; // 메뉴 타입 변환 (admin/user -> 0/1) let convertedMenuType = menuType; if (menuType === "admin" || menuType === "0") { convertedMenuType = "0"; } else if (menuType === "user" || menuType === "1") { convertedMenuType = "1"; } // 상태 변환 (active/inactive/inActive -> ACTIVE/INACTIVE) let convertedStatus = status; if (status === "active") { convertedStatus = "ACTIVE"; } else if (status === "inactive" || status === "inActive") { convertedStatus = "INACTIVE"; } const menuUrl = menu.menu_url || menu.MENU_URL || ""; // URL이 "/screens/"로 시작하면 화면 할당으로 판단 (실제 라우팅 패턴에 맞게 수정) const isScreenUrl = menuUrl.startsWith("/screens/"); setFormData({ objid: menu.objid || menu.OBJID, parentObjId: menu.parent_obj_id || menu.PARENT_OBJ_ID || "0", menuNameKor: menu.menu_name_kor || menu.MENU_NAME_KOR || "", menuUrl: menuUrl, menuDesc: menu.menu_desc || menu.MENU_DESC || "", seq: menu.seq || menu.SEQ || 1, menuType: convertedMenuType, status: convertedStatus, companyCode: companyCode, langKey: langKey, // 다국어 키 설정 }); // URL 타입 설정 if (isScreenUrl) { setUrlType("screen"); // "/screens/123" 또는 "/screens/123?mode=admin" 형태에서 ID 추출 const screenId = menuUrl.match(/\/screens\/(\d+)/)?.[1]; if (screenId) { // console.log("🔍 기존 메뉴에서 화면 ID 추출:", { // menuUrl, // screenId, // hasAdminParam: menuUrl.includes("mode=admin"), // currentScreensCount: screens.length, // }); // 화면 설정 함수 const setScreenFromId = () => { const screen = screens.find((s) => s.screenId.toString() === screenId || s.id?.toString() === screenId); if (screen) { setSelectedScreen(screen); // console.log("🖥️ 기존 메뉴의 할당된 화면 설정:", { // screen, // originalUrl: menuUrl, // hasAdminParam: menuUrl.includes("mode=admin"), // }); return true; } else { // console.warn("⚠️ 해당 ID의 화면을 찾을 수 없음:", { // screenId, // availableScreens: screens.map((s) => ({ screenId: s.screenId, id: s.id, name: s.screenName })), // }); return false; } }; // 화면 목록이 이미 있으면 즉시 설정, 없으면 로드 완료 대기 if (screens.length > 0) { console.log("📋 화면 목록이 이미 로드됨 - 즉시 설정"); setScreenFromId(); } else { console.log("⏳ 화면 목록 로드 대기 중..."); // 화면 ID를 저장해두고, 화면 목록 로드 완료 후 설정 setTimeout(() => { console.log("🔄 재시도: 화면 목록 로드 후 설정"); setScreenFromId(); }, 500); } } } else { setUrlType("direct"); setSelectedScreen(null); } // console.log("설정된 폼 데이터:", { // objid: menu.objid || menu.OBJID, // parentObjId: menu.parent_obj_id || menu.PARENT_OBJ_ID || "0", // menuNameKor: menu.menu_name_kor || menu.MENU_NAME_KOR || "", // menuUrl: menu.menu_url || menu.MENU_URL || "", // menuDesc: menu.menu_desc || menu.MENU_DESC || "", // seq: menu.seq || menu.SEQ || 1, // menuType: convertedMenuType, // status: convertedStatus, // companyCode: companyCode, // langKey: langKey, // }); } } catch (error: any) { console.error("메뉴 정보 로딩 오류:", error); // console.error("오류 상세 정보:", { // message: error?.message, // stack: error?.stack, // response: error?.response, // }); toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_MENU_INFO)); } finally { setLoading(false); } }; // useEffect를 loadMenuData 함수 정의 후로 이동 useEffect(() => { console.log("🚀 MenuFormModal useEffect 실행됨!"); console.log("📋 useEffect 파라미터:", { menuId, parentId, menuType }); console.log("MenuFormModal useEffect - menuId:", menuId, "parentId:", parentId, "menuType:", menuType); if (menuId) { console.log("메뉴 수정 모드 - menuId:", menuId); setIsEdit(true); loadMenuData(); } else { console.log("메뉴 등록 모드 - parentId:", parentId, "menuType:", menuType); setIsEdit(false); // 메뉴 타입 변환 (0 -> 0, 1 -> 1, admin -> 0, user -> 1) let defaultMenuType = "1"; // 기본값은 사용자 if (menuType === "0" || menuType === "admin") { defaultMenuType = "0"; // 관리자 } else if (menuType === "1" || menuType === "user") { defaultMenuType = "1"; // 사용자 } setFormData({ parentObjId: parentId || "0", menuNameKor: "", menuUrl: "", menuDesc: "", seq: 1, menuType: defaultMenuType, status: "ACTIVE", // 기본값은 활성화 companyCode: parentCompanyCode || "none", // 상위 메뉴의 회사 코드를 기본값으로 설정 langKey: "", // 다국어 키 초기화 }); // console.log("메뉴 등록 기본값 설정:", { // parentObjId: parentId || "0", // menuType: defaultMenuType, // status: "ACTIVE", // companyCode: "", // langKey: "", // }); } }, [menuId, parentId, menuType]); // 강제로 useEffect 실행시키기 위한 별도 useEffect useEffect(() => { console.log("🔧 강제 useEffect 실행 - 컴포넌트 마운트됨"); console.log("🔧 현재 props:", { isOpen, menuId, parentId, menuType }); // isOpen이 true일 때만 실행 if (isOpen && menuId) { console.log("🔧 모달이 열렸고 menuId가 있음 - 강제 실행"); // 약간의 지연 후 실행 setTimeout(() => { console.log("🔧 setTimeout으로 loadMenuData 실행"); loadMenuData(); }, 100); } }, [isOpen]); // isOpen만 의존성으로 설정 // 회사 목록 로드 useEffect(() => { if (isOpen) { loadCompanies(); } }, [isOpen]); // 다국어 키 목록 로드 useEffect(() => { if (isOpen && formData.companyCode) { loadLangKeys(); } }, [isOpen, formData.companyCode]); // 화면 목록 로드 useEffect(() => { if (isOpen) { loadScreens(); } }, [isOpen]); // 화면 목록 로드 완료 후 기존 메뉴의 할당된 화면 설정 useEffect(() => { if (screens.length > 0 && isEdit && formData.menuUrl && urlType === "screen") { const menuUrl = formData.menuUrl; if (menuUrl.startsWith("/screens/")) { const screenId = menuUrl.match(/\/screens\/(\d+)/)?.[1]; if (screenId && !selectedScreen) { console.log("🔄 화면 목록 로드 완료 - 기존 할당 화면 자동 설정"); const screen = screens.find((s) => s.screenId.toString() === screenId || s.id?.toString() === screenId); if (screen) { setSelectedScreen(screen); // console.log("✅ 기존 메뉴의 할당된 화면 자동 설정 완료:", { // screenId, // screenName: screen.screenName, // menuUrl, // }); } } } } }, [screens, isEdit, formData.menuUrl, urlType, selectedScreen]); // 드롭다운 외부 클릭 시 닫기 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const target = event.target as Element; if (!target.closest(".langkey-dropdown")) { setIsLangKeyDropdownOpen(false); setLangKeySearchText(""); } if (!target.closest(".screen-dropdown")) { setIsScreenDropdownOpen(false); setScreenSearchText(""); } }; if (isLangKeyDropdownOpen || isScreenDropdownOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isLangKeyDropdownOpen, isScreenDropdownOpen]); const loadCompanies = async () => { try { const companyList = await companyAPI.getList({ status: "active" }); setCompanies(companyList); } catch (error) { console.error("회사 목록 로딩 오류:", error); toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_COMPANY_LIST)); } }; const loadLangKeys = async () => { console.log("🔤 다국어 키 목록 조회 시작 - companyCode:", formData.companyCode); try { const response = await menuApi.getLangKeys({ companyCode: formData.companyCode === "none" ? "*" : formData.companyCode, }); if (response.success && response.data) { // 활성화된 다국어 키만 필터링 const activeKeys = response.data.filter((key) => key.isActive === "Y"); console.log("🔤 다국어 키 목록 조회 성공:", activeKeys.length, "개 (활성화된 키)"); setLangKeys(activeKeys); } } catch (error) { console.error("❌ 다국어 키 목록 로딩 오류:", error); toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_LANG_KEY_LIST)); setLangKeys([]); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!formData.menuNameKor.trim()) { toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_MENU_NAME_REQUIRED)); return; } if (!formData.companyCode) { toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_COMPANY_REQUIRED)); return; } try { setLoading(true); // 백엔드에 전송할 데이터 변환 const submitData = { ...formData, // 상태를 소문자로 변환 (백엔드에서 소문자 기대) status: formData.status.toLowerCase(), }; console.log("저장할 데이터:", submitData); let response; if (isEdit && menuId) { // 수정 모드: updateMenu API 호출 console.log("🔧 메뉴 수정 API 호출:", menuId); response = await menuApi.updateMenu(menuId, submitData); } else { // 추가 모드: saveMenu API 호출 console.log("➕ 메뉴 추가 API 호출"); response = await menuApi.saveMenu(submitData); } if (response.success) { toast.success(response.message); onSuccess(); onClose(); } else { toast.error(response.message); } } catch (error) { console.error("메뉴 저장/수정 실패:", error); toast.error(getText(MENU_MANAGEMENT_KEYS.MESSAGE_MENU_SAVE_FAILED)); } finally { setLoading(false); } }; const handleInputChange = (field: keyof MenuFormData, value: string | number) => { setFormData((prev) => ({ ...prev, [field]: value, })); }; // 선택된 다국어 키 정보 가져오기 const getSelectedLangKeyInfo = () => { if (!formData.langKey) return null; return langKeys.find((key) => key.langKey === formData.langKey); }; const selectedLangKeyInfo = getSelectedLangKeyInfo(); // 전역 사용자 로케일 가져오기 const getCurrentUserLang = () => { return (window as any).__GLOBAL_USER_LANG || localStorage.getItem("userLocale") || "KR"; }; return ( {isEdit ? getText(MENU_MANAGEMENT_KEYS.MODAL_MENU_MODIFY_TITLE) : getText(MENU_MANAGEMENT_KEYS.MODAL_MENU_REGISTER_TITLE)}
{!isEdit && level !== 1 && (

{getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SUBMENU_NOTE)}

)}
{isLangKeyDropdownOpen && (
{/* 검색 입력 */}
setLangKeySearchText(e.target.value)} className="h-8 text-sm" onClick={(e) => e.stopPropagation()} />
{/* 다국어 키 목록 */}
{ handleInputChange("langKey", ""); setIsLangKeyDropdownOpen(false); setLangKeySearchText(""); }} > {getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_NONE)}
{langKeys .filter( (key) => key.langKey.toLowerCase().includes(langKeySearchText.toLowerCase()) || key.description.toLowerCase().includes(langKeySearchText.toLowerCase()), ) .map((key) => (
{ handleInputChange("langKey", key.langKey); setIsLangKeyDropdownOpen(false); setLangKeySearchText(""); }} >
{key.langKey}
{key.description &&
{key.description}
}
))}
)}
{selectedLangKeyInfo && (

{getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECTED) .replace("{key}", selectedLangKeyInfo.langKey) .replace("{description}", selectedLangKeyInfo.description)}

)}
handleInputChange("menuNameKor", e.target.value)} placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_MENU_NAME_PLACEHOLDER)} required />
{/* URL 타입 선택 */}
{/* 화면 할당 */} {urlType === "screen" && (
{/* 화면 선택 드롭다운 */}
{isScreenDropdownOpen && (
{/* 검색 입력 */}
setScreenSearchText(e.target.value)} className="pl-8" />
{/* 화면 목록 */}
{screens .filter( (screen) => screen.screenName.toLowerCase().includes(screenSearchText.toLowerCase()) || screen.screenCode.toLowerCase().includes(screenSearchText.toLowerCase()), ) .map((screen, index) => (
handleScreenSelect(screen)} className="cursor-pointer border-b px-3 py-2 last:border-b-0 hover:bg-gray-100" >
{screen.screenName}
{screen.screenCode}
ID: {screen.screenId || screen.id || "N/A"}
))} {screens.filter( (screen) => screen.screenName.toLowerCase().includes(screenSearchText.toLowerCase()) || screen.screenCode.toLowerCase().includes(screenSearchText.toLowerCase()), ).length === 0 &&
검색 결과가 없습니다.
}
)}
{/* 선택된 화면 정보 표시 */} {selectedScreen && (
{selectedScreen.screenName}
코드: {selectedScreen.screenCode}
생성된 URL: {formData.menuUrl}
)}
)} {/* URL 직접 입력 */} {urlType === "direct" && ( handleInputChange("menuUrl", e.target.value)} placeholder={getText(MENU_MANAGEMENT_KEYS.FORM_MENU_URL_PLACEHOLDER)} /> )}