# Admin Console 리팩토링 지침서 v1.0

> **작성일**: 2026-04-03
> **목적**: Admin UI 스타일을 Partner UI와 통일하고, API 오류를 수정하며, 기능 정의를 명확히 정리한다.
> **작업 환경**: IntelliJ (admin-api), VS Code (admin-ui)

---

## 1. 현황 요약

### 1.1 Backend (admin-api)

| 항목 | 수량 |
|------|------|
| Controller | 25 |
| Service | 25 |
| Mapper | 20 |
| DTO (Request) | 52 |
| DTO (Response) | 40 |
| 총 엔드포인트 | 120+ |

**컴파일 오류**: 1건 (PartnerSearchMapper — `login_email` 컬럼 조회하나 PartnerListResponse에 매핑 필드 없음)

### 1.2 Frontend (admin-ui)

| 항목 | 수량 |
|------|------|
| Vue 컴포넌트 | 168 |
| View (페이지) | 40 |
| 라우트 | 29 |
| API 서비스 | 11 |
| 사이드바 메뉴 | 9그룹 |

**주요 문제점**:
- Tailwind CSS v4 + OKLCh 색상 (Partner UI는 Tailwind v3 + HSL)
- Reka UI 사용 (Partner UI는 Radix Vue)
- Header에 브레드크럼 없음, Partner 배지 없음
- 사이드바 너비 w-60 (Partner는 w-56)
- 전반적으로 Partner UI 대비 시각적 일관성 부족

---

## 2. 스타일 통일 지침 (Admin UI → Partner UI 기준)

### 2.1 CSS 테마 변경 — OKLCh → HSL

**파일**: `admin-ui/src/assets/css/app.css`

현재 Admin UI는 OKLCh 색상 체계를 사용하고 있으나, Partner UI의 HSL 기반으로 통일한다.

**변경 후 `:root`**:

```css
:root {
  --radius: 0.5rem;
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --card: 0 0% 100%;
  --card-foreground: 222.2 84% 4.9%;
  --popover: 0 0% 100%;
  --popover-foreground: 222.2 84% 4.9%;
  --primary: 222.2 47.4% 11.2%;
  --primary-foreground: 210 40% 98%;
  --secondary: 210 40% 96.1%;
  --secondary-foreground: 222.2 47.4% 11.2%;
  --muted: 210 40% 96.1%;
  --muted-foreground: 215.4 16.3% 46.9%;
  --accent: 210 40% 96.1%;
  --accent-foreground: 222.2 47.4% 11.2%;
  --destructive: 0 84.2% 60.2%;
  --destructive-foreground: 210 40% 98%;
  --border: 214.3 31.8% 91.4%;
  --input: 214.3 31.8% 91.4%;
  --ring: 222.2 84% 4.9%;
  /* Sidebar (Admin 전용) */
  --sidebar: 0 0% 100%;
  --sidebar-foreground: 222.2 84% 4.9%;
  --sidebar-primary: 222.2 47.4% 11.2%;
  --sidebar-primary-foreground: 210 40% 98%;
  --sidebar-accent: 210 40% 96.1%;
  --sidebar-accent-foreground: 222.2 47.4% 11.2%;
  --sidebar-border: 214.3 31.8% 91.4%;
  --sidebar-ring: 222.2 84% 4.9%;
  /* Chart */
  --chart-1: 12 76% 61%;
  --chart-2: 173 58% 39%;
  --chart-3: 197 37% 24%;
  --chart-4: 43 74% 66%;
  --chart-5: 27 87% 67%;
}

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  --card: 222.2 84% 4.9%;
  --card-foreground: 210 40% 98%;
  --popover: 222.2 84% 4.9%;
  --popover-foreground: 210 40% 98%;
  --primary: 210 40% 98%;
  --primary-foreground: 222.2 47.4% 11.2%;
  --secondary: 217.2 32.6% 17.5%;
  --secondary-foreground: 210 40% 98%;
  --muted: 217.2 32.6% 17.5%;
  --muted-foreground: 215 20.2% 65.1%;
  --accent: 217.2 32.6% 17.5%;
  --accent-foreground: 210 40% 98%;
  --destructive: 0 62.8% 30.6%;
  --destructive-foreground: 210 40% 98%;
  --border: 217.2 32.6% 17.5%;
  --input: 217.2 32.6% 17.5%;
  --ring: 212.7 26.8% 83.9%;
  --sidebar: 222.2 84% 4.9%;
  --sidebar-foreground: 210 40% 98%;
  --sidebar-primary: 210 40% 98%;
  --sidebar-primary-foreground: 222.2 47.4% 11.2%;
  --sidebar-accent: 217.2 32.6% 17.5%;
  --sidebar-accent-foreground: 210 40% 98%;
  --sidebar-border: 217.2 32.6% 17.5%;
  --sidebar-ring: 212.7 26.8% 83.9%;
}
```

