Files
ga-commission-system/ga-frontend/src/layouts/MainLayout.tsx
T

144 lines
4.6 KiB
TypeScript
Raw Normal View History

import { useState } from 'react';
import { ProLayout, type MenuDataItem } from '@ant-design/pro-components';
import { Avatar, Badge, Button, DatePicker, Dropdown, Space } from 'antd';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { IconBell, IconLogout, IconUser, IconChevronDown } 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';
/** 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;
});
}
export default function MainLayout() {
const navigate = useNavigate();
const location = useLocation();
const auth = useAuthStore();
const [settleMonth, setSettleMonth] = useState(dayjs());
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');
};
return (
<ProLayout
title="GA 정산시스템"
logo={
<div
style={{
width: 28, height: 28, borderRadius: 6,
background: 'linear-gradient(135deg, #1677FF 0%, #0C447C 100%)',
color: '#fff', display: 'flex', alignItems: 'center',
justifyContent: 'center', fontWeight: 800, fontSize: 13,
}}
>GA</div>
}
layout="mix"
navTheme="light"
contentWidth="Fluid"
fixedHeader
fixSiderbar
siderWidth={220}
menu={{ locale: false }}
menuDataRender={() => toProMenu(menus)}
location={{ pathname: location.pathname }}
menuItemRender={(item, dom) => (
<div onClick={() => item.path && !item.path.startsWith('/_grp/') && navigate(item.path)}>
{dom}
</div>
)}
breadcrumbRender={(routers = []) => routers}
onMenuHeaderClick={() => navigate('/')}
token={{
// ProLayout 디자인 토큰 — antd pro 컬러 시스템
sider: {
colorMenuBackground: '#ffffff',
colorTextMenuSelected: '#185FA5',
colorBgMenuItemSelected: '#E6F1FB',
colorTextMenuItemHover: '#1677FF',
colorMenuItemDivider: '#f0f0f0',
},
header: {
colorBgHeader: '#ffffff',
colorHeaderTitle: '#185FA5',
colorBgRightActionsItemHover: '#f5f7fa',
heightLayoutHeader: 48,
},
bgLayout: '#f5f7fa',
pageContainer: {
paddingBlockPageContainerContent: 16,
paddingInlinePageContainerContent: 20,
},
}}
actionsRender={() => [
<Space key="month" size={6}>
<span style={{ fontSize: 12, color: '#999' }}></span>
<DatePicker
picker="month"
value={settleMonth}
onChange={(d) => d && setSettleMonth(d)}
allowClear={false}
format="YYYY-MM"
size="small"
style={{ width: 110 }}
/>
</Space>,
<Badge key="bell" count={3} size="small" offset={[-2, 4]}>
<Button type="text" icon={<IconBell size={18} />} />
</Badge>,
]}
avatarProps={{
src: undefined,
size: 'small',
title: auth.userName ?? auth.loginId ?? '관리자',
icon: <IconUser size={14} />,
style: { background: 'linear-gradient(135deg, #1677FF 0%, #185FA5 100%)' },
render: (_props, dom) => (
<Dropdown
menu={{
items: [
{ key: 'logout', label: '로그아웃', icon: <IconLogout size={14} />, onClick: handleLogout },
],
}}
>
<Space style={{ cursor: 'pointer' }}>
{dom}
<IconChevronDown size={12} color="#999" />
</Space>
</Dropdown>
),
}}
footerRender={() => (
<div style={{ textAlign: 'center', padding: '8px 0', fontSize: 11, color: '#bbb' }}>
© GA Commission System v1.0.0-alpha · trading_ai
</div>
)}
>
<Outlet />
</ProLayout>
);
}