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>
This commit is contained in:
@@ -38,3 +38,7 @@ target/
|
|||||||
|
|
||||||
# Claude Code 개인 설정 (Agent Teams 등)
|
# Claude Code 개인 설정 (Agent Teams 등)
|
||||||
.claude/
|
.claude/
|
||||||
|
|
||||||
|
# vite/tsc 빌드 산출물
|
||||||
|
ga-frontend/tsconfig.tsbuildinfo
|
||||||
|
ga-frontend/dist/
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.css" />
|
||||||
<title>GA 수수료 정산 솔루션</title>
|
<title>GA 수수료 정산 솔루션</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Generated
+27
@@ -12,6 +12,7 @@
|
|||||||
"@ag-grid-community/core": "^31.3.2",
|
"@ag-grid-community/core": "^31.3.2",
|
||||||
"@ag-grid-community/react": "^31.3.2",
|
"@ag-grid-community/react": "^31.3.2",
|
||||||
"@ag-grid-community/styles": "^31.3.2",
|
"@ag-grid-community/styles": "^31.3.2",
|
||||||
|
"@tabler/icons-react": "^3.5.0",
|
||||||
"@tanstack/react-query": "^5.40.0",
|
"@tanstack/react-query": "^5.40.0",
|
||||||
"antd": "^5.18.0",
|
"antd": "^5.18.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
@@ -1462,6 +1463,32 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@tabler/icons": {
|
||||||
|
"version": "3.44.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.44.0.tgz",
|
||||||
|
"integrity": "sha512-Wn0AOZG9sg0L+bjfMqq4eNhC6pQjIrk94LvvWYNYkY8KH8wC3YILRzQlrnVJc4FUeMxH/AK97QsYCX35H3LndA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/codecalm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tabler/icons-react": {
|
||||||
|
"version": "3.44.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.44.0.tgz",
|
||||||
|
"integrity": "sha512-8+rvzBbVm/1Z3sG3x7GUNAaxIKxwgz8xaMhRs23nrCnMTKRFAhEC+82zAIFeAA0seXdrAGX5HFCkaLpGK2rVHg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tabler/icons": "3.44.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/codecalm"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/query-core": {
|
"node_modules/@tanstack/query-core": {
|
||||||
"version": "5.100.9",
|
"version": "5.100.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.9.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.9.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@ag-grid-community/core": "^31.3.2",
|
"@ag-grid-community/core": "^31.3.2",
|
||||||
"@ag-grid-community/react": "^31.3.2",
|
"@ag-grid-community/react": "^31.3.2",
|
||||||
"@ag-grid-community/styles": "^31.3.2",
|
"@ag-grid-community/styles": "^31.3.2",
|
||||||
|
"@tabler/icons-react": "^3.5.0",
|
||||||
"@tanstack/react-query": "^5.40.0",
|
"@tanstack/react-query": "^5.40.0",
|
||||||
"antd": "^5.18.0",
|
"antd": "^5.18.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
|
|||||||
@@ -6,11 +6,31 @@ interface Props {
|
|||||||
value?: string;
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 09_UI_DESIGN.md §5 CodeBadge — 4단계 색상 (success/info/warning/danger/default).
|
||||||
|
* antd Tag 의 preset 컬러 사용: success(초록) / processing(파랑) / warning(주황) / error(빨강) / default(회색).
|
||||||
|
*/
|
||||||
const COLOR_MAP: Record<string, string> = {
|
const COLOR_MAP: Record<string, string> = {
|
||||||
ACTIVE: 'green', CONFIRMED: 'green', APPROVED: 'green', PAID: 'green', MATCHED: 'green', DONE: 'green',
|
// 초록 — 긍정적 완료
|
||||||
PENDING: 'blue', CALCULATED: 'blue', SENT: 'blue',
|
CONFIRMED: 'success', APPROVED: 'success', ACTIVE: 'success',
|
||||||
HOLD: 'red', SUSPEND: 'red', REJECTED: 'red', FAIL: 'red', LAPSE: 'red', DIFF: 'red', LOCKED: 'red',
|
PAID: 'success', COMPLETED: 'success', MATCH: 'success',
|
||||||
DRAFT: 'default', NEW: 'default', LEAVE: 'default', RETIRED: 'default',
|
MATCHED: 'success', DONE: 'success', SUCCESS: 'success',
|
||||||
|
|
||||||
|
// 파랑 — 진행 중
|
||||||
|
CALCULATED: 'processing', PENDING: 'processing', PROCESSING: 'processing',
|
||||||
|
REVIEWED: 'processing', NEW: 'processing', SENT: 'processing',
|
||||||
|
|
||||||
|
// 주황 — 주의
|
||||||
|
HOLD: 'warning', SUSPEND: 'warning', MISMATCH: 'warning',
|
||||||
|
PARTIAL: 'warning', DIFF: 'warning',
|
||||||
|
|
||||||
|
// 빨강 — 부정적
|
||||||
|
REJECTED: 'error', LEAVE: 'error', CANCEL: 'error',
|
||||||
|
FAILED: 'error', FAIL: 'error', LAPSED: 'error',
|
||||||
|
LAPSE: 'error', LOCKED: 'error',
|
||||||
|
|
||||||
|
// 회색 — 기본
|
||||||
|
DRAFT: 'default', READY: 'default', RETIRED: 'default',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CodeBadge({ groupCode, value }: Props) {
|
export default function CodeBadge({ groupCode, value }: Props) {
|
||||||
|
|||||||
@@ -8,6 +8,21 @@ import '@ag-grid-community/styles/ag-theme-quartz.css';
|
|||||||
|
|
||||||
ModuleRegistry.registerModules([ClientSideRowModelModule]);
|
ModuleRegistry.registerModules([ClientSideRowModelModule]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AG Grid 한국어 로케일 (자주 쓰이는 항목 위주).
|
||||||
|
* 전체 키 목록은 AG Grid 공식 문서 참조.
|
||||||
|
*/
|
||||||
|
const AG_GRID_LOCALE_KO: Record<string, string> = {
|
||||||
|
page: '페이지', more: '더보기', to: '~', of: '/', next: '다음', last: '마지막',
|
||||||
|
first: '처음', previous: '이전', loadingOoo: '불러오는 중...',
|
||||||
|
selectAll: '전체 선택', searchOoo: '검색...', blanks: '(빈값)',
|
||||||
|
filterOoo: '필터...', equals: '같음', notEqual: '같지않음',
|
||||||
|
contains: '포함', notContains: '포함하지않음', startsWith: '시작',
|
||||||
|
endsWith: '끝', noRowsToShow: '데이터가 없습니다', resetColumns: '컬럼 초기화',
|
||||||
|
pinColumn: '컬럼 고정', valueAggregation: '집계',
|
||||||
|
sum: '합계', min: '최소', max: '최대', count: '건수',
|
||||||
|
};
|
||||||
|
|
||||||
interface Props<T> {
|
interface Props<T> {
|
||||||
rows: T[] | undefined;
|
rows: T[] | undefined;
|
||||||
columns: ColDef<T>[];
|
columns: ColDef<T>[];
|
||||||
@@ -65,9 +80,18 @@ export default function DataGrid<T extends Record<string, unknown>>({
|
|||||||
columnDefs={columns}
|
columnDefs={columns}
|
||||||
defaultColDef={defaultColDef}
|
defaultColDef={defaultColDef}
|
||||||
animateRows
|
animateRows
|
||||||
|
rowHeight={36}
|
||||||
|
headerHeight={38}
|
||||||
|
localeText={AG_GRID_LOCALE_KO}
|
||||||
rowSelection="single"
|
rowSelection="single"
|
||||||
getRowId={rowKey ? (p) => String(p.data[rowKey]) : undefined}
|
getRowId={rowKey ? (p) => String(p.data[rowKey]) : undefined}
|
||||||
pinnedBottomRowData={pinnedBottomRow ? [pinnedBottomRow as T] : undefined}
|
pinnedBottomRowData={pinnedBottomRow ? [pinnedBottomRow as T] : undefined}
|
||||||
|
getRowStyle={(params) => {
|
||||||
|
if (params.node.rowPinned) {
|
||||||
|
return { background: '#fafafa', fontWeight: 500 };
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}}
|
||||||
onCellValueChanged={(e) => {
|
onCellValueChanged={(e) => {
|
||||||
if (onCellChanged && e.data) {
|
if (onCellChanged && e.data) {
|
||||||
onCellChanged(e.data, String(e.colDef.field), e.newValue);
|
onCellChanged(e.data, String(e.colDef.field), e.newValue);
|
||||||
|
|||||||
@@ -6,15 +6,30 @@ interface Props {
|
|||||||
fraction?: number;
|
fraction?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 09_UI_DESIGN.md §5 MoneyText — 양수 #0F6E56(초록), 음수 #E24B4A(빨강), 0 #888(회색).
|
||||||
|
* 천 단위 콤마, tabular-nums 적용.
|
||||||
|
*/
|
||||||
export default function MoneyText({ value, showSign, fraction = 0 }: Props) {
|
export default function MoneyText({ value, showSign, fraction = 0 }: Props) {
|
||||||
if (value === null || value === undefined || value === '') return <span>-</span>;
|
if (value === null || value === undefined || value === '') return <span>-</span>;
|
||||||
const n = typeof value === 'string' ? Number(value) : value;
|
const n = typeof value === 'string' ? Number(value) : value;
|
||||||
if (Number.isNaN(n)) return <span>-</span>;
|
if (Number.isNaN(n)) return <span>-</span>;
|
||||||
|
|
||||||
const formatted = n.toLocaleString(undefined, {
|
const formatted = n.toLocaleString(undefined, {
|
||||||
minimumFractionDigits: fraction,
|
minimumFractionDigits: fraction,
|
||||||
maximumFractionDigits: fraction,
|
maximumFractionDigits: fraction,
|
||||||
});
|
});
|
||||||
const sign = n < 0 ? '' : showSign ? '+' : '';
|
|
||||||
const color = n < 0 ? '#cf1322' : n > 0 && showSign ? '#1677ff' : 'inherit';
|
let color: string;
|
||||||
return <span style={{ color, fontVariantNumeric: 'tabular-nums' }}>{sign}{formatted}</span>;
|
if (n > 0) color = 'var(--ga-money-plus)';
|
||||||
|
else if (n < 0) color = 'var(--ga-money-minus)';
|
||||||
|
else color = 'var(--ga-money-zero)';
|
||||||
|
|
||||||
|
const sign = n < 0 ? '' : showSign && n > 0 ? '+' : '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span style={{ color, fontVariantNumeric: 'tabular-nums' }}>
|
||||||
|
{sign}{formatted}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function SearchForm({ conditions, initialValues, onSearch, onRese
|
|||||||
layout="vertical"
|
layout="vertical"
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
onFinish={handleSearch}
|
onFinish={handleSearch}
|
||||||
style={{ background: '#fff', padding: 16, marginBottom: 16, borderRadius: 4 }}
|
style={{ background: '#fafafa', padding: '12px 16px', marginBottom: 16, borderRadius: 8 }}
|
||||||
>
|
>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
{conditions.map((c) => (
|
{conditions.map((c) => (
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
|
@import './styles/variables.css';
|
||||||
|
|
||||||
html, body, #root { height: 100%; margin: 0; padding: 0; }
|
html, body, #root { height: 100%; margin: 0; padding: 0; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", sans-serif;
|
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo",
|
||||||
|
"Malgun Gothic", "맑은 고딕", system-ui, sans-serif;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
|
font-size: 13px;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 숫자 셀에 등폭 숫자 (정렬 일관) */
|
||||||
|
.ga-tabular { font-variant-numeric: tabular-nums; }
|
||||||
|
|
||||||
|
/* 레이아웃 콘테이너 */
|
||||||
|
.ga-page-container { max-width: var(--ga-page-max-width); margin: 0 auto; }
|
||||||
|
|
||||||
|
/* AG Grid 합계행 강조 */
|
||||||
|
.ag-theme-quartz .ag-row-pinned {
|
||||||
|
background: #fafafa !important;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { Layout, Menu, Dropdown, Badge, Space, Avatar, Button } from 'antd';
|
import { useState } from 'react';
|
||||||
import { useNavigate, Outlet } from 'react-router-dom';
|
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 { useQuery } from '@tanstack/react-query';
|
||||||
import { BellOutlined, UserOutlined, LogoutOutlined } from '@ant-design/icons';
|
import {
|
||||||
|
IconMenu2,
|
||||||
|
IconBell,
|
||||||
|
IconUser,
|
||||||
|
IconLogout,
|
||||||
|
} from '@tabler/icons-react';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { menuApi, MenuResp } from '@/api/menu';
|
import { menuApi, MenuResp } from '@/api/menu';
|
||||||
import { authApi } from '@/api/auth';
|
import { authApi } from '@/api/auth';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
@@ -18,7 +25,10 @@ function toMenuItems(menus: MenuResp[]): any[] {
|
|||||||
|
|
||||||
export default function MainLayout() {
|
export default function MainLayout() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
const [settleMonth, setSettleMonth] = useState(dayjs());
|
||||||
|
|
||||||
const { data: menus = [] } = useQuery({
|
const { data: menus = [] } = useQuery({
|
||||||
queryKey: ['menu', 'my'],
|
queryKey: ['menu', 'my'],
|
||||||
@@ -35,36 +45,80 @@ export default function MainLayout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: '100vh' }}>
|
<Layout style={{ minHeight: '100vh' }}>
|
||||||
<Header style={{ display: 'flex', justifyContent: 'space-between',
|
<Header
|
||||||
background: '#001529', color: '#fff' }}>
|
style={{
|
||||||
<div style={{ fontSize: 18, fontWeight: 600 }}>GA 수수료 정산 솔루션</div>
|
height: 48,
|
||||||
<Space>
|
lineHeight: '48px',
|
||||||
<Badge count={0}>
|
background: '#ffffff',
|
||||||
<Button type="text" icon={<BellOutlined style={{ color: '#fff' }} />} />
|
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>
|
</Badge>
|
||||||
<Dropdown menu={{
|
<Dropdown
|
||||||
items: [
|
menu={{
|
||||||
{ key: 'logout', label: '로그아웃', icon: <LogoutOutlined />, onClick: handleLogout },
|
items: [
|
||||||
],
|
{
|
||||||
}}>
|
key: 'logout',
|
||||||
<Space style={{ color: '#fff', cursor: 'pointer' }}>
|
label: '로그아웃',
|
||||||
<Avatar size="small" icon={<UserOutlined />} />
|
icon: <IconLogout size={14} />,
|
||||||
{auth.userName ?? auth.loginId}
|
onClick: handleLogout,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Space style={{ cursor: 'pointer' }}>
|
||||||
|
<Avatar size="small" icon={<IconUser size={14} />} />
|
||||||
|
<span style={{ fontSize: 13 }}>{auth.userName ?? auth.loginId}</span>
|
||||||
</Space>
|
</Space>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</Space>
|
</Space>
|
||||||
</Header>
|
</Header>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Sider width={240} theme="light">
|
<Sider
|
||||||
|
width={220}
|
||||||
|
collapsedWidth={64}
|
||||||
|
collapsed={collapsed}
|
||||||
|
theme="light"
|
||||||
|
style={{ borderRight: '1px solid #f0f0f0' }}
|
||||||
|
>
|
||||||
<Menu
|
<Menu
|
||||||
mode="inline"
|
mode="inline"
|
||||||
items={toMenuItems(menus)}
|
items={toMenuItems(menus)}
|
||||||
|
selectedKeys={[location.pathname]}
|
||||||
onClick={({ key }) => navigate(String(key))}
|
onClick={({ key }) => navigate(String(key))}
|
||||||
style={{ borderRight: 0, height: '100%' }}
|
style={{ borderRight: 0, height: '100%', paddingTop: 8 }}
|
||||||
/>
|
/>
|
||||||
</Sider>
|
</Sider>
|
||||||
<Content style={{ padding: 24 }}>
|
<Content style={{ padding: 'var(--ga-content-padding)', overflow: 'auto' }}>
|
||||||
<Outlet />
|
<div className="ga-page-container">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ConfigProvider } from 'antd';
|
import { ConfigProvider, ThemeConfig } from 'antd';
|
||||||
import koKR from 'antd/locale/ko_KR';
|
import koKR from 'antd/locale/ko_KR';
|
||||||
import 'dayjs/locale/ko';
|
import 'dayjs/locale/ko';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
@@ -12,9 +12,73 @@ const queryClient = new QueryClient({
|
|||||||
defaultOptions: { queries: { retry: 1, staleTime: 30_000 } },
|
defaultOptions: { queries: { retry: 1, staleTime: 30_000 } },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const theme: ThemeConfig = {
|
||||||
|
token: {
|
||||||
|
// 브랜드
|
||||||
|
colorPrimary: '#1677FF',
|
||||||
|
colorSuccess: '#0F6E56',
|
||||||
|
colorWarning: '#BA7517',
|
||||||
|
colorError: '#E24B4A',
|
||||||
|
colorInfo: '#1677FF',
|
||||||
|
|
||||||
|
// 폰트
|
||||||
|
fontFamily: "'Pretendard', -apple-system, BlinkMacSystemFont, sans-serif",
|
||||||
|
fontSize: 13,
|
||||||
|
fontSizeHeading1: 22,
|
||||||
|
fontSizeHeading2: 18,
|
||||||
|
fontSizeHeading3: 16,
|
||||||
|
|
||||||
|
// 라운딩
|
||||||
|
borderRadius: 6,
|
||||||
|
borderRadiusLG: 8,
|
||||||
|
|
||||||
|
// 간격
|
||||||
|
padding: 16,
|
||||||
|
paddingLG: 24,
|
||||||
|
margin: 16,
|
||||||
|
|
||||||
|
// 배경
|
||||||
|
colorBgContainer: '#ffffff',
|
||||||
|
colorBgLayout: '#f5f5f5',
|
||||||
|
colorBgElevated: '#ffffff',
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Menu: {
|
||||||
|
itemHeight: 40,
|
||||||
|
itemMarginInline: 8,
|
||||||
|
itemBorderRadius: 6,
|
||||||
|
subMenuItemBg: 'transparent',
|
||||||
|
itemSelectedBg: '#e6f1fb',
|
||||||
|
itemSelectedColor: '#185fa5',
|
||||||
|
},
|
||||||
|
Table: {
|
||||||
|
headerBg: '#fafafa',
|
||||||
|
rowHoverBg: '#f5f7fa',
|
||||||
|
cellPaddingBlock: 10,
|
||||||
|
cellPaddingInline: 12,
|
||||||
|
headerSplitColor: '#f0f0f0',
|
||||||
|
},
|
||||||
|
Button: {
|
||||||
|
controlHeight: 32,
|
||||||
|
controlHeightSM: 28,
|
||||||
|
paddingInline: 16,
|
||||||
|
},
|
||||||
|
Input: { controlHeight: 32, paddingInline: 10 },
|
||||||
|
Select: { controlHeight: 32 },
|
||||||
|
Card: { paddingLG: 16 },
|
||||||
|
Layout: {
|
||||||
|
headerHeight: 48,
|
||||||
|
headerBg: '#ffffff',
|
||||||
|
headerPadding: '0 16px',
|
||||||
|
siderBg: '#ffffff',
|
||||||
|
bodyBg: '#f5f5f5',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ConfigProvider locale={koKR}>
|
<ConfigProvider locale={koKR} theme={theme}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SearchForm from '@/components/common/SearchForm';
|
|||||||
import CodeBadge from '@/components/common/CodeBadge';
|
import CodeBadge from '@/components/common/CodeBadge';
|
||||||
import MoneyText from '@/components/common/MoneyText';
|
import MoneyText from '@/components/common/MoneyText';
|
||||||
import PermissionButton from '@/components/common/PermissionButton';
|
import PermissionButton from '@/components/common/PermissionButton';
|
||||||
|
import { defaultPagination } from '@/utils/tablePagination';
|
||||||
import { exceptionApi, ExceptionLedgerResp } from '@/api/exceptionLedger';
|
import { exceptionApi, ExceptionLedgerResp } from '@/api/exceptionLedger';
|
||||||
|
|
||||||
export default function ExceptionLedger() {
|
export default function ExceptionLedger() {
|
||||||
@@ -49,8 +50,8 @@ export default function ExceptionLedger() {
|
|||||||
size="small"
|
size="small"
|
||||||
scroll={{ x: 1400 }}
|
scroll={{ x: 1400 }}
|
||||||
pagination={{
|
pagination={{
|
||||||
|
...defaultPagination,
|
||||||
current: param.pageNum, pageSize: param.pageSize, total: data?.total,
|
current: param.pageNum, pageSize: param.pageSize, total: data?.total,
|
||||||
showSizeChanger: true,
|
|
||||||
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={[
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SearchForm from '@/components/common/SearchForm';
|
|||||||
import MoneyText from '@/components/common/MoneyText';
|
import MoneyText from '@/components/common/MoneyText';
|
||||||
import CodeBadge from '@/components/common/CodeBadge';
|
import CodeBadge from '@/components/common/CodeBadge';
|
||||||
import ExcelExportButton from '@/components/common/ExcelExportButton';
|
import ExcelExportButton from '@/components/common/ExcelExportButton';
|
||||||
|
import { defaultPagination } from '@/utils/tablePagination';
|
||||||
import { ledgerApi, LedgerResp, LedgerSearchParam } from '@/api/ledger';
|
import { ledgerApi, LedgerResp, LedgerSearchParam } from '@/api/ledger';
|
||||||
|
|
||||||
export default function RecruitLedger() {
|
export default function RecruitLedger() {
|
||||||
@@ -41,7 +42,8 @@ export default function RecruitLedger() {
|
|||||||
size="small"
|
size="small"
|
||||||
scroll={{ x: 1500 }}
|
scroll={{ x: 1500 }}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: param.pageNum, pageSize: param.pageSize, total: data?.total, showSizeChanger: true,
|
...defaultPagination,
|
||||||
|
current: param.pageNum, pageSize: param.pageSize, total: data?.total,
|
||||||
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={[
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import CodeBadge from '@/components/common/CodeBadge';
|
|||||||
import MoneyText from '@/components/common/MoneyText';
|
import MoneyText from '@/components/common/MoneyText';
|
||||||
import PermissionButton from '@/components/common/PermissionButton';
|
import PermissionButton from '@/components/common/PermissionButton';
|
||||||
import ExcelExportButton from '@/components/common/ExcelExportButton';
|
import ExcelExportButton from '@/components/common/ExcelExportButton';
|
||||||
|
import { defaultPagination } from '@/utils/tablePagination';
|
||||||
import { agentApi, AgentResp, AgentSearchParam } from '@/api/agent';
|
import { agentApi, AgentResp, AgentSearchParam } from '@/api/agent';
|
||||||
|
|
||||||
export default function AgentList() {
|
export default function AgentList() {
|
||||||
@@ -46,10 +47,10 @@ export default function AgentList() {
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
dataSource={data?.list}
|
dataSource={data?.list}
|
||||||
pagination={{
|
pagination={{
|
||||||
|
...defaultPagination,
|
||||||
current: param.pageNum,
|
current: param.pageNum,
|
||||||
pageSize: param.pageSize,
|
pageSize: param.pageSize,
|
||||||
total: data?.total,
|
total: data?.total,
|
||||||
showSizeChanger: true,
|
|
||||||
onChange: (page, size) => setParam({ ...param, pageNum: page, pageSize: size }),
|
onChange: (page, size) => setParam({ ...param, pageNum: page, pageSize: size }),
|
||||||
}}
|
}}
|
||||||
onRow={(r) => ({ onClick: () => navigate(`/org/agents/${r.agentId}`) })}
|
onRow={(r) => ({ onClick: () => navigate(`/org/agents/${r.agentId}`) })}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import PageContainer from '@/components/common/PageContainer';
|
|||||||
import SearchForm from '@/components/common/SearchForm';
|
import SearchForm from '@/components/common/SearchForm';
|
||||||
import CodeBadge from '@/components/common/CodeBadge';
|
import CodeBadge from '@/components/common/CodeBadge';
|
||||||
import MoneyText from '@/components/common/MoneyText';
|
import MoneyText from '@/components/common/MoneyText';
|
||||||
|
import { defaultPagination } from '@/utils/tablePagination';
|
||||||
import { contractApi, ContractResp, ContractSearchParam } from '@/api/contract';
|
import { contractApi, ContractResp, ContractSearchParam } from '@/api/contract';
|
||||||
|
|
||||||
export default function ContractList() {
|
export default function ContractList() {
|
||||||
@@ -33,7 +34,8 @@ export default function ContractList() {
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
dataSource={data?.list}
|
dataSource={data?.list}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: param.pageNum, pageSize: param.pageSize, total: data?.total, showSizeChanger: true,
|
...defaultPagination,
|
||||||
|
current: param.pageNum, pageSize: param.pageSize, total: data?.total,
|
||||||
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={[
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import PageContainer from '@/components/common/PageContainer';
|
|||||||
import SearchForm from '@/components/common/SearchForm';
|
import SearchForm from '@/components/common/SearchForm';
|
||||||
import CodeBadge from '@/components/common/CodeBadge';
|
import CodeBadge from '@/components/common/CodeBadge';
|
||||||
import MoneyText from '@/components/common/MoneyText';
|
import MoneyText from '@/components/common/MoneyText';
|
||||||
|
import { defaultPagination } from '@/utils/tablePagination';
|
||||||
import { paymentApi, PaymentResp } from '@/api/payment';
|
import { paymentApi, PaymentResp } from '@/api/payment';
|
||||||
|
|
||||||
export default function PaymentList() {
|
export default function PaymentList() {
|
||||||
@@ -34,8 +35,8 @@ export default function PaymentList() {
|
|||||||
dataSource={data?.list}
|
dataSource={data?.list}
|
||||||
size="small"
|
size="small"
|
||||||
pagination={{
|
pagination={{
|
||||||
|
...defaultPagination,
|
||||||
current: param.pageNum, pageSize: param.pageSize, total: data?.total,
|
current: param.pageNum, pageSize: param.pageSize, total: data?.total,
|
||||||
showSizeChanger: true,
|
|
||||||
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={[
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SearchForm from '@/components/common/SearchForm';
|
|||||||
import CodeBadge from '@/components/common/CodeBadge';
|
import CodeBadge from '@/components/common/CodeBadge';
|
||||||
import MoneyText from '@/components/common/MoneyText';
|
import MoneyText from '@/components/common/MoneyText';
|
||||||
import PermissionButton from '@/components/common/PermissionButton';
|
import PermissionButton from '@/components/common/PermissionButton';
|
||||||
|
import { defaultPagination } from '@/utils/tablePagination';
|
||||||
import { settleApi, SettleResp, SettleSearchParam } from '@/api/settle';
|
import { settleApi, SettleResp, SettleSearchParam } from '@/api/settle';
|
||||||
|
|
||||||
export default function SettleList() {
|
export default function SettleList() {
|
||||||
@@ -67,7 +68,8 @@ export default function SettleList() {
|
|||||||
size="small"
|
size="small"
|
||||||
scroll={{ x: 1600 }}
|
scroll={{ x: 1600 }}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: param.pageNum, pageSize: param.pageSize, total: data?.total, showSizeChanger: true,
|
...defaultPagination,
|
||||||
|
current: param.pageNum, pageSize: param.pageSize, total: data?.total,
|
||||||
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={[
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import PageContainer from '@/components/common/PageContainer';
|
|||||||
import SearchForm from '@/components/common/SearchForm';
|
import SearchForm from '@/components/common/SearchForm';
|
||||||
import CodeBadge from '@/components/common/CodeBadge';
|
import CodeBadge from '@/components/common/CodeBadge';
|
||||||
import PermissionButton from '@/components/common/PermissionButton';
|
import PermissionButton from '@/components/common/PermissionButton';
|
||||||
|
import { defaultPagination } from '@/utils/tablePagination';
|
||||||
import { userApi, UserResp } from '@/api/user';
|
import { userApi, UserResp } from '@/api/user';
|
||||||
|
|
||||||
export default function UserList() {
|
export default function UserList() {
|
||||||
@@ -49,7 +50,8 @@ export default function UserList() {
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
dataSource={data?.list}
|
dataSource={data?.list}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: param.pageNum, pageSize: param.pageSize, total: data?.total, showSizeChanger: true,
|
...defaultPagination,
|
||||||
|
current: param.pageNum, pageSize: param.pageSize, total: data?.total,
|
||||||
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }),
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={[
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
:root {
|
||||||
|
/* 브랜드 */
|
||||||
|
--ga-primary: #1677FF;
|
||||||
|
--ga-primary-light: #E6F1FB;
|
||||||
|
--ga-primary-dark: #0C447C;
|
||||||
|
|
||||||
|
/* 상태 색상 (CodeBadge 등) */
|
||||||
|
--ga-success: #0F6E56;
|
||||||
|
--ga-success-bg: #E1F5EE;
|
||||||
|
--ga-warning: #BA7517;
|
||||||
|
--ga-warning-bg: #FAEEDA;
|
||||||
|
--ga-danger: #E24B4A;
|
||||||
|
--ga-danger-bg: #FCEBEB;
|
||||||
|
--ga-info: #185FA5;
|
||||||
|
--ga-info-bg: #E6F1FB;
|
||||||
|
|
||||||
|
/* 금액 */
|
||||||
|
--ga-money-plus: #0F6E56;
|
||||||
|
--ga-money-minus: #E24B4A;
|
||||||
|
--ga-money-zero: #888888;
|
||||||
|
|
||||||
|
/* 레이아웃 */
|
||||||
|
--ga-sider-width: 220px;
|
||||||
|
--ga-sider-collapsed-width: 64px;
|
||||||
|
--ga-header-height: 48px;
|
||||||
|
--ga-content-padding: 20px;
|
||||||
|
--ga-page-max-width: 1400px;
|
||||||
|
|
||||||
|
/* 간격 단위 */
|
||||||
|
--ga-spacing-xs: 4px;
|
||||||
|
--ga-spacing-sm: 8px;
|
||||||
|
--ga-spacing-md: 12px;
|
||||||
|
--ga-spacing-lg: 16px;
|
||||||
|
--ga-spacing-xl: 24px;
|
||||||
|
|
||||||
|
/* 그림자 (최소한만 사용) */
|
||||||
|
--ga-shadow-card: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||||
|
--ga-shadow-dropdown: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { TablePaginationConfig } from 'antd';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 09_UI_DESIGN.md §5 DataTable — 표준 페이지네이션 설정.
|
||||||
|
* 사용: pagination={{ ...defaultPagination, current, pageSize, total, onChange }}
|
||||||
|
*/
|
||||||
|
export const defaultPagination: TablePaginationConfig = {
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: ['20', '50', '100'],
|
||||||
|
defaultPageSize: 20,
|
||||||
|
showTotal: (total) => `총 ${total.toLocaleString()}건`,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user