"use client"; import { useState, Suspense, useEffect } from "react"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Shield, Menu, Home, Settings, BarChart3, FileText, Users, Package, ChevronDown, ChevronRight, UserCheck, LogOut, User, } from "lucide-react"; import { useMenu } from "@/contexts/MenuContext"; import { useAuth } from "@/hooks/useAuth"; import { useProfile } from "@/hooks/useProfile"; import { MenuItem } from "@/lib/api/menu"; import { menuScreenApi } from "@/lib/api/screen"; import { toast } from "sonner"; import { ProfileModal } from "./ProfileModal"; import { Logo } from "./Logo"; import { SideMenu } from "./SideMenu"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; // useAuth의 UserInfo 타입을 확장 interface ExtendedUserInfo { userId: string; userName: string; userNameEng?: string; userNameCn?: string; deptCode?: string; deptName?: string; positionCode?: string; positionName?: string; email?: string; tel?: string; cellPhone?: string; userType?: string; userTypeName?: string; authName?: string; partnerCd?: string; isAdmin: boolean; sabun?: string; photo?: string | null; companyCode?: string; locale?: string; } interface AppLayoutProps { children: React.ReactNode; } // 메뉴 아이콘 매핑 함수 const getMenuIcon = (menuName: string) => { const name = menuName.toLowerCase(); if (name.includes("대시보드") || name.includes("dashboard")) return ; if (name.includes("관리자") || name.includes("admin")) return ; if (name.includes("사용자") || name.includes("user")) return ; if (name.includes("프로젝트") || name.includes("project")) return ; if (name.includes("제품") || name.includes("product")) return ; if (name.includes("설정") || name.includes("setting")) return ; if (name.includes("로그") || name.includes("log")) return ; if (name.includes("메뉴") || name.includes("menu")) return ; if (name.includes("화면관리") || name.includes("screen")) return ; return ; }; // 메뉴 데이터를 UI용으로 변환하는 함수 (최상위 "사용자", "관리자" 제외) const convertMenuToUI = (menus: MenuItem[], userInfo: ExtendedUserInfo | null, parentId: string = "0"): any[] => { const filteredMenus = menus .filter((menu) => (menu.parent_obj_id || menu.PARENT_OBJ_ID) === parentId) .filter((menu) => (menu.status || menu.STATUS) === "active") .sort((a, b) => (a.seq || a.SEQ || 0) - (b.seq || b.SEQ || 0)); // 최상위 레벨에서 "사용자", "관리자" 카테고리가 있으면 그 하위 메뉴들을 직접 표시 if (parentId === "0") { const allMenus: any[] = []; for (const menu of filteredMenus) { const menuName = (menu.menu_name_kor || menu.MENU_NAME_KOR || "").toLowerCase(); // "사용자" 또는 "관리자" 카테고리면 하위 메뉴들을 직접 추가 if (menuName.includes("사용자") || menuName.includes("관리자")) { const childMenus = convertMenuToUI(menus, userInfo, menu.objid || menu.OBJID); allMenus.push(...childMenus); } else { // 일반 메뉴는 그대로 추가 allMenus.push(convertSingleMenu(menu, menus, userInfo)); } } return allMenus; } return filteredMenus.map((menu) => convertSingleMenu(menu, menus, userInfo)); }; // 단일 메뉴 변환 함수 const convertSingleMenu = (menu: MenuItem, allMenus: MenuItem[], userInfo: ExtendedUserInfo | null): any => { const menuId = menu.objid || menu.OBJID; // 사용자 locale 기준으로 번역 처리 const getDisplayText = (menu: MenuItem) => { // 다국어 텍스트가 있으면 사용, 없으면 기본 텍스트 사용 if (menu.translated_name || menu.TRANSLATED_NAME) { return menu.translated_name || menu.TRANSLATED_NAME; } const baseName = menu.menu_name_kor || menu.MENU_NAME_KOR || "메뉴명 없음"; // 사용자 정보에서 locale 가져오기 const userLocale = userInfo?.locale || "ko"; if (userLocale === "EN") { // 영어 번역 const translations: { [key: string]: string } = { 관리자: "Administrator", 사용자: "User Management", 메뉴: "Menu Management", 대시보드: "Dashboard", 권한: "Permission Management", 코드: "Code Management", 설정: "Settings", 로그: "Log Management", 프로젝트: "Project Management", 제품: "Product Management", }; for (const [korean, english] of Object.entries(translations)) { if (baseName.includes(korean)) { return baseName.replace(korean, english); } } } else if (userLocale === "JA") { // 일본어 번역 const translations: { [key: string]: string } = { 관리자: "管理者", 사용자: "ユーザー管理", 메뉴: "メニュー管理", 대시보드: "ダッシュボード", 권한: "権限管理", 코드: "コード管理", 설정: "設定", 로그: "ログ管理", 프로젝트: "プロジェクト管理", 제품: "製品管理", }; for (const [korean, japanese] of Object.entries(translations)) { if (baseName.includes(korean)) { return baseName.replace(korean, japanese); } } } else if (userLocale === "ZH") { // 중국어 번역 const translations: { [key: string]: string } = { 관리자: "管理员", 사용자: "用户管理", 메뉴: "菜单管理", 대시보드: "仪表板", 권한: "权限管理", 코드: "代码管理", 설정: "设置", 로그: "日志管理", 프로젝트: "项目管理", 제품: "产品管理", }; for (const [korean, chinese] of Object.entries(translations)) { if (baseName.includes(korean)) { return baseName.replace(korean, chinese); } } } return baseName; }; const children = convertMenuToUI(allMenus, userInfo, menuId); return { id: menuId, name: getDisplayText(menu), icon: getMenuIcon(menu.menu_name_kor || menu.MENU_NAME_KOR || ""), url: menu.menu_url || menu.MENU_URL || "#", children: children.length > 0 ? children : undefined, hasChildren: children.length > 0, }; }; function AppLayoutInner({ children }: AppLayoutProps) { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const { user, logout, refreshUserData } = useAuth(); const { userMenus, adminMenus, loading, refreshMenus } = useMenu(); const [sidebarOpen, setSidebarOpen] = useState(true); const [expandedMenus, setExpandedMenus] = useState>(new Set()); const [isMobile, setIsMobile] = useState(false); // 화면 크기 감지 및 사이드바 초기 상태 설정 useEffect(() => { const checkIsMobile = () => { const mobile = window.innerWidth < 1024; // lg 브레이크포인트 setIsMobile(mobile); // 모바일에서만 사이드바를 닫음 if (mobile) { setSidebarOpen(false); } else { setSidebarOpen(true); } }; checkIsMobile(); window.addEventListener("resize", checkIsMobile); return () => window.removeEventListener("resize", checkIsMobile); }, []); // 프로필 관련 로직 const { isModalOpen, formData, selectedImage, isSaving, departments, alertModal, closeAlert, openProfileModal, closeProfileModal, updateFormData, selectImage, removeImage, saveProfile, // 운전자 관련 isDriver, hasVehicle, driverInfo, driverFormData, updateDriverFormData, handleDriverStatusChange, handleDriverAccountDelete, handleDeleteVehicle, openVehicleRegisterModal, closeVehicleRegisterModal, isVehicleRegisterModalOpen, newVehicleData, updateNewVehicleData, handleRegisterVehicle, } = useProfile(user, refreshUserData, refreshMenus); // 현재 경로에 따라 어드민 모드인지 판단 (쿼리 파라미터도 고려) const isAdminMode = pathname.startsWith("/admin") || searchParams.get("mode") === "admin"; // 현재 모드에 따라 표시할 메뉴 결정 // 관리자 모드에서는 관리자 메뉴만, 사용자 모드에서는 사용자 메뉴만 표시 const currentMenus = isAdminMode ? adminMenus : userMenus; // 메뉴 토글 함수 const toggleMenu = (menuId: string) => { const newExpanded = new Set(expandedMenus); if (newExpanded.has(menuId)) { newExpanded.delete(menuId); } else { newExpanded.add(menuId); } setExpandedMenus(newExpanded); }; // 메뉴 클릭 핸들러 const handleMenuClick = async (menu: any) => { if (menu.hasChildren) { toggleMenu(menu.id); } else { // 메뉴 이름 저장 (엑셀 다운로드 파일명에 사용) const menuName = menu.label || menu.name || "메뉴"; if (typeof window !== "undefined") { localStorage.setItem("currentMenuName", menuName); } // 먼저 할당된 화면이 있는지 확인 (URL 유무와 관계없이) try { const menuObjid = menu.objid || menu.id; const assignedScreens = await menuScreenApi.getScreensByMenu(menuObjid); if (assignedScreens.length > 0) { // 할당된 화면이 있으면 첫 번째 화면으로 이동 const firstScreen = assignedScreens[0]; // 관리자 모드 상태와 menuObjid를 쿼리 파라미터로 전달 const params = new URLSearchParams(); if (isAdminMode) { params.set("mode", "admin"); } params.set("menuObjid", menuObjid.toString()); const screenPath = `/screens/${firstScreen.screenId}?${params.toString()}`; router.push(screenPath); if (isMobile) { setSidebarOpen(false); } return; } } catch (error) { console.warn("할당된 화면 조회 실패:", error); } // 할당된 화면이 없고 URL이 있으면 기존 URL로 이동 if (menu.url && menu.url !== "#") { router.push(menu.url); if (isMobile) { setSidebarOpen(false); } } else { // URL도 없고 할당된 화면도 없으면 경고 메시지 toast.warning("이 메뉴에는 연결된 페이지나 화면이 없습니다."); } } }; // 모드 전환 핸들러 const handleModeSwitch = () => { if (isAdminMode) { router.push("/main"); } else { router.push("/admin"); } }; // 로그아웃 핸들러 const handleLogout = async () => { try { await logout(); router.push("/login"); } catch (error) { // 로그아웃 실패 시 처리 } }; // 메뉴 트리 렌더링 (기존 MainLayout 스타일 적용) const renderMenu = (menu: any, level: number = 0) => { const isExpanded = expandedMenus.has(menu.id); return (
0 ? "ml-6" : ""}`} onClick={() => handleMenuClick(menu)} >
{menu.icon} {menu.name}
{menu.hasChildren && (
{isExpanded ? : }
)}
{/* 서브메뉴 */} {menu.hasChildren && isExpanded && (
{menu.children?.map((child: any) => (
handleMenuClick(child)} >
{child.icon} {child.name}
))}
)}
); }; // 사용자 정보가 없으면 로딩 표시 if (!user) { return (

로딩중...

); } // UI 변환된 메뉴 데이터 const uiMenus = convertMenuToUI(currentMenus, user as ExtendedUserInfo); return (
{/* 모바일 사이드바 오버레이 */} {sidebarOpen && isMobile && (
setSidebarOpen(false)} /> )} {/* 왼쪽 사이드바 */} {/* 가운데 컨텐츠 영역 - 스크롤 가능 */}
{children}
{/* 프로필 수정 모달 */}
); } export function AppLayout({ children }: AppLayoutProps) { return (

로딩중...

} > {children} ); }