Files
ga-commission-system/ga-frontend/src/layouts/MainLayout.tsx
T
GA Pro 24fc187e2e feat(frontend): 09_UI_DESIGN 디자인 시스템 전체 적용
테마 / 폰트 / 아이콘:
- ConfigProvider theme 토큰 30+ 추가 (브랜드 컬러, 폰트, 라운딩, 메뉴/테이블/버튼)
- Pretendard 폰트 link + index.css fontFamily 적용
- @tabler/icons-react 추가, MainLayout 에 적용

레이아웃:
- styles/variables.css 신규 (--ga-* 변수 30+)
- MainLayout: Header 48px 흰색 + bottom border, Sider 220px + 토글,
  Content 1400px 중앙 정렬, 정산월 MonthPicker, 햄버거 토글

공통 컴포넌트:
- CodeBadge: 4단계 색상 매핑 (success/info/warning/danger), 누락 코드 보강
- MoneyText: 양수 #0F6E56, 음수 #E24B4A, 0 #888 (CSS 변수 사용)
- SearchForm: 배경 #fafafa, borderRadius 8, padding 12px 16px
- DataGrid: rowHeight 36, headerHeight 38, AG_GRID_LOCALE_KO,
  합계행 getRowStyle 회색 배경 + 굵게

페이지 페이지네이션 통일:
- utils/tablePagination.ts 신규 (showTotal '총 N건', pageSizeOptions [20/50/100])
- 7개 페이지 (Agent/Contract/Recruit/Exception/Settle/Payment/User) 적용

검증: tsc -b 통과 / vite build 성공 (10s) / 백엔드 단위테스트 76건 영향 없음

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:34:17 +09:00

127 lines
3.7 KiB
TypeScript

import { useState } from 'react';
import { Layout, Menu, Dropdown, Badge, Space, Avatar, Button, DatePicker } from 'antd';
import { useNavigate, Outlet, useLocation } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import {
IconMenu2,
IconBell,
IconUser,
IconLogout,
} from '@tabler/icons-react';
import dayjs from 'dayjs';
import { menuApi, MenuResp } from '@/api/menu';
import { authApi } from '@/api/auth';
import { useAuthStore } from '@/stores/auth';
const { Header, Sider, Content } = Layout;
function toMenuItems(menus: MenuResp[]): any[] {
return menus.map((m) => ({
key: m.menuPath ?? m.menuCode,
label: m.menuName,
children: m.children?.length ? toMenuItems(m.children) : undefined,
}));
}
export default function MainLayout() {
const navigate = useNavigate();
const location = useLocation();
const auth = useAuthStore();
const [collapsed, setCollapsed] = useState(false);
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 (
<Layout style={{ minHeight: '100vh' }}>
<Header
style={{
height: 48,
lineHeight: '48px',
background: '#ffffff',
borderBottom: '1px solid #f0f0f0',
padding: '0 16px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
boxShadow: 'var(--ga-shadow-card)',
}}
>
<Space size={12}>
<Button
type="text"
icon={<IconMenu2 size={18} />}
onClick={() => setCollapsed((c) => !c)}
/>
<span style={{ fontSize: 15, fontWeight: 600, color: '#185FA5' }}>
GA
</span>
</Space>
<Space size={12}>
<DatePicker
picker="month"
value={settleMonth}
onChange={(d) => d && setSettleMonth(d)}
allowClear={false}
format="YYYY-MM"
size="small"
/>
<Badge count={0} size="small">
<Button type="text" icon={<IconBell size={18} />} />
</Badge>
<Dropdown
menu={{
items: [
{
key: 'logout',
label: '로그아웃',
icon: <IconLogout size={14} />,
onClick: handleLogout,
},
],
}}
>
<Space style={{ cursor: 'pointer' }}>
<Avatar size="small" icon={<IconUser size={14} />} />
<span style={{ fontSize: 13 }}>{auth.userName ?? auth.loginId}</span>
</Space>
</Dropdown>
</Space>
</Header>
<Layout>
<Sider
width={220}
collapsedWidth={64}
collapsed={collapsed}
theme="light"
style={{ borderRight: '1px solid #f0f0f0' }}
>
<Menu
mode="inline"
items={toMenuItems(menus)}
selectedKeys={[location.pathname]}
onClick={({ key }) => navigate(String(key))}
style={{ borderRight: 0, height: '100%', paddingTop: 8 }}
/>
</Sider>
<Content style={{ padding: 'var(--ga-content-padding)', overflow: 'auto' }}>
<div className="ga-page-container">
<Outlet />
</div>
</Content>
</Layout>
</Layout>
);
}