> **주의**: Tailwind v4의 `@theme inline` 블록 안에서 `oklch()` → `hsl()` 기반 var() 참조로 변환 필요.
> `--color-background: hsl(var(--background))` 형태로 교체.

### 2.2 `@theme inline` 블록 수정

현재:
```css
@theme inline {
  --color-background: var(--background);
  ...
}
```

Tailwind v4에서 HSL 색상을 사용하려면 `color()` 함수 대신, 각 `--color-*` 변수가 올바른 HSL 값을 참조하도록 유지한다. OKLCh 값이 제거되면 자동으로 HSL 기반으로 동작한다.

### 2.3 사이드바 통일

**변경 사항**:

| 항목 | 현재 (Admin) | 변경 후 (Partner 기준) |
|------|-------------|---------------------|
| 너비 | `w-60` (240px) | `w-56` (224px) |
| 배지 | 없음 | `<Badge variant="secondary">Admin</Badge>` 추가 |
| 활성 상태 | `bg-sidebar-accent text-sidebar-accent-foreground` | `bg-primary text-primary-foreground` |
| 하위 메뉴 | `ml-4 border-l pl-3` | `ml-2` (왼쪽 여백만, 세로선 제거) |
| 하위 활성 | `bg-sidebar-accent font-medium` | `bg-primary text-primary-foreground` |
| 섹션 헤더 | Collapsible 컴포넌트 | 단순 button + v-if (Partner 패턴) |

**Sidebar.vue 수정 핵심**:

```vue
<!-- 브랜드 영역 -->
<div class="flex h-14 items-center gap-2 border-b border-border px-4">
  <span class="text-lg font-semibold text-foreground">Cryptoments</span>
  <Badge variant="secondary" class="text-xs">Admin</Badge>
</div>

<!-- 단일 메뉴 활성 상태 -->
:class="isActive(item.to)
  ? 'bg-primary text-primary-foreground'
  : 'text-muted-foreground hover:bg-muted hover:text-foreground'"

<!-- 하위 메뉴 활성 상태 -->
:class="isActive(child.to)
  ? 'bg-primary text-primary-foreground'
  : 'text-muted-foreground hover:bg-muted hover:text-foreground'"
```

### 2.4 Header 통일

**현재 Admin Header**: 모바일 토글 + 관리자명 + 로그아웃만 있음

**변경 후** (Partner 패턴 적용):

```vue
<header class="sticky top-0 z-30 flex h-14 items-center justify-between border-b border-border bg-background px-6">
  <div class="flex items-center gap-4">
    <!-- 모바일 메뉴 버튼 -->
    <Button variant="ghost" size="icon" class="md:hidden" @click="app.toggleSidebar()">
      <Menu class="h-4 w-4" />
    </Button>
    <!-- 브레드크럼 추가 -->
    <nav class="flex items-center gap-1 text-sm text-muted-foreground">
      <div v-for="(b, i) in breadcrumbs" :key="i" class="flex items-center gap-1">
        <ChevronRight v-if="i > 0" class="h-3.5 w-3.5" />
        <router-link :to="b.path ?? ''" class="hover:text-foreground">
          {{ b.label }}
        </router-link>
      </div>
    </nav>
  </div>
  <div class="flex items-center gap-2">
    <span class="text-sm text-muted-foreground">{{ auth.adminName }}</span>
    <Badge variant="outline">{{ auth.adminRole ?? '—' }}</Badge>
    <Button variant="ghost" size="sm" @click="handleLogout">
      <LogOut class="mr-1 h-4 w-4" />
      로그아웃
    </Button>
  </div>
</header>
```

