ERP-node/docs/V2_COMPONENT_COUPLING_ANALY...

569 lines
24 KiB
Markdown
Raw Normal View History

# V2 컴포넌트 및 Unified 폼 컴포넌트 결합도 분석 보고서
> 작성일: 2026-01-26
> 목적: 컴포넌트 간 결합도 분석 및 느슨한 결합 전환 가능성 평가
---
## 1. 분석 대상 컴포넌트 목록
### 1.1 V2 컴포넌트 (18개)
| # | 컴포넌트 | 경로 | 주요 용도 |
|---|---------|------|----------|
| 1 | v2-aggregation-widget | `v2-aggregation-widget/` | 데이터 집계 표시 |
| 2 | v2-button-primary | `v2-button-primary/` | 기본 버튼 (저장/삭제/모달 등) |
| 3 | v2-card-display | `v2-card-display/` | 카드 형태 데이터 표시 |
| 4 | v2-category-manager | `v2-category-manager/` | 카테고리 트리 관리 |
| 5 | v2-divider-line | `v2-divider-line/` | 구분선 |
| 6 | v2-location-swap-selector | `v2-location-swap-selector/` | 출발지/도착지 선택 |
| 7 | v2-numbering-rule | `v2-numbering-rule/` | 채번 규칙 표시 |
| 8 | v2-pivot-grid | `v2-pivot-grid/` | 피벗 테이블 |
| 9 | v2-rack-structure | `v2-rack-structure/` | 렉 구조 표시 |
| 10 | v2-repeat-container | `v2-repeat-container/` | 리피터 컨테이너 |
| 11 | v2-repeat-screen-modal | `v2-repeat-screen-modal/` | 반복 화면 모달 |
| 12 | v2-section-card | `v2-section-card/` | 섹션 카드 |
| 13 | v2-section-paper | `v2-section-paper/` | 섹션 페이퍼 |
| 14 | v2-split-panel-layout | `v2-split-panel-layout/` | 분할 패널 레이아웃 |
| 15 | v2-table-list | `v2-table-list/` | 테이블 리스트 |
| 16 | v2-table-search-widget | `v2-table-search-widget/` | 테이블 검색 위젯 |
| 17 | v2-tabs-widget | `v2-tabs-widget/` | 탭 위젯 |
| 18 | v2-text-display | `v2-text-display/` | 텍스트 표시 |
| 19 | v2-unified-repeater | `v2-unified-repeater/` | 통합 리피터 |
### 1.2 Unified 폼 컴포넌트 (11개)
| # | 컴포넌트 | 파일 | 주요 용도 |
|---|---------|------|----------|
| 1 | UnifiedInput | `UnifiedInput.tsx` | 텍스트/숫자/이메일 등 입력 |
| 2 | UnifiedSelect | `UnifiedSelect.tsx` | 선택박스/라디오/체크박스 |
| 3 | UnifiedDate | `UnifiedDate.tsx` | 날짜/시간 입력 |
| 4 | UnifiedRepeater | `UnifiedRepeater.tsx` | 리피터 (테이블 형태) |
| 5 | UnifiedLayout | `UnifiedLayout.tsx` | 레이아웃 컨테이너 |
| 6 | UnifiedGroup | `UnifiedGroup.tsx` | 그룹 컨테이너 (카드/탭/접기) |
| 7 | UnifiedHierarchy | `UnifiedHierarchy.tsx` | 계층 구조 표시 |
| 8 | UnifiedList | `UnifiedList.tsx` | 리스트 표시 |
| 9 | UnifiedMedia | `UnifiedMedia.tsx` | 파일/이미지/비디오 업로드 |
| 10 | UnifiedBiz | `UnifiedBiz.tsx` | 비즈니스 컴포넌트 |
| 11 | UnifiedFormContext | `UnifiedFormContext.tsx` | 폼 상태 관리 컨텍스트 |
---
## 2. 결합도 분석 결과
### 2.1 결합도 유형 분류
| 유형 | 설명 | 문제점 |
|------|------|--------|
| **직접 Import** | 다른 모듈을 직접 import하여 사용 | 변경 시 영향 범위 큼 |
| **CustomEvent** | window.dispatchEvent로 이벤트 발생/수신 | 암묵적 의존성, 타입 안전성 부족 |
| **전역 상태 (window.__)** | window 객체에 전역 변수 저장 | 네임스페이스 충돌, 테스트 어려움 |
| **Context API** | React Context로 상태 공유 | 상대적으로 안전하지만 범위 확장 시 주의 |
### 2.2 V2 컴포넌트 결합도 상세
#### 2.2.1 높은 결합도 (High Coupling) - 우선 개선 대상
| 컴포넌트 | buttonActions Import | CustomEvent 사용 | window.__ 사용 | 결합도 점수 |
|---------|---------------------|------------------|----------------|------------|
| **v2-button-primary** | ✅ 직접 Import | 4개 발생 | ❌ | 🔴 8/10 |
| **v2-table-list** | ❌ | 16개 수신/발생 | 4개 사용 | 🔴 9/10 |
**v2-button-primary 상세:**
```typescript
// 직접 의존
import { ButtonActionExecutor, ButtonActionContext } from "@/lib/utils/buttonActions";
// CustomEvent 발생
window.dispatchEvent(new CustomEvent("refreshTable"));
window.dispatchEvent(new CustomEvent("closeEditModal"));
window.dispatchEvent(new CustomEvent("saveSuccessInModal"));
```
**v2-table-list 상세:**
```typescript
// 전역 상태 사용
window.__relatedButtonsTargetTables
window.__relatedButtonsSelectedData
// CustomEvent 발생
window.dispatchEvent(new CustomEvent("tableListDataChange", { ... }));
// CustomEvent 수신
window.addEventListener("refreshTable", handleRefreshTable);
window.addEventListener("related-button-register", ...);
window.addEventListener("related-button-unregister", ...);
window.addEventListener("related-button-select", ...);
```
#### 2.2.2 중간 결합도 (Medium Coupling)
| 컴포넌트 | buttonActions Import | CustomEvent 사용 | window.__ 사용 | 결합도 점수 |
|---------|---------------------|------------------|----------------|------------|
| **v2-repeat-container** | ❌ | 5개 수신/발생 | ❌ | 🟠 6/10 |
| **v2-split-panel-layout** | ❌ | 3개 수신/발생 | ❌ | 🟠 5/10 |
| **v2-aggregation-widget** | ❌ | 14개 수신 | ❌ | 🟠 6/10 |
| **v2-tabs-widget** | ❌ | 2개 | ❌ | 🟠 4/10 |
**v2-repeat-container 상세:**
```typescript
// CustomEvent 수신
window.addEventListener("beforeFormSave", handleBeforeFormSave);
window.addEventListener("repeaterDataChange", handleDataChange);
window.addEventListener("tableListDataChange", handleDataChange);
```
**v2-aggregation-widget 상세:**
```typescript
// CustomEvent 수신 (다수)
window.addEventListener("tableListDataChange", handleTableListDataChange);
window.addEventListener("repeaterDataChange", handleRepeaterDataChange);
window.addEventListener("selectionChange", handleSelectionChange);
window.addEventListener("tableSelectionChange", handleSelectionChange);
window.addEventListener("rowSelectionChange", handleSelectionChange);
window.addEventListener("checkboxSelectionChange", handleSelectionChange);
```
#### 2.2.3 낮은 결합도 (Low Coupling) - 독립적
| 컴포넌트 | buttonActions Import | CustomEvent 사용 | window.__ 사용 | 결합도 점수 |
|---------|---------------------|------------------|----------------|------------|
| v2-pivot-grid | ❌ | 0개 | window.open만 | 🟢 2/10 |
| v2-card-display | ❌ | 1개 수신 | ❌ | 🟢 2/10 |
| v2-category-manager | ❌ | 2개 (ConfigPanel) | ❌ | 🟢 2/10 |
| v2-divider-line | ❌ | 0개 | ❌ | 🟢 1/10 |
| v2-location-swap-selector | ❌ | 0개 | ❌ | 🟢 1/10 |
| v2-numbering-rule | ❌ | 0개 | ❌ | 🟢 1/10 |
| v2-rack-structure | ❌ | 0개 | ❌ | 🟢 1/10 |
| v2-section-card | ❌ | 1개 (ConfigPanel) | ❌ | 🟢 1/10 |
| v2-section-paper | ❌ | 1개 (ConfigPanel) | ❌ | 🟢 1/10 |
| v2-table-search-widget | ❌ | 0개 | ❌ | 🟢 1/10 |
| v2-text-display | ❌ | 0개 | ❌ | 🟢 1/10 |
| v2-repeat-screen-modal | ❌ | 0개 | ❌ | 🟢 1/10 |
| v2-unified-repeater | ❌ | 0개 | ❌ | 🟢 1/10 |
### 2.3 Unified 폼 컴포넌트 결합도 상세
| 컴포넌트 | buttonActions Import | CustomEvent 사용 | window.__ 사용 | 결합도 점수 |
|---------|---------------------|------------------|----------------|------------|
| **UnifiedRepeater** | ❌ | 7개 수신/발생 | 2개 사용 | 🔴 8/10 |
| **UnifiedFormContext** | ❌ | 3개 발생 | ❌ | 🟠 4/10 |
| UnifiedInput | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedSelect | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedDate | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedLayout | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedGroup | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedHierarchy | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedList | ❌ | 0개 (TableList 래핑) | ❌ | 🟢 2/10 |
| UnifiedMedia | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedBiz | ❌ | 0개 | ❌ | 🟢 1/10 |
**UnifiedRepeater 상세:**
```typescript
// 전역 상태 사용
window.__unifiedRepeaterInstances = new Set();
window.__unifiedRepeaterInstances.add(targetTableName);
// CustomEvent 수신
window.addEventListener("repeaterSave", handleSaveEvent);
window.addEventListener("beforeFormSave", handleBeforeFormSave);
window.addEventListener("componentDataTransfer", handleComponentDataTransfer);
window.addEventListener("splitPanelDataTransfer", handleSplitPanelDataTransfer);
```
**UnifiedFormContext 상세:**
```typescript
// CustomEvent 발생 (레거시 호환)
window.dispatchEvent(new CustomEvent("beforeFormSave", { detail: eventDetail }));
window.dispatchEvent(new CustomEvent("afterFormSave", { detail: { ... } }));
```
---
## 3. 주요 결합 지점 시각화
```
┌─────────────────────────────────────────────────────────────────────────┐
│ buttonActions.ts (7,145줄) │
│ ⬇️ 직접 Import │
│ v2-button-primary ───────────────────────────────────────────────┐
│ │
└─────────────────────────────────────────────────────────────────────────┘
│ CustomEvent
┌─────────────────────────────────────────────────────────────────────────┐
│ Event Bus (현재: window) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ refreshTable │ │beforeFormSave│ │tableListData │ │
│ │ │ │ │ │ Change │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
└─────────│──────────────────│──────────────────│─────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────────┐
│v2-table │ │v2-repeat │ │v2-aggregation │
│ -list │ │-container │ │ -widget │
└───────────┘ └───────────┘ └───────────────┘
│ │
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│Unified │ │Unified │
│Repeater │ │FormContext│
└───────────┘ └───────────┘
```
---
## 4. 이벤트 매트릭스
### 4.1 이벤트 발생 컴포넌트
| 이벤트명 | 발생 컴포넌트 | 용도 |
|---------|-------------|------|
| `refreshTable` | v2-button-primary, buttonActions | 테이블 데이터 새로고침 |
| `closeEditModal` | v2-button-primary, buttonActions | 수정 모달 닫기 |
| `saveSuccessInModal` | v2-button-primary, buttonActions | 저장 성공 알림 (연속 등록) |
| `beforeFormSave` | UnifiedFormContext, buttonActions | 저장 전 데이터 수집 |
| `afterFormSave` | UnifiedFormContext | 저장 완료 알림 |
| `tableListDataChange` | v2-table-list | 테이블 데이터 변경 알림 |
| `repeaterDataChange` | UnifiedRepeater | 리피터 데이터 변경 알림 |
| `repeaterSave` | buttonActions | 리피터 저장 요청 |
| `openScreenModal` | v2-split-panel-layout | 화면 모달 열기 |
| `refreshCardDisplay` | buttonActions | 카드 디스플레이 새로고침 |
### 4.2 이벤트 수신 컴포넌트
| 이벤트명 | 수신 컴포넌트 | 처리 내용 |
|---------|-------------|----------|
| `refreshTable` | v2-table-list, v2-split-panel-layout | 데이터 재조회 |
| `beforeFormSave` | v2-repeat-container, UnifiedRepeater | formData에 섹션 데이터 추가 |
| `tableListDataChange` | v2-aggregation-widget, v2-repeat-container | 집계 재계산, 데이터 동기화 |
| `repeaterDataChange` | v2-aggregation-widget, v2-repeat-container | 집계 재계산, 데이터 동기화 |
| `repeaterSave` | UnifiedRepeater | 리피터 데이터 저장 실행 |
| `selectionChange` | v2-aggregation-widget | 선택 기반 집계 |
| `componentDataTransfer` | UnifiedRepeater | 컴포넌트 간 데이터 전달 |
| `splitPanelDataTransfer` | UnifiedRepeater | 분할 패널 데이터 전달 |
| `refreshCardDisplay` | v2-card-display | 카드 데이터 재조회 |
---
## 5. 전역 상태 사용 현황
| 전역 변수 | 사용 컴포넌트 | 용도 | 위험도 |
|----------|-------------|------|--------|
| `window.__unifiedRepeaterInstances` | UnifiedRepeater, buttonActions | 리피터 인스턴스 추적 | 🟠 중간 |
| `window.__relatedButtonsTargetTables` | v2-table-list | 관련 버튼 대상 테이블 | 🟠 중간 |
| `window.__relatedButtonsSelectedData` | v2-table-list, buttonActions | 관련 버튼 선택 데이터 | 🟠 중간 |
| `window.__dataRegistry` | v2-table-list (v1/v2) | 테이블 데이터 레지스트리 | 🟠 중간 |
---
## 6. 결합도 요약 점수
### 6.1 V2 컴포넌트 (18개)
| 결합도 수준 | 개수 | 컴포넌트 |
|------------|------|---------|
| 🔴 높음 (7-10점) | 2개 | v2-button-primary, v2-table-list |
| 🟠 중간 (4-6점) | 4개 | v2-repeat-container, v2-split-panel-layout, v2-aggregation-widget, v2-tabs-widget |
| 🟢 낮음 (1-3점) | 12개 | 나머지 |
### 6.2 Unified 컴포넌트 (11개)
| 결합도 수준 | 개수 | 컴포넌트 |
|------------|------|---------|
| 🔴 높음 (7-10점) | 1개 | UnifiedRepeater |
| 🟠 중간 (4-6점) | 1개 | UnifiedFormContext |
| 🟢 낮음 (1-3점) | 9개 | 나머지 |
### 6.3 전체 결합도 분포
```
전체 29개 컴포넌트
높은 결합도 (🔴): 3개 (10.3%)
├── v2-button-primary
├── v2-table-list
└── UnifiedRepeater
중간 결합도 (🟠): 5개 (17.2%)
├── v2-repeat-container
├── v2-split-panel-layout
├── v2-aggregation-widget
├── v2-tabs-widget
└── UnifiedFormContext
낮은 결합도 (🟢): 21개 (72.5%)
└── 나머지 모든 컴포넌트
```
---
## 7. 장애 영향 분석
### 7.1 현재 구조에서의 장애 전파 경로
```
v2-button-primary 오류 발생 시:
├── buttonActions.ts 영향 → 모든 저장/삭제 기능 중단
├── refreshTable 이벤트 미발생 → 테이블 갱신 안됨
└── closeEditModal 이벤트 미발생 → 모달 닫기 안됨
v2-table-list 오류 발생 시:
├── tableListDataChange 미발생 → 집계 위젯 업데이트 안됨
├── related-button 이벤트 미발생 → 관련 버튼 비활성화
└── 전역 상태 오염 가능성
UnifiedRepeater 오류 발생 시:
├── beforeFormSave 처리 실패 → 리피터 데이터 저장 누락
├── repeaterSave 수신 실패 → 저장 요청 무시
└── 전역 인스턴스 레지스트리 오류
```
### 7.2 장애 격리 현황
| 컴포넌트 | 장애 시 영향 범위 | 격리 수준 |
|---------|-----------------|----------|
| v2-button-primary | 저장/삭제 전체 | ❌ 격리 안됨 |
| v2-table-list | 집계/관련버튼 | ❌ 격리 안됨 |
| UnifiedRepeater | 리피터 저장 | ❌ 격리 안됨 |
| v2-aggregation-widget | 자신만 | ✅ 부분 격리 |
| v2-repeat-container | 자신만 | ✅ 부분 격리 |
| 나머지 21개 | 자신만 | ✅ 완전 격리 |
---
## 8. 느슨한 결합 전환 권장사항
### 8.1 1단계: 인프라 구축 (1-2일)
1. **V2 EventBus 생성**
- 타입 안전한 이벤트 시스템
- 에러 격리 (Promise.allSettled)
- 구독/발행 패턴
2. **V2 ErrorBoundary 생성**
- 컴포넌트별 장애 격리
- 폴백 UI 제공
- 재시도 기능
### 8.2 2단계: 핵심 컴포넌트 분리 (3-4일)
| 우선순위 | 컴포넌트 | 작업 내용 |
|---------|---------|----------|
| 1 | v2-button-primary | buttonActions 의존성 제거, 독립 저장 서비스 |
| 2 | v2-table-list | 전역 상태 제거, EventBus 전환 |
| 3 | UnifiedRepeater | 전역 상태 제거, EventBus 전환 |
### 8.3 3단계: 이벤트 통합 (2-3일)
| 기존 이벤트 | 신규 이벤트 | 변환 방식 |
|------------|------------|----------|
| `refreshTable` | `v2:table:refresh` | EventBus 발행 |
| `beforeFormSave` | `v2:form:save:before` | EventBus 발행 |
| `tableListDataChange` | `v2:table:data:change` | EventBus 발행 |
| `repeaterSave` | `v2:repeater:save` | EventBus 발행 |
### 8.4 4단계: 레거시 제거 (1-2일)
- `window.__` 전역 변수 → Context API 또는 Zustand
- 기존 CustomEvent → V2 EventBus로 완전 전환
- buttonActions.ts 경량화 (7,145줄 → 분할)
---
## 9. 예상 효과
### 9.1 장애 격리
| 현재 | 전환 후 |
|------|--------|
| 한 컴포넌트 오류 → 연쇄 실패 | 한 컴포넌트 오류 → 해당만 실패 표시 |
| 저장 실패 → 전체 중단 | 저장 실패 → 부분 저장 + 에러 표시 |
### 9.2 유지보수성
| 현재 | 전환 후 |
|------|--------|
| buttonActions.ts 7,145줄 | 여러 서비스로 분리 (각 500줄 이하) |
| 암묵적 이벤트 계약 | 타입 정의된 이벤트 |
| 전역 상태 오염 위험 | Context/Store로 관리 |
### 9.3 테스트 용이성
| 현재 | 전환 후 |
|------|--------|
| 통합 테스트만 가능 | 단위 테스트 가능 |
| 모킹 어려움 | EventBus 모킹 용이 |
---
## 10. 구현 현황 (2026-01-26 업데이트)
### 10.1 V2 Core 인프라 (✅ 완료)
다음 핵심 인프라가 구현되었습니다:
| 모듈 | 경로 | 설명 | 상태 |
|------|------|------|------|
| **V2 EventBus** | `lib/v2-core/events/EventBus.ts` | 타입 안전한 이벤트 시스템 | ✅ 완료 |
| **V2 이벤트 타입** | `lib/v2-core/events/types.ts` | 모든 이벤트 타입 정의 | ✅ 완료 |
| **V2 ErrorBoundary** | `lib/v2-core/components/V2ErrorBoundary.tsx` | 컴포넌트별 에러 격리 | ✅ 완료 |
| **레거시 어댑터** | `lib/v2-core/adapters/LegacyEventAdapter.ts` | CustomEvent ↔ EventBus 브릿지 | ✅ 완료 |
| **V2 Core 초기화** | `lib/v2-core/init.ts` | 앱 시작 시 초기화 | ✅ 완료 |
### 10.2 컴포넌트 마이그레이션 현황
| 컴포넌트 | V2 EventBus 적용 | ErrorBoundary 적용 | 레거시 지원 | 상태 |
|---------|-----------------|-------------------|-------------|------|
| **v2-button-primary** | ✅ | ✅ | ✅ | 완료 |
| **v2-table-list** | ✅ | - | ✅ | 완료 |
| **UnifiedRepeater** | ✅ | - | ✅ | 완료 |
### 10.3 아키텍처 특징
**점진적 마이그레이션 지원:**
- 레거시 `window.dispatchEvent` 이벤트와 V2 EventBus 이벤트가 **양방향 브릿지**로 연결됨
- 기존 코드 수정 없이 새 시스템 도입 가능
- 모든 V2 이벤트는 자동으로 레거시 CustomEvent로도 발행됨
**에러 격리:**
- V2ErrorBoundary로 감싼 컴포넌트는 에러 발생 시 해당 컴포넌트만 에러 UI 표시
- 다른 컴포넌트는 정상 작동 유지
- 재시도 버튼으로 복구 가능
### 10.4 사용 방법
```typescript
// 이벤트 발행
import { v2EventBus, V2_EVENTS } from "@/lib/v2-core";
v2EventBus.emit(V2_EVENTS.TABLE_REFRESH, {
tableName: "item_info",
target: "single",
});
// 이벤트 구독
const unsubscribe = v2EventBus.subscribe(
V2_EVENTS.TABLE_REFRESH,
(payload) => {
console.log("테이블 새로고침:", payload.tableName);
},
{ componentId: "my-component" }
);
// 정리
useEffect(() => {
return () => unsubscribe();
}, []);
```
---
## 11. 결론
### 11.1 현재 상태 요약
- **전체 29개 컴포넌트 중 72.5%(21개)는 이미 낮은 결합도**를 가지고 있어 독립적으로 동작
- **핵심 문제 컴포넌트 3개 (v2-button-primary, v2-table-list, UnifiedRepeater) 마이그레이션 완료**
- **buttonActions.ts (7,145줄)**는 추후 분할 예정 (현재는 동작 유지)
### 11.2 달성 목표
**V2 Core 인프라 구축 완료**
- 타입 안전한 EventBus
- 컴포넌트별 ErrorBoundary
- 레거시 호환 어댑터
- 앱 초기화 연동
### 11.3 다음 단계
1. **buttonActions.ts 분할** - 서비스별 모듈 분리
2. **나머지 중간 결합도 컴포넌트 마이그레이션** (v2-repeat-container, v2-split-panel-layout 등)
3. **전역 상태 (window.__) 제거** - Context API 또는 Zustand로 전환
---
## 부록 A: 파일 위치 참조
```
frontend/
├── lib/
│ ├── registry/
│ │ └── components/
│ │ ├── v2-aggregation-widget/
│ │ ├── v2-button-primary/
│ │ ├── v2-card-display/
│ │ ├── v2-category-manager/
│ │ ├── v2-divider-line/
│ │ ├── v2-location-swap-selector/
│ │ ├── v2-numbering-rule/
│ │ ├── v2-pivot-grid/
│ │ ├── v2-rack-structure/
│ │ ├── v2-repeat-container/
│ │ ├── v2-repeat-screen-modal/
│ │ ├── v2-section-card/
│ │ ├── v2-section-paper/
│ │ ├── v2-split-panel-layout/
│ │ ├── v2-table-list/
│ │ ├── v2-table-search-widget/
│ │ ├── v2-tabs-widget/
│ │ ├── v2-text-display/
│ │ └── v2-unified-repeater/
│ └── utils/
│ └── buttonActions.ts (7,145줄)
└── components/
└── unified/
├── UnifiedInput.tsx
├── UnifiedSelect.tsx
├── UnifiedDate.tsx
├── UnifiedRepeater.tsx
├── UnifiedLayout.tsx
├── UnifiedGroup.tsx
├── UnifiedHierarchy.tsx
├── UnifiedList.tsx
├── UnifiedMedia.tsx
├── UnifiedBiz.tsx
└── UnifiedFormContext.tsx
```
## 부록 B: V2 Core 파일 구조 (구현됨)
```
frontend/lib/v2-core/
├── index.ts # 메인 내보내기
├── init.ts # 앱 초기화
├── events/
│ ├── index.ts
│ ├── types.ts # 이벤트 타입 정의
│ └── EventBus.ts # 이벤트 버스 구현
├── components/
│ ├── index.ts
│ └── V2ErrorBoundary.tsx # 에러 바운더리
└── adapters/
├── index.ts
└── LegacyEventAdapter.ts # 레거시 브릿지
```
## 부록 C: 이벤트 타입 정의 (구현됨)
전체 이벤트 타입은 `frontend/lib/v2-core/events/types.ts`에 정의되어 있습니다.
주요 이벤트:
| 이벤트 | 설명 |
|--------|------|
| `v2:table:refresh` | 테이블 새로고침 |
| `v2:table:data:change` | 테이블 데이터 변경 |
| `v2:form:save:collect` | 폼 저장 전 데이터 수집 |
| `v2:modal:close` | 모달 닫기 |
| `v2:modal:save:success` | 모달 저장 성공 |
| `v2:repeater:save` | 리피터 저장 |
| `v2:component:error` | 컴포넌트 에러 |