import { useEffect, useState, type ReactNode, type ElementType } from 'react'; import { PageType } from '@/types'; import { useAuth } from '@/features/auth/useAuth'; import { useBranding } from '@/features/settings/useCompanySettings'; 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 { LayoutDashboard, BarChart3, Briefcase, Users, UserCog, UserPen, FileSpreadsheet, Layers, LogOut, Search, Sun, Moon, Building, ChevronRight, Menu, X, } from 'lucide-react'; interface LayoutProps { children: ReactNode; currentPage: PageType; setPage: (page: PageType) => void; onLogout: () => void; } type SidebarUser = ReturnType['user']; type MenuItem = { type: PageType; label: string; icon: ElementType; id: string; ownerOnly?: boolean }; // ownerOnly 항목은 최고관리자에게만 노출된다(렌더 시 user.role 로 필터). // 그룹 라벨은 사이드바 섹션 헤더로 노출(접힘 상태에선 숨김). 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 }, { type: 'SETTINGS', label: '회사 설정', icon: Building, id: 'sidebar-settings', ownerOnly: true }, ], }, ]; const menuItems: MenuItem[] = menuGroups.flatMap((g) => g.items); const pageLabelMap: Record = { DASHBOARD: '대시보드', STATISTICS: '통계', PRODUCTS: '상품관리', PARTNERS: '협력사관리', QUOTATION: '견적관리', CARDS: '협상카드관리', MEMBERS: '회원관리', SETTINGS: '회사 설정', NOTIFICATIONS: '알림', }; export default function Layout({ children, currentPage, setPage, onLogout }: LayoutProps) { const { user } = useAuth(); const branding = useBranding(); // 회사 설정 브랜딩(서비스명/로고). 미설정 시 기본 NegoData // 기준일시(오늘) — 로컬 타임존 기준 YYYY-MM-DD const today = new Date().toLocaleDateString('sv-SE'); const [isDark, setIsDark] = useState(false); 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) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }; return (
{/* 토스트는 sonner (main.tsx)가 전역으로 처리한다 */} {/* 모바일 드로어 백드롭 */} {isMobileOpen && (
setIsMobileOpen(false)} aria-hidden /> )} {/* Main Sidebar */} {/* Main Container Wrapper */}
{/* Global Header — 좌: 페이지명 / 중: 빠른 이동(⌘K) / 우: 알림·계정 메타 */}
{/* 모바일 햄버거 */} {/* 현재 페이지 — 모바일·데스크톱 모두 표시(로고는 사이드바/드로어 몫) */} {pageLabelMap[currentPage]}
{/* 빠른 이동 트리거 */}
{/* Content Body Area */}
{children}
{/* Compact Admin footer info */}
Copyright © O2O Inc. All rights reserved
{ setPage(type); setIsCmdOpen(false); }} /> {isProfileOpen && setIsProfileOpen(false)} />}
); } /** ⌘K 빠른 이동 — 메뉴 검색·이동. 검색 대상 확장(견적명 등)은 추후 단계 */ function CommandMenu({ open, onOpenChange, items, currentPage, onSelect, }: { open: boolean; onOpenChange: (open: boolean) => void; items: MenuItem[]; currentPage: PageType; onSelect: (type: PageType) => void; }) { const [query, setQuery] = useState(''); useEffect(() => { if (!open) setQuery(''); }, [open]); const q = query.trim().toLowerCase(); const filtered = q ? items.filter((i) => i.label.toLowerCase().includes(q)) : items; return ( 메뉴 빠른 이동
setQuery(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && filtered.length > 0) onSelect(filtered[0].type); }} placeholder="이동할 메뉴 검색…" className="h-11 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground" />
{filtered.length === 0 ? ( 일치하는 메뉴가 없습니다 ) : ( filtered.map((item) => { const Icon = item.icon; return ( ); }) )}
); } /** 라벨:값 한 줄 (사이드바 프로필 카드용) */ function InfoRow({ label, value, valueClassName, title, }: { label: string; value?: string; valueClassName?: string; title?: string; }) { return (
{label} {value}
); } /** 사이드바 사용자 프로필 카드 */ function SidebarProfileCard({ user }: { user: SidebarUser }) { return (
{user?.company}
권한등급: {user?.role}
); } /** 사이드바 메뉴 항목 */ function NavItem({ item, isActive, isOpen, onClick, }: { item: MenuItem; isActive: boolean; isOpen: boolean; onClick: () => void; }) { const Icon = item.icon; return ( ); } /** 글로벌 헤더 우측 메타 정보 */ function HeaderMeta({ user, today }: { user: SidebarUser; today: string }) { return (
기준일시: {today} {/* 회사명은 모바일에서만 — 데스크톱은 사이드바 브랜드에 이미 노출 */}
회사: {user?.company}
관리자: {user?.name} ({user?.loginId}) {user?.role}
); }