**브레드크럼 computed** (라우트 meta.title 활용):
```typescript
const breadcrumbs = computed(() => {
  const title = (route.meta?.title as string) ?? 'Admin'
  if (route.path === '/dashboard') return [{ label: '대시보드' }]
  return [{ label: '대시보드', path: '/dashboard' }, { label: title }]
})
```

### 2.5 레이아웃 pl 값 변경

`AdminLayout.vue`에서 main 영역의 `pl-` 값을 사이드바 너비와 일치시킨다.

```vue
<!-- 변경: pl-60 → pl-56 (w-56과 일치) -->
<div class="flex flex-1 flex-col overflow-hidden" :class="!isMobile && 'pl-56'">
```

### 2.6 공통 컴포넌트 스타일 확인

Admin UI 와 Partner UI 모두 shadcn/vue 기반이므로 Button, Card, Badge, Table 등의 CVA 정의가 동일한지 확인한다.

**필수 확인 항목**:

| 컴포넌트 | Partner 기준 | 확인 파일 |
|----------|-------------|----------|
| Button | `h-9 px-4 py-2` (default) | `admin-ui/src/components/ui/button/Button.vue` |
| Card | `rounded-xl border bg-card shadow` | `admin-ui/src/components/ui/card/Card.vue` |
| Badge | 6 variants (default, secondary, destructive, outline, success, warning) | `admin-ui/src/components/ui/badge/Badge.vue` |
| Input | `h-9 rounded-md border` | `admin-ui/src/components/ui/input/Input.vue` |
| Table | `hover:bg-muted/50` 행 호버 | `admin-ui/src/components/ui/table/` |

**Badge에 success/warning variant 추가** (Partner에 있지만 Admin에 없을 수 있음):

```typescript
success: 'border-transparent bg-green-600 text-white shadow hover:bg-green-700',
warning: 'border-transparent bg-amber-500 text-white shadow hover:bg-amber-600',
```

---

## 3. Backend API 수정 사항

### 3.1 [P0] PartnerSearchMapper — loginEmail 매핑 누락

**파일**: `admin-api/.../mapper/PartnerSearchMapper.java`

**문제**: SELECT 절에 `p.login_email`을 포함하지만, `PartnerListResponse` DTO에 `loginEmail` 필드가 없어서 MyBatis 매핑 실패.

**수정 방법 (택 1)**:
- (A) `PartnerListResponse`에 `loginEmail` 필드 추가 (권장)
- (B) SELECT에서 `p.login_email` 제거

```java
// PartnerListResponse.java — (A) 필드 추가
/** 로그인 이메일 */
private String loginEmail;
```

### 3.2 [P1] API 응답 일관성 점검

모든 목록 조회 API가 `XPage<T>` 형태로 반환되는지 확인한다. 프론트엔드에서 페이지네이션 처리 시 `totalCount`, `page`, `size` 필드가 필수.

**확인 대상 Mapper**:
- PartnerSearchMapper
- DepositSearchMapper
- WithdrawalSearchMapper
- CollectionSearchMapper
- WalletSearchMapper
- GasInvoiceSearchMapper
- GasCostSearchMapper
- LedgerSearchMapper
- SystemSearchMapper (AuditLog)

**각 Mapper의 @Select 쿼리가 반드시 지켜야 할 3가지 규칙**:
1. 첫 번째 파라미터: `XPagination pagination`
2. 마지막 파라미터: `Class<?> cls`
3. ORDER BY / LIMIT 작성 금지 (XResultInterceptor가 자동 처리)

### 3.3 [P1] AuditAction enum 정합성

