# UI 디자인 시스템 (UI_DESIGN.md)

> GA 수수료 정산 솔루션의 UI 표준. 모든 화면은 이 규칙을 따른다.
> 기술: React 18 + Ant Design 5.x + AG Grid Community
> 신입 개발자가 복붙으로 일관된 UI를 만들 수 있도록 설계.

---

## 1. Ant Design 테마 커스터마이징

### ConfigProvider 설정 (App.tsx)
```tsx
import { ConfigProvider } from 'antd';
import koKR from 'antd/locale/ko_KR';

const theme = {
  token: {
    // 브랜드 컬러
    colorPrimary: '#1677FF',
    colorSuccess: '#0F6E56',
    colorWarning: '#BA7517',
    colorError: '#E24B4A',
    colorInfo: '#1677FF',

    // 폰트
    fontFamily: "'Pretendard', -apple-system, 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',

    // 테이블
    tablePaddingHorizontal: 12,
    tablePaddingVertical: 10,
    tableHeaderBg: '#fafafa',
    tableRowHoverBg: '#f5f7fa',
    tableBorderColor: '#f0f0f0',
  },
  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,
    },
  },
};

<ConfigProvider theme={theme} locale={koKR}>
  <App />
</ConfigProvider>
```

### 폰트 설치 (index.html)
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css" />
```

### 아이콘: Tabler Icons
```bash
pnpm add @tabler/icons-react
```
```tsx
import { IconUsers, IconCalculator, IconDownload } from '@tabler/icons-react';
<IconUsers size={16} />
```

---

## 2. 글로벌 CSS 변수 (styles/variables.css)

```css
: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-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);
}
```

---

## 3. 레이아웃 규칙

### MainLayout 구조
```
┌─────────────────────────────────────────────┐
│ Header (48px)                                │
│  [☰] GA 정산시스템    [2026-05 ▼] [🔔3] [관리자]│
├──────┬──────────────────────────────────────┤
│Sider │ Content                               │
│220px │  Breadcrumb                           │
│      │  ┌─────────────────────────────────┐ │
│ 메뉴  │  │ 페이지 내용                      │ │
│      │  │                                 │ │
│      │  └─────────────────────────────────┘ │
└──────┴──────────────────────────────────────┘
```

### Header
- 높이: 48px, 배경: 흰색, 하단 border
- 왼쪽: 햄버거 메뉴(접기/펼치기) + 로고 "GA 정산시스템"
- 오른쪽: 정산월 MonthPicker + 알림벨(미읽음 숫자뱃지) + 사용자 Dropdown

### Sider (사이드 메뉴)
- 너비: 220px (접으면 64px, 아이콘만)
- 배경: 흰색
- 메뉴 아이템: 높이 40px, 좌측 아이콘 16px + 텍스트 13px
- 활성 메뉴: 배경 #E6F1FB, 텍스트 #185FA5, font-weight 500
- 서브메뉴: 좌측 30px 인덴트
- 메뉴 데이터: API에서 동적 로드 (GET /api/menus/my)

### Content
- Breadcrumb: 상단 (홈 > 조직관리 > 설계사목록)
- 패딩: 20px
- 최대 너비: 1400px (중앙 정렬)

---

## 4. 페이지 표준 레이아웃 (3가지)

### 타입 A: 목록 페이지 (가장 많이 사용)
```
┌──── 검색 폼 (SearchForm) ─────────────────┐
│ [설계사명 ___] [상태 ▼전체] [기간 ___~___]  [조회] [초기화] │
└────────────────────────────────────────────┘
┌──── 버튼 영역 ─────────────────────────────┐
│                        [+ 등록] [⬇ 엑셀]  │
└────────────────────────────────────────────┘
┌──── 데이터 테이블 ─────────────────────────┐
│ □ 설계사명   소속      직급    수수료합계 상태│
│ □ 김영수    서울1팀   수석    12,450,000  위촉│
│ □ 이미영    경기2팀   설계사   8,320,000  위촉│
├────────────────────────────────────────────┤
│ 총 1,234건                  ‹ 1 2 3 ... ›  │
└────────────────────────────────────────────┘
```
- 검색 폼: 회색 배경(#fafafa), border-radius 8px, padding 12px 16px
- 검색 조건: 한 줄에 3~4개, 버튼은 우측
- 버튼 영역: 우측 정렬, 테이블 바로 위 margin-bottom 12px
- 등록 버튼: Primary(파란색), 엑셀: Default(흰색)
- 테이블: antd Table 또는 AG Grid

### 타입 B: 상세 페이지
```
┌──── 기본 정보 카드 ─────────────────────────┐
│ 설계사명: 김영수     소속: 서울1팀   상태: [위촉] │
│ 전화번호: 010-****-5678  이메일: kim@ga.com    │
│                              [수정] [삭제]   │
└─────────────────────────────────────────────┘
┌──── 탭 영역 ────────────────────────────────┐
│ [기본정보] [실적현황] [소속이력] [변경이력]      │
├─────────────────────────────────────────────┤
│ 탭 내용...                                   │
└─────────────────────────────────────────────┘
```
- 상단 카드: 흰색 배경, border, border-radius 8px
- 탭: antd Tabs, 카드 안에 포함
- 버튼: 카드 우상단

### 타입 C: 대시보드
```
┌──── 지표 카드 (4열) ───────────────────────┐
│ [모집 2.4억] [유지 8,200만] [환수 1,350만] [순 3.1억]│
└────────────────────────────────────────────┘
┌─── 차트 (2:1) ──────┬─── 현황 ────────────┐
│ 월별 추이 (recharts) │ 배치 진행 상태        │
│ ▆▆▆▇█               │ ● 수신 완료          │
│                     │ ● 계산 진행중...      │
└─────────────────────┴──────────────────────┘
┌──── 알림/오류 ──────────────────────────────┐
│ [대사불일치] 삼성생명 5월 3건 차이  10분 전     │
│ [파싱에러] 한화생명 12행 형식 오류   1시간 전   │
└────────────────────────────────────────────┘
```
- 지표 카드: 배경 #fafafa, border-radius 8px, padding 12px
- 숫자: 20~22px, weight 500
- 증감: ▲ 초록(양수), ▼ 빨강(음수)
- 차트: recharts, 높이 200~240px

---

## 5. 공통 컴포넌트 스타일 규칙

### CodeBadge (상태 뱃지)
```tsx
// 상태값 → 색상 자동 매핑
const COLOR_MAP = {
  // 초록 (긍정적 완료 상태)
  CONFIRMED: 'success', APPROVED: 'success', ACTIVE: 'success',
  PAID: 'success', COMPLETED: 'success', MATCH: 'success',

  // 파랑 (진행 중)
  CALCULATED: 'info', PENDING: 'info', PROCESSING: 'info',
  REVIEWED: 'info', NEW: 'info',

  // 주황 (주의)
  HOLD: 'warning', SUSPEND: 'warning', MISMATCH: 'warning',
  PARTIAL: 'warning',

  // 빨강 (부정적)
  REJECTED: 'danger', LEAVE: 'danger', CANCEL: 'danger',
  FAILED: 'danger', LAPSED: 'danger',

  // 회색 (기본)
  DRAFT: 'default', READY: 'default',
};

