2026-05-10 21:34:17 +09:00
|
|
|
import { useState } from 'react';
|
2026-05-10 22:20:33 +09:00
|
|
|
import { ProLayout, type MenuDataItem } from '@ant-design/pro-components';
|
2026-05-10 22:43:57 +09:00
|
|
|
import { Badge, Button, DatePicker, Dropdown, Space } from 'antd';
|
2026-05-10 22:20:33 +09:00
|
|
|
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
2026-05-09 22:00:53 +09:00
|
|
|
import { useQuery } from '@tanstack/react-query';
|
2026-05-10 22:43:57 +09:00
|
|
|
import { IconBell, IconLogout, IconUser } from '@tabler/icons-react';
|
2026-05-10 21:34:17 +09:00
|
|
|
import dayjs from 'dayjs';
|
2026-05-10 22:20:33 +09:00
|
|
|
import { menuApi, type MenuResp } from '@/api/menu';
|
2026-05-09 22:00:53 +09:00
|
|
|
import { authApi } from '@/api/auth';
|
|
|
|
|
import { useAuthStore } from '@/stores/auth';
|
2026-05-10 22:09:17 +09:00
|
|
|
import { getMenuIcon } from '@/components/common/MenuIcon';
|
2026-05-09 22:00:53 +09:00
|
|
|
|
2026-05-10 22:20:33 +09:00
|
|
|
/** 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;
|
|
|
|
|
});
|
2026-05-09 22:00:53 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function MainLayout() {
|
|
|
|
|
const navigate = useNavigate();
|
2026-05-10 21:34:17 +09:00
|
|
|
const location = useLocation();
|
2026-05-09 22:00:53 +09:00
|
|
|
const auth = useAuthStore();
|
2026-05-10 21:34:17 +09:00
|
|
|
const [settleMonth, setSettleMonth] = useState(dayjs());
|
2026-05-09 22:00:53 +09:00
|
|
|
|
|
|
|
|
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 (
|
2026-05-10 22:20:33 +09:00
|
|
|
<ProLayout
|
|
|
|
|
title="GA 정산시스템"
|
2026-05-10 22:43:57 +09:00
|
|
|
logo="/vite.svg"
|
2026-05-10 22:20:33 +09:00
|
|
|
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}
|
2026-05-10 22:09:17 +09:00
|
|
|
</div>
|
2026-05-10 22:20:33 +09:00
|
|
|
)}
|
|
|
|
|
breadcrumbRender={(routers = []) => routers}
|
|
|
|
|
onMenuHeaderClick={() => navigate('/')}
|
|
|
|
|
token={{
|
|
|
|
|
sider: {
|
|
|
|
|
colorMenuBackground: '#ffffff',
|
|
|
|
|
colorTextMenuSelected: '#185FA5',
|
|
|
|
|
colorBgMenuItemSelected: '#E6F1FB',
|
|
|
|
|
colorTextMenuItemHover: '#1677FF',
|
|
|
|
|
},
|
|
|
|
|
header: {
|
|
|
|
|
colorBgHeader: '#ffffff',
|
|
|
|
|
colorHeaderTitle: '#185FA5',
|
2026-05-10 22:43:57 +09:00
|
|
|
heightLayoutHeader: 56,
|
2026-05-10 22:20:33 +09:00
|
|
|
},
|
|
|
|
|
bgLayout: '#f5f7fa',
|
|
|
|
|
}}
|
|
|
|
|
actionsRender={() => [
|
2026-05-10 22:43:57 +09:00
|
|
|
<Space key="month" size={4} align="center">
|
|
|
|
|
<span style={{ fontSize: 12, color: '#666' }}>정산월</span>
|
2026-05-10 22:20:33 +09:00
|
|
|
<DatePicker
|
|
|
|
|
picker="month"
|
|
|
|
|
value={settleMonth}
|
|
|
|
|
onChange={(d) => d && setSettleMonth(d)}
|
|
|
|
|
allowClear={false}
|
|
|
|
|
format="YYYY-MM"
|
|
|
|
|
size="small"
|
2026-05-10 22:43:57 +09:00
|
|
|
style={{ width: 100 }}
|
2026-05-10 22:20:33 +09:00
|
|
|
/>
|
|
|
|
|
</Space>,
|
|
|
|
|
<Badge key="bell" count={3} size="small" offset={[-2, 4]}>
|
|
|
|
|
<Button type="text" icon={<IconBell size={18} />} />
|
|
|
|
|
</Badge>,
|
|
|
|
|
]}
|
|
|
|
|
avatarProps={{
|
|
|
|
|
size: 'small',
|
|
|
|
|
title: auth.userName ?? auth.loginId ?? '관리자',
|
|
|
|
|
icon: <IconUser size={14} />,
|
|
|
|
|
render: (_props, dom) => (
|
|
|
|
|
<Dropdown
|
|
|
|
|
menu={{
|
|
|
|
|
items: [
|
|
|
|
|
{ key: 'logout', label: '로그아웃', icon: <IconLogout size={14} />, onClick: handleLogout },
|
|
|
|
|
],
|
2026-05-10 21:34:17 +09:00
|
|
|
}}
|
|
|
|
|
>
|
2026-05-10 22:43:57 +09:00
|
|
|
{dom}
|
2026-05-10 22:20:33 +09:00
|
|
|
</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>
|
2026-05-09 22:00:53 +09:00
|
|
|
);
|
|
|
|
|
}
|