# 지갑 상세 — Monitor 등록 기능 UI 지침서

> **버전**: v1.0
> **작성일**: 2026-03-30
> **대상**: Admin Console UI (Vue 3 + shadcn/vue)
> **관련 화면**: 5.7 전체 지갑 (GlobalWalletView) → 지갑 상세 (WalletDetailView)

---

## 1. 개요

### 1.1 배경

모든 지갑 주소는 외부 blockchain_monitor 서비스에 등록되어야 트랜잭션 감지가 가능합니다.
지갑 생성 시 자동 등록되지만, 네트워크 오류 등으로 미등록 상태(`monitorRegistered = false`)가 발생할 수 있습니다.

### 1.2 변경 범위

| 항목 | 설명 |
|------|------|
| API 응답 필드 추가 | `WalletDetailResponse`에 `monitorRegistered: boolean` 추가 |
| 단건 등록 API 추가 | `POST /api/admin/wallets/{id}/monitor/register` |
| 일괄 등록 API | `POST /api/admin/wallets/monitor/register-unregistered` (기존) |
| UI 변경 | 지갑 상세 화면에 모니터 상태 표시 + 등록 버튼 |
| UI 변경 | 지갑 목록에 모니터 상태 컬럼 추가 |

---

## 2. 백엔드 API

### 2.1 지갑 상세 조회 (기존 — 응답 필드 추가)

```
GET /api/admin/wallets/{id}
```

**응답 (WalletDetailResponse):**

```json
{
  "id": 42,
  "address": "0xAbC123...",
  "networkId": 1,
  "networkCode": "ETH",
  "networkName": "Ethereum",
  "hdWalletId": 1,
  "derivationIndex": 6,
  "derivationPath": "m/44'/60'/1'/3/6",
  "status": "ACTIVE",
  "monitorRegistered": false,       // ★ 신규 필드
  "assignment": {
    "id": 42,
    "walletType": "MASTER",
    "partnerId": 3,
    "partnerName": "파트너A",
    "partnerUserId": null
  },
  "balances": [...],
  "approvals": [...],
  "nonceTracker": {...},
  "createdAt": "2026-03-28 14:30:00",
  "updatedAt": "2026-03-28 14:30:00"
}
```

### 2.2 단건 Monitor 등록 (★ 신규)

```
POST /api/admin/wallets/{id}/monitor/register
```

| 항목 | 값 |
|------|---|
| Path Parameter | `id` — 지갑 주소 PK |
| Request Body | 없음 |
| 성공 응답 | `true` (boolean) |
| 실패 응답 | `false` (monitor 서비스 오류 — HTTP 200) |
| 에러 응답 | `404` (지갑 없음) |

### 2.3 미등록 일괄 재등록 (기존)

```
POST /api/admin/wallets/monitor/register-unregistered
```

| 항목 | 값 |
|------|---|
| Request Body | 없음 |
| 응답 | `void` (HTTP 200) |
| 동작 | `monitor_registered = false`인 모든 지갑을 순차 등록 |

---

## 3. TypeScript 타입 정의

### 3.1 `api/types/infra.ts` 추가/수정

```typescript
/** 지갑 상세 응답 */
export interface WalletDetailResponse {
  id: number
  address: string
  networkId: number
  networkCode: string | null
  networkName: string | null
  hdWalletId: number | null
  derivationIndex: number | null
  derivationPath: string | null
  status: WalletAddressStatus
  monitorRegistered: boolean | null     // ★ 추가
  assignment: WalletAssignmentInfo | null
  balances: WalletBalanceDetail[]
  approvals: WalletApprovalDetail[]
  nonceTracker: NonceTracker | null
  createdAt: string
  updatedAt: string
}
```

### 3.2 `api/services/infra.service.ts` 추가

```typescript
/** 단건 Monitor 등록 */
export function registerWalletMonitor(walletId: number): Promise<boolean> {
  return client.post(`/admin/wallets/${walletId}/monitor/register`)
    .then(res => res.data)
}

/** 미등록 일괄 재등록 */
export function registerUnregisteredMonitors(): Promise<void> {
  return client.post('/admin/wallets/monitor/register-unregistered')
}
```

---

## 4. UI 구현

### 4.1 지갑 목록 (GlobalWalletView) — 컬럼 추가

지갑 목록 테이블에 `monitorRegistered` 컬럼을 추가합니다.
`wallet_addresses` 엔티티에 해당 필드가 이미 포함되어 있으므로 목록 API 응답에서 바로 사용 가능합니다.

```vue
<!-- 테이블 컬럼 정의에 추가 -->
{
  accessorKey: 'monitorRegistered',
  header: '모니터',
  cell: ({ row }) => {
    const registered = row.getValue('monitorRegistered')
    return registered
      ? h(Badge, { variant: 'outline', class: 'text-green-600 border-green-300' }, () => '등록')
      : h(Badge, { variant: 'outline', class: 'text-red-600 border-red-300' }, () => '미등록')
  },
  size: 80,
}
```

상단에 **"미등록 일괄 등록"** 버튼 추가 (기존 API 활용):

```vue
<Button
  variant="outline"
  size="sm"
  :disabled="isRegistering"
  @click="handleBatchRegister"
>
  <Radio class="w-4 h-4 mr-1" />
  미등록 일괄 등록
</Button>
```

### 4.2 지갑 상세 (WalletDetailView) — 핵심 변경

#### 4.2.1 라우터 추가

현재 `GlobalWalletView`에 상세 페이지 라우트가 없으므로 추가합니다:

```typescript
// router/index.ts
{
  path: 'infra/global-wallets/:id',
  name: 'wallet-detail',
  component: () => import('@/views/infra/WalletDetailView.vue')
},
```

#### 4.2.2 기본 정보 섹션 — Monitor 상태 표시

지갑 상세 "기본 정보" 카드에 Monitor 등록 상태를 행으로 추가합니다.

```
┌─────────────────────────────────────────────────────────┐
│  기본 정보                                               │
├─────────────────────────────────────────────────────────┤
│  주소          │  0xAbC123...def456          [복사]      │
│  네트워크       │  Ethereum (ETH)                        │
│  HD Wallet     │  #1                                    │
│  파생 경로      │  m/44'/60'/1'/3/6                      │
│  상태          │  🟢 ACTIVE                              │
│  모니터 등록    │  ⚠️ 미등록     [Monitor 등록]           │  ← ★ 추가
│  생성일        │  2026-03-28 14:30:00                    │
└─────────────────────────────────────────────────────────┘
```

#### 4.2.3 Monitor 상태 행 구현

```vue
<template>
  <!-- 기본 정보 카드 내부 -->
  <div class="grid grid-cols-2 gap-4">
    <!-- ... 기존 행들 ... -->

    <!-- ★ Monitor 등록 상태 -->
    <div class="text-sm text-muted-foreground">모니터 등록</div>
    <div class="flex items-center gap-2">
      <Badge
        :variant="'outline'"
        :class="wallet.monitorRegistered
          ? 'text-green-600 border-green-300'
          : 'text-orange-600 border-orange-300'"
      >
        {{ wallet.monitorRegistered ? '등록 완료' : '미등록' }}
      </Badge>

      <!-- 미등록일 때만 등록 버튼 표시 -->
      <Button
        v-if="!wallet.monitorRegistered"
        size="sm"
        variant="outline"
        :disabled="isRegistering"
        @click="handleRegisterMonitor"
      >
        <Radio class="w-3.5 h-3.5 mr-1" />
        {{ isRegistering ? '등록 중...' : 'Monitor 등록' }}
      </Button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useToast } from '@/composables/useToast'
import { registerWalletMonitor } from '@/api/services/infra.service'

const { toast } = useToast()
const isRegistering = ref(false)

async function handleRegisterMonitor() {
  isRegistering.value = true
  try {
    const success = await registerWalletMonitor(wallet.value.id)
    if (success) {
      wallet.value.monitorRegistered = true
      toast({ title: 'Monitor 등록 완료', description: '지갑이 블록체인 모니터에 등록되었습니다.' })
    } else {
      toast({ title: 'Monitor 등록 실패', description: '모니터 서비스 연결에 실패했습니다. 잠시 후 재시도해 주세요.', variant: 'destructive' })
    }
  } catch (err) {
    toast({ title: '오류', description: '요청 처리 중 오류가 발생했습니다.', variant: 'destructive' })
  } finally {
    isRegistering.value = false
  }
}
</script>
```

#### 4.2.4 UX 동작 정리

| 상태 | Badge | 버튼 | 동작 |
|------|-------|------|------|
| `monitorRegistered = true` | 🟢 "등록 완료" (green) | 숨김 | — |
| `monitorRegistered = false` | 🟠 "미등록" (orange) | "Monitor 등록" 표시 | 클릭 → POST API 호출 |
| `monitorRegistered = null` | 🔘 "알 수 없음" (gray) | "Monitor 등록" 표시 | 클릭 → POST API 호출 |
| 등록 요청 중 | — | disabled + "등록 중..." | 중복 요청 방지 |
| API 성공 (`true`) | → 🟢 전환 | → 숨김 | toast 성공 메시지 |
| API 실패 (`false`) | 유지 | 유지 | toast 실패 메시지 |

---

## 5. 지갑 목록에서의 표시

### 5.1 목록 컬럼 추가

`wallet_addresses` 엔티티 목록 응답(`GET /api/admin/wallets`)에 `monitorRegistered` 필드가 이미 포함되어 있습니다.

**추가 컬럼 (목록 테이블 맨 뒤, status 다음):**

| 헤더 | 필드 | 너비 | 표시 |
|------|------|------|------|
| 모니터 | `monitorRegistered` | 80px | Badge: "등록" (green) / "미등록" (red) |

### 5.2 검색 필터 (선택)

검색 필터에 Monitor 상태를 추가하면, 미등록 지갑만 빠르게 확인할 수 있습니다:

```vue
<Select v-model="search.monitorRegistered" placeholder="모니터 상태">
  <SelectItem :value="undefined">전체</SelectItem>
  <SelectItem :value="true">등록</SelectItem>
  <SelectItem :value="false">미등록</SelectItem>
</Select>
```

> **참고**: 이 필터는 백엔드 `WalletSearchRequest`에 `monitorRegistered` 필드가 추가되어야 동작합니다.
> 현재 미구현이므로, 필요 시 백엔드에 요청하세요.

---

## 6. 전체 화면 흐름

```
GlobalWalletView (목록)
  │
  │  모니터 컬럼에서 "미등록" 확인
  │  → 행 클릭
  │
  ▼
WalletDetailView (상세)
  │
  │  기본 정보 섹션에서 "⚠️ 미등록" 확인
  │  → [Monitor 등록] 버튼 클릭
  │
  ▼
POST /api/admin/wallets/{id}/monitor/register
  │
  ├─ true  → Badge "등록 완료" 전환 + toast 성공
  └─ false → toast 실패 ("모니터 서비스 연결 실패")
```

---

## 7. 파일 변경 요약

### 신규 파일

| 파일 | 설명 |
|------|------|
| `views/infra/WalletDetailView.vue` | 지갑 상세 화면 (기본정보 + 잔액 + Approve + 논스 + Monitor 등록) |

### 수정 파일

| 파일 | 변경 내용 |
|------|----------|
| `api/types/infra.ts` | `WalletDetailResponse`에 `monitorRegistered` 필드 추가 |
| `api/services/infra.service.ts` | `registerWalletMonitor()`, `registerUnregisteredMonitors()` 함수 추가 |
| `router/index.ts` | `infra/global-wallets/:id` 라우트 추가 |
| `views/infra/GlobalWalletView.vue` | 테이블에 모니터 컬럼 추가 + 행 클릭 → 상세 이동 + 일괄 등록 버튼 |

---

## 8. 체크리스트

- [ ] `WalletDetailResponse` 타입에 `monitorRegistered` 필드 반영
- [ ] `infra.service.ts`에 `registerWalletMonitor()` API 함수 추가
- [ ] `GlobalWalletView` 테이블에 모니터 상태 컬럼 추가 (Badge)
- [ ] `GlobalWalletView` 행 클릭 → `WalletDetailView` 이동 연결
- [ ] `GlobalWalletView` 상단에 "미등록 일괄 등록" 버튼 추가
- [ ] `WalletDetailView` 신규 생성 (기본정보 + 잔액 + Approve + 논스)
- [ ] `WalletDetailView` 기본정보에 Monitor 상태 행 + 등록 버튼
- [ ] 등록 성공/실패 시 toast 알림 + Badge 상태 전환
- [ ] 라우터에 상세 페이지 경로 등록