// 렌더링
<Tag color={COLOR_MAP[value] || 'default'}>{codeName}</Tag>
```

### MoneyText (금액 표시)
```tsx
// 양수: 초록, 음수: 빨강, 0: 회색
// 항상 천 단위 콤마, 정수 표시
<span style={{ color: amount > 0 ? '#0F6E56' : amount < 0 ? '#E24B4A' : '#888' }}>
  {amount >= 0 ? '' : '-'}{Math.abs(amount).toLocaleString()}
</span>
```

### SearchForm (검색 폼)
```tsx
// 스타일 규칙
// 배경: #fafafa, border-radius: 8px, padding: 12px 16px
// 조건: Row + Col (span 기준)
//   - 텍스트 입력: span=6 (4개/행)
//   - Select: span=4 (6개/행까지)
//   - DateRange: span=8
//   - 버튼: 우측 정렬

<div style={{
  background: '#fafafa',
  borderRadius: 8,
  padding: '12px 16px',
  marginBottom: 16,
}}>
  <Row gutter={[12, 12]} align="bottom">
    <Col span={6}>
      <div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>설계사명</div>
      <Input placeholder="이름 검색" />
    </Col>
    <Col span={4}>
      <div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>상태</div>
      <CodeSelect groupCode="AGENT_STATUS" allowClear placeholder="전체" />
    </Col>
    <Col span={4} style={{ marginLeft: 'auto' }}>
      <Space>
        <Button type="primary" icon={<IconSearch size={14}/>}>조회</Button>
        <Button type="link">초기화</Button>
      </Space>
    </Col>
  </Row>