`AuditAction` enum에 47개 값이 정의되어 있는지 확인. 프론트엔드 감사 로그 필터의 드롭다운 옵션과 1:1 매칭되어야 한다.

### 3.4 [P2] 온체인 TX 호출 타임아웃

컨트랙트/Relayer/인프라 지갑 관련 엔드포인트는 응답 시간이 5~60초. 프론트엔드에서 `timeout: 60000` 이상 설정이 필요.

**관련 엔드포인트**:
- `POST /api/admin/contracts/register`
- `POST /api/admin/contracts/add-relayer`
- `POST /api/admin/contracts/remove-relayer`
- `POST /api/admin/contracts/pause`
- `POST /api/admin/contracts/{networkId}/unpause`
- `POST /api/admin/relayers` (등록)
- `POST /api/admin/relayers/{id}/deactivate`
- `POST /api/admin/infra-wallets/admin`
- `POST /api/admin/infra-wallets/gas`
- `POST /api/admin/infra-wallets/settlement`
- `POST /api/admin/wallets/{id}/monitor/register`
- `POST /api/admin/wallets/monitor/register-unregistered`

**프론트엔드 처리 필수**:
- 버튼 disable + 로딩 스피너
- Progress bar 또는 "처리 중..." 텍스트
- axios 요청 시 `{ timeout: 90000 }` 개별 설정

---

## 4. 기능 정의서 — 9개 메뉴 그룹

### 4.1 대시보드 (`/dashboard`)

| 영역 | API | 설명 |
|------|-----|------|
| 요약 카드 | `GET /api/admin/dashboard/summary` | 오늘 입금/출금 건수+금액, 활성 파트너, 대기 중 출금 |
| 이상 알림 | `GET /api/admin/dashboard/alerts` | 미식별 입금, 실패 TX, 잔액 부족 등 |
| 미식별 입금 | `GET /api/admin/dashboard/unidentified` | 최근 미식별 입금 목록 (간략) |
| 정산 현황 | `GET /api/admin/dashboard/settlement` | 오늘/이번 주/이번 달 정산 요약 |

### 4.2 파트너 관리 (`/partners`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/partners` | 검색/필터/페이지네이션 |
| 등록 | `POST /api/admin/partners` | 파트너 신규 등록 |
| 상세 | `GET /api/admin/partners/{id}` | 7개 탭 (기본정보, 지갑, 체인설정, 출금정책, 연동, 하위파트너, 로그) |

**상세 화면 7개 탭**:

| 탭 | API(조회) | API(수정) |
|----|----------|----------|
| 기본 정보 | 상세 API에 포함 | `PUT /{id}`, `PATCH /{id}/status` |
| 지갑 | `GET /{id}/wallets` | `POST /{id}/wallets` |
| 체인 설정 | `GET /{id}/chain-configs` | `PUT /{id}/chain-configs` |
| 출금 정책 | `GET /{id}/withdrawal-policy` | `PUT /{id}/withdrawal-policy` |
| 연동 설정 | `GET /{id}/telegram`, `GET /{id}/axim` | `PUT /{id}/telegram`, `PUT /{id}/axim`, `POST /{id}/api-key/regenerate` |
| 화이트리스트 | `GET /{id}/whitelist` | `POST /{id}/whitelist`, `DELETE /{id}/whitelist/{wid}` |
| 감사 로그 | `GET /{id}/webhook-logs` | — |

**추가 액션**: `POST /{id}/2fa/reset` (2FA 초기화)

### 4.3 거래 관리

#### 입금 조회 (`/deposits`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/deposits` | 파트너/상태/기간 필터 |
| 상세 | `GET /api/admin/deposits/{id}` | 입금 상세 정보 |
| 이력 | `GET /api/admin/deposits/{id}/history` | 상태 변경 이력 |

#### 출금 조회 (`/withdrawals`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/withdrawals` | 파트너/상태/기간 필터 |
| 상세 | `GET /api/admin/withdrawals/{id}` | 출금 상세 정보 |
| 이력 | `GET /api/admin/withdrawals/{id}/history` | 상태 변경 이력 |

