import { useEffect, useState } from 'react'; import { ProLayout, type MenuDataItem } from '@ant-design/pro-components'; import { Avatar, Button, DatePicker, Dropdown, Space, Typography } from 'antd'; import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { IconChevronDown, IconLogout, IconSettings, IconUser } from '@tabler/icons-react'; import dayjs from 'dayjs'; import { menuApi, type MenuResp } from '@/api/menu'; import { authApi } from '@/api/auth'; import { useAuthStore } from '@/stores/auth'; import { getMenuIcon } from '@/components/common/MenuIcon'; import ErrorBoundary from '@/components/common/ErrorBoundary'; import NotificationBell from '@/components/biz/NotificationBell'; import { COLOR_PRIMARY, COLOR_PRIMARY_LIGHT, GRAY, LAYOUT, } from '@/theme/tokens'; const { Text } = Typography; const COLLAPSE_KEY = 'ga-sider-collapsed'; /** API 메뉴 트리 → ProLayout MenuDataItem */ function toProMenu(menus: MenuResp[]): MenuDataItem[] { return menus.map((m) => { const item: MenuDataItem = { name: m.menuName, path: m.menuPath ?? `/_grp/${m.menuCode}`, key: m.menuCode, icon: getMenuIcon(m.menuCode, m.menuPath), }; if (m.children?.length) item.children = toProMenu(m.children); return item; }); } /** 로컬스토리지에서 collapse 상태 읽기 */ function readCollapsed(): boolean { try { return localStorage.getItem(COLLAPSE_KEY) === 'true'; } catch { return false; } } export default function MainLayout() { const navigate = useNavigate(); const location = useLocation(); const auth = useAuthStore(); const [settleMonth, setSettleMonth] = useState(dayjs()); const [collapsed, setCollapsed] = useState(readCollapsed); // collapse 상태 localStorage 저장 useEffect(() => { try { localStorage.setItem(COLLAPSE_KEY, String(collapsed)); } catch { /* ignore */ } }, [collapsed]); const { data: menus = [] } = useQuery({ queryKey: ['menu', 'my'], queryFn: menuApi.myMenus, }); const handleLogout = async () => { try { await authApi.logout(); } catch { /* ignore */ } localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); auth.clear(); navigate('/login'); }; const displayName = auth.userName ?? auth.loginId ?? '관리자'; const roleLabel = auth.roles?.[0] ?? 'USER'; const userMenuItems = [ { key: 'profile', label: '내 프로필', icon: , onClick: () => navigate('/system/users'), }, { key: 'config', label: '시스템 설정', icon: , onClick: () => navigate('/system/config'), }, { type: 'divider' as const }, { key: 'logout', label: '로그아웃', icon: , danger: true, onClick: handleLogout, }, ]; return ( GA } layout="mix" navTheme="light" contentWidth="Fluid" fixedHeader fixSiderbar siderWidth={LAYOUT.siderWidth} collapsed={collapsed} onCollapse={setCollapsed} menu={{ locale: false }} menuDataRender={() => toProMenu(menus)} location={{ pathname: location.pathname }} menuItemRender={(item, dom) => (
item.path && !item.path.startsWith('/_grp/') && navigate(item.path) } > {dom}
)} breadcrumbRender={(routers = []) => routers} onMenuHeaderClick={() => navigate('/')} token={{ sider: { colorMenuBackground: '#ffffff', colorTextMenuSelected: COLOR_PRIMARY, colorBgMenuItemSelected: COLOR_PRIMARY_LIGHT, colorTextMenuItemHover: GRAY[800], colorTextMenu: GRAY[600], colorTextMenuSecondary: GRAY[500], colorTextSubMenuSelected: COLOR_PRIMARY, }, header: { colorBgHeader: '#ffffff', colorHeaderTitle: GRAY[800], heightLayoutHeader: LAYOUT.headerHeight, colorBgRightActionsItemHover: GRAY[100], }, bgLayout: GRAY[50], colorTextAppListIconHover: COLOR_PRIMARY, }} actionsRender={() => [ // 정산월 선택 정산월 d && setSettleMonth(d)} allowClear={false} format="YYYY-MM" size="small" style={{ width: 96 }} /> , // 알림 벨 (NotificationBell — unread-count API 연동) , ]} avatarProps={{ size: 'small', style: { background: COLOR_PRIMARY, color: '#fff', fontWeight: 600, fontSize: 12, }, src: undefined, children: displayName.charAt(0), render: (_props, dom) => ( { (e.currentTarget as HTMLElement).style.background = GRAY[100]; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.background = 'transparent'; }} > {dom}
{displayName}
{roleLabel}
), }} footerRender={() => (
GA Commission System v1.0.0-alpha
)} >
); }