# INVYONE 화면 디자이너 기반 템플릿 구조 — FieldConfig + DataPort 통합

**작성일**: 2026-04-11
**진입점**: `/admin/builder`
**기반**: `frontend/components/screen/ScreenDesigner.tsx` (VEX 화면 디자이너)

---

## 0. 방향성

- INVYONE 의 **템플릿 = 화면 디자이너로 제작한 화면**
- 제작 도구: VEX 화면 디자이너 (`ScreenDesigner`)
- 제작된 화면은 대시보드에서 카드로 배치된다
- 각 컴포넌트는 **FieldConfig** 로 필드를 정의하고, **DataPort** 로 통신한다
- 화면 디자이너의 자유배치 UX 는 그대로 유지

---

## 1. 현재 구조

### 1.1 ScreenDefinition
- 위치: `frontend/types/index.ts`
- 핵심 필드: `screen_id`, `screen_name`, `screen_code`, `table_name`, `layout_data`
- `layout_data.components: ComponentData[]` — 자유배치 컴포넌트 배열

### 1.2 ComponentData
- `type`, `position { x, y, w, h }`, `config`, `style`
- `config` 는 컴포넌트 타입마다 다른 구조:
  - `v2-table-list`: `{ columns: ColumnConfig[], cardConfig, pagination, filter, actions, ... }`
  - `v2-table-search-widget`: 검색 필드 정의
  - `v2-button-primary`: 버튼 액션
  - 등

### 1.3 ComponentRegistry
- 위치: `frontend/lib/registry/ComponentRegistry.ts`
- 각 `v2-*` 컴포넌트가 `ComponentDefinition` 으로 등록
- 필드: `id`, `default_config`, `default_size`, `component`, `config_panel`, `category`, `tags`

### 1.4 이벤트 통신
- 위치: `frontend/lib/v2-core/events/EventBus.ts`
- 현재는 각 컴포넌트가 암묵적으로 publish/subscribe
- DataPort 의 런타임 구현체로 재사용 예정

---

## 2. FieldConfig 통합

### 2.1 목표
화면의 모든 컴포넌트가 **유일한 필드 규격** 을 공유한다. 테이블 컬럼, 폼 입력, 검색 필터가 전부 같은 `FieldConfig` 배열에서 파생된다.

### 2.2 FieldConfig 스펙 (이미 정의됨)

```typescript
// frontend/types/invyone-component.ts
interface FieldConfig {
  column: string;           // DB 컬럼명 (유일 키)
  label: string;            // 표시 라벨
  type: FieldType;          // text/number/date/select/entity/...
  visible: boolean;
  order: number;
  required: boolean;
  editable: boolean;
  searchable?: boolean;
  sortable?: boolean;
  width?: number;
  align?: 'left' | 'center' | 'right';
  defaultValue?: unknown;
  options?: FieldOption[];  // select 타입
  ref?: FieldRef;           // entity 타입 (FK)
  format?: string;
  computed?: string;
  pk?: boolean;
  system?: boolean;
}
```

### 2.3 ScreenDefinition 확장

```typescript
interface ScreenDefinition {
  // ... 기존 필드
  fields?: FieldConfig[];       // 신규: 화면 수준 필드 규격
  connections?: Connection[];   // 신규: DataPort 연결 (§3)
}
```

- 화면 수준에서 필드 목록을 **한 번만** 정의
- 각 컴포넌트는 이 `fields` 를 참조해 자기 용도로 파생
- `table_name` 이 있으면 빌더에서 메타 API 로 자동 초기화

### 2.4 어댑터 함수

`frontend/lib/fieldConfig/adapters.ts` (신규)

```typescript
export function fieldsToColumns(fields: FieldConfig[]): ColumnConfig[]
export function fieldsToSearchFields(fields: FieldConfig[]): SearchFieldConfig[]
export function fieldsToFormFields(fields: FieldConfig[]): FormFieldConfig[]
```

- FieldConfig → 각 컴포넌트 내부 포맷으로 변환
- 역방향 필요 시 `columnsToFields` 등도 추가

### 2.5 컴포넌트 props 경로

각 `v2-*` 컴포넌트는 두 경로를 지원:

| 경로 | 설명 | 우선순위 |
|---|---|---|
| **(a) 구 경로** | `config.columns: ColumnConfig[]` 등 컴포넌트별 구조 | 호환용 |
| **(b) 신 경로** | 화면 수준 `fields: FieldConfig[]` 를 props 로 받아 내부 변환 | 신규 권장 |

fields 가 있으면 (b) 우선, 없으면 (a) fallback.

### 2.6 빌더 UX — "필드 관리" 패널

`ScreenDesigner` 의 기존 패널 시스템에 "필드" 탭 추가:

- 화면 수준 `fields` 배열 편집
- `table_name` 설정 시 메타 API `/api/meta/fields/{table}` 로 초기 FieldConfig[] 자동 생성
- 각 필드 편집:
  - label, type, visible, searchable, required, editable, order
  - type=select 시 options 편집
  - type=entity 시 ref 편집 (팝업 검색 대상 테이블/컬럼)
- 저장 시 `ScreenDefinition.fields` 로 직렬화

각 컴포넌트의 속성 패널에는:
- "이 컴포넌트에서 사용할 필드 선택" (체크박스, 화면 수준 fields 의 subset)
- 또는 "필드별 오버라이드" (라벨/폭 등 컴포넌트 단위 조정)

---

## 3. DataPort 통합

### 3.1 목표
컴포넌트 간 통신을 **명시적 Port 연결** 로 표현한다. 현재 `v2EventBus` 는 암묵적이다. DataPort 는 "어느 컴포넌트가 무엇을 받고 내보내는지" 를 **메타로 선언** 하고, 빌더에서 시각적으로 연결한다.

### 3.2 DataPort 스펙 (이미 정의됨)

```typescript
// frontend/types/invyone-component.ts
type DataPortType = 'row' | 'rows' | 'value' | 'params';

interface DataPort {
  name: string;          // 'selectedRow', 'searchParams' 등
  type: DataPortType;
  connectedTo?: string;  // 대상 컴포넌트 ID (Connection 으로 대체 가능)
}

interface Connection {
  id: string;
  from: { componentId: string; port: string };
  to: { componentId: string; port: string };
}
```

### 3.3 ComponentDefinition 확장

각 `v2-*` 컴포넌트의 `createComponentDefinition({ ... })` 에 `dataPorts` 추가:

```typescript
createComponentDefinition({
  id: 'v2-table-list',
  // ... 기존 필드
  dataPorts: {
    inputs: [
      { name: 'searchParams', type: 'params' },
      { name: 'refreshTrigger', type: 'value' },
    ],
    outputs: [
      { name: 'selectedRow', type: 'row' },
      { name: 'selectedRows', type: 'rows' },
    ],
  },
});
```

우선순위 1 컴포넌트 정의 (초안):

| 컴포넌트 | inputs | outputs |
|---|---|---|
| `v2-table-list` | `searchParams`, `refreshTrigger` | `selectedRow`, `selectedRows` |
| `v2-table-search-widget` | — | `searchParams` |
| `v2-button-primary` | `disabled`, `formData` | `clicked` |
| `v2-input` / `v2-select` / `v2-date` | `value` | `value`, `changed` |
| `v2-aggregation-widget` | `data` | — |
| `v2-text-display` | `text` | — |
| `v2-card-display` | `items` | `selected` |

### 3.4 ScreenDefinition 확장

```typescript
interface ScreenDefinition {
  // ... 기존 + fields
  connections?: Connection[];
}
```

- 빌더에서 사용자가 시각적으로 연결 → `connections` 배열로 저장
- 화면 로드 시 런타임이 이 배열을 읽어 EventBus 브리지 설정

### 3.5 런타임 구현

`frontend/lib/dataPort/runtime.ts` (신규)

```typescript
/**
 * 화면 로드 시 Connection 배열을 EventBus 브리지로 변환.
 * from 컴포넌트가 output 포트에 publish → to 컴포넌트가 input 포트로 subscribe.
 */
export function setupConnections(
  connections: Connection[],
  bus: EventBus,
): () => void  // cleanup 함수 반환
```

- 기존 `v2-core/events/EventBus` 를 그대로 사용
- DataPort 는 그 위의 **선언적 레이어**
- 컴포넌트는 포트 단위로 publish/subscribe — 직접 이벤트 이름 다룰 필요 없음

### 3.6 빌더 UX — Connection 시각화

`ScreenDesigner` 캔버스 위에 포트 UI 추가:

- 선택된 컴포넌트의 `inputs` / `outputs` 가 컴포넌트 **가장자리에 원형 포트** 로 표시
  - inputs: 왼쪽
  - outputs: 오른쪽
- 포트를 드래그 → 다른 컴포넌트의 포트로 드롭 → Connection 생성
- 타입 체크: `output.type === input.type` 이어야 연결 허용 (`row` → `row`, `params` → `params`)
- 연결선은 SVG 로 렌더 (베지어 곡선)
- 속성 패널에 "연결" 탭 추가 — 현재 연결 목록 + 수동 추가/삭제

---

## 4. 구현 순서

### Step 1: 타입 확장
- [ ] `ScreenDefinition.fields?: FieldConfig[]` 추가
- [ ] `ScreenDefinition.connections?: Connection[]` 추가
- [ ] `ComponentDefinition.dataPorts?: { inputs, outputs }` 추가
- 위치: `frontend/types/index.ts` 와 `frontend/types/component.ts`

### Step 2: 어댑터 함수
- [ ] `frontend/lib/fieldConfig/adapters.ts` 신규
- [ ] `fieldsToColumns(fields)` — v2-table-list 의 `ColumnConfig` 로 변환
- [ ] `fieldsToSearchFields(fields)` — v2-table-search-widget 포맷으로
- [ ] `fieldsToFormFields(fields)` — 폼 컴포넌트 포맷으로
- [ ] 단위 테스트 (선택)

### Step 3: v2-table-list 통합
- [ ] TableListComponent props 에 `fields?: FieldConfig[]` 추가
- [ ] 내부에서 fields 가 있으면 `fieldsToColumns` 로 `config.columns` override
- [ ] 기존 `config.columns` 전달 경로 유지 (호환)
- [ ] ComponentDefinition 에 `dataPorts` 선언

### Step 4: ScreenDesigner "필드" 패널
- [ ] 기존 패널 시스템에 `"fields"` 탭 추가
- [ ] `table_name` 선택 시 `/api/meta/fields/{table}` 호출 → FieldConfig[] 자동 생성
- [ ] 필드 편집 UI (label/type/visible/searchable/required/order)
- [ ] 저장 시 `ScreenDefinition.fields` 에 직렬화
- [ ] 기존 v2-table-list 의 컬럼 설정과 연동 (fields 변경 → table-list 자동 반영)

### Step 5: DataPort 런타임
- [ ] `frontend/lib/dataPort/runtime.ts` 신규
- [ ] `setupConnections(connections, bus)` 구현
- [ ] 화면 로드 시점 (ScreenDefinition 렌더러) 에 호출
- [ ] cleanup 함수로 unmount 시 브리지 해제

### Step 6: DataPort 빌더 UX
- [ ] 컴포넌트 가장자리 포트 원 렌더
- [ ] 포트 드래그 & 드롭으로 Connection 생성
- [ ] SVG 연결선 렌더 (베지어)
- [ ] 타입 호환성 체크
- [ ] 속성 패널 "연결" 탭

### Step 7: 나머지 v2-* 컴포넌트 통합
- [ ] 우선순위 1 컴포넌트 전부에 `dataPorts` 선언
- [ ] 각 컴포넌트 props 에 `fields` 경로 추가
- [ ] 점진 확대 (우선순위 2/3 는 이후)

---

## 5. 호환성 원칙

1. **기존 데이터 그대로 동작** — `ScreenDefinition.fields` / `connections` 는 옵셔널. 기존 화면은 수정 없이 그대로 작동.
2. **컴포넌트 props 이중 경로** — 구 경로 (`config.columns`) 와 신 경로 (`fields`) 공존. fields 가 있으면 우선.
3. **EventBus 내부 유지** — DataPort 는 그 위의 선언 레이어. EventBus 자체는 재작성 안 함.
4. **점진 마이그레이션** — 한 번에 전부 바꾸지 않고 컴포넌트 단위로 통합.

---

## 6. 확인 필요 사항 (다음 세션에서 코드 확인)

- `frontend/types/index.ts` 의 `ScreenDefinition` 정확한 정의 — 어떤 필드들이 이미 있는지
- `frontend/lib/v2-core/events/EventBus.ts` 의 publish/subscribe API 시그니처
- `v2-table-list` 가 내부에서 `ColumnConfig[]` 를 어떻게 consuming 하는지 (props → state → render)
- `/api/meta/fields/{table}` 응답 포맷 — FieldConfig 와 매핑 가능한지
- 기존 ScreenDefinition 에 `fields` 비슷한 필드가 이미 있는지 (중복 정의 방지)
- `ComponentDefinition` 타입 경로 — `dataPorts` 필드 추가할 위치

---

## 7. 연결 관계도

```
ScreenDefinition
  ├── fields: FieldConfig[]          ← 화면 수준 필드 규격
  ├── layout_data.components[]       ← 자유배치 컴포넌트 (기존)
  │      └── 각 컴포넌트가 fields 참조 (adapter 로 변환)
  └── connections: Connection[]      ← DataPort 연결 목록
           │
           └── 런타임에 EventBus 브리지로 변환

ComponentRegistry
  └── 각 v2-* ComponentDefinition
         ├── dataPorts.inputs[]      ← 이 컴포넌트가 받는 포트
         └── dataPorts.outputs[]     ← 이 컴포넌트가 내보내는 포트

v2-core EventBus (내부 구현체)
  └── DataPort 런타임이 사용
```

---

## 끝

화면 디자이너 = 템플릿 제작 도구  
FieldConfig = 필드 규격 통합  
DataPort = 컴포넌트 통신 명시  

이 3가지로 VEX 화면 디자이너 위에 INVYONE 계층을 얹는다.