#### 출금 승인 (`/withdrawals/approval`)

| 화면 | API | 설명 |
|------|-----|------|
| 대기 목록 | `GET /api/admin/withdrawals/pending-approval` | PENDING_APPROVAL 건만 |
| 단건 승인 | `POST /api/admin/withdrawals/{id}/approve` | 개별 승인 |
| 단건 거부 | `POST /api/admin/withdrawals/{id}/reject` | 사유 필수 |
| 일괄 승인 | `POST /api/admin/withdrawals/batch-approve` | 체크박스 선택 |
| 일괄 거부 | `POST /api/admin/withdrawals/batch-reject` | 체크박스 선택 + 사유 |
| 재시도 | `POST /api/admin/withdrawals/{id}/retry` | FAILED 건 재시도 |
| 취소 | `POST /api/admin/withdrawals/{id}/cancel` | REQUESTED/PENDING 건만 |

#### 집금 현황 (`/collections`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/collections` | 배치/상태별 조회 |
| 상세 | `GET /api/admin/collections/{id}` | 집금 상세 |
| STALE 실패 처리 | `POST /api/admin/collections/batches/{batchId}/fail` | STALE 배치 수동 실패 |

### 4.4 CS 도구

#### 미식별 입금 (`/cs/unidentified`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/cs/unidentified` | 미식별 입금 목록 |
| 매칭 | `POST /api/admin/cs/unidentified/{id}/match` | 파트너+유저 지정 |
| 환불 | `POST /api/admin/cs/unidentified/{id}/refund` | 원 주소로 환불 |

#### TX 검색 (`/cs/tx-search`)

| 화면 | API | 설명 |
|------|-----|------|
| 글로벌 검색 | `GET /api/admin/cs/tx-search` | TX Hash로 5개 테이블 동시 검색 |

### 4.5 인프라 관리

#### 네트워크 (`/infra/networks`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/networks` | 블록체인 네트워크 목록 |
| 추가 | `POST /api/admin/networks` | 네트워크 추가 |
| 수정 | `PUT /api/admin/networks/{id}` | 네트워크 수정 |

#### 통화 (`/infra/currencies`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/currencies` | 통화(토큰) 목록 |
| 추가 | `POST /api/admin/currencies` | 통화 추가 |
| 수정 | `PUT /api/admin/currencies/{id}` | 통화 수정 |

#### HD Wallet (`/infra/hd-wallets`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/hd-wallets` | HD Wallet 목록 |
| 상세 | `GET /api/admin/hd-wallets/{id}` | HD Wallet 상세 |

#### 인프라 지갑 (`/infra/wallets`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/infra-wallets` | ADMIN/GAS/SETTLEMENT 지갑 |
| ADMIN 생성 | `POST /api/admin/infra-wallets/admin` | ⚠️ 온체인 TX (5~60초) |
| GAS 생성 | `POST /api/admin/infra-wallets/gas` | ⚠️ 온체인 TX |
| SETTLEMENT 생성 | `POST /api/admin/infra-wallets/settlement` | ⚠️ 온체인 TX |

#### Approve 현황 (`/infra/approvals`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/wallet-approvals` | Approve 상태 목록 |
| 재시도 | `POST /api/admin/wallet-approvals/{id}/retry` | Approve 재시도 |

#### 논스 관리 (`/infra/nonce`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/nonce-trackers` | 논스 추적기 목록 |
| 동기화 | `POST /api/admin/nonce-trackers/{id}/sync` | 온체인 논스 동기화 |
| 락 해제 | `POST /api/admin/nonce-trackers/{id}/unlock` | 논스 강제 락 해제 |

