diff --git a/negodata/front/src/components/layout/Layout.tsx b/negodata/front/src/components/layout/Layout.tsx index e9589ab..eef9f3c 100644 --- a/negodata/front/src/components/layout/Layout.tsx +++ b/negodata/front/src/components/layout/Layout.tsx @@ -1,10 +1,11 @@ -import { useState, type ReactNode, type ElementType } from 'react'; +import { useEffect, useState, type ReactNode, type ElementType } from 'react'; import { PageType } from '@/types'; import { useAuth } from '@/features/auth/useAuth'; import { ProfileSheet } from '@/features/auth/components/ProfileSheet'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Typography } from '@/components/ui/typography'; +import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'; import { cn } from '@/lib/utils'; import { NotificationBell } from './NotificationBell'; import { @@ -17,6 +18,7 @@ import { FileSpreadsheet, Layers, LogOut, + Search, Sun, Moon, Building, @@ -34,17 +36,34 @@ interface LayoutProps { type SidebarUser = ReturnType['user']; +type MenuItem = { type: PageType; label: string; icon: ElementType; id: string; ownerOnly?: boolean }; + // ownerOnly 항목은 최고관리자에게만 노출된다(렌더 시 user.role 로 필터). -const menuItems: { type: PageType; label: string; icon: ElementType; id: string; ownerOnly?: boolean }[] = [ - { type: 'DASHBOARD', label: '대시보드', icon: LayoutDashboard, id: 'sidebar-dashboard' }, - { type: 'STATISTICS', label: '통계', icon: BarChart3, id: 'sidebar-statistics' }, - { type: 'PRODUCTS', label: '상품관리', icon: Briefcase, id: 'sidebar-products' }, - { type: 'PARTNERS', label: '협력사관리', icon: Users, id: 'sidebar-partners' }, - { type: 'QUOTATION', label: '견적관리', icon: FileSpreadsheet, id: 'sidebar-quotation' }, - { type: 'CARDS', label: '협상카드관리', icon: Layers, id: 'sidebar-cards' }, - { type: 'MEMBERS', label: '회원관리', icon: UserCog, id: 'sidebar-members', ownerOnly: true }, +// 그룹 라벨은 사이드바 섹션 헤더로 노출(접힘 상태에선 숨김). +const menuGroups: { label?: string; items: MenuItem[] }[] = [ + { + items: [ + { type: 'DASHBOARD', label: '대시보드', icon: LayoutDashboard, id: 'sidebar-dashboard' }, + { type: 'STATISTICS', label: '통계', icon: BarChart3, id: 'sidebar-statistics' }, + ], + }, + { + label: '업무', + items: [ + { type: 'PRODUCTS', label: '상품관리', icon: Briefcase, id: 'sidebar-products' }, + { type: 'PARTNERS', label: '협력사관리', icon: Users, id: 'sidebar-partners' }, + { type: 'QUOTATION', label: '견적관리', icon: FileSpreadsheet, id: 'sidebar-quotation' }, + { type: 'CARDS', label: '협상카드관리', icon: Layers, id: 'sidebar-cards' }, + ], + }, + { + label: '관리', + items: [{ type: 'MEMBERS', label: '회원관리', icon: UserCog, id: 'sidebar-members', ownerOnly: true }], + }, ]; +const menuItems: MenuItem[] = menuGroups.flatMap((g) => g.items); + const pageLabelMap: Record = { DASHBOARD: '대시보드', STATISTICS: '통계', @@ -65,10 +84,25 @@ export default function Layout({ children, currentPage, setPage, onLogout }: Lay const [isSidebarOpen, setIsSidebarOpen] = useState(true); // 데스크톱 접기 토글 const [isMobileOpen, setIsMobileOpen] = useState(false); // 모바일 드로어 열림 const [isProfileOpen, setIsProfileOpen] = useState(false); // 내 정보 수정 시트 + const [isCmdOpen, setIsCmdOpen] = useState(false); // ⌘K 빠른 이동 // 사이드바 펼침 여부(라벨/프로필 노출 기준). 모바일 드로어는 항상 펼친 상태로 본다. const expanded = isMobileOpen || isSidebarOpen; + const visibleItems = menuItems.filter((item) => !item.ownerOnly || user?.role === '최고관리자'); + + // ⌘K / Ctrl+K 로 빠른 이동 열기 + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { + e.preventDefault(); + setIsCmdOpen((v) => !v); + } + }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, []); + const toggleDarkMode = () => { setIsDark(!isDark); if (!isDark) { @@ -94,7 +128,7 @@ export default function Layout({ children, currentPage, setPage, onLogout }: Lay {/* Main Sidebar */}