From 24fc187e2e75986159496c33f1cf2e84001fa08b Mon Sep 17 00:00:00 2001 From: GA Pro Date: Sun, 10 May 2026 21:34:17 +0900 Subject: [PATCH] =?UTF-8?q?feat(frontend):=2009=5FUI=5FDESIGN=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 테마 / 폰트 / 아이콘: - 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) --- .gitignore | 4 + ga-frontend/index.html | 1 + ga-frontend/package-lock.json | 27 ++++++ ga-frontend/package.json | 1 + .../src/components/common/CodeBadge.tsx | 28 +++++- .../src/components/common/DataGrid.tsx | 24 +++++ .../src/components/common/MoneyText.tsx | 21 +++- .../src/components/common/SearchForm.tsx | 2 +- ga-frontend/src/index.css | 21 +++- ga-frontend/src/layouts/MainLayout.tsx | 96 +++++++++++++++---- ga-frontend/src/main.tsx | 68 ++++++++++++- .../src/pages/ledger/ExceptionLedger.tsx | 3 +- .../src/pages/ledger/RecruitLedger.tsx | 4 +- ga-frontend/src/pages/org/AgentList.tsx | 3 +- .../src/pages/product/ContractList.tsx | 4 +- ga-frontend/src/pages/settle/PaymentList.tsx | 3 +- ga-frontend/src/pages/settle/SettleList.tsx | 4 +- ga-frontend/src/pages/system/UserList.tsx | 4 +- ga-frontend/src/styles/variables.css | 39 ++++++++ ga-frontend/src/utils/tablePagination.ts | 12 +++ 20 files changed, 330 insertions(+), 39 deletions(-) create mode 100644 ga-frontend/src/styles/variables.css create mode 100644 ga-frontend/src/utils/tablePagination.ts diff --git a/.gitignore b/.gitignore index c01513c..bc360a1 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,7 @@ target/ # Claude Code 개인 설정 (Agent Teams 등) .claude/ + +# vite/tsc 빌드 산출물 +ga-frontend/tsconfig.tsbuildinfo +ga-frontend/dist/ diff --git a/ga-frontend/index.html b/ga-frontend/index.html index 3792c08..66d7027 100644 --- a/ga-frontend/index.html +++ b/ga-frontend/index.html @@ -4,6 +4,7 @@ + GA 수수료 정산 솔루션 diff --git a/ga-frontend/package-lock.json b/ga-frontend/package-lock.json index 633f57a..0849fba 100644 --- a/ga-frontend/package-lock.json +++ b/ga-frontend/package-lock.json @@ -12,6 +12,7 @@ "@ag-grid-community/core": "^31.3.2", "@ag-grid-community/react": "^31.3.2", "@ag-grid-community/styles": "^31.3.2", + "@tabler/icons-react": "^3.5.0", "@tanstack/react-query": "^5.40.0", "antd": "^5.18.0", "axios": "^1.7.2", @@ -1462,6 +1463,32 @@ "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": { "version": "5.100.9", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.9.tgz", diff --git a/ga-frontend/package.json b/ga-frontend/package.json index dc5828a..975674e 100644 --- a/ga-frontend/package.json +++ b/ga-frontend/package.json @@ -14,6 +14,7 @@ "@ag-grid-community/core": "^31.3.2", "@ag-grid-community/react": "^31.3.2", "@ag-grid-community/styles": "^31.3.2", + "@tabler/icons-react": "^3.5.0", "@tanstack/react-query": "^5.40.0", "antd": "^5.18.0", "axios": "^1.7.2", diff --git a/ga-frontend/src/components/common/CodeBadge.tsx b/ga-frontend/src/components/common/CodeBadge.tsx index 5df2249..23aecfe 100644 --- a/ga-frontend/src/components/common/CodeBadge.tsx +++ b/ga-frontend/src/components/common/CodeBadge.tsx @@ -6,11 +6,31 @@ interface Props { 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 = { - ACTIVE: 'green', CONFIRMED: 'green', APPROVED: 'green', PAID: 'green', MATCHED: 'green', DONE: 'green', - PENDING: 'blue', CALCULATED: 'blue', SENT: 'blue', - HOLD: 'red', SUSPEND: 'red', REJECTED: 'red', FAIL: 'red', LAPSE: 'red', DIFF: 'red', LOCKED: 'red', - DRAFT: 'default', NEW: 'default', LEAVE: 'default', RETIRED: 'default', + // 초록 — 긍정적 완료 + CONFIRMED: 'success', APPROVED: 'success', ACTIVE: 'success', + PAID: 'success', COMPLETED: 'success', MATCH: 'success', + 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) { diff --git a/ga-frontend/src/components/common/DataGrid.tsx b/ga-frontend/src/components/common/DataGrid.tsx index 8281cf4..535e643 100644 --- a/ga-frontend/src/components/common/DataGrid.tsx +++ b/ga-frontend/src/components/common/DataGrid.tsx @@ -8,6 +8,21 @@ import '@ag-grid-community/styles/ag-theme-quartz.css'; ModuleRegistry.registerModules([ClientSideRowModelModule]); +/** + * AG Grid 한국어 로케일 (자주 쓰이는 항목 위주). + * 전체 키 목록은 AG Grid 공식 문서 참조. + */ +const AG_GRID_LOCALE_KO: Record = { + 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 { rows: T[] | undefined; columns: ColDef[]; @@ -65,9 +80,18 @@ export default function DataGrid>({ columnDefs={columns} defaultColDef={defaultColDef} animateRows + rowHeight={36} + headerHeight={38} + localeText={AG_GRID_LOCALE_KO} rowSelection="single" getRowId={rowKey ? (p) => String(p.data[rowKey]) : undefined} pinnedBottomRowData={pinnedBottomRow ? [pinnedBottomRow as T] : undefined} + getRowStyle={(params) => { + if (params.node.rowPinned) { + return { background: '#fafafa', fontWeight: 500 }; + } + return undefined; + }} onCellValueChanged={(e) => { if (onCellChanged && e.data) { onCellChanged(e.data, String(e.colDef.field), e.newValue); diff --git a/ga-frontend/src/components/common/MoneyText.tsx b/ga-frontend/src/components/common/MoneyText.tsx index bd1051d..420549b 100644 --- a/ga-frontend/src/components/common/MoneyText.tsx +++ b/ga-frontend/src/components/common/MoneyText.tsx @@ -6,15 +6,30 @@ interface Props { fraction?: number; } +/** + * 09_UI_DESIGN.md §5 MoneyText — 양수 #0F6E56(초록), 음수 #E24B4A(빨강), 0 #888(회색). + * 천 단위 콤마, tabular-nums 적용. + */ export default function MoneyText({ value, showSign, fraction = 0 }: Props) { if (value === null || value === undefined || value === '') return -; const n = typeof value === 'string' ? Number(value) : value; if (Number.isNaN(n)) return -; + const formatted = n.toLocaleString(undefined, { minimumFractionDigits: fraction, maximumFractionDigits: fraction, }); - const sign = n < 0 ? '' : showSign ? '+' : ''; - const color = n < 0 ? '#cf1322' : n > 0 && showSign ? '#1677ff' : 'inherit'; - return {sign}{formatted}; + + let color: string; + 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 ( + + {sign}{formatted} + + ); } diff --git a/ga-frontend/src/components/common/SearchForm.tsx b/ga-frontend/src/components/common/SearchForm.tsx index 90e64fa..15c2cf4 100644 --- a/ga-frontend/src/components/common/SearchForm.tsx +++ b/ga-frontend/src/components/common/SearchForm.tsx @@ -55,7 +55,7 @@ export default function SearchForm({ conditions, initialValues, onSearch, onRese layout="vertical" initialValues={initialValues} onFinish={handleSearch} - style={{ background: '#fff', padding: 16, marginBottom: 16, borderRadius: 4 }} + style={{ background: '#fafafa', padding: '12px 16px', marginBottom: 16, borderRadius: 8 }} > {conditions.map((c) => ( diff --git a/ga-frontend/src/index.css b/ga-frontend/src/index.css index 5bf0e79..96be04b 100644 --- a/ga-frontend/src/index.css +++ b/ga-frontend/src/index.css @@ -1,5 +1,24 @@ +@import './styles/variables.css'; + html, body, #root { height: 100%; margin: 0; padding: 0; } + 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; + 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; } diff --git a/ga-frontend/src/layouts/MainLayout.tsx b/ga-frontend/src/layouts/MainLayout.tsx index aed83aa..34669c0 100644 --- a/ga-frontend/src/layouts/MainLayout.tsx +++ b/ga-frontend/src/layouts/MainLayout.tsx @@ -1,7 +1,14 @@ -import { Layout, Menu, Dropdown, Badge, Space, Avatar, Button } from 'antd'; -import { useNavigate, Outlet } from 'react-router-dom'; +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 { 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 { authApi } from '@/api/auth'; import { useAuthStore } from '@/stores/auth'; @@ -18,7 +25,10 @@ function toMenuItems(menus: MenuResp[]): any[] { 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'], @@ -35,36 +45,80 @@ export default function MainLayout() { return ( -
-
GA 수수료 정산 솔루션
- - -
- + navigate(String(key))} - style={{ borderRight: 0, height: '100%' }} + style={{ borderRight: 0, height: '100%', paddingTop: 8 }} /> - - + +
+ +
diff --git a/ga-frontend/src/main.tsx b/ga-frontend/src/main.tsx index 2b29369..f0566ee 100644 --- a/ga-frontend/src/main.tsx +++ b/ga-frontend/src/main.tsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ConfigProvider } from 'antd'; +import { ConfigProvider, ThemeConfig } from 'antd'; import koKR from 'antd/locale/ko_KR'; import 'dayjs/locale/ko'; import App from './App'; @@ -12,9 +12,73 @@ const queryClient = new QueryClient({ 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( - + diff --git a/ga-frontend/src/pages/ledger/ExceptionLedger.tsx b/ga-frontend/src/pages/ledger/ExceptionLedger.tsx index ae228f0..2431d2c 100644 --- a/ga-frontend/src/pages/ledger/ExceptionLedger.tsx +++ b/ga-frontend/src/pages/ledger/ExceptionLedger.tsx @@ -6,6 +6,7 @@ import SearchForm from '@/components/common/SearchForm'; import CodeBadge from '@/components/common/CodeBadge'; import MoneyText from '@/components/common/MoneyText'; import PermissionButton from '@/components/common/PermissionButton'; +import { defaultPagination } from '@/utils/tablePagination'; import { exceptionApi, ExceptionLedgerResp } from '@/api/exceptionLedger'; export default function ExceptionLedger() { @@ -49,8 +50,8 @@ export default function ExceptionLedger() { size="small" scroll={{ x: 1400 }} pagination={{ + ...defaultPagination, current: param.pageNum, pageSize: param.pageSize, total: data?.total, - showSizeChanger: true, onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }), }} columns={[ diff --git a/ga-frontend/src/pages/ledger/RecruitLedger.tsx b/ga-frontend/src/pages/ledger/RecruitLedger.tsx index 5cd1096..d59cdc6 100644 --- a/ga-frontend/src/pages/ledger/RecruitLedger.tsx +++ b/ga-frontend/src/pages/ledger/RecruitLedger.tsx @@ -6,6 +6,7 @@ import SearchForm from '@/components/common/SearchForm'; import MoneyText from '@/components/common/MoneyText'; import CodeBadge from '@/components/common/CodeBadge'; import ExcelExportButton from '@/components/common/ExcelExportButton'; +import { defaultPagination } from '@/utils/tablePagination'; import { ledgerApi, LedgerResp, LedgerSearchParam } from '@/api/ledger'; export default function RecruitLedger() { @@ -41,7 +42,8 @@ export default function RecruitLedger() { size="small" scroll={{ x: 1500 }} 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 }), }} columns={[ diff --git a/ga-frontend/src/pages/org/AgentList.tsx b/ga-frontend/src/pages/org/AgentList.tsx index 8874168..03862f4 100644 --- a/ga-frontend/src/pages/org/AgentList.tsx +++ b/ga-frontend/src/pages/org/AgentList.tsx @@ -8,6 +8,7 @@ import CodeBadge from '@/components/common/CodeBadge'; import MoneyText from '@/components/common/MoneyText'; import PermissionButton from '@/components/common/PermissionButton'; import ExcelExportButton from '@/components/common/ExcelExportButton'; +import { defaultPagination } from '@/utils/tablePagination'; import { agentApi, AgentResp, AgentSearchParam } from '@/api/agent'; export default function AgentList() { @@ -46,10 +47,10 @@ export default function AgentList() { loading={isLoading} dataSource={data?.list} pagination={{ + ...defaultPagination, current: param.pageNum, pageSize: param.pageSize, total: data?.total, - showSizeChanger: true, onChange: (page, size) => setParam({ ...param, pageNum: page, pageSize: size }), }} onRow={(r) => ({ onClick: () => navigate(`/org/agents/${r.agentId}`) })} diff --git a/ga-frontend/src/pages/product/ContractList.tsx b/ga-frontend/src/pages/product/ContractList.tsx index b319980..de6e3d0 100644 --- a/ga-frontend/src/pages/product/ContractList.tsx +++ b/ga-frontend/src/pages/product/ContractList.tsx @@ -5,6 +5,7 @@ import PageContainer from '@/components/common/PageContainer'; import SearchForm from '@/components/common/SearchForm'; import CodeBadge from '@/components/common/CodeBadge'; import MoneyText from '@/components/common/MoneyText'; +import { defaultPagination } from '@/utils/tablePagination'; import { contractApi, ContractResp, ContractSearchParam } from '@/api/contract'; export default function ContractList() { @@ -33,7 +34,8 @@ export default function ContractList() { loading={isLoading} dataSource={data?.list} 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 }), }} columns={[ diff --git a/ga-frontend/src/pages/settle/PaymentList.tsx b/ga-frontend/src/pages/settle/PaymentList.tsx index 110298a..2ad2ae9 100644 --- a/ga-frontend/src/pages/settle/PaymentList.tsx +++ b/ga-frontend/src/pages/settle/PaymentList.tsx @@ -5,6 +5,7 @@ import PageContainer from '@/components/common/PageContainer'; import SearchForm from '@/components/common/SearchForm'; import CodeBadge from '@/components/common/CodeBadge'; import MoneyText from '@/components/common/MoneyText'; +import { defaultPagination } from '@/utils/tablePagination'; import { paymentApi, PaymentResp } from '@/api/payment'; export default function PaymentList() { @@ -34,8 +35,8 @@ export default function PaymentList() { dataSource={data?.list} size="small" pagination={{ + ...defaultPagination, current: param.pageNum, pageSize: param.pageSize, total: data?.total, - showSizeChanger: true, onChange: (p, s) => setParam({ ...param, pageNum: p, pageSize: s }), }} columns={[ diff --git a/ga-frontend/src/pages/settle/SettleList.tsx b/ga-frontend/src/pages/settle/SettleList.tsx index f373ece..3639293 100644 --- a/ga-frontend/src/pages/settle/SettleList.tsx +++ b/ga-frontend/src/pages/settle/SettleList.tsx @@ -6,6 +6,7 @@ import SearchForm from '@/components/common/SearchForm'; import CodeBadge from '@/components/common/CodeBadge'; import MoneyText from '@/components/common/MoneyText'; import PermissionButton from '@/components/common/PermissionButton'; +import { defaultPagination } from '@/utils/tablePagination'; import { settleApi, SettleResp, SettleSearchParam } from '@/api/settle'; export default function SettleList() { @@ -67,7 +68,8 @@ export default function SettleList() { size="small" scroll={{ x: 1600 }} 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 }), }} columns={[ diff --git a/ga-frontend/src/pages/system/UserList.tsx b/ga-frontend/src/pages/system/UserList.tsx index f94cc50..5cd692b 100644 --- a/ga-frontend/src/pages/system/UserList.tsx +++ b/ga-frontend/src/pages/system/UserList.tsx @@ -5,6 +5,7 @@ import PageContainer from '@/components/common/PageContainer'; import SearchForm from '@/components/common/SearchForm'; import CodeBadge from '@/components/common/CodeBadge'; import PermissionButton from '@/components/common/PermissionButton'; +import { defaultPagination } from '@/utils/tablePagination'; import { userApi, UserResp } from '@/api/user'; export default function UserList() { @@ -49,7 +50,8 @@ export default function UserList() { loading={isLoading} dataSource={data?.list} 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 }), }} columns={[ diff --git a/ga-frontend/src/styles/variables.css b/ga-frontend/src/styles/variables.css new file mode 100644 index 0000000..978888a --- /dev/null +++ b/ga-frontend/src/styles/variables.css @@ -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); +} diff --git a/ga-frontend/src/utils/tablePagination.ts b/ga-frontend/src/utils/tablePagination.ts new file mode 100644 index 0000000..1b8c185 --- /dev/null +++ b/ga-frontend/src/utils/tablePagination.ts @@ -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()}건`, +};