#### 전체 지갑 (`/infra/global-wallets`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/wallets` | 전체 지갑 조회 |
| 상세 | `GET /api/admin/wallets/{id}` | 지갑 상세 |
| 잔액 | `GET /api/admin/wallets/{id}/balances` | 지갑별 잔액 |
| 잔액 동기화 | `POST /api/admin/wallets/{id}/sync-balance` | 단일 지갑 잔액 동기화 |
| 일괄 동기화 | `POST /api/admin/wallets/sync-balances` | 전체 잔액 동기화 |
| TX 상태 | `GET /api/admin/tx/status/{txHash}` | TX 상태 조회 |
| 지갑 인덱스 | `GET /api/admin/wallet-index` | 지갑 인덱스 관리 |
| Monitor 등록 | `POST /api/admin/wallets/{id}/monitor/register` | 단건 Monitor 등록 |
| 미등록 재등록 | `POST /api/admin/wallets/monitor/register-unregistered` | 미등록 주소 재등록 |

#### 컨트랙트 (`/infra/contracts`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/contracts` | 컨트랙트 목록 |
| 상태 | `GET /api/admin/contracts/{networkId}/status` | ⚠️ 온체인 조회 |
| 등록 | `POST /api/admin/contracts/register` | ⚠️ 온체인 TX |
| Relayer 추가 | `POST /api/admin/contracts/add-relayer` | ⚠️ 온체인 TX |
| Relayer 제거 | `POST /api/admin/contracts/remove-relayer` | ⚠️ 온체인 TX |
| 긴급 정지 | `POST /api/admin/contracts/pause` | ⚠️ 온체인 TX |
| 정지 해제 | `POST /api/admin/contracts/{networkId}/unpause` | ⚠️ 온체인 TX |

#### Relayer (`/infra/relayers`)

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/relayers` | Relayer 목록 |
| 상세 | `GET /api/admin/relayers/{id}` | Relayer 상세 |
| 등록 | `POST /api/admin/relayers` | ⚠️ 온체인 TX |
| 해제 | `POST /api/admin/relayers/{id}/deactivate` | ⚠️ 온체인 TX |
| 상태 변경 | `PATCH /api/admin/relayers/{id}/status` | DB 상태만 변경 |
| 헬스 | `GET /api/admin/relayers/health` | 실시간 헬스 |
| 실시간 | `GET /api/admin/relayers/live` | relayer-api 실시간 조회 |

### 4.6 정산 관리

| 화면 | API | 설명 |
|------|-----|------|
| 일별 수수료 | `GET /api/admin/settlements/daily-fees` | 일별 수수료 집계 |
| 수수료 요약 | `GET /api/admin/settlements/daily-fees/summary` | 기간별 요약 |
| 실현 목록 | `GET /api/admin/settlements/realizations` | 수익 실현 내역 |
| 실현 상세 | `GET /api/admin/settlements/realizations/{id}` | 실현 상세 |
| 참여자 잔액 | `GET /api/admin/settlements/balances` | 시스템/파트너 잔액 |
| 시스템 출금 | `POST /api/admin/settlements/withdraw` | 시스템 쉐어 출금 |

### 4.7 가스비/원장

| 화면 | API | 설명 |
|------|-----|------|
| 가스비 기록 | `GET /api/admin/gas-costs` | 가스비 기록 목록 |
| 가스비 상세 | `GET /api/admin/gas-costs/{id}` | 가스비 상세 |
| 가스비 면제 | `PATCH /api/admin/gas-costs/{id}/waive` | 가스비 면제 처리 |
| 인보이스 목록 | `GET /api/admin/gas-invoices` | 인보이스 목록 |
| 인보이스 상세 | `GET /api/admin/gas-invoices/{id}` | 인보이스 상세 |
| 인보이스 발행 | `POST /api/admin/gas-invoices/generate` | 인보이스 일괄 발행 |
| 입금 확인 | `POST /api/admin/gas-invoices/{id}/confirm-payment` | 인보이스 입금 확인 |
| 연체 처리 | `PATCH /api/admin/gas-invoices/{id}/overdue` | 인보이스 연체 |
| 원장 | `GET /api/admin/ledgers` | 원장 엔트리 목록 |
| 원장 잔액 | `GET /api/admin/ledgers/balance` | 파트너 원장 잔액 |
| 현재 환율 | `GET /api/admin/prices/current` | 현재 시세 |
| 가격 이력 | `GET /api/admin/prices/history` | 시세 이력 |

### 4.8 시스템 설정

| 화면 | API | 설명 |
|------|-----|------|
| 런타임 설정 | `GET /api/admin/settings` | 시스템 설정 목록 |
| 설정 수정 | `PUT /api/admin/settings/{key}` | 개별 설정 수정 |
| 감사 로그 목록 | `GET /api/admin/audit-logs` | 감사 로그 조회 |
| 감사 로그 상세 | `GET /api/admin/audit-logs/{id}` | 감사 로그 상세 |
| 유지보수 상태 | `GET /api/admin/maintenance` | 현재 유지보수 상태 |
| 유지보수 ON | `POST /api/admin/maintenance/enable` | 유지보수 모드 활성화 |
| 유지보수 OFF | `POST /api/admin/maintenance/disable` | 유지보수 모드 비활성화 |

### 4.9 관리자 계정

| 화면 | API | 설명 |
|------|-----|------|
| 목록 | `GET /api/admin/admins` | 관리자 목록 |
| 등록 | `POST /api/admin/admins` | 관리자 등록 |
| 상세 | `GET /api/admin/admins/{id}` | 관리자 상세 |
| 수정 | `PUT /api/admin/admins/{id}` | 관리자 수정 |
| 상태 변경 | `PATCH /api/admin/admins/{id}/status` | 관리자 활성/비활성 |
| 2FA 초기화 | `POST /api/admin/admins/{id}/2fa/reset` | 2FA 리셋 |
| 비밀번호 리셋 | `POST /api/admin/admins/{id}/reset-password` | 비밀번호 리셋 |
| 본인 2FA 설정 | `POST /api/admin/admins/me/2fa/setup` | QR코드 반환 |
| 본인 2FA 검증 | `POST /api/admin/admins/me/2fa/verify` | TOTP 코드로 활성화 |

---

## 5. 프론트엔드 코딩 규칙

### 5.1 API 서비스 계층

```typescript
// ✅ 올바른 패턴 — 서비스 파일에서 호출
export const partnerService = {
  getList: (params: PartnerSearchParams) =>
    apiClient.get<XPage<PartnerListItem>>('/admin/partners', { params }),
  getDetail: (id: number) =>
    apiClient.get<PartnerDetail>(`/admin/partners/${id}`),
}

