"use client"; import React, { useState, useEffect, useRef } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Label } from "@/components/ui/label"; import { toast } from "sonner"; import { Search, Monitor, Settings, X, Plus } from "lucide-react"; import { menuScreenApi } from "@/lib/api/screen"; import { apiClient } from "@/lib/api/client"; import type { MenuItem } from "@/lib/api/menu"; import { ScreenDefinition } from "@/types/screen"; interface MenuAssignmentModalProps { isOpen: boolean; onClose: () => void; screenInfo: ScreenDefinition | null; onAssignmentComplete?: () => void; onBackToList?: () => void; // 화면 목록으로 돌아가는 콜백 추가 } export const MenuAssignmentModal: React.FC = ({ isOpen, onClose, screenInfo, onAssignmentComplete, onBackToList, }) => { const [menus, setMenus] = useState([]); const [selectedMenuId, setSelectedMenuId] = useState(""); const [selectedMenu, setSelectedMenu] = useState(null); const [searchTerm, setSearchTerm] = useState(""); const [loading, setLoading] = useState(false); const [assigning, setAssigning] = useState(false); const [existingScreens, setExistingScreens] = useState([]); const [showReplaceDialog, setShowReplaceDialog] = useState(false); const [assignmentSuccess, setAssignmentSuccess] = useState(false); const [assignmentMessage, setAssignmentMessage] = useState(""); const searchInputRef = useRef(null); const autoRedirectTimerRef = useRef(null); // 메뉴 목록 로드 (관리자 메뉴 + 사용자 메뉴) const loadMenus = async () => { try { setLoading(true); // 관리자 메뉴 가져오기 const adminResponse = await apiClient.get("/admin/menus", { params: { menuType: "0" } }); const adminMenus = adminResponse.data?.data || []; // 사용자 메뉴 가져오기 const userResponse = await apiClient.get("/admin/menus", { params: { menuType: "1" } }); const userMenus = userResponse.data?.data || []; // 메뉴 정규화 함수 const normalizeMenu = (menu: any) => ({ objid: menu.objid || menu.OBJID, parent_obj_id: menu.parent_obj_id || menu.PARENT_OBJ_ID, menu_name_kor: menu.menu_name_kor || menu.MENU_NAME_KOR, menu_url: menu.menu_url || menu.MENU_URL, menu_desc: menu.menu_desc || menu.MENU_DESC, seq: menu.seq || menu.SEQ, menu_type: menu.menu_type || menu.MENU_TYPE, status: menu.status || menu.STATUS, lev: menu.lev || menu.LEV, company_code: menu.company_code || menu.COMPANY_CODE, company_name: menu.company_name || menu.COMPANY_NAME, }); // 관리자 메뉴 정규화 const normalizedAdminMenus = adminMenus.map((menu: any) => normalizeMenu(menu)); // 사용자 메뉴 정규화 const normalizedUserMenus = userMenus.map((menu: any) => normalizeMenu(menu)); // 모든 메뉴 합치기 const allMenus = [...normalizedAdminMenus, ...normalizedUserMenus]; // console.log("로드된 전체 메뉴 목록:", { // totalAdmin: normalizedAdminMenus.length, // totalUser: normalizedUserMenus.length, // total: allMenus.length, // }); setMenus(allMenus); } catch (error) { // console.error("메뉴 목록 로드 실패:", error); toast.error("메뉴 목록을 불러오는데 실패했습니다."); } finally { setLoading(false); } }; // 모달이 열릴 때 메뉴 목록 로드 및 정리 useEffect(() => { if (isOpen) { loadMenus(); setSelectedMenuId(""); setSelectedMenu(null); setSearchTerm(""); setAssignmentSuccess(false); setAssignmentMessage(""); } else { // 모달이 닫힐 때 타이머 정리 if (autoRedirectTimerRef.current) { clearTimeout(autoRedirectTimerRef.current); autoRedirectTimerRef.current = null; } } // 컴포넌트 언마운트 시 타이머 정리 return () => { if (autoRedirectTimerRef.current) { clearTimeout(autoRedirectTimerRef.current); autoRedirectTimerRef.current = null; } }; }, [isOpen]); // 메뉴 선택 처리 const handleMenuSelect = async (menuId: string) => { // 유효하지 않은 메뉴 ID인 경우 처리하지 않음 if (!menuId || menuId === "no-menu") { setSelectedMenuId(""); setSelectedMenu(null); setExistingScreens([]); return; } setSelectedMenuId(menuId); const menu = menus.find((m) => m.objid?.toString() === menuId); setSelectedMenu(menu || null); // 선택된 메뉴에 할당된 화면들 확인 if (menu) { try { const menuObjid = parseInt(menu.objid?.toString() || "0"); if (menuObjid > 0) { const screens = await menuScreenApi.getScreensByMenu(menuObjid); setExistingScreens(screens); // console.log(`메뉴 "${menu.menu_name_kor}"에 할당된 화면:`, screens); } } catch (error) { // console.error("할당된 화면 조회 실패:", error); setExistingScreens([]); } } }; // 화면 할당 처리 const handleAssignScreen = async () => { if (!selectedMenu || !screenInfo) { toast.error("메뉴와 화면 정보가 필요합니다."); return; } // 기존에 할당된 화면이 있는지 확인 if (existingScreens.length > 0) { // 이미 같은 화면이 할당되어 있는지 확인 const alreadyAssigned = existingScreens.some((screen) => screen.screenId === screenInfo.screenId); if (alreadyAssigned) { toast.info("이미 해당 메뉴에 할당된 화면입니다."); return; } // 다른 화면이 할당되어 있으면 교체 확인 setShowReplaceDialog(true); return; } // 기존 화면이 없으면 바로 할당 await performAssignment(); }; // 실제 할당 수행 const performAssignment = async (replaceExisting = false) => { if (!selectedMenu || !screenInfo) return; try { setAssigning(true); const menuObjid = parseInt(selectedMenu.objid?.toString() || "0"); if (menuObjid === 0) { toast.error("유효하지 않은 메뉴 ID입니다."); return; } // 기존 화면 교체인 경우 기존 화면들 먼저 제거 if (replaceExisting && existingScreens.length > 0) { // console.log("기존 화면들 제거 중...", existingScreens); for (const existingScreen of existingScreens) { try { await menuScreenApi.unassignScreenFromMenu(existingScreen.screenId, menuObjid); // console.log(`기존 화면 "${existingScreen.screenName}" 제거 완료`); } catch (error) { // console.error(`기존 화면 "${existingScreen.screenName}" 제거 실패:`, error); } } } // 새 화면 할당 await menuScreenApi.assignScreenToMenu(screenInfo.screenId, menuObjid); const successMessage = replaceExisting ? `기존 화면을 제거하고 "${screenInfo.screenName}" 화면이 "${selectedMenu.menu_name_kor}" 메뉴에 할당되었습니다.` : `"${screenInfo.screenName}" 화면이 "${selectedMenu.menu_name_kor}" 메뉴에 성공적으로 할당되었습니다.`; // 성공 상태 설정 setAssignmentSuccess(true); setAssignmentMessage(successMessage); // 할당 완료 콜백 호출 (모달은 아직 열린 상태 유지) if (onAssignmentComplete) { onAssignmentComplete(); } // 3초 후 자동으로 모달 닫고 화면 목록으로 이동 autoRedirectTimerRef.current = setTimeout(() => { onClose(); // 모달 닫기 if (onBackToList) { onBackToList(); } }, 3000); } catch (error: any) { // console.error("화면 할당 실패:", error); const errorMessage = error.response?.data?.message || "화면 할당에 실패했습니다."; toast.error(errorMessage); } finally { setAssigning(false); } }; // "나중에 할당" 처리 - 시각적 효과 포함 const handleAssignLater = () => { if (!screenInfo) return; // 성공 상태 설정 (나중에 할당 메시지) setAssignmentSuccess(true); setAssignmentMessage(`"${screenInfo.screenName}" 화면이 저장되었습니다. 나중에 메뉴에 할당할 수 있습니다.`); // 할당 완료 콜백 호출 (모달은 아직 열린 상태 유지) if (onAssignmentComplete) { onAssignmentComplete(); } // 3초 후 자동으로 모달 닫고 화면 목록으로 이동 autoRedirectTimerRef.current = setTimeout(() => { onClose(); // 모달 닫기 if (onBackToList) { onBackToList(); } }, 3000); }; // 필터된 메뉴 목록 const filteredMenus = menus.filter((menu) => { if (!searchTerm) return true; const searchLower = searchTerm.toLowerCase(); return ( menu.menu_name_kor?.toLowerCase().includes(searchLower) || menu.menu_url?.toLowerCase().includes(searchLower) || menu.menu_desc?.toLowerCase().includes(searchLower) ); }); // 메뉴 옵션 생성 (계층 구조 표시, 타입별 그룹화) const getMenuOptions = (): React.ReactNode[] => { if (loading) { return [ 메뉴 로딩 중... , ]; } if (filteredMenus.length === 0) { return [ {searchTerm ? `"${searchTerm}"에 대한 검색 결과가 없습니다` : "메뉴가 없습니다"} , ]; } // 관리자 메뉴와 사용자 메뉴 분리 const adminMenus = filteredMenus.filter( (menu) => menu.menu_type === "0" && menu.objid && menu.objid.toString().trim() !== "", ); const userMenus = filteredMenus.filter( (menu) => menu.menu_type === "1" && menu.objid && menu.objid.toString().trim() !== "", ); const options: React.ReactNode[] = []; // 관리자 메뉴 섹션 if (adminMenus.length > 0) { options.push(
👤 관리자 메뉴
, ); adminMenus.forEach((menu) => { const indent = " ".repeat(Math.max(0, menu.lev || 0)); const menuId = menu.objid!.toString(); options.push( {indent} {menu.menu_name_kor} , ); }); } // 사용자 메뉴 섹션 if (userMenus.length > 0) { if (adminMenus.length > 0) { options.push(
); } options.push(
👥 사용자 메뉴
, ); userMenus.forEach((menu) => { const indent = " ".repeat(Math.max(0, menu.lev || 0)); const menuId = menu.objid!.toString(); options.push( {indent} {menu.menu_name_kor} , ); }); } return options; }; return ( <> {assignmentSuccess ? ( // 성공 화면 <>
{assignmentMessage.includes("나중에") ? "화면 저장 완료" : "화면 할당 완료"}
{assignmentMessage.includes("나중에") ? "화면이 성공적으로 저장되었습니다. 나중에 메뉴에 할당할 수 있습니다." : "화면이 성공적으로 메뉴에 할당되었습니다."}

{assignmentMessage}

3초 후 자동으로 화면 목록으로 이동합니다...

) : ( // 기본 할당 화면 <> 메뉴에 화면 할당 저장된 화면을 메뉴에 할당하여 사용자가 접근할 수 있도록 설정합니다. {screenInfo && (
{screenInfo.screenName} {screenInfo.screenCode}
{screenInfo.description &&

{screenInfo.description}

}
)}
{/* 메뉴 선택 (검색 기능 포함) */}
{ e.stopPropagation(); setSearchTerm(e.target.value); }} onKeyDown={(e) => { // 이벤트가 Select로 전파되지 않도록 완전 차단 e.stopPropagation(); }} onClick={(e) => e.stopPropagation()} onFocus={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-8 w-full rounded-md border px-3 py-2 pr-8 pl-10 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" /> {searchTerm && ( )}
{/* 메뉴 옵션들 */}
{getMenuOptions()}
{/* 선택된 메뉴 정보 */} {selectedMenu && (

{selectedMenu.menu_name_kor}

{selectedMenu.menu_type === "0" ? "관리자" : "사용자"} {selectedMenu.status === "active" ? "활성" : "비활성"}
{selectedMenu.menu_url &&

URL: {selectedMenu.menu_url}

} {selectedMenu.menu_desc &&

설명: {selectedMenu.menu_desc}

} {selectedMenu.company_name &&

회사: {selectedMenu.company_name}

}
{/* 기존 할당된 화면 정보 */} {existingScreens.length > 0 && (

⚠️ 이미 할당된 화면 ({existingScreens.length}개)

{existingScreens.map((screen) => (
{screen.screenName} {screen.screenCode}
))}

새 화면을 할당하면 기존 화면들이 제거됩니다.

)}
)} )} {/* 화면 교체 확인 대화상자 */} 화면 교체 확인 선택한 메뉴에 이미 할당된 화면이 있습니다.
{/* 기존 화면 목록 */}

제거될 화면 ({existingScreens.length}개):

{existingScreens.map((screen) => (
{screen.screenName} {screen.screenCode}
))}
{/* 새로 할당될 화면 */} {screenInfo && (

새로 할당될 화면:

{screenInfo.screenName} {screenInfo.screenCode}
)}

주의: 기존 화면들이 메뉴에서 제거되고 새 화면으로 교체됩니다. 이 작업은 되돌릴 수 없습니다.

); };