</div>
```

### DataTable (목록 테이블)
```tsx
// 스타일 규칙
// 행 높이: 42px (compact)
// 헤더: 배경 #fafafa, font-weight 500, font-size 12px
// 본문: font-size 13px
// hover: 배경 #f5f7fa
// 금액 컬럼: 우측 정렬, MoneyText
// 상태 컬럼: 중앙 정렬, CodeBadge
// 이름 컬럼: font-weight 500, 클릭 시 상세 이동
// 페이지네이션: 우측, "총 N건" 좌측

<Table
  size="small"
  rowSelection={{ type: 'checkbox' }}
  pagination={{
    showTotal: (total) => `총 ${total.toLocaleString()}건`,
    showSizeChanger: true,
    pageSizeOptions: [20, 50, 100],
    defaultPageSize: 20,
  }}
/>
```

### DataGrid (AG Grid 래퍼)
```tsx
// AG Grid 공통 설정
const defaultColDef = {
  sortable: true,
  resizable: true,
  filter: true,
  minWidth: 80,
};

const gridOptions = {
  rowHeight: 36,
  headerHeight: 38,
  // 한글 로케일
  localeText: AG_GRID_LOCALE_KO,
  // 합계행
  pinnedBottomRowData: [summaryRow],
  // 셀 스타일
  getRowStyle: (params) => {
    if (params.node.rowPinned) return { background: '#fafafa', fontWeight: 500 };
  },
};

// AG Grid 사용 화면: 원장관리, 정산결과, 수수료율편집, 대사현황
// 나머지는 Ant Design Table
```

---

## 6. 등록/수정 폼 규칙

```tsx
// 폼 레이아웃 규칙
// 2열 배치 (Row > Col span=12)
// 레이블: 12px, color #999, 필수 표시 빨간 *
// 입력: height 32px
// Select: 공통코드는 CodeSelect 사용
// 하단 버튼: 우측 정렬 [취소] [저장]

<Card>
  <Form layout="vertical" form={form}>
    <Row gutter={16}>
      <Col span={12}>
        <Form.Item label="설계사명" name="agentName" rules={[{ required: true }]}>
          <Input />
        </Form.Item>
      </Col>
      <Col span={12}>
        <Form.Item label="소속" name="orgId" rules={[{ required: true }]}>
          <TreeSelect treeData={orgTree} />
        </Form.Item>
      </Col>
    </Row>
    <Row gutter={16}>
      <Col span={12}>
        <Form.Item label="직급" name="gradeId">
          <CodeSelect groupCode="GRADE" />
        </Form.Item>
      </Col>
      <Col span={12}>
        <Form.Item label="상태" name="status">
          <CodeSelect groupCode="AGENT_STATUS" />
        </Form.Item>
      </Col>
    </Row>
    <div style={{ textAlign: 'right', marginTop: 16 }}>
      <Space>
        <Button onClick={onCancel}>취소</Button>
        <Button type="primary" onClick={onSubmit}>저장</Button>
      </Space>
    </div>
  </Form>
</Card>
```

---

## 7. 모달/드로워 규칙

```
// 등록/수정 폼 → Drawer (우측에서 슬라이드)
//   너비: 600px (2열 폼), 400px (단순 폼)
//   제목: "설계사 등록" / "설계사 수정"
//   하단: 취소 + 저장 버튼

// 상세 보기 → Modal
//   너비: 720px
//   Descriptions 컴포넌트로 상세 표시

// 확인/경고 → Modal.confirm()
//   "정산을 확정하시겠습니까?" [취소] [확정]

// 삭제 → Modal.confirm({ type: 'warning' })
//   "삭제하면 복구할 수 없습니다" [취소] [삭제]
```

---

## 8. 화면별 상세 와이어프레임

### 8.1 대시보드 (/)
```
레이아웃: 타입 C (대시보드)
├── 지표 카드 4열: 모집수수료, 유지수수료, 환수금, 순지급액
│   - 이번 달 금액 (억/만 단위 자동)
│   - 전월 대비 증감률 (▲초록/▼빨강)
│   - 카드 클릭 → 해당 원장 페이지 이동
├── 2열 영역
│   ├── 왼쪽(60%): 월별 추이 차트 (recharts BarChart)
│   │   - X축: 최근 6개월, Y축: 금액
│   │   - 시리즈: 모집(파랑), 유지(초록), 환수(빨강)
│   └── 오른쪽(40%): 배치 현황 카드
│       - Step별 상태: ● 완료(초록), ● 진행중(파랑+pulse), ○ 대기(회색)
│       - 각 Step: 이름 + 완료시간/진행시간
│       - 에러 시: ● 빨강 + 에러 메시지
└── 알림/오류 패널
    - 대사 불일치, 파싱 에러, 승인 대기 등
    - 뱃지 색상: 에러=빨강, 경고=주황, 정보=파랑
    - 클릭 → 해당 화면 이동
```

### 8.2 조직관리 (/org)
```
레이아웃: 2열 (좌 300px 트리 + 우 나머지 목록)
├── 좌측: 조직 트리 (antd Tree)
│   - 본사 > 지역본부 > 지점 > 팀
│   - 드래그앤드롭 정렬 (관리자만)
│   - 노드 클릭 → 우측 설계사 목록 필터링
│   - 우클릭 → 추가/수정/삭제 컨텍스트메뉴
└── 우측: 설계사 목록 (타입 A)
    - 검색: 이름, 상태, 직급
    - 테이블 컬럼: 설계사명, 직급, 상태, 위촉일, 수수료합계
    - 행 클릭 → 상세 (타입 B, 탭: 기본정보/실적/이력)
```

### 8.3 원장관리 (/ledger)
```
레이아웃: 탭 3개 (모집/유지/예외)
├── 공통 검색: 정산월, 보험사, 설계사, 상태
├── AG Grid (DataGrid) 사용 ← 대량 데이터
│   - 서버사이드 페이징/정렬/필터
│   - 컬럼 고정: 왼쪽(증권번호, 설계사명), 오른쪽(수수료합계)
│   - 합계행: 하단 고정 (pinnedBottomRow)
│   - 금액 셀: 우측 정렬, MoneyText 스타일
│   - 상태 셀: CodeBadge
├── 예외금액 탭 추가 기능:
│   - [+ 등록] → Drawer (예외유형, 금액, 방향, 설명)
│   - 승인 워크플로우: 작성→대기→승인/반려
│   - 승인 버튼: PermissionButton(permCode="APPROVE")
└── 엑셀 다운로드: ExcelExportButton (100만건, 진행률)
```

### 8.4 정산관리 (/settle)
```
레이아웃: 탭 4개 (배치실행/정산결과/정산상세/대사)

[배치실행 탭]
├── 정산월 선택 + [정산실행] 버튼
├── 진행 상태: Steps 컴포넌트 (8단계)
│   - 각 Step: 이름 + 처리건수 + 소요시간 + 상태아이콘
│   - 진행 중: 폴링으로 실시간 업데이트 (3초 간격)
│   - 에러: Step 클릭 → 에러 상세 모달
└── 완료 시: 결과 요약 카드 (총건수, 성공, 에러, 소요시간)

[정산결과 탭]
├── AG Grid: 설계사별 정산 결과
│   - 컬럼: 설계사명, 소속, 모집, 유지, 예외가산, 예외차감, 오버라이드, 환수, 세전, 세금, 순지급
│   - 합계행
│   - 상태별 필터: 계산완료/검토/확정/지급/보류
│   - 행 클릭 → 정산상세 탭으로 이동
└── 버튼: [일괄확정] [엑셀다운로드]

[정산상세 탭]
├── 설계사 선택 (상단)
├── 요약 카드 4열: 모집합계, 유지합계, 예외합계, 순지급액
└── 탭 5개: 모집수수료 / 유지수수료 / 예외금액 / 오버라이드 / 환수
    - 각 탭 AG Grid로 상세 내역 표시

[대사 탭]
├── 보험사별 대사 결과 테이블
│   - 보험사, 보험사건수, 시스템건수, 차이, 보험사금액, 시스템금액, 차이, 상태
│   - 상태: MATCH(초록), MISMATCH(빨강)
└── 불일치 행 클릭 → 상세 모달 (건별 차이 목록)
```

### 8.5 수수료규정 (/rules)
```
레이아웃: 탭 4개 (수수료율/지급률/오버라이드/환수)

각 탭 공통:
├── 검색: 보험사/보험종류/적용기간
├── AG Grid: 셀 편집 가능 (인라인 수정)
│   - 수정된 셀: 노란색 배경 강조
│   - 하단: [저장] [취소] 버튼
├── 버전 관리:
│   - 규정 수정 시 → version 자동 증가 + 기존 규정 effective_to 설정
│   - [이력보기] 버튼 → 버전별 변경 내역 타임라인
└── 신규 등록: [+ 규정 등록] → Drawer
```

### 8.6 시스템관리 (/system)

#### 역할관리 (/system/roles)
```
├── 역할 목록 테이블
└── 역할 클릭 → 권한 매트릭스
    ┌──────────┬──────┬──────┬──────┬──────┬──────┬──────┐
    │ 메뉴      │ 조회 │ 등록 │ 수정 │ 삭제 │ 승인 │ 엑셀 │
    ├──────────┼──────┼──────┼──────┼──────┼──────┼──────┤
    │ 조직관리   │ ☑   │ ☑   │ ☑   │ ☐   │ ☐   │ ☑   │
    │ 정산관리   │ ☑   │ ☐   │ ☐   │ ☐   │ ☑   │ ☑   │
    │ 시스템관리 │ ☑   │ ☑   │ ☑   │ ☑   │ ☐   │ ☐   │
    └──────────┴──────┴──────┴──────┴──────┴──────┴──────┘
    [전체선택/해제 토글] [저장]
```

#### 공통코드관리 (/system/codes)
```
2열 레이아웃:
├── 좌측 (300px): 코드 그룹 리스트
│   - 선택 시 우측에 상세 코드 표시
│   - 시스템 코드: 🔒 아이콘 + 수정 불가
└── 우측: 상세 코드 테이블
    - 코드값, 코드명, 영문명, 정렬순서, 활성여부
    - 인라인 편집 가능 (시스템코드 제외)
```

#### 업로드 템플릿 관리 (/system/upload-templates)
```
2열 레이아웃:
├── 좌측 (300px): 템플릿 목록 (카테고리별 필터)
└── 우측: 컬럼 매핑 편집기
    - 각 행: [엑셀 컬럼 입력] → [DB 필드 Select] → [변환규칙 Select] → [파라미터]
    - 드래그앤드롭 순서 변경
    - [+ 행 추가] [미리보기] [저장]
    - 미리보기: 샘플 엑셀 업로드 → 첫 5행 매핑 결과 테이블
```

---

## 9. 반응형 규칙

```
// 이 솔루션은 데스크톱 전용 (1280px 이상)
// 모바일 대응 불필요 (사내 관리 시스템)
// 최소 해상도: 1280 x 720
// 권장 해상도: 1920 x 1080

// 1280px 미만: Sider 자동 접힘 (아이콘만)
// 1920px 이상: Content 최대 너비 1400px 중앙 정렬
```

---

## 10. 신입 개발자 UI 체크리스트

새 화면 만들 때 아래 항목 확인:
- [ ] 검색 폼: SearchForm 컴포넌트 사용, 배경 #fafafa
- [ ] 목록: 단순이면 DataTable, 대량/편집이면 DataGrid
- [ ] 상태 컬럼: CodeBadge 사용 (하드코딩 색상 금지)
- [ ] 금액 컬럼: MoneyText 사용 (양수 초록, 음수 빨강)
- [ ] Select: CodeSelect 사용 (공통코드 연동)
- [ ] 버튼: PermissionButton 사용 (권한 제어)
- [ ] 엑셀: ExcelExportButton 사용
- [ ] 등록/수정: Drawer 사용 (모달 아님)
- [ ] 삭제: Modal.confirm 사용
- [ ] 폼: antd Form + 2열(Col span=12) + layout="vertical"
- [ ] 금액 입력: InputNumber + formatter (천단위 콤마)
- [ ] 날짜: DatePicker + dayjs
- [ ] 정산월: MonthPicker 공통 컴포넌트