// ❌ 금지 — 컴포넌트에서 직접 axios 호출
import axios from 'axios'
axios.get('/api/admin/partners')
```

### 5.2 페이지네이션

```typescript
// XPage<T> 응답 구조
interface XPage<T> {
  list: T[]
  totalCount: number
  page: number      // 1-based
  size: number
}

// 기본값: page=1, size=20
// 정렬: createdAt,DESC (서버 기본값)
```

### 5.3 상태 뱃지

`constants.ts`의 `STATUS_COLORS` 맵 사용:

```vue
<Badge :class="STATUS_COLORS[status] ?? 'bg-gray-100 text-gray-800'">
  {{ status }}
</Badge>
```

### 5.4 금액 표시

```typescript
function formatAmount(value: string | number, decimals = 6): string {
  const num = typeof value === 'string' ? parseFloat(value) : value
  return num.toLocaleString('en-US', {
    minimumFractionDigits: 2,
    maximumFractionDigits: decimals,
  })
}
```

### 5.5 날짜 표시

```typescript
import dayjs from 'dayjs'
function formatDate(date: string): string {
  return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
}
```

### 5.6 주소/TX Hash 표시

```typescript
// 주소: 앞 6자 + ... + 뒤 4자
function shortenAddress(addr: string): string {
  if (!addr || addr.length < 12) return addr
  return `${addr.slice(0, 6)}...${addr.slice(-4)}`
}

// TX Hash: 앞 10자 + ... + 뒤 6자
function shortenTxHash(hash: string): string {
  if (!hash || hash.length < 18) return hash
  return `${hash.slice(0, 10)}...${hash.slice(-6)}`
}
```

### 5.7 온체인 TX 처리 패턴

```vue
<script setup>
const isProcessing = ref(false)

