'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, } from 'lucide-react'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Sheet, SheetContent, SheetTrigger } 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 { deserializeMenuItems } from '@/lib/utils/menuTransform'; interface DashboardLayoutProps { children: React.ReactNode; } export default function DashboardLayout({ children }: DashboardLayoutProps) { const { menuItems, activeMenu, setActiveMenu, setMenuItems, sidebarCollapsed, toggleSidebar, _hasHydrated } = useMenuStore(); const { theme, setTheme } = useTheme(); 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 { // HttpOnly Cookie 방식: Next.js API Route로 프록시 await fetch('/api/auth/logout', { method: 'POST', }); // localStorage 정리 localStorage.removeItem('user'); // 로그인 페이지로 리다이렉트 router.push('/login'); } catch (error) { console.error('로그아웃 처리 중 오류:', error); // 에러가 나도 로그인 페이지로 이동 localStorage.removeItem('user'); 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. return (
{/* 헤더 - 전체 너비 상단 고정 */}
{/* SAM 로고 섹션 */}
S

SAM

Smart Automation Management

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

{userName}

{userPosition}

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