"use client"; import React, { useState, useEffect, useMemo } from "react"; import { menuApi } from "@/lib/api/menu"; import type { MenuItem } from "@/lib/api/menu"; import { MenuTable } from "./MenuTable"; import { MenuFormModal } from "./MenuFormModal"; import { Button } from "@/components/ui/button"; import { LoadingSpinner, LoadingOverlay } from "@/components/common/LoadingSpinner"; import { toast } from "sonner"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { useMenu } from "@/contexts/MenuContext"; import { useMenuManagementText, setTranslationCache } from "@/lib/utils/multilang"; import { useMultiLang } from "@/hooks/useMultiLang"; import { apiClient } from "@/lib/api/client"; import { ScreenAssignmentTab } from "./ScreenAssignmentTab"; type MenuType = "admin" | "user"; export const MenuManagement: React.FC = () => { const { adminMenus, userMenus, refreshMenus } = useMenu(); const [selectedMenuType, setSelectedMenuType] = useState("admin"); const [loading, setLoading] = useState(false); const [deleting, setDeleting] = useState(false); const [formModalOpen, setFormModalOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [selectedMenuId, setSelectedMenuId] = useState(""); const [selectedMenus, setSelectedMenus] = useState>(new Set()); // 다국어 텍스트 훅 사용 // getMenuText는 더 이상 사용하지 않음 - getUITextSync만 사용 const { userLang } = useMultiLang({ companyCode: "*" }); // 다국어 텍스트 상태 const [uiTexts, setUiTexts] = useState>({}); const [uiTextsLoading, setUiTextsLoading] = useState(false); // 회사 목록 상태 const [companies, setCompanies] = useState>([]); const [selectedCompany, setSelectedCompany] = useState("all"); const [searchText, setSearchText] = useState(""); const [expandedMenus, setExpandedMenus] = useState>(new Set()); const [companySearchText, setCompanySearchText] = useState(""); const [isCompanyDropdownOpen, setIsCompanyDropdownOpen] = useState(false); const [formData, setFormData] = useState({ menuId: "", parentId: "", menuType: "", level: 0, parentCompanyCode: "", }); // 언어별 텍스트 매핑 테이블 제거 - DB에서 직접 가져옴 // 메뉴관리 페이지에서 사용할 다국어 키들 (실제 DB에 등록된 키들) const MENU_MANAGEMENT_LANG_KEYS = [ // 페이지 제목 및 설명 "menu.management.title", "menu.management.description", "menu.type.title", "menu.type.admin", "menu.type.user", "menu.management.admin", "menu.management.user", "menu.management.admin.description", "menu.management.user.description", // 버튼 "button.add", "button.add.top.level", "button.add.sub", "button.edit", "button.delete", "button.delete.selected", "button.delete.selected.count", "button.delete.processing", "button.cancel", "button.save", "button.register", "button.modify", // 필터 및 검색 "filter.company", "filter.company.all", "filter.company.common", "filter.company.search", "filter.search", "filter.search.placeholder", "filter.reset", // 테이블 헤더 "table.header.select", "table.header.menu.name", "table.header.menu.url", "table.header.menu.type", "table.header.status", "table.header.company", "table.header.sequence", "table.header.actions", // 상태 "status.active", "status.inactive", "status.unspecified", // 폼 "form.menu.type", "form.menu.type.admin", "form.menu.type.user", "form.company", "form.company.select", "form.company.common", "form.company.submenu.note", "form.lang.key", "form.lang.key.select", "form.lang.key.none", "form.lang.key.search", "form.lang.key.selected", "form.menu.name", "form.menu.name.placeholder", "form.menu.url", "form.menu.url.placeholder", "form.menu.description", "form.menu.description.placeholder", "form.menu.sequence", // 모달 "modal.menu.register.title", "modal.menu.modify.title", "modal.delete.title", "modal.delete.description", "modal.delete.batch.description", // 메시지 "message.loading", "message.menu.delete.processing", "message.menu.save.success", "message.menu.save.failed", "message.menu.delete.success", "message.menu.delete.failed", "message.menu.delete.batch.success", "message.menu.delete.batch.partial", "message.menu.status.toggle.success", "message.menu.status.toggle.failed", "message.validation.menu.name.required", "message.validation.company.required", "message.validation.select.menu.delete", "message.error.load.menu.list", "message.error.load.menu.info", "message.error.load.company.list", "message.error.load.lang.key.list", // 리스트 정보 "menu.list.title", "menu.list.total", "menu.list.search.result", // UI "ui.expand", "ui.collapse", "ui.menu.collapse", "ui.language", ]; // 초기 로딩 useEffect(() => { loadCompanies(); // 사용자 언어가 설정되지 않았을 때만 기본 텍스트 설정 if (!userLang) { initializeDefaultTexts(); } }, [userLang]); // userLang 변경 시마다 실행 // 초기 기본 텍스트 설정 함수 const initializeDefaultTexts = () => { const defaultTexts: Record = {}; MENU_MANAGEMENT_LANG_KEYS.forEach((key) => { // 기본 한국어 텍스트 제공 const defaultText = getDefaultText(key); defaultTexts[key] = defaultText; }); setUiTexts(defaultTexts); console.log("🌐 초기 기본 텍스트 설정 완료:", Object.keys(defaultTexts).length); }; // 기본 텍스트 반환 함수 const getDefaultText = (key: string): string => { const defaultTexts: Record = { "menu.management.title": "메뉴 관리", "menu.management.description": "시스템의 메뉴 구조와 권한을 관리합니다.", "menu.type.title": "메뉴 타입", "menu.type.admin": "관리자", "menu.type.user": "사용자", "menu.management.admin": "관리자 메뉴", "menu.management.user": "사용자 메뉴", "menu.management.admin.description": "시스템 관리 및 설정 메뉴", "menu.management.user.description": "일반 사용자 업무 메뉴", "button.add": "추가", "button.add.top.level": "최상위 메뉴 추가", "button.add.sub": "하위 메뉴 추가", "button.edit": "수정", "button.delete": "삭제", "button.delete.selected": "선택 삭제", "button.delete.selected.count": "선택 삭제 ({count})", "button.delete.processing": "삭제 중...", "button.cancel": "취소", "button.save": "저장", "button.register": "등록", "button.modify": "수정", "filter.company": "회사", "filter.company.all": "전체", "filter.company.common": "공통", "filter.company.search": "회사 검색", "filter.search": "검색", "filter.search.placeholder": "메뉴명 또는 URL로 검색...", "filter.reset": "초기화", "table.header.select": "선택", "table.header.menu.name": "메뉴명", "table.header.menu.url": "URL", "table.header.menu.type": "메뉴 타입", "table.header.status": "상태", "table.header.company": "회사", "table.header.sequence": "순서", "table.header.actions": "작업", "status.active": "활성화", "status.inactive": "비활성화", "status.unspecified": "미지정", "form.menu.type": "메뉴 타입", "form.menu.type.admin": "관리자", "form.menu.type.user": "사용자", "form.company": "회사", "form.company.select": "회사를 선택하세요", "form.company.common": "공통", "form.company.submenu.note": "하위 메뉴는 상위 메뉴와 동일한 회사를 가져야 합니다.", "form.lang.key": "다국어 키", "form.lang.key.select": "다국어 키를 선택하세요", "form.lang.key.none": "다국어 키 없음", "form.lang.key.search": "다국어 키 검색...", "form.lang.key.selected": "선택된 키: {key} - {description}", "form.menu.name": "메뉴명", "form.menu.name.placeholder": "메뉴명을 입력하세요", "form.menu.url": "URL", "form.menu.url.placeholder": "메뉴 URL을 입력하세요", "form.menu.description": "설명", "form.menu.description.placeholder": "메뉴 설명을 입력하세요", "form.menu.sequence": "순서", "modal.menu.register.title": "메뉴 등록", "modal.menu.modify.title": "메뉴 수정", "modal.delete.title": "메뉴 삭제", "modal.delete.description": "해당 메뉴를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", "modal.delete.batch.description": "선택된 {count}개의 메뉴를 영구적으로 삭제하시겠습니까?\n\n⚠️ 주의: 상위 메뉴를 삭제하면 하위 메뉴들도 함께 삭제됩니다.\n이 작업은 되돌릴 수 없습니다.", "message.loading": "로딩 중...", "message.menu.delete.processing": "메뉴 삭제 중...", "message.menu.save.success": "메뉴가 성공적으로 저장되었습니다.", "message.menu.save.failed": "메뉴 저장에 실패했습니다.", "message.menu.delete.success": "메뉴가 성공적으로 삭제되었습니다.", "message.menu.delete.failed": "메뉴 삭제에 실패했습니다.", "message.menu.delete.batch.success": "선택된 메뉴들이 성공적으로 삭제되었습니다.", "message.menu.delete.batch.partial": "일부 메뉴 삭제에 실패했습니다.", "message.menu.status.toggle.success": "메뉴 상태가 변경되었습니다.", "message.menu.status.toggle.failed": "메뉴 상태 변경에 실패했습니다.", "message.validation.menu.name.required": "메뉴명을 입력해주세요.", "message.validation.company.required": "회사를 선택해주세요.", "message.validation.select.menu.delete": "삭제할 메뉴를 선택해주세요.", "message.error.load.menu.list": "메뉴 목록을 불러오는데 실패했습니다.", "message.error.load.menu.info": "메뉴 정보를 불러오는데 실패했습니다.", "message.error.load.company.list": "회사 목록을 불러오는데 실패했습니다.", "message.error.load.lang.key.list": "다국어 키 목록을 불러오는데 실패했습니다.", "menu.list.title": "메뉴 목록", "menu.list.total": "총 {count}개", "menu.list.search.result": "검색 결과: {count}개", "ui.expand": "펼치기", "ui.collapse": "접기", "ui.menu.collapse": "메뉴 접기", "ui.language": "언어", }; return defaultTexts[key] || key; }; // 컴포넌트 마운트 시 및 userLang 변경 시 다국어 텍스트 로드 useEffect(() => { if (userLang && !uiTextsLoading) { loadUITexts(); } }, [userLang]); // userLang 변경 시마다 실행 // uiTexts 상태 변경 감지 useEffect(() => { console.log("🔄 uiTexts 상태 변경됨:", { count: Object.keys(uiTexts).length, sampleKeys: Object.keys(uiTexts).slice(0, 5), sampleValues: Object.entries(uiTexts) .slice(0, 3) .map(([k, v]) => `${k}: ${v}`), }); }, [uiTexts]); // 컴포넌트 마운트 후 다국어 텍스트 강제 로드 (userLang이 아직 설정되지 않았을 수 있음) useEffect(() => { const timer = setTimeout(() => { if (userLang && !uiTextsLoading) { console.log("🔄 컴포넌트 마운트 후 다국어 텍스트 강제 로드"); loadUITexts(); } }, 300); // 300ms 후 실행 return () => clearTimeout(timer); }, [userLang]); // userLang이 설정된 후 실행 // 추가 안전장치: 컴포넌트 마운트 후 일정 시간이 지나면 강제로 다국어 텍스트 로드 useEffect(() => { const fallbackTimer = setTimeout(() => { if (!uiTextsLoading && Object.keys(uiTexts).length === 0) { console.log("🔄 안전장치: 컴포넌트 마운트 후 강제 다국어 텍스트 로드"); // 사용자 언어가 설정되지 않았을 때만 기본 텍스트 설정 if (!userLang) { initializeDefaultTexts(); } else { // 사용자 언어가 설정된 경우 다국어 텍스트 로드 loadUITexts(); } } }, 1000); // 1초 후 실행 return () => clearTimeout(fallbackTimer); }, [userLang]); // userLang 변경 시마다 실행 // 번역 로드 이벤트 감지 useEffect(() => { const handleTranslationLoaded = (event: CustomEvent) => { const { key, text, userLang: loadedLang } = event.detail; if (loadedLang === userLang) { setUiTexts((prev) => ({ ...prev, [key]: text })); } }; window.addEventListener("translation-loaded", handleTranslationLoaded as EventListener); return () => { window.removeEventListener("translation-loaded", handleTranslationLoaded as EventListener); }; }, [userLang]); // 드롭다운 외부 클릭 시 닫기 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const target = event.target as Element; if (!target.closest(".company-dropdown")) { setIsCompanyDropdownOpen(false); setCompanySearchText(""); } }; if (isCompanyDropdownOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isCompanyDropdownOpen]); const loadMenus = async (showLoading = true) => { console.log(`📋 메뉴 목록 조회 시작 (showLoading: ${showLoading})`); try { if (showLoading) { setLoading(true); } await refreshMenus(); console.log("📋 메뉴 목록 조회 성공"); } catch (error) { console.error("❌ 메뉴 목록 조회 실패:", error); toast.error(getUITextSync("message.error.load.menu.list")); } finally { if (showLoading) { setLoading(false); } } }; // 회사 목록 조회 const loadCompanies = async () => { console.log("🏢 회사 목록 조회 시작"); try { const response = await apiClient.get("/admin/companies"); if (response.data.success) { console.log("🏢 회사 목록 응답:", response.data); const companyList = response.data.data.map((company: any) => ({ code: company.company_code || company.companyCode, name: company.company_name || company.companyName, })); console.log("🏢 변환된 회사 목록:", companyList); setCompanies(companyList); } } catch (error) { console.error("❌ 회사 목록 조회 실패:", error); } }; // 다국어 텍스트 로드 함수 - 배치 API 사용 const loadUITexts = async () => { if (uiTextsLoading) return; // 이미 로딩 중이면 중단 // userLang이 설정되지 않았으면 기본값 설정 if (!userLang) { console.log("🌐 사용자 언어가 설정되지 않음, 기본값 설정"); const defaultTexts: Record = {}; MENU_MANAGEMENT_LANG_KEYS.forEach((key) => { defaultTexts[key] = getDefaultText(key); // 기본 한국어 텍스트 사용 }); setUiTexts(defaultTexts); return; } // 사용자 언어가 설정된 경우, 기존 uiTexts가 비어있으면 기본 텍스트로 초기화 if (Object.keys(uiTexts).length === 0) { console.log("🌐 기존 uiTexts가 비어있음, 기본 텍스트로 초기화"); const defaultTexts: Record = {}; MENU_MANAGEMENT_LANG_KEYS.forEach((key) => { defaultTexts[key] = getDefaultText(key); }); setUiTexts(defaultTexts); } console.log("🌐 UI 다국어 텍스트 로드 시작", { userLang, apiParams: { companyCode: "*", menuCode: "menu.management", userLang: userLang, }, }); setUiTextsLoading(true); try { // 배치 API를 사용하여 모든 다국어 키를 한 번에 조회 const response = await apiClient.post( "/multilang/batch", { langKeys: MENU_MANAGEMENT_LANG_KEYS, companyCode: "*", // 모든 회사 menuCode: "menu.management", // 메뉴관리 메뉴 userLang: userLang, // body에 포함 }, { params: {}, // query params는 비움 }, ); if (response.data.success) { const translations = response.data.data; console.log("🌐 배치 다국어 텍스트 응답:", translations); // 번역 결과를 상태에 저장 (기존 uiTexts와 병합) const mergedTranslations = { ...uiTexts, ...translations }; console.log("🔧 setUiTexts 호출 전:", { translationsCount: Object.keys(translations).length, mergedCount: Object.keys(mergedTranslations).length, }); setUiTexts(mergedTranslations); console.log("🔧 setUiTexts 호출 후 - mergedTranslations:", mergedTranslations); // 번역 캐시에 저장 (다른 컴포넌트에서도 사용할 수 있도록) setTranslationCache(userLang, mergedTranslations); } else { console.error("❌ 다국어 텍스트 배치 조회 실패:", response.data.message); // API 실패 시에도 기존 uiTexts는 유지 console.log("🔄 API 실패로 인해 기존 uiTexts 유지"); } } catch (error) { console.error("❌ UI 다국어 텍스트 로드 실패:", error); // API 실패 시에도 기존 uiTexts는 유지 console.log("🔄 API 실패로 인해 기존 uiTexts 유지"); } finally { setUiTextsLoading(false); } }; // UI 텍스트 가져오기 함수 (동기 버전만 사용) // getUIText 함수는 제거 - getUITextSync만 사용 // 동기 버전 (DB에서 가져온 번역 텍스트 사용) const getUITextSync = (key: string, params?: Record, fallback?: string): string => { // uiTexts에서 번역 텍스트 찾기 let text = uiTexts[key]; // uiTexts에 없으면 fallback 또는 키 사용 if (!text) { text = fallback || key; } // 파라미터 치환 if (params && text) { Object.entries(params).forEach(([paramKey, paramValue]) => { text = text!.replace(`{${paramKey}}`, String(paramValue)); }); } return text || key; }; // 다국어 API 테스트 함수 (getUITextSync 사용) const testMultiLangAPI = async () => { console.log("🧪 다국어 API 테스트 시작"); try { const text = getUITextSync("menu.management.admin"); console.log("🧪 다국어 API 테스트 결과:", text); } catch (error) { console.error("❌ 다국어 API 테스트 실패:", error); } }; // 대문자 키를 소문자 키로 변환하는 함수 const convertMenuData = (data: any[]): MenuItem[] => { return data.map((item) => ({ objid: item.OBJID || item.objid, parent_obj_id: item.PARENT_OBJ_ID || item.parent_obj_id, menu_name_kor: item.MENU_NAME_KOR || item.menu_name_kor, menu_url: item.MENU_URL || item.menu_url, menu_desc: item.MENU_DESC || item.menu_desc, seq: item.SEQ || item.seq, menu_type: item.MENU_TYPE || item.menu_type, status: item.STATUS || item.status, lev: item.LEV || item.lev, lpad_menu_name_kor: item.LPAD_MENU_NAME_KOR || item.lpad_menu_name_kor, status_title: item.STATUS_TITLE || item.status_title, writer: item.WRITER || item.writer, regdate: item.REGDATE || item.regdate, company_code: item.COMPANY_CODE || item.company_code, company_name: item.COMPANY_NAME || item.company_name, })); }; const handleAddTopLevelMenu = () => { setFormData({ menuId: "", parentId: "0", // 최상위 메뉴는 parentId가 0 menuType: getMenuTypeValue(), level: 1, // 최상위 메뉴는 level 1 parentCompanyCode: "", // 최상위 메뉴는 상위 회사 정보 없음 }); setFormModalOpen(true); }; const handleAddMenu = (parentId: string, menuType: string, level: number) => { // 상위 메뉴의 회사 정보 찾기 const currentMenus = selectedMenuType === "admin" ? adminMenus : userMenus; const parentMenu = currentMenus.find((menu) => menu.objid === parentId); setFormData({ menuId: "", parentId, menuType, level: level + 1, parentCompanyCode: parentMenu?.company_code || "", }); setFormModalOpen(true); }; const handleEditMenu = (menuId: string) => { console.log("🔧 메뉴 수정 시작 - menuId:", menuId); // 현재 메뉴 정보 찾기 const currentMenus = selectedMenuType === "admin" ? adminMenus : userMenus; const menuToEdit = currentMenus.find((menu) => (menu.objid || menu.OBJID) === menuId); if (menuToEdit) { console.log("수정할 메뉴 정보:", menuToEdit); setFormData({ menuId: menuId, parentId: menuToEdit.parent_obj_id || menuToEdit.PARENT_OBJ_ID || "", menuType: selectedMenuType, // 현재 선택된 메뉴 타입 level: 0, // 기본값 parentCompanyCode: menuToEdit.company_code || menuToEdit.COMPANY_CODE || "", }); console.log("설정된 formData:", { menuId: menuId, parentId: menuToEdit.parent_obj_id || menuToEdit.PARENT_OBJ_ID || "", menuType: selectedMenuType, level: 0, parentCompanyCode: menuToEdit.company_code || menuToEdit.COMPANY_CODE || "", }); } else { console.error("수정할 메뉴를 찾을 수 없음:", menuId); } setFormModalOpen(true); }; const handleMenuSelectionChange = (menuId: string, checked: boolean) => { const newSelected = new Set(selectedMenus); if (checked) { newSelected.add(menuId); } else { newSelected.delete(menuId); } setSelectedMenus(newSelected); }; const handleSelectAllMenus = (checked: boolean) => { const currentMenus = selectedMenuType === "admin" ? adminMenus : userMenus; if (checked) { // 모든 메뉴 선택 (최상위 메뉴 포함) setSelectedMenus(new Set(currentMenus.map((menu) => menu.objid || menu.OBJID || ""))); } else { setSelectedMenus(new Set()); } }; const handleDeleteSelectedMenus = async () => { if (selectedMenus.size === 0) { toast.error(getUITextSync("message.validation.select.menu.delete")); return; } if (!confirm(getUITextSync("modal.delete.batch.description", { count: selectedMenus.size }))) { return; } setDeleting(true); try { const menuIds = Array.from(selectedMenus); console.log("삭제할 메뉴 IDs:", menuIds); toast.info(getUITextSync("message.menu.delete.processing")); const response = await menuApi.deleteMenusBatch(menuIds); console.log("삭제 API 응답:", response); console.log("응답 구조:", { success: response.success, data: response.data, message: response.message, }); if (response.success && response.data) { const { deletedCount, failedCount } = response.data; console.log("삭제 결과:", { deletedCount, failedCount }); // 선택된 메뉴 초기화 setSelectedMenus(new Set()); // 메뉴 목록 즉시 새로고침 (로딩 상태 없이) console.log("메뉴 목록 새로고침 시작"); await loadMenus(false); // 전역 메뉴 상태도 업데이트 await refreshMenus(); console.log("메뉴 목록 새로고침 완료"); // 삭제 결과 메시지 if (failedCount === 0) { toast.success(getUITextSync("message.menu.delete.batch.success", { count: deletedCount })); } else { toast.success( getUITextSync("message.menu.delete.batch.partial", { success: deletedCount, failed: failedCount, }), ); } } else { console.error("삭제 실패:", response); toast.error(response.message || "메뉴 삭제에 실패했습니다."); } } catch (error) { console.error("메뉴 삭제 중 오류:", error); toast.error(getUITextSync("message.menu.delete.failed")); } finally { setDeleting(false); } }; const confirmDelete = async () => { try { const response = await menuApi.deleteMenu(selectedMenuId); if (response.success) { toast.success(response.message); await loadMenus(false); } else { toast.error(response.message); } } catch (error) { toast.error("메뉴 삭제에 실패했습니다."); } finally { setDeleteDialogOpen(false); setSelectedMenuId(""); } }; const handleToggleStatus = async (menuId: string) => { try { const response = await menuApi.toggleMenuStatus(menuId); if (response.success) { toast.success(response.message); await loadMenus(false); // 메뉴 목록 새로고침 // 전역 메뉴 상태도 업데이트 await refreshMenus(); } else { toast.error(response.message); } } catch (error) { console.error("메뉴 상태 토글 오류:", error); toast.error(getUITextSync("message.menu.status.toggle.failed")); } }; const handleFormSuccess = () => { loadMenus(false); // 전역 메뉴 상태도 업데이트 refreshMenus(); }; const getCurrentMenus = () => { const currentMenus = selectedMenuType === "admin" ? adminMenus : userMenus; // 검색어 필터링 let filteredMenus = currentMenus; if (searchText.trim()) { const searchLower = searchText.toLowerCase(); filteredMenus = currentMenus.filter((menu) => { const menuName = (menu.menu_name_kor || menu.MENU_NAME_KOR || "").toLowerCase(); const menuUrl = (menu.menu_url || menu.MENU_URL || "").toLowerCase(); return menuName.includes(searchLower) || menuUrl.includes(searchLower); }); } // 회사 필터링 if (selectedCompany !== "all") { filteredMenus = filteredMenus.filter((menu) => { const menuCompanyCode = menu.company_code || menu.COMPANY_CODE || ""; return menuCompanyCode === selectedCompany; }); } return filteredMenus; }; // 메뉴 타입 변경 시 선택된 메뉴 초기화 const handleMenuTypeChange = (type: MenuType) => { setSelectedMenuType(type); setSelectedMenus(new Set()); // 선택된 메뉴 초기화 setExpandedMenus(new Set()); // 메뉴 타입 변경 시 확장 상태 초기화 }; const handleToggleExpand = (menuId: string) => { const newExpandedMenus = new Set(expandedMenus); if (newExpandedMenus.has(menuId)) { newExpandedMenus.delete(menuId); } else { newExpandedMenus.add(menuId); } setExpandedMenus(newExpandedMenus); }; const getMenuTypeString = () => { return selectedMenuType === "admin" ? getUITextSync("menu.type.admin") : getUITextSync("menu.type.user"); }; const getMenuTypeValue = () => { return selectedMenuType === "admin" ? "0" : "1"; }; // uiTextsCount를 useMemo로 계산하여 상태 변경 시에만 재계산 const uiTextsCount = useMemo(() => Object.keys(uiTexts).length, [uiTexts]); const adminMenusCount = useMemo(() => adminMenus?.length || 0, [adminMenus]); const userMenusCount = useMemo(() => userMenus?.length || 0, [userMenus]); // 디버깅을 위한 간단한 상태 표시 console.log("🔍 MenuManagement 렌더링 상태:", { loading, uiTextsLoading, uiTextsCount, adminMenusCount, userMenusCount, selectedMenuType, userLang, }); if (loading) { return (
); } return (
{/* 탭 컨테이너 */} 메뉴 관리 화면 할당 {/* 메뉴 관리 탭 */}
{/* 메인 컨텐츠 - 2:8 비율 */}
{/* 좌측 사이드바 - 메뉴 타입 선택 (20%) */}

{getUITextSync("menu.type.title")}

handleMenuTypeChange("admin")} >

{getUITextSync("menu.management.admin")}

{getUITextSync("menu.management.admin.description")}

{adminMenus.length}
handleMenuTypeChange("user")} >

{getUITextSync("menu.management.user")}

{getUITextSync("menu.management.user.description")}

{userMenus.length}
{/* 우측 메인 영역 - 메뉴 목록 (80%) */}

{getMenuTypeString()} {getUITextSync("menu.list.title")}

{/* 검색 및 필터 영역 */}
{isCompanyDropdownOpen && (
{/* 검색 입력 */}
setCompanySearchText(e.target.value)} className="h-8 text-sm" onClick={(e) => e.stopPropagation()} />
{/* 회사 목록 */}
{ setSelectedCompany("all"); setIsCompanyDropdownOpen(false); setCompanySearchText(""); }} > {getUITextSync("filter.company.all")}
{ setSelectedCompany("*"); setIsCompanyDropdownOpen(false); setCompanySearchText(""); }} > {getUITextSync("filter.company.common")}
{companies .filter((company) => company.code && company.code.trim() !== "") .filter( (company) => company.name.toLowerCase().includes(companySearchText.toLowerCase()) || company.code.toLowerCase().includes(companySearchText.toLowerCase()), ) .map((company, index) => (
{ setSelectedCompany(company.code); setIsCompanyDropdownOpen(false); setCompanySearchText(""); }} > {company.code === "*" ? getUITextSync("filter.company.common") : company.name}
))}
)}
setSearchText(e.target.value)} />
{getUITextSync("menu.list.search.result", { count: getCurrentMenus().length })}
{getUITextSync("menu.list.total", { count: getCurrentMenus().length })}
{selectedMenus.size > 0 && ( )}
{/* 화면 할당 탭 */}
setFormModalOpen(false)} onSuccess={handleFormSuccess} menuId={formData.menuId} parentId={formData.parentId} menuType={formData.menuType} level={formData.level} parentCompanyCode={formData.parentCompanyCode} uiTexts={uiTexts} /> 메뉴 삭제 해당 메뉴를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다. 취소 삭제
); };