async function handleOnchainAction() {
  isProcessing.value = true
  try {
    await infraService.createAdminWallet(payload, { timeout: 90000 })
    toast.success('지갑이 생성되었습니다.')
    await refresh()
  } catch (e) {
    // apiClient interceptor에서 자동 toast 처리됨
  } finally {
    isProcessing.value = false
  }
}
</script>

<template>
  <Button :disabled="isProcessing" @click="handleOnchainAction">
    <Loader2 v-if="isProcessing" class="mr-2 h-4 w-4 animate-spin" />
    {{ isProcessing ? '처리 중...' : '생성' }}
  </Button>
</template>
```

### 5.8 ConfirmDialog 패턴

위험한 작업 (상태 변경, 삭제, 온체인 TX)은 반드시 확인 다이얼로그를 거친다:

```vue
<Dialog v-model:open="showConfirm">
  <DialogContent>
    <DialogHeader>
      <DialogTitle>출금을 승인하시겠습니까?</DialogTitle>
      <DialogDescription>
        {{ selectedCount }}건, 총 {{ totalAmount }} USDT를 승인합니다.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <Button variant="outline" @click="showConfirm = false">취소</Button>
      <Button @click="confirmAction">확인</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>
```

---

## 6. 작업 우선순위

### Phase 1 — 스타일 통일 (1~2일)

1. `app.css` 색상 변수 HSL로 변환
2. Sidebar.vue 수정 (w-56, 활성 상태 색상, 배지 추가)
3. Header.vue 수정 (브레드크럼 추가, 역할 배지)
4. AdminLayout.vue 수정 (pl-56, 모바일 오버레이)
5. Badge에 success/warning variant 추가

### Phase 2 — API 오류 수정 (1일)

1. PartnerSearchMapper `loginEmail` 매핑 수정 (P0)
2. 전체 Mapper @Select 쿼리 점검 (XPagination 3규칙)
3. 온체인 TX 엔드포인트 타임아웃 설정 확인

### Phase 3 — 기능 안정화 (2~3일)

1. 각 View에서 API 호출 테스트 (실제 운영 데이터 연동)
2. 폼 유효성 검증 (Zod 스키마) 점검
3. 에러 핸들링 (토스트 메시지) 일관성 확인
4. 출금 일괄 승인/거부 체크박스 동작 확인
5. 온체인 TX 호출 시 로딩 상태 + 타임아웃 처리 확인

### Phase 4 — 최종 QA (1일)

1. 모든 9개 메뉴 그룹 순회 테스트
2. 모바일 반응형 확인
3. 다크모드 확인 (선택)
4. Partner UI와 시각적 비교 검수

---

## 7. 참고: Partner UI vs Admin UI 스타일 차이 요약

| 항목 | Partner UI | Admin UI (현재) | Admin UI (변경 후) |
|------|-----------|----------------|-------------------|
| CSS 색상 체계 | HSL | OKLCh | HSL |
| Tailwind 버전 | v3.4.15 | v4.2.1 | v4 유지, HSL 변수 |
| 사이드바 너비 | w-56 (224px) | w-60 (240px) | w-56 |
| 사이드바 배지 | "Partner" | 없음 | "Admin" |
| 활성 메뉴 색상 | `bg-primary text-primary-foreground` | `bg-sidebar-accent` | `bg-primary text-primary-foreground` |
| Header 브레드크럼 | 있음 | 없음 | 추가 |
| Header 역할 배지 | 파트너 타입 표시 | 없음 | 관리자 역할 표시 |
| 2FA 경고 배너 | 있음 | 없음 | 선택 (추후) |
| UI 라이브러리 | Radix Vue | Reka UI | Reka UI 유지 |
| border-radius | 0.5rem | 0.625rem | 0.5rem |
| 컴포넌트 패턴 | shadcn/vue | shadcn/vue | 동일 |
