diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 04c5474..3f9bda4 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -3,43 +3,51 @@ import { useEffect } from "react"; import { useRouter } from "next/navigation"; import { useAuthStore } from "@/store/auth-store"; +import { useMenuStore } from "@/store/menu-store"; import { Sidebar } from "@/components/layout/sidebar"; import { Header } from "@/components/layout/header"; import { Loading } from "@/components/ui/loading"; -// mainFS.jsp 대응 - 프레임셋 → Sidebar + Header + Content 레이아웃 export default function MainLayout({ children }: { children: React.ReactNode }) { const router = useRouter(); const { user, isLoading, fetchUser } = useAuthStore(); + const { mobileOpen, setMobileOpen } = useMenuStore(); + + useEffect(() => { fetchUser(); }, [fetchUser]); useEffect(() => { - fetchUser(); - }, [fetchUser]); - - useEffect(() => { - if (!isLoading && !user) { - router.push("/login"); - } + if (!isLoading && !user) router.push("/login"); }, [isLoading, user, router]); - if (isLoading) { - return ; - } - + if (isLoading) return ; if (!user) return null; return (
- {/* 사이드바 (menu.jsp 대응) */} - + {/* 사이드바 — 데스크탑은 정상, 모바일은 오버레이로 등장 */} +
+ +
+ + {/* 모바일 오버레이 배경 — 사이드바 펼쳤을 때 어둡게 */} + {mobileOpen && ( + + + {/* 좌측 여백 */}
{/* 매뉴얼 보기 (새 탭) */} @@ -43,12 +52,13 @@ export function Header() { {/* 우측: 사용자명(프로필 링크) + 로그아웃 */} - - - {user?.userName} {user?.deptName ? `(${user.deptName})` : ""} + + + {user?.userName} + {user?.deptName ? ` (${user.deptName})` : ""} diff --git a/src/components/layout/sidebar.tsx b/src/components/layout/sidebar.tsx index dbf5577..2a0556b 100644 --- a/src/components/layout/sidebar.tsx +++ b/src/components/layout/sidebar.tsx @@ -11,7 +11,7 @@ import { ShoppingCart, FileText, Warehouse, Boxes, Factory, Wrench, Headset, Clock, Calculator, Coins, Truck, Settings, ClipboardCheck, Compass, GitBranch, Puzzle, Stamp, Folder, - ChevronDown, Menu as MenuIcon, + ChevronDown, Menu as MenuIcon, X, } from "lucide-react"; const ICON_COMPONENTS: Record = { @@ -34,7 +34,7 @@ export function Sidebar() { const router = useRouter(); const { sideMenus, activeSubMenu, isCollapsed, - setActiveSubMenu, toggleCollapsed, + setActiveSubMenu, toggleCollapsed, setMobileOpen, } = useMenuStore(); const [openCategories, setOpenCategories] = useState>(new Set()); // 축소 모드 호버 팝업 @@ -61,6 +61,7 @@ export function Sidebar() { const handleSubMenuClick = (menuObjId: string, menuUrl: string) => { setActiveSubMenu(menuObjId); setHoveredCategory(null); // 팝업 닫기 + setMobileOpen(false); // 모바일에서 메뉴 선택 시 자동 닫기 const path = mapMenuUrl(menuUrl); if (path) router.push(path); }; @@ -120,9 +121,18 @@ export function Sidebar() { ) : ( MOMO )} + {/* 모바일: X로 닫기, 데스크탑: 햄버거로 축소/확장 */} + diff --git a/src/store/menu-store.ts b/src/store/menu-store.ts index 36a5be7..b6e4ba9 100644 --- a/src/store/menu-store.ts +++ b/src/store/menu-store.ts @@ -7,11 +7,14 @@ interface MenuState { activeTopMenu: string; activeSubMenu: string; isCollapsed: boolean; + /** 모바일에서 사이드바 오버레이로 펼침 여부 */ + mobileOpen: boolean; setTopMenus: (menus: { OBJID: string; MENU_NAME_KOR: string }[]) => void; setSideMenus: (menus: MenuItem[]) => void; setActiveTopMenu: (id: string) => void; setActiveSubMenu: (id: string) => void; toggleCollapsed: () => void; + setMobileOpen: (v: boolean) => void; fetchTopMenus: () => Promise; fetchSideMenus: (menuObjId: string) => Promise; } @@ -22,12 +25,14 @@ export const useMenuStore = create((set) => ({ activeTopMenu: "", activeSubMenu: "", isCollapsed: false, + mobileOpen: false, setTopMenus: (topMenus) => set({ topMenus }), setSideMenus: (sideMenus) => set({ sideMenus }), setActiveTopMenu: (activeTopMenu) => set({ activeTopMenu }), setActiveSubMenu: (activeSubMenu) => set({ activeSubMenu }), toggleCollapsed: () => set((s) => ({ isCollapsed: !s.isCollapsed })), + setMobileOpen: (v) => set({ mobileOpen: v }), fetchTopMenus: async () => { const res = await fetch("/api/menu/top");