# [계획서] 버튼 아이콘화 - 화면 디자이너 버튼 표시 모드 확장

> 관련 문서: [맥락노트](./BIC[맥락]-버튼-아이콘화.md) | [체크리스트](./BIC[체크]-버튼-아이콘화.md)

## 개요

화면 디자이너에서 버튼을 텍스트 모드(현행), 아이콘 모드, 아이콘+텍스트 모드 중 선택할 수 있도록 확장한다.
아이콘 모드 선택 시 버튼 액션에 맞는 아이콘 후보군이 제시되고, 관리자가 원하는 아이콘을 선택한다.
아이콘 크기 비율(버튼 높이 대비 4단계 프리셋), 아이콘 색상, 텍스트 위치(4방향), 아이콘-텍스트 간격 설정을 제공한다.
관리자가 lucide 검색 또는 외부 SVG 붙여넣기로 커스텀 아이콘을 추가/삭제할 수 있다.

---

## 현재 동작

- 버튼은 항상 **텍스트 모드**로만 표시됨
- `ButtonConfigPanel.tsx`에서 "버튼 텍스트" 입력 → 실제 화면에서 해당 텍스트가 버튼에 표시
- 아이콘 표시 기능 없음

### 현재 코드 위치

| 구분 | 파일 | 설명 |
|------|------|------|
| 설정 패널 | `frontend/components/screen/config-panels/ButtonConfigPanel.tsx` | 버튼 텍스트, 액션 설정 (784~854행) |
| 뷰어 렌더링 | `frontend/components/screen/InteractiveScreenViewerDynamic.tsx` | 실제 버튼 렌더링 (961~983행) |
| 뷰어 렌더링 | `frontend/components/screen/InteractiveScreenViewer.tsx` | 실제 버튼 렌더링 (2041~2059행) |
| 위젯 렌더링 | `frontend/components/screen/widgets/types/ButtonWidget.tsx` | 위젯 기반 버튼 렌더링 (67~86행) |
| 최적화 컴포넌트 | `frontend/components/screen/OptimizedButtonComponent.tsx` | 최적화된 버튼 컴포넌트 (643~674행) |

---

## 변경 후 동작

### 1. 표시 모드 선택 (라디오 그룹)

ButtonConfigPanel에 "버튼 텍스트" 입력 위에 표시 모드 선택 UI 추가:

- **텍스트 모드** (기본값, 현행 유지): 버튼에 텍스트만 표시
- **아이콘 모드**: 버튼에 아이콘만 표시
- **아이콘+텍스트 모드**: 버튼에 아이콘과 텍스트를 함께 표시

```
[ 텍스트 | 아이콘 | 아이콘+텍스트 ]  ← 라디오 그룹 (토글 형태)
```

### 2. 텍스트 모드 선택 시

- 현재와 동일하게 "버튼 텍스트" 입력 필드 표시
- 변경 사항 없음

### 2-1. 아이콘+텍스트 모드 선택 시

- 아이콘 선택 UI (3장과 동일) + 버튼 텍스트 입력 필드 **둘 다 표시**
- 렌더링: 텍스트 위치에 따라 아이콘과 텍스트 배치 방향이 달라짐
- 텍스트 위치 4방향: 오른쪽(기본), 왼쪽, 위쪽, 아래쪽
- 예시: `[ ✓ 저장 ]` (오른쪽), `[ 저장 ✓ ]` (왼쪽), 세로 배치 (위쪽/아래쪽)
- 아이콘과 텍스트 사이 간격: 기본 6px, 관리자가 0~무제한 조절 가능 (슬라이더 0~32px + 직접 입력)

### 3. 아이콘 모드 선택 시

#### 3-1. 버튼 액션별 추천 아이콘 목록

버튼 액션(`action.type`)에 따라 해당 액션에 어울리는 아이콘 후보군을 그리드로 표시:

| 버튼 액션 | 값 | 추천 아이콘 (lucide-react) |
|-----------|-----|---------------------------|
| 저장 | `save` | Check, Save, CheckCircle, CircleCheck, FileCheck, ShieldCheck |
| 삭제 | `delete` | Trash2, Trash, XCircle, X, Eraser, CircleX |
| 편집 | `edit` | Pencil, PenLine, Edit, SquarePen, FilePen, PenTool |
| 페이지 이동 | `navigate` | ArrowRight, ExternalLink, MoveRight, Navigation, CornerUpRight, Link |
| 모달 열기 | `modal` | Maximize2, PanelTop, AppWindow, LayoutGrid, Layers, FolderOpen |
| 데이터 전달 | `transferData` | SendHorizontal, ArrowRightLeft, Repeat, PackageCheck, Upload, Share2 |
| 엑셀 다운로드 | `excel_download` | Download, FileDown, FileSpreadsheet, Sheet, Table, FileOutput |
| 엑셀 업로드 | `excel_upload` | Upload, FileUp, FileSpreadsheet, Sheet, ImportIcon, FileInput |
| 즉시 저장 | `quickInsert` | Zap, Plus, PlusCircle, SquarePlus, FilePlus, BadgePlus |
| 제어 흐름 | `control` | Settings, SlidersHorizontal, ToggleLeft, Workflow, GitBranch, Cog |
| 바코드 스캔 | `barcode_scan` | ScanLine, QrCode, Camera, Scan, ScanBarcode, Focus |
| 운행알림 및 종료 | `operation_control` | Truck, Car, MapPin, Navigation2, Route, Bell |
| 이벤트 발송 | `event` | Send, Bell, Radio, Megaphone, Podcast, BellRing |
| 복사 | `copy` | Copy, ClipboardCopy, Files, CopyPlus, Duplicate, ClipboardList |

**적절한 아이콘이 없는 액션 (숨김 처리된 deprecated 액션들):**

| 버튼 액션 | 값 | 안내 문구 |
|-----------|-----|----------|
| 연관 데이터 버튼 모달 열기 | `openRelatedModal` | 안내 문구 표시 + 커스텀 아이콘 추가 가능 |
| (deprecated) 데이터 전달 + 모달 | `openModalWithData` | 안내 문구 표시 + 커스텀 아이콘 추가 가능 |
| 테이블 이력 보기 | `view_table_history` | 안내 문구 표시 + 커스텀 아이콘 추가 가능 |
| 코드 병합 | `code_merge` | 안내 문구 표시 + 커스텀 아이콘 추가 가능 |
| 공차등록 | `empty_vehicle` | 안내 문구 표시 + 커스텀 아이콘 추가 가능 |

> 안내 문구: "적절한 추천 아이콘이 없습니다. 텍스트 모드를 사용하거나 아래에서 아이콘을 직접 추가하세요."
> 안내 문구 아래에 커스텀 아이콘 목록 + lucide 검색/SVG 붙여넣기 버튼이 표시됨

#### 3-2. 아이콘 선택 UI

- 액션별 추천 아이콘을 4~6열 그리드로 표시
- 각 아이콘은 32x32 크기, 호버 시 하이라이트, 선택 시 ring 표시
- 아이콘 아래에 이름 표시 (`text-[10px]`)
- 관리자가 추가한 커스텀 아이콘이 있으면 "커스텀 아이콘" 구분선 아래 함께 표시

#### 3-3. 아이콘 크기 비율 설정

버튼 높이 대비 비율로 아이콘 크기를 설정 (정사각형 유지):

**프리셋 (ToggleGroup, 4단계):**

| 이름 | 버튼 높이 대비 | 설명 |
|------|--------------|------|
| 작게 | 40% | 컴팩트한 아이콘 |
| 보통 | 55% | 기본값, 대부분의 버튼에 적합 |
| 크게 | 70% | 존재감 있는 크기 |
| 매우 크게 | 85% | 아이콘 강조, 버튼에 꽉 차는 느낌 |

- px 직접 입력은 제거 (비율 기반이므로 버튼 크기 변경 시 아이콘도 자동 비례)
- 저장: `icon.size`에 프리셋 문자열(`"보통"`) 저장
- 렌더링: `height: N%` + `aspect-ratio: 1/1`로 정사각형 유지

#### 3-4. 아이콘 색상 설정

아이콘 크기 아래에 아이콘 전용 색상 설정:

- **컬러 피커**: 기존 버튼 색상 설정과 동일한 UI 사용
- **기본값**: 미설정 (= `textColor` 상속, 기존 동작과 동일)
- **설정 시**: lucide 아이콘은 지정한 색상으로 덮어쓰기
- **외부 SVG**: 고유 색상이 하드코딩된 SVG는 이 설정의 영향을 받지 않음 (원본 유지)
- **초기화 버튼**: "텍스트 색상과 동일" 버튼으로 별도 색상 해제 가능

| 상황 | iconColor 설정 | 결과 |
|------|---------------|------|
| lucide 아이콘, iconColor 미설정 | 없음 | textColor 상속 (기존 동작) |
| lucide 아이콘, iconColor 설정 | `#22c55e` | 초록색 아이콘 |
| 외부 SVG (고유 색상), iconColor 설정 | `#22c55e` | SVG 원본 색상 유지 (무시) |
| 외부 SVG (currentColor), iconColor 설정 | `#22c55e` | 초록색 아이콘 |

#### 3-5. 텍스트 위치 설정 (아이콘+텍스트 모드 전용)

아이콘 대비 텍스트의 배치 방향을 4방향으로 설정:

| 위치 | 값 | 레이아웃 | 설명 |
|------|-----|---------|------|
| 왼쪽 | `left` | `텍스트 ← 아이콘` | 텍스트가 아이콘 왼쪽 (가로) |
| 오른쪽 | `right` | `아이콘 → 텍스트` | 기본값, 아이콘 뒤에 텍스트 (가로) |
| 위쪽 | `top` | 텍스트 위, 아이콘 아래 | 세로 배치 |
| 아래쪽 | `bottom` | 아이콘 위, 텍스트 아래 | 세로 배치 |

- 기본값: `"right"` (아이콘 오른쪽에 텍스트)
- 저장: `componentConfig.iconTextPosition`
- 아이콘 모드에서는 이 옵션이 숨겨짐 (텍스트가 없으므로 불필요)

#### 3-6. 아이콘-텍스트 간격 설정 (아이콘+텍스트 모드 전용)

아이콘+텍스트 모드에서 아이콘과 텍스트 사이 간격을 조절:

- **슬라이더**: 0~32px 범위 시각적 조절
- **직접 입력**: px 수치 직접 입력 (최솟값 0, 최댓값 제한 없음)
- **기본값**: 6px
- 아이콘 모드에서는 이 옵션이 숨겨짐 (텍스트가 없으므로 불필요)

#### 3-7. 아이콘 모드 레이아웃 안내

아이콘만 표시하면 텍스트보다 좁은 공간으로 충분하므로 안내 문구 표시:

```
ℹ 아이콘만 표시할 때는 버튼 영역의 가로 폭을 줄여 정사각형에 가깝게 만들면 더 깔끔합니다.
```

- `bg-blue-50 dark:bg-blue-950/20` 배경의 안내 박스
- 아이콘 모드(`"icon"`)에서만 표시, 아이콘+텍스트 모드에서는 숨김

#### 3-8. 디폴트 아이콘 자동 부여

아이콘/아이콘+텍스트 모드 전환 시 아이콘이 미선택 상태이면 **디폴트 아이콘을 자동으로 부여**한다.

| 상황 | 디폴트 아이콘 |
|------|-------------|
| 추천 아이콘이 있는 액션 (save, delete 등) | 해당 액션의 **첫 번째 추천 아이콘** (예: save → Check) |
| 추천 아이콘이 없는 액션 (deprecated 등) | 범용 폴백 아이콘: `SquareMousePointer` |

**커스텀 아이콘 삭제 시:**
- 현재 선택된 커스텀 아이콘을 삭제하면 **디폴트 아이콘으로 자동 복귀** (텍스트 모드로 빠지지 않음)
- 아이콘 모드를 유지한 채 디폴트 아이콘이 캔버스에 즉시 반영됨

#### 3-9. 커스텀 아이콘 추가/삭제

**방법 1: lucide 아이콘 검색으로 추가**
- "아이콘 추가" 버튼 클릭 시 lucide 아이콘 전체 검색 가능한 모달/팝오버 표시
- 검색 입력 → 아이콘 이름으로 필터링 → 선택하면 커스텀 목록에 추가

**방법 2: 외부 SVG 붙여넣기로 추가**
- "SVG 붙여넣기" 버튼 클릭 시 텍스트 입력 영역(textarea) 표시
- 외부에서 복사한 SVG 코드를 붙여넣기 → 미리보기로 확인 → "추가" 버튼으로 등록
- SVG 유효성 검사: `<svg` 태그가 포함된 올바른 SVG인지 확인, 아니면 에러 메시지
- 추가 시 관리자가 아이콘 이름을 직접 입력 (목록에서 구분용)
- 저장 형태: SVG 문자열을 `customSvgIcons` 배열에 `{ name, svg }` 객체로 저장

**공통 규칙:**
- 추가된 커스텀 아이콘(lucide/SVG 모두)은 **모든 버튼 액션의 아이콘 후보에 공통으로 노출**
- 커스텀 아이콘에 X 버튼으로 삭제 가능

---

## 데이터 구조

### componentConfig 확장

```typescript
interface ButtonComponentConfig {
  text: string;                    // 기존: 버튼 텍스트
  displayMode: "text" | "icon" | "icon-text"; // 신규: 표시 모드 (기본값: "text")
  icon?: {
    name: string;                  // lucide 아이콘 이름 또는 커스텀 SVG 아이콘 이름
    type: "lucide" | "svg";        // 아이콘 출처 구분 (기본값: "lucide")
    size: "작게" | "보통" | "크게" | "매우 크게"; // 버튼 높이 대비 비율 프리셋 (기본값: "보통")
    color?: string;                  // 아이콘 색상 (미설정 시 textColor 상속)
  };
  iconGap?: number;                // 아이콘-텍스트 간격 px (기본값: 6, 아이콘+텍스트 모드 전용)
  iconTextPosition?: "right" | "left" | "top" | "bottom"; // 텍스트 위치 (기본값: "right", 아이콘+텍스트 모드 전용)
  customIcons?: string[];          // 관리자가 추가한 lucide 커스텀 아이콘 이름 목록
  customSvgIcons?: Array<{         // 관리자가 붙여넣기한 외부 SVG 아이콘 목록
    name: string;                  // 관리자가 지정한 아이콘 이름
    svg: string;                   // SVG 문자열 원본
  }>;
  action: {
    type: string;                  // 기존: 버튼 액션 타입
    // ...기존 action 속성들 유지
  };
}
```

### 저장 예시

```json
{
  "text": "저장",
  "displayMode": "icon",
  "icon": {
    "name": "Check",
    "type": "lucide",
    "size": "보통",
    "color": "#22c55e"
  },
  "customIcons": ["Rocket", "Star"],
  "customSvgIcons": [
    {
      "name": "회사로고",
      "svg": "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'>...</svg>"
    }
  ],
  "action": {
    "type": "save"
  }
}
```

---

## 시각적 동작 예시

### ButtonConfigPanel (디자이너 편집 모드)

```
표시 모드: [ 텍스트 | (아이콘) | 아이콘+텍스트 ]  ← 아이콘 선택됨

아이콘 선택:
┌──────────────────────────────────┐
│  추천 아이콘 (저장)               │
│  ┌────┐ ┌────┐ ┌────┐ ┌────┐   │
│  │ ✓  │ │ 💾 │ │ ✓○ │ │ ○✓ │   │
│  │Check│ │Save│ │Chk○│ │○Chk│   │
│  └────┘ └────┘ └────┘ └────┘   │
│  ┌────┐ ┌────┐                   │
│  │📄✓│ │🛡✓│                   │
│  │FChk│ │ShCk│                   │
│  └────┘ └────┘                   │
│                                  │
│  ── 커스텀 아이콘 ──             │
│  ┌────┐ ┌────┐ ┌────┐           │
│  │ 🚀 │ │ ⭐ │ │[로고]│          │
│  │Rckt │ │Star│ │회사  │          │
│  │  ✕ │ │  ✕│ │  ✕ │          │
│  └────┘ └────┘ └────┘           │
│  [+ lucide 검색] [+ SVG 붙여넣기]│
└──────────────────────────────────┘

아이콘 크기 비율: [ 작게 | (보통) | 크게 | 매우 크게 ]
텍스트 위치:     [ 왼쪽 | (오른쪽) | 위쪽 | 아래쪽 ]  ← 아이콘+텍스트 모드에서만 표시
아이콘-텍스트 간격: [━━━━━○━━] [6] px  ← 아이콘+텍스트 모드에서만 표시
아이콘 색상: [■ #22c55e] [텍스트 색상과 동일]

ℹ 아이콘만 표시할 때는 버튼 영역의 가로 폭을 줄여 정사각형에 가깝게 만들면 더 깔끔합니다.
```

### 실제 화면 렌더링

| 모드 | 표시 |
|------|------|
| 텍스트 모드 | `[  저장  ]` |
| 아이콘 모드 (보통, 55%) | `[ ✓ ]` |
| 아이콘 모드 (매우 크게, 85%) | `[ ✓ ]` |
| 아이콘+텍스트 (텍스트 오른쪽) | `[ ✓ 저장 ]` (간격 6px) |
| 아이콘+텍스트 (텍스트 왼쪽) | `[ 저장 ✓ ]` |
| 아이콘+텍스트 (텍스트 아래쪽) | 아이콘 위, 텍스트 아래 (세로) |
| 아이콘+텍스트 (색상 분리) | `[ 초록✓ 검정저장 ]` |

---

## 변경 대상

### 수정 파일

| 파일 | 변경 내용 |
|------|----------|
| `ButtonConfigPanel.tsx` | 표시 모드 3종 라디오, 아이콘 그리드, 크기, 색상, 간격 설정, 레이아웃 안내, 커스텀 아이콘 UI |
| `InteractiveScreenViewerDynamic.tsx` | `displayMode` 3종 분기 → 아이콘/아이콘+텍스트/텍스트 렌더링 |
| `InteractiveScreenViewer.tsx` | 동일 분기 추가 |
| `ButtonWidget.tsx` | 동일 분기 추가 |
| `OptimizedButtonComponent.tsx` | 동일 분기 추가 |
| `ScreenDesigner.tsx` | 입력 필드 포커스 시 키보드 단축키 기본 동작 허용 (Ctrl+A/C/V/Z) |
| `RealtimePreviewDynamic.tsx` | 버튼 컴포넌트 position wrapper에서 border 속성 분리 (이중 테두리 방지) |

### 신규 파일

| 파일 | 내용 |
|------|------|
| `frontend/lib/button-icon-map.tsx` | 버튼 액션별 추천 아이콘 매핑 + 아이콘 동적 렌더링 유틸 |

---

## 설계 원칙

- 기본값은 `"text"` 모드 → 기존 모든 버튼은 변경 없이 동작
- `displayMode`가 없거나 `"text"`이면 현행 텍스트 렌더링 유지
- 아이콘/아이콘+텍스트 모드 전환 시 아이콘 미선택이면 **디폴트 아이콘 자동 부여** (빈 상태 방지)
- 커스텀 아이콘 삭제 시 텍스트 모드로 빠지지 않고 **디폴트 아이콘으로 자동 복귀**
- 아이콘 모드에서도 `text` 값은 유지 (접근성 aria-label로 활용)
- 기본 아이콘은 lucide-react 사용 (프로젝트 일관성)
- 외부 SVG 붙여넣기도 지원 → 관리자가 회사 로고 등 자체 아이콘을 등록 가능
- lucide 커스텀 아이콘은 `componentConfig.customIcons`에, SVG 아이콘은 `componentConfig.customSvgIcons`에 저장
- lucide 아이콘 렌더링: 아이콘 이름 → 컴포넌트 매핑, SVG 아이콘 렌더링: `dangerouslySetInnerHTML` + DOMPurify 정화
- **동적 아이콘 로딩**: `iconMap`에 명시적으로 import되지 않은 lucide 아이콘도 `getLucideIcon()` 호출 시 `lucide-react`의 전체 아이콘(`icons`)에서 자동 조회 후 캐싱 → 화면 관리에서 선택한 모든 lucide 아이콘이 실제 화면에서도 렌더링됨
- **커스텀 아이콘 전역 관리 (미구현)**: 커스텀 아이콘을 버튼별(`componentConfig`)이 아닌 시스템 전역(`custom_icon_registry` 테이블)으로 관리하여, 한번 추가한 커스텀 아이콘이 모든 화면의 모든 버튼에서 사용 가능하도록 확장 예정

---

## [미구현] 커스텀 아이콘 전역 관리

### 현재 문제

- 커스텀 아이콘이 `componentConfig.customIcons`에 저장 → **해당 버튼에서만** 보임
- 저장1 버튼에 추가한 커스텀 아이콘이 저장2 버튼, 다른 화면에서는 안 보임
- 같은 아이콘을 쓰려면 매번 검색해서 다시 추가해야 함

### 변경 후 동작

- 커스텀 아이콘을 **회사(company_code) 단위 전역**으로 관리
- 어떤 화면의 어떤 버튼에서든 커스텀 아이콘 추가 → 모든 화면의 모든 버튼에서 커스텀란에 표시
- 버튼 액션 종류와 무관하게 모든 커스텀 아이콘이 노출

### DB 테이블 (신규)

```sql
CREATE TABLE custom_icon_registry (
  id VARCHAR(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,
  company_code VARCHAR(500) NOT NULL,
  icon_name VARCHAR(500) NOT NULL,
  icon_type VARCHAR(500) DEFAULT 'lucide',  -- 'lucide' | 'svg'
  svg_data TEXT,                             -- SVG일 경우 원본 데이터
  created_date TIMESTAMP DEFAULT now(),
  updated_date TIMESTAMP DEFAULT now(),
  writer VARCHAR(500)
);

CREATE INDEX idx_custom_icon_registry_company ON custom_icon_registry(company_code);
```

### 백엔드 API (신규)

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/custom-icons` | 커스텀 아이콘 목록 조회 (company_code 필터) |
| POST | `/api/custom-icons` | 커스텀 아이콘 추가 |
| DELETE | `/api/custom-icons/:id` | 커스텀 아이콘 삭제 |

### 프론트엔드 변경

- `ButtonConfigPanel` — 커스텀 아이콘 조회/추가/삭제를 API 호출로 변경
- 기존 `componentConfig.customIcons` 데이터는 하위 호환으로 병합 표시 (점진적 마이그레이션)
- `componentConfig.customSvgIcons`도 동일하게 전역 테이블로 이관
