'use client'; import { useMenuStore } from '@/store/menuStore'; import type { SerializableMenuItem } from '@/store/menuStore'; import type { MenuItem } from '@/store/menuStore'; import { useRouter, usePathname } from 'next/navigation'; import { useEffect, useState } from 'react'; import { Menu, Search, User, LogOut, LayoutDashboard, Sun, Moon, Accessibility, ShoppingCart, Building2, Receipt, Package, Settings, DollarSign, ChevronLeft, Home, X, } from 'lucide-react'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Sheet, SheetContent, SheetTrigger, SheetHeader, SheetTitle } from '@/components/ui/sheet'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import Sidebar from '@/components/layout/Sidebar'; import { useTheme } from '@/contexts/ThemeContext'; import { useAuth } from '@/contexts/AuthContext'; import { deserializeMenuItems } from '@/lib/utils/menuTransform'; interface AuthenticatedLayoutProps { children: React.ReactNode; } export default function AuthenticatedLayout({ children }: AuthenticatedLayoutProps) { const { menuItems, activeMenu, setActiveMenu, setMenuItems, sidebarCollapsed, toggleSidebar, _hasHydrated } = useMenuStore(); const { theme, setTheme } = useTheme(); const { logout } = useAuth(); const router = useRouter(); const pathname = usePathname(); // 현재 경로 추적 // 확장된 서브메뉴 관리 (기본적으로 sales, master-data 확장) const [expandedMenus, setExpandedMenus] = useState(['sales', 'master-data']); // 모바일 상태 관리 const [isMobile, setIsMobile] = useState(false); const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false); // 사용자 정보 상태 const [userName, setUserName] = useState("사용자"); const [userPosition, setUserPosition] = useState("직책"); // 모바일 감지 useEffect(() => { const checkScreenSize = () => { setIsMobile(window.innerWidth < 768); }; checkScreenSize(); window.addEventListener('resize', checkScreenSize); return () => window.removeEventListener('resize', checkScreenSize); }, []); // 서버에서 받은 사용자 정보로 초기화 useEffect(() => { // ⚠️ Allow rendering even before hydration (Zustand persist rehydration can be slow) // Commenting out the hydration check prevents infinite loading spinner // if (!_hasHydrated) return; // localStorage에서 사용자 정보 가져오기 const userDataStr = localStorage.getItem("user"); if (userDataStr) { const userData = JSON.parse(userDataStr); // 사용자 이름과 직책 설정 setUserName(userData.name || "사용자"); setUserPosition(userData.position || "직책"); // 서버에서 받은 메뉴 배열이 있으면 사용, 없으면 기본 메뉴 사용 if (userData.menu && Array.isArray(userData.menu) && userData.menu.length > 0) { // SerializableMenuItem (iconName string)을 MenuItem (icon component)로 변환 const deserializedMenus = deserializeMenuItems(userData.menu as SerializableMenuItem[]); setMenuItems(deserializedMenus); } else { // API가 준비될 때까지 임시 기본 메뉴 const defaultMenu: MenuItem[] = [ { id: "dashboard", label: "대시보드", icon: LayoutDashboard, path: "/dashboard" }, { id: "sales", label: "판매관리", icon: ShoppingCart, path: "#", children: [ { id: "customer-management", label: "거래처관리", icon: Building2, path: "/sales/client-management-sales-admin" }, { id: "quote-management", label: "견적관리", icon: Receipt, path: "/sales/quote-management" }, { id: "pricing-management", label: "단가관리", icon: DollarSign, path: "/sales/pricing-management" }, ], }, { id: "master-data", label: "기준정보", icon: Settings, path: "#", children: [ { id: "item-master", label: "품목기준관리", icon: Package, path: "/master-data/item-master-data-management" }, ], }, ]; setMenuItems(defaultMenu); } } }, [_hasHydrated, setMenuItems]); // 현재 경로에 맞는 메뉴 자동 활성화 (URL 직접 입력, 뒤로가기 대응) useEffect(() => { if (!pathname || menuItems.length === 0) return; // 경로 정규화 (로케일 제거) const normalizedPath = pathname.replace(/^\/(ko|en|ja)/, ''); // 메뉴 탐색 함수: 메인 메뉴와 서브메뉴 모두 탐색 const findActiveMenu = (items: MenuItem[]): { menuId: string; parentId?: string } | null => { for (const item of items) { // 서브메뉴가 있으면 먼저 확인 (더 구체적인 경로 우선) if (item.children && item.children.length > 0) { for (const child of item.children) { if (child.path && normalizedPath.startsWith(child.path)) { return { menuId: child.id, parentId: item.id }; } } } // 서브메뉴에서 매칭되지 않으면 현재 메뉴 확인 if (item.path && normalizedPath.startsWith(item.path)) { return { menuId: item.id }; } } return null; }; const result = findActiveMenu(menuItems); if (result) { // 활성 메뉴 설정 setActiveMenu(result.menuId); // 부모 메뉴가 있으면 자동으로 확장 if (result.parentId && !expandedMenus.includes(result.parentId)) { setExpandedMenus(prev => [...prev, result.parentId!]); } } }, [pathname, menuItems, setActiveMenu, expandedMenus]); const handleMenuClick = (menuId: string, path: string) => { setActiveMenu(menuId); router.push(path); }; // 서브메뉴 토글 함수 const toggleSubmenu = (menuId: string) => { setExpandedMenus(prev => prev.includes(menuId) ? prev.filter(id => id !== menuId) : [...prev, menuId] ); }; const handleLogout = async () => { try { // AuthContext의 logout() 호출 (완전한 캐시 정리 수행) // - Zustand 스토어 초기화 // - sessionStorage 캐시 삭제 (page_config_*, mes-*) // - localStorage 사용자 데이터 삭제 // - 서버 로그아웃 API 호출 (HttpOnly 쿠키 삭제) await logout(); // 로그인 페이지로 리다이렉트 router.push('/login'); } catch (error) { console.error('로그아웃 처리 중 오류:', error); // 에러가 나도 로그인 페이지로 이동 router.push('/login'); } }; // ⚠️ FIXED: Removed hydration check to prevent infinite loading spinner // The hydration check was causing the dashboard to show a loading spinner indefinitely // because Zustand persist rehydration was taking too long or not completing properly. // By removing this check, we allow the component to render immediately with default values // and update once hydration completes through the useEffect above. // 현재 페이지가 대시보드인지 확인 const isDashboard = pathname?.includes('/dashboard') || activeMenu === 'dashboard'; // 이전 페이지로 이동 const handleGoBack = () => { router.back(); }; // 홈(대시보드)으로 이동 const handleGoHome = () => { router.push('/dashboard'); }; // 모바일 레이아웃 if (isMobile) { return (
{/* 모바일 헤더 - sam-design 스타일 */}
{/* 좌측 영역: 대시보드일 때는 로고, 다른 페이지일 때는 이전/홈 버튼 */}
{isDashboard ? ( // 대시보드: 로고만 표시
S

SAM

) : ( // 다른 페이지: 이전/홈 버튼 표시
)}
{/* 우측 영역: 검색, 테마, 유저, 메뉴 */}
{/* 검색 아이콘 */} {/* 테마 토글 */} setTheme('light')}> 일반모드 setTheme('dark')}> 다크모드 setTheme('senior')}> 시니어모드 {/* 유저 아이콘 */}
{/* 로그아웃 버튼 */} {/* 햄버거 메뉴 - 맨 우측 */} 메뉴 setIsMobileSidebarOpen(false)} />
{/* 모바일 콘텐츠 */}
{children}
); } // 데스크톱 레이아웃 return (
{/* 헤더 - 전체 너비 상단 고정 */}
{/* SAM 로고 섹션 */}
S

SAM

Smart Automation Management

{/* Menu 버튼 - 사이드바 토글 */} {/* 검색바 */}
{/* 테마 선택 - React 프로젝트 스타일 */} setTheme('light')}> 일반모드 setTheme('dark')}> 다크모드 setTheme('senior')}> 시니어모드 {/* 유저 프로필 */}

{userName}

{userPosition}

{/* 로그아웃 버튼 - 아이콘 형태 */}
{/* Subtle gradient overlay */}
{/* 사이드바 + 메인 콘텐츠 영역 */}
{/* 데스크톱 사이드바 */}
{/* 메인 콘텐츠 */}
{children}
); }