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:
@@ -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<string, string> = {
|
||||
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) {
|
||||
|
||||
@@ -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<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> {
|
||||
rows: T[] | undefined;
|
||||
columns: ColDef<T>[];
|
||||
@@ -65,9 +80,18 @@ export default function DataGrid<T extends Record<string, unknown>>({
|
||||
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);
|
||||
|
||||
@@ -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 <span>-</span>;
|
||||
const n = typeof value === 'string' ? Number(value) : value;
|
||||
if (Number.isNaN(n)) return <span>-</span>;
|
||||
|
||||
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 <span style={{ color, fontVariantNumeric: 'tabular-nums' }}>{sign}{formatted}</span>;
|
||||
|
||||
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 (
|
||||
<span style={{ color, fontVariantNumeric: 'tabular-nums' }}>
|
||||
{sign}{formatted}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 }}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
{conditions.map((c) => (
|
||||
|
||||
Reference in New Issue
Block a user