ERP-node/docs/V2_컴포넌트_연동_가이드.md

1497 lines
74 KiB
Markdown
Raw Normal View History

# V2 컴포넌트 연동 가이드
## 목차
1. [개요](#1-개요)
2. [V2 표준 이벤트 시스템](#2-v2-표준-이벤트-시스템)
3. [이벤트 시스템 상세](#3-이벤트-시스템-상세)
4. [Context 시스템](#4-context-시스템)
5. [데이터 전달 인터페이스](#5-데이터-전달-인터페이스)
6. [컴포넌트별 연동 능력](#6-컴포넌트별-연동-능력)
7. [연동 가능한 조합](#7-연동-가능한-조합)
8. [연동 설정 방법](#8-연동-설정-방법)
---
## 1. 개요
V2 컴포넌트들은 세 가지 메커니즘을 통해 상호 통신합니다:
| 메커니즘 | 용도 | 특징 |
|----------|------|------|
| **이벤트 시스템** | 비동기 통신, 느슨한 결합 | V2 표준 이벤트 타입 사용 |
| **Context 시스템** | 상태 공유, 동기 통신 | React Context API |
| **데이터 전달 인터페이스** | 명시적 데이터 전송 | `DataProvidable` / `DataReceivable` |
---
## 2. V2 표준 이벤트 시스템
### 2.1 이벤트 타입 정의 파일
**파일 위치**: `frontend/types/component-events.ts`
모든 V2 컴포넌트는 이 파일에 정의된 **타입 안전한 이벤트 시스템**을 사용해야 합니다.
### 2.2 이벤트 이름 상수
```typescript
import { V2_EVENTS } from "@/types/component-events";
// 사용 가능한 이벤트
V2_EVENTS.TABLE_LIST_DATA_CHANGE // "tableListDataChange"
V2_EVENTS.REPEATER_DATA_CHANGE // "repeaterDataChange"
V2_EVENTS.BEFORE_FORM_SAVE // "beforeFormSave"
V2_EVENTS.AFTER_FORM_SAVE // "afterFormSave"
V2_EVENTS.REPEATER_SAVE // "repeaterSave"
V2_EVENTS.REFRESH_TABLE // "refreshTable"
V2_EVENTS.REFRESH_CARD_DISPLAY // "refreshCardDisplay"
V2_EVENTS.COMPONENT_DATA_TRANSFER // "componentDataTransfer"
V2_EVENTS.SPLIT_PANEL_DATA_TRANSFER // "splitPanelDataTransfer"
```
### 2.3 유틸리티 함수
#### 타입 안전한 이벤트 발행
```typescript
import { dispatchV2Event, V2_EVENTS } from "@/types/component-events";
// 올바른 방법 (타입 안전)
dispatchV2Event(V2_EVENTS.REPEATER_DATA_CHANGE, {
componentId: "my-repeater",
tableName: "order_detail",
data: rows,
selectedData: selectedRows,
});
// 잘못된 방법 (타입 오류 발생)
dispatchV2Event(V2_EVENTS.REPEATER_DATA_CHANGE, {
wrongField: "value", // 타입 에러!
});
```
#### 타입 안전한 이벤트 구독
```typescript
import { subscribeV2Event, V2_EVENTS, type RepeaterDataChangeDetail } from "@/types/component-events";
useEffect(() => {
// 구독 (자동 cleanup 함수 반환)
const unsubscribe = subscribeV2Event(
V2_EVENTS.REPEATER_DATA_CHANGE,
(event: CustomEvent<RepeaterDataChangeDetail>) => {
const { componentId, data } = event.detail;
// 타입 안전하게 데이터 접근
}
);
return () => unsubscribe();
}, []);
```
### 2.4 이벤트 상세 타입
```typescript
// 테이블 리스트 데이터 변경
interface TableListDataChangeDetail {
componentId: string;
tableName: string;
data: any[];
selectedRows: string[] | number[];
}
// 리피터 데이터 변경
interface RepeaterDataChangeDetail {
componentId: string;
tableName: string;
data: any[];
selectedData?: any[];
}
// 폼 저장 전
interface BeforeFormSaveDetail {
formData: Record<string, any>;
skipDefaultSave?: boolean;
}
// 리피터 저장 (마스터-디테일 FK 연결용)
interface RepeaterSaveDetail {
parentId?: string | number;
masterRecordId: string | number;
mainFormData: Record<string, any>;
tableName: string;
}
// 컴포넌트 간 데이터 전달
interface ComponentDataTransferDetail {
sourceComponentId: string;
targetComponentId: string;
data: any[];
mode: "append" | "replace" | "merge";
mappingRules?: MappingRule[];
}
```
### 2.5 마이그레이션 가이드
**이전 방식 (사용 금지)**:
```typescript
// ❌ 타입 안전하지 않음
window.addEventListener("tableListDataChange" as any, handler);
window.dispatchEvent(new CustomEvent("repeaterDataChange", { detail }));
```
**새로운 방식 (권장)**:
```typescript
// ✅ 타입 안전함
import { subscribeV2Event, dispatchV2Event, V2_EVENTS } from "@/types/component-events";
const unsubscribe = subscribeV2Event(V2_EVENTS.TABLE_LIST_DATA_CHANGE, handler);
dispatchV2Event(V2_EVENTS.REPEATER_DATA_CHANGE, detail);
```
---
## 3. 이벤트 시스템 상세
### 3.1 저장 관련 이벤트
#### `beforeFormSave`
폼 저장 직전에 발생하여 각 컴포넌트가 데이터를 수집할 기회를 제공합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `buttonActions.ts`, `UnifiedFormContext.tsx` |
| **구독자** | `UnifiedRepeater`, `SimpleRepeaterTable`, `ModalRepeaterTable`, `SelectedItemsDetailInput`, `RepeatScreenModal`, `UniversalFormModal` |
| **데이터 구조** | `{ formData: Record<string, any>, skipDefaultSave?: boolean }` |
```typescript
// 발행 예시
window.dispatchEvent(new CustomEvent("beforeFormSave", {
detail: { formData: {}, skipDefaultSave: false }
}));
// 구독 예시
window.addEventListener("beforeFormSave", (event: CustomEvent) => {
const { formData } = event.detail;
formData["myField"] = myValue; // 데이터 추가
});
```
#### `afterFormSave`
폼 저장 완료 후 발생합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `UnifiedFormContext.tsx` |
| **데이터 구조** | `{ success: boolean, data?: any }` |
#### `repeaterSave`
마스터 저장 후 리피터에 FK를 전달하기 위해 발생합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `InteractiveScreenViewerDynamic.tsx` |
| **구독자** | `UnifiedRepeater.tsx` |
| **데이터 구조** | `{ parentId, masterRecordId, mainFormData, tableName }` |
```typescript
// 마스터-디테일 저장 흐름
// 1. 마스터 저장 완료
// 2. repeaterSave 이벤트 발행
window.dispatchEvent(new CustomEvent("repeaterSave", {
detail: {
masterRecordId: savedId, // 마스터 ID
tableName: "receiving_mng",
mainFormData: formData
}
}));
// 3. UnifiedRepeater에서 수신
// → 모든 행의 foreignKeyColumn에 masterRecordId 설정
// → 디테일 테이블에 저장
```
---
### 3.2 데이터 변경 이벤트
#### `tableListDataChange`
테이블 리스트의 데이터가 변경될 때 발생합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `v2-table-list`, `table-list` |
| **구독자** | `v2-repeat-container`, `v2-aggregation-widget`, `repeat-container`, `aggregation-widget` |
| **데이터 구조** | `{ componentId, tableName, data: any[], selectedRows: string[] }` |
```typescript
// 테이블 리스트 → 집계 위젯 연동
// 테이블 데이터 변경 시 자동으로 집계 갱신
```
#### `repeaterDataChange`
리피터 컴포넌트의 데이터가 변경될 때 발생합니다.
| 항목 | 내용 |
|------|------|
| **구독자** | `v2-repeat-container`, `v2-aggregation-widget` |
---
### 3.3 UI 갱신 이벤트
#### `refreshTable`
테이블 데이터를 다시 로드합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `v2-button-primary`, `InteractiveScreenViewerDynamic`, `ScreenModal`, `buttonActions.ts` |
| **구독자** | `v2-table-list`, `v2-split-panel-layout`, `InteractiveDataTable` |
```typescript
// 저장 후 테이블 새로고침
window.dispatchEvent(new CustomEvent("refreshTable"));
```
#### `refreshCardDisplay`
카드 디스플레이를 다시 로드합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `InteractiveScreenViewerDynamic`, `buttonActions.ts` |
| **구독자** | `v2-card-display`, `card-display` |
---
### 3.4 모달 제어 이벤트
#### `openEditModal`
편집 모달을 엽니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `SplitPanelLayout2`, `InteractiveScreenViewer`, `InteractiveDataTable` |
| **구독자** | `EditModal.tsx`, 화면 페이지 |
#### `closeEditModal`
편집 모달을 닫습니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `v2-button-primary`, `buttonActions.ts` |
| **구독자** | `EditModal.tsx` |
#### `saveSuccessInModal`
모달 내 저장 성공 시 발생합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `v2-button-primary`, `buttonActions.ts` |
| **구독자** | `ScreenModal.tsx` |
---
### 3.5 데이터 전달 이벤트
#### `componentDataTransfer`
컴포넌트 간 데이터 전달 시 발생합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `buttonActions.ts` |
| **구독자** | `UnifiedRepeater.tsx` |
#### `splitPanelDataTransfer`
분할 패널 간 데이터 전달 시 발생합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `buttonActions.ts` |
| **구독자** | `UnifiedRepeater.tsx`, `RepeaterFieldGroupRenderer.tsx` |
#### `screenDataTransfer`
화면 간 데이터 전달 시 발생합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `buttonActions.ts`, `useScreenDataTransfer.ts` |
| **구독자** | `useScreenDataTransfer.ts` |
---
### 3.6 연관 데이터 버튼 이벤트
#### `related-button-select`
연관 데이터 버튼 클릭 시 발생합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `RelatedDataButtonsComponent.tsx` |
| **구독자** | `v2-table-list`, `table-list`, `InteractiveDataTable` |
| **데이터 구조** | `{ targetTable, filterColumn, filterValue, selectedData }` |
#### `related-button-register` / `related-button-unregister`
연관 데이터 버튼이 대상 테이블을 등록/해제합니다.
| 항목 | 내용 |
|------|------|
| **발행자** | `RelatedDataButtonsComponent.tsx` |
| **구독자** | `v2-table-list`, `table-list` |
---
### 3.7 이벤트 흐름 다이어그램
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 저장 플로우 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [저장 버튼 클릭] │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ beforeFormSave │ ────────────────────────────────────────────┐ │
│ └────────┬────────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ UnifiedRepeater │ │ SimpleRepeater │ │ ModalRepeater │ ... │
│ │ (데이터 수집) │ │ (데이터 수집) │ │ (데이터 수집) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ API 저장 │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ afterFormSave │ │ repeaterSave │ (마스터-디테일 시) │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ refreshTable │ │ UnifiedRepeater │ │
│ └─────────────────┘ │ (FK 설정 후 저장)│ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 데이터 변경 플로우 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ v2-table-list │ │
│ │ (데이터 로드/변경)│ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ tableListDataChange │ │
│ └────────┬────────────┘ │
│ │ │
│ ├─────────────────────┬─────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │v2-aggregation- │ │v2-repeat- │ │ 기타 구독자 │ │
│ │widget (집계갱신) │ │container │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## 4. Context 시스템
### 4.1 TableOptionsContext
**역할**: 화면 내 테이블 컴포넌트 등록/관리 및 필터링 연동
**파일**: `frontend/contexts/TableOptionsContext.tsx`
#### 제공하는 기능
| 함수/상태 | 설명 |
|-----------|------|
| `registeredTables` | 등록된 테이블 Map |
| `selectedTableId` | 현재 선택된 테이블 ID |
| `registerTable(tableId, registration)` | 테이블 등록 |
| `unregisterTable(tableId)` | 테이블 해제 |
| `getTable(tableId)` | 테이블 조회 |
| `setSelectedTableId(id)` | 선택 테이블 설정 |
| `updateTableDataCount(tableId, count)` | 데이터 건수 업데이트 |
| `getActiveTabTables()` | 활성 탭의 테이블만 반환 |
#### TableRegistration 구조
```typescript
interface TableRegistration {
tableId: string;
tableName: string;
columns: ColumnInfo[];
dataCount: number;
parentTabId?: string; // 소속 탭 ID
onFilterChange: (filters: TableFilter[]) => void;
getColumnUniqueValues: (columnName: string) => Promise<SelectOption[]>;
}
```
#### 연동 흐름
```
┌─────────────────────────────────────────────────────────────────────────┐
│ TableOptionsContext 연동 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ v2-table-list │ │
│ └────────┬────────┘ │
│ │ registerTable() │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ TableOptionsContext │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ registeredTables │ │ │
│ │ │ - tableId │ │ │
│ │ │ - onFilterChange() │ │ │
│ │ │ - columns │ │ │
│ │ └─────────────────────────────┘ │ │
│ └────────────────┬────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ v2-table-search-widget │ │
│ │ - 등록된 테이블 목록 표시 │ │
│ │ - 필터 입력 │ │
│ │ - currentTable.onFilterChange() │ │
│ └────────────────┬────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ v2-table-list (자동 재조회) │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
#### 사용 컴포넌트
| 컴포넌트 | 사용 방식 |
|----------|----------|
| `v2-table-list` | 테이블 등록/해제, 데이터 건수 업데이트 |
| `v2-table-search-widget` | 등록된 테이블 목록 조회, 필터 적용 |
| `v2-split-panel-layout` | 내부 테이블 등록/해제 |
| `v2-card-display` | 테이블 등록 (선택적) |
---
### 4.2 SplitPanelContext
**역할**: 좌우 분할 패널 간 데이터 전달 및 상태 관리
**파일**: `frontend/contexts/SplitPanelContext.tsx`
#### 제공하는 기능
| 함수/상태 | 설명 |
|-----------|------|
| `splitPanelId` | 분할 패널 ID |
| `leftScreenId`, `rightScreenId` | 좌우 화면 ID |
| `selectedLeftData` | 좌측 선택 데이터 |
| `setSelectedLeftData(data)` | 좌측 선택 데이터 설정 |
| `addedItemIds` | 우측에 추가된 항목 ID Set |
| `addItemIds(ids)` | 항목 ID 추가 |
| `registerReceiver(receiver)` | 데이터 수신자 등록 |
| `transferToOtherSide(data)` | 반대편으로 데이터 전달 |
| `linkedFilters` | 연결 필터 설정 |
| `parentDataMapping` | 부모 데이터 매핑 설정 |
#### 연동 흐름
```
┌─────────────────────────────────────────────────────────────────────────┐
│ SplitPanelContext 연동 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-split-panel-layout │ │
│ │ (SplitPanelProvider) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 좌측 패널 │ │ 우측 패널 │ │
│ │ (CardDisplay) │ │ (TableList) │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ │ 행 클릭 │ │
│ ▼ │ │
│ setSelectedLeftData(rowData) │ │
│ │ │ │
│ └──────────────────────────────────────────▶│ │
│ │ │
│ relation 설정에 따라 │ │
│ 자동 필터링 (FK 기반) │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 필터링된 데이터 │ │
│ │ 표시 │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
#### 사용 컴포넌트
| 컴포넌트 | 사용 방식 |
|----------|----------|
| `v2-split-panel-layout` | Provider 제공, 좌우 패널 관리 |
| `v2-table-list` | 분할 패널 데이터 수신자로 등록 |
| `v2-card-display` | 분할 패널 위치 확인, 데이터 수신 |
| `v2-button-primary` | 분할 패널 컨텍스트 확인 |
---
### 4.3 ScreenContext
**역할**: 같은 화면 내 컴포넌트 간 통신 (데이터 제공자/수신자 등록)
**파일**: `frontend/contexts/ScreenContext.tsx`
#### 제공하는 기능
| 함수/상태 | 설명 |
|-----------|------|
| `screenId` | 화면 ID |
| `tableName` | 테이블명 |
| `menuObjid` | 메뉴 OBJID (카테고리 값 조회용) |
| `splitPanelPosition` | 분할 패널 위치 (`left` \| `right`) |
| `formData` | 폼 데이터 |
| `updateFormData(field, value)` | 폼 데이터 업데이트 |
| `registerDataProvider(provider)` | 데이터 제공자 등록 |
| `registerDataReceiver(receiver)` | 데이터 수신자 등록 |
| `getDataProvider(id)` | 데이터 제공자 조회 |
| `getDataReceiver(id)` | 데이터 수신자 조회 |
| `getAllDataProviders()` | 모든 데이터 제공자 조회 |
#### 사용 컴포넌트
| 컴포넌트 | 사용 방식 |
|----------|----------|
| `v2-table-list` | 데이터 제공자/수신자로 등록 |
| `v2-card-display` | 화면 컨텍스트 확인 |
| `v2-button-primary` | 화면 컨텍스트 확인, 데이터 전달 실행 |
| `repeater-field-group` | 데이터 수신자 등록, formData 사용 |
---
### 4.4 UnifiedFormContext
**역할**: 폼 상태 관리, 조건부 로직, 저장/검증/초기화
**파일**: `frontend/components/unified/UnifiedFormContext.tsx`
#### 제공하는 기능
| 함수/상태 | 설명 |
|-----------|------|
| `formData` | 폼 데이터 |
| `originalData` | 원본 데이터 (수정 모드) |
| `status` | 폼 상태 (isSubmitting, isDirty, isValid 등) |
| `errors` | 필드 에러 배열 |
| `getValue(field)`, `setValue(field, value)` | 값 관리 |
| `submit(options)` | 폼 저장 |
| `reset()` | 폼 초기화 |
| `validate()` | 폼 검증 |
| `evaluateCondition(condition)` | 조건 평가 |
| `getRepeaterData(key)`, `setRepeaterData(key, data)` | 리피터 데이터 관리 |
---
### 4.5 ActiveTabContext
**역할**: 탭 컴포넌트의 활성 탭 추적
**파일**: `frontend/contexts/ActiveTabContext.tsx`
#### 제공하는 기능
| 함수/상태 | 설명 |
|-----------|------|
| `activeTabs` | 활성 탭 정보 Map |
| `setActiveTab(tabsId, tabId)` | 활성 탭 설정 |
| `getActiveTabId(tabsId)` | 특정 탭 컴포넌트의 활성 탭 ID |
| `getAllActiveTabIds()` | 전체 활성 탭 ID 목록 |
#### 사용 컴포넌트
| 컴포넌트 | 사용 방식 |
|----------|----------|
| `v2-table-search-widget` | 활성 탭 기반 테이블 필터링 |
| `v2-tabs-widget` | 탭 활성화 관리 |
---
### 4.6 ScreenPreviewContext
**역할**: 디자이너 모드와 실제 화면 모드 구분
**파일**: `frontend/contexts/ScreenPreviewContext.tsx`
#### 제공하는 기능
| 함수/상태 | 설명 |
|-----------|------|
| `isPreviewMode` | 미리보기 모드 여부 |
#### 사용 컴포넌트
| 컴포넌트 | 사용 방식 |
|----------|----------|
| `v2-table-search-widget` | 미리보기 모드에서 설정 버튼 비활성화 |
| `v2-button-primary` | 프리뷰 모드 확인 |
---
### 4.7 Context 계층 구조
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Context 계층 구조 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ScreenPreviewContext (최상위 - 디자이너/실제 화면 구분) │
│ │ │
│ └─── ScreenContext (화면 레벨) │
│ │ │
│ ├─── TableOptionsContext (테이블 관리) │
│ │ │ │
│ │ └─── ActiveTabContext (탭 필터링) │
│ │ │
│ └─── SplitPanelContext (분할 패널 - 선택적) │
│ │
│ UnifiedFormContext (폼 상태 관리 - 독립적, 선택적 사용) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## 5. 데이터 전달 인터페이스
### 5.1 DataProvidable 인터페이스
**데이터를 제공하는 컴포넌트**가 구현하는 인터페이스
```typescript
interface DataProvidable {
componentId: string;
componentType: string;
// 선택된 데이터 반환
getSelectedData(): any[];
// 모든 데이터 반환
getAllData(): any[];
// 선택 초기화
clearSelection(): void;
}
```
#### 구현 컴포넌트
| 컴포넌트 | 제공 데이터 |
|----------|------------|
| `v2-table-list` | 선택된 행 데이터, 전체 데이터 |
| `v2-card-display` | 선택된 카드 데이터 |
| `select-basic` | 선택된 값 |
| `conditional-container` | 조건부 컨테이너의 선택 값 |
### 5.2 DataReceivable 인터페이스
**데이터를 수신하는 컴포넌트**가 구현하는 인터페이스
```typescript
interface DataReceivable {
componentId: string;
componentType: DataReceivableComponentType;
// 데이터 수신
receiveData(data: any[], config: DataReceiverConfig): Promise<void>;
// 현재 데이터 반환
getData(): any;
}
type DataReceivableComponentType =
| "table-list"
| "unified-repeater"
| "repeater-field-group"
| "simple-repeater-table";
```
#### 구현 컴포넌트
| 컴포넌트 | 수신 모드 |
|----------|----------|
| `v2-table-list` | append, replace, merge |
| `repeater-field-group` | append |
| `embedded-screen` | 화면 임베딩 데이터 수신 |
### 5.3 DataReceiverConfig
데이터 전달 시 설정
```typescript
interface DataReceiverConfig {
// 타겟 컴포넌트 정보
targetComponentId: string;
targetComponentType: DataReceivableComponentType;
// 수신 모드
mode: "append" | "replace" | "merge";
// 필드 매핑 규칙
mappingRules: Array<{
sourceField: string; // 소스 필드
targetField: string; // 타겟 필드
transform?: string; // 변환 함수 (선택)
defaultValue?: any; // 기본값 (선택)
}>;
// 조건부 전달
condition?: {
field: string;
operator: "=" | "!=" | ">" | "<";
value: any;
};
// 검증 규칙
validation?: {
required: string[]; // 필수 필드
unique?: string[]; // 중복 불가 필드
};
}
```
### 5.4 데이터 전달 흐름
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 데이터 전달 흐름 (버튼 액션) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ v2-button- │ │
│ │ primary │ action.type = "transferData" │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ handleTransferDataAction() │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ 1. ScreenContext에서 소스 컴포넌트 조회 │ │
│ │ getDataProvider(sourceComponentId) │ │
│ │ │ │
│ │ 2. 소스에서 데이터 가져오기 │ │
│ │ source.getSelectedData() 또는 source.getAllData() │ │
│ │ │ │
│ │ 3. 매핑 규칙 적용 │ │
│ │ mappingRules.forEach(rule => ...) │ │
│ │ │ │
│ │ 4. ScreenContext에서 타겟 컴포넌트 조회 │ │
│ │ getDataReceiver(targetComponentId) │ │
│ │ │ │
│ │ 5. 타겟에 데이터 전달 │ │
│ │ target.receiveData(mappedData, config) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 타겟 컴포넌트 │ │
│ │ (v2-table-list, │ │
│ │ repeater 등) │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## 6. 컴포넌트별 연동 능력
### 6.1 연동 능력 매트릭스
| 컴포넌트 | 이벤트 발행 | 이벤트 구독 | DataProvider | DataReceiver | Context 사용 |
|----------|:-----------:|:-----------:|:------------:|:------------:|:------------:|
| `v2-table-list` | ✅ | ✅ | ✅ | ✅ | TableOptions, Screen, SplitPanel |
| `v2-split-panel-layout` | ✅ | ✅ | ❌ | ❌ | TableOptions (Provider) |
| `v2-unified-repeater` | ✅ | ✅ | ✅ | ✅ | Screen |
| `v2-button-primary` | ✅ | ❌ | ❌ | ❌ | Screen, SplitPanel |
| `v2-table-search-widget` | ❌ | ❌ | ❌ | ❌ | TableOptions, ActiveTab |
| `v2-aggregation-widget` | ❌ | ✅ | ❌ | ❌ | - |
| `v2-repeat-container` | ❌ | ✅ | ❌ | ❌ | - |
| `v2-card-display` | ✅ | ✅ | ✅ | ❌ | TableOptions, Screen, SplitPanel |
| `v2-pivot-grid` | ❌ | ❌ | ❌ | ❌ | - |
| `v2-tabs-widget` | ❌ | ❌ | ❌ | ❌ | ActiveTab |
### 6.2 컴포넌트별 상세
#### v2-table-list
**발행 이벤트**:
- `tableListDataChange` - 데이터 로드/변경 시
**구독 이벤트**:
- `refreshTable` - 테이블 새로고침
- `related-button-select` - 연관 버튼 선택
- `related-button-register/unregister` - 연관 버튼 등록/해제
**DataProvidable 구현**:
```typescript
getSelectedData(): any[] // 체크된 행 데이터
getAllData(): any[] // 전체 데이터
clearSelection(): void // 선택 초기화
```
**DataReceivable 구현**:
```typescript
receiveData(data, config): Promise<void>
// mode: "append" - 기존 데이터에 추가
// mode: "replace" - 데이터 교체
// mode: "merge" - 키 기준 병합
```
---
#### v2-unified-repeater
**발행 이벤트**:
- `repeaterDataChange` - 데이터 변경 시 (V2 표준 이벤트)
**구독 이벤트**:
- `beforeFormSave` - 저장 전 데이터 수집
- `repeaterSave` - 마스터 저장 후 FK 설정
- `componentDataTransfer` - 컴포넌트 간 데이터 전달
- `splitPanelDataTransfer` - 분할 패널 간 데이터 전달
**DataProvidable 구현**:
- `getSelectedData()` - 선택된 행 데이터 반환
- `getAllData()` - 전체 데이터 반환
- `clearSelection()` - 선택 초기화
**DataReceivable 구현**:
- `receiveData(data, config)` - 데이터 수신 (append, replace, merge 모드 지원)
- `getData()` - 현재 데이터 반환
**Context 등록**:
- ScreenContext에 DataProvider/DataReceiver 자동 등록
---
#### v2-button-primary
**발행 이벤트**:
- `refreshTable` - 저장 후 테이블 갱신
- `closeEditModal` - 모달 닫기
- `saveSuccessInModal` - 모달 저장 성공
**역할**:
- 저장, 삭제, 데이터 전달 등 액션 실행
- `buttonActions.ts`의 함수들 호출
---
#### v2-table-search-widget
**Context 의존**:
- `TableOptionsContext` - 등록된 테이블 조회, 필터 적용
- `ActiveTabContext` - 활성 탭 기반 테이블 필터링
**동작**:
1. `TableOptionsContext.registeredTables`에서 테이블 목록 조회
2. 사용자가 필터 입력
3. `currentTable.onFilterChange(filters)` 호출
4. 해당 테이블이 자동으로 재조회
---
#### v2-aggregation-widget
**구독 이벤트**:
- `tableListDataChange` - 테이블 데이터 변경 시 집계 갱신
- `repeaterDataChange` - 리피터 데이터 변경 시 집계 갱신
---
#### v2-split-panel-layout
**Provider 제공**:
- `SplitPanelContext` - 좌우 패널 데이터 전달
**발행 이벤트**:
- `openScreenModal` - 화면 모달 열기
**구독 이벤트**:
- `refreshTable` - 내부 테이블 갱신
---
## 7. 연동 가능한 조합
### 7.1 검색/필터 연동
#### v2-table-search-widget ↔ v2-table-list
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 검색 위젯 ↔ 테이블 리스트 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-search-widget │ │
│ │ ┌─────┐ ┌─────────┐ ┌──────────┐ ┌────────┐ │ │
│ │ │ 이름 │ │ 날짜범위 │ │ 상태선택 │ │ 초기화 │ │ │
│ │ └──┬──┘ └────┬────┘ └────┬─────┘ └────────┘ │ │
│ └─────┼─────────┼───────────┼──────────────────────────────────────┘ │
│ │ │ │ │
│ └─────────┴───────────┘ │
│ │ onFilterChange(filters) │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-list │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ 필터링된 데이터 표시 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 연결 방식: TableOptionsContext │
│ 설정: v2-table-search-widget의 targetPanelPosition 설정 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
**설정 방법**:
- `v2-table-search-widget``filterMode`: `"dynamic"` 또는 `"preset"`
- `v2-table-search-widget``targetPanelPosition`: `"left"`, `"right"`, `"auto"`
- `v2-table-list`는 자동으로 `TableOptionsContext`에 등록됨
---
### 7.2 마스터-디테일 연동
#### v2-split-panel-layout (좌측 ↔ 우측)
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 마스터-디테일 (분할 패널) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ v2-split-panel-layout │ │
│ │ ┌────────────────────┐ │ ┌────────────────────────────────┐ │ │
│ │ │ 좌측 패널 │ │ │ 우측 패널 │ │ │
│ │ │ (마스터 목록) │ │ │ (디테일 정보) │ │ │
│ │ │ │ │ │ │ │ │
│ │ │ - 부서 목록 │ │ │ - 선택된 부서의 직원 목록 │ │ │
│ │ │ - dept_info │ ──▶ │ - user_info │ │ │
│ │ │ │ │ │ - dept_code = 선택값 │ │ │
│ │ └────────────────────┘ │ └────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ 연결 방식: SplitPanelContext + relation 설정 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
**설정 방법**:
```typescript
rightPanel: {
tableName: "user_info",
relation: {
type: "detail",
leftColumn: "dept_code", // 좌측 테이블의 컬럼
rightColumn: "dept_code", // 우측 테이블의 필터 컬럼
// 또는 복합키
keys: [
{ leftColumn: "company_id", rightColumn: "company_id" },
{ leftColumn: "dept_code", rightColumn: "dept_code" }
]
}
}
```
---
### 7.3 폼 저장 연동
#### v2-button-primary → v2-unified-repeater
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 폼 저장 + 리피터 저장 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ [폼 입력 필드들] │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 입고번호 │ │ 입고일자 │ │ 거래처 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-unified-repeater (입고 상세) │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ 품목코드 │ 품목명 │ 수량 │ 단가 │ 금액 │ │ │
│ │ ├─────────────────────────────────────────────────────────┤ │ │
│ │ │ ITEM001 │ ... │ 10 │ 1000 │ 10000 │ │ │
│ │ │ ITEM002 │ ... │ 5 │ 2000 │ 10000 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ [저장 버튼] │ │
│ │ v2-button- │ │
│ │ primary │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ 1. beforeFormSave 발행 → 리피터가 데이터 수집 │
│ 2. 마스터 테이블 저장 (receiving_mng) │
│ 3. repeaterSave 발행 → 리피터가 FK 설정 후 저장 │
│ 4. 디테일 테이블 저장 (receiving_detail) │
│ 5. refreshTable 발행 → 테이블 갱신 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
**설정 방법**:
```typescript
// v2-button-primary
action: {
type: "save",
saveMode: "withRepeater", // 리피터와 함께 저장
tableName: "receiving_mng"
}
// v2-unified-repeater
dataSource: {
tableName: "receiving_detail",
foreignKey: "receiving_id", // 마스터 FK 컬럼
referenceKey: "id" // 마스터 PK 컬럼
}
```
---
### 7.4 데이터 전달 연동
#### v2-table-list → v2-unified-repeater (버튼으로 데이터 추가)
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 테이블 선택 → 리피터 추가 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-list (품목 선택) │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ ☑ │ 품목코드 │ 품목명 │ 단가 │ │ │
│ │ ├─────────────────────────────────────────────────────────┤ │ │
│ │ │ ☑ │ ITEM001 │ 노트북 │ 1,000,000 │ │ │
│ │ │ ☑ │ ITEM002 │ 마우스 │ 50,000 │ │ │
│ │ │ ☐ │ ITEM003 │ 키보드 │ 100,000 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ [추가 버튼] │ action.type = "transferData" │
│ │ v2-button- │ │
│ │ primary │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-unified-repeater (주문 상세) │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ 품목코드 │ 품목명 │ 수량 │ 단가 │ 금액 │ │ │
│ │ ├─────────────────────────────────────────────────────────┤ │ │
│ │ │ ITEM001 │ 노트북 │ 1 │ 1,000,000 │ 1,000,000│ │ │
│ │ │ ITEM002 │ 마우스 │ 1 │ 50,000 │ 50,000 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
**설정 방법**:
```typescript
// v2-button-primary
action: {
type: "transferData",
sourceComponentId: "item-table-list",
targetComponentId: "order-detail-repeater",
mappingRules: [
{ sourceField: "item_code", targetField: "item_code" },
{ sourceField: "item_name", targetField: "item_name" },
{ sourceField: "unit_price", targetField: "unit_price" },
{ sourceField: "", targetField: "quantity", defaultValue: 1 }
],
mode: "append"
}
```
---
### 7.5 데이터 집계 연동
#### v2-table-list → v2-aggregation-widget
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 테이블 데이터 → 집계 위젯 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-aggregation-widget │ │
│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
│ │ │ 📊 총 건수 │ │ 💰 총 금액 │ │ 📈 평균 단가 │ │ │
│ │ │ 15건 │ │ ₩3,500,000 │ │ ₩233,333 │ │ │
│ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ tableListDataChange 이벤트 │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-list │ │
│ │ (데이터 변경 시 자동으로 이벤트 발행) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 연결 방식: tableListDataChange 이벤트 자동 구독 │
│ 설정: v2-aggregation-widget의 dataSourceType = "table" │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
**설정 방법**:
```typescript
// v2-aggregation-widget
{
dataSourceType: "table",
items: [
{ columnName: "id", aggregationType: "count", label: "총 건수" },
{ columnName: "amount", aggregationType: "sum", label: "총 금액" },
{ columnName: "unit_price", aggregationType: "avg", label: "평균 단가" }
]
}
```
---
### 7.6 연관 데이터 버튼 연동
#### related-data-buttons ↔ v2-table-list
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 연관 데이터 버튼 ↔ 테이블 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 좌측 패널 (거래처 선택) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-card-display (거래처 목록) │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ [선택됨] ABC상사 │ │ │
│ │ │ DEF물산 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ 선택 데이터 전달 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ related-data-buttons │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 품목정보 │ │ 단가정보 │ │ 거래내역 │ │ │
│ │ │ (5) │ │ (3) │ │ (12) │ │ │
│ │ └────┬─────┘ └──────────┘ └──────────┘ │ │
│ └───────┼─────────────────────────────────────────────────────────┘ │
│ │ related-button-select 이벤트 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-list (품목정보) │ │
│ │ - customer_code = "ABC상사" 조건으로 필터링 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
### 7.7 탭 기반 테이블 필터링
#### v2-tabs-widget → v2-table-search-widget → v2-table-list
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 탭 기반 테이블 필터링 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-tabs-widget │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ [주문] │ │ 입고 │ │ 재고 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ ActiveTabContext │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-search-widget │ │
│ │ (활성 탭에 해당하는 테이블만 대상으로 표시) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ onFilterChange │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-list (주문 탭 내) │ │
│ │ - parentTabId가 활성 탭과 일치하는 테이블만 필터 적용 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## 8. 연동 설정 방법
### 8.1 검색 위젯 + 테이블 리스트
**필요한 컴포넌트**:
- `v2-table-search-widget`
- `v2-table-list`
**설정**:
```typescript
// v2-table-search-widget 설정
{
filterMode: "preset", // 또는 "dynamic"
presetFilters: [
{ columnName: "name", filterType: "text", columnLabel: "이름" },
{ columnName: "created_at", filterType: "date", columnLabel: "등록일" },
{ columnName: "status", filterType: "select", columnLabel: "상태" }
],
targetPanelPosition: "auto" // "left" | "right" | "auto"
}
// v2-table-list는 특별한 설정 불필요 (자동 등록)
```
---
### 8.2 분할 패널 마스터-디테일
**필요한 컴포넌트**:
- `v2-split-panel-layout`
**설정**:
```typescript
{
leftPanel: {
title: "부서 목록",
tableName: "dept_info",
displayMode: "list",
columns: [
{ name: "dept_code", label: "부서코드" },
{ name: "dept_name", label: "부서명" }
]
},
rightPanel: {
title: "직원 목록",
tableName: "user_info",
displayMode: "table",
columns: [
{ name: "user_id", label: "사번" },
{ name: "user_name", label: "이름" },
{ name: "position", label: "직책" }
],
relation: {
type: "detail",
leftColumn: "dept_code",
rightColumn: "dept_code"
}
},
splitRatio: 30,
resizable: true
}
```
---
### 8.3 폼 + 리피터 저장
**필요한 컴포넌트**:
- 입력 컴포넌트들 (text-input, date-input 등)
- `v2-unified-repeater`
- `v2-button-primary`
**설정**:
```typescript
// v2-unified-repeater 설정
{
renderMode: "inline",
dataSource: {
tableName: "order_detail",
foreignKey: "order_id", // 마스터 테이블의 FK
referenceKey: "id" // 마스터 테이블의 PK
},
columns: [
{ name: "item_code", label: "품목코드" },
{ name: "quantity", label: "수량" },
{ name: "unit_price", label: "단가" }
],
features: {
showAddButton: true,
showDeleteButton: true,
inlineEdit: true
}
}
// v2-button-primary 설정
{
text: "저장",
action: {
type: "save",
saveMode: "withRepeater"
}
}
```
---
### 8.4 데이터 전달 (테이블 → 리피터)
**필요한 컴포넌트**:
- `v2-table-list` (소스)
- `v2-button-primary` (전달 트리거)
- `v2-unified-repeater` (타겟)
**설정**:
```typescript
// v2-button-primary 설정
{
text: "추가",
action: {
type: "transferData",
config: {
sourceComponentId: "item-selection-table",
targetComponentId: "order-detail-repeater",
mode: "append",
mappingRules: [
{ sourceField: "item_code", targetField: "item_code" },
{ sourceField: "item_name", targetField: "item_name" },
{ sourceField: "unit_price", targetField: "unit_price" },
{ sourceField: "", targetField: "quantity", defaultValue: 1 }
],
validation: {
unique: ["item_code"] // 중복 방지
}
}
}
}
```
---
### 8.5 집계 위젯 연동
**필요한 컴포넌트**:
- `v2-table-list` 또는 `v2-unified-repeater`
- `v2-aggregation-widget`
**설정**:
```typescript
// v2-aggregation-widget 설정
{
dataSourceType: "table", // 또는 "repeater"
// sourceComponentId는 자동 감지 (같은 화면의 첫 번째 테이블)
items: [
{
id: "total-count",
label: "총 건수",
columnName: "id",
aggregationType: "count",
icon: "FileText"
},
{
id: "total-amount",
label: "총 금액",
columnName: "amount",
aggregationType: "sum",
format: {
prefix: "₩",
thousandSeparator: true
}
}
],
layout: "horizontal",
refreshOnFormChange: true
}
```
---
## 연동 조합 요약표
| 소스 컴포넌트 | 타겟 컴포넌트 | 연동 방식 | 용도 |
|--------------|--------------|----------|------|
| `v2-table-search-widget` | `v2-table-list` | TableOptionsContext | 검색/필터 |
| `v2-split-panel-layout` 좌 | `v2-split-panel-layout` 우 | SplitPanelContext | 마스터-디테일 |
| `v2-button-primary` | `v2-unified-repeater` | beforeFormSave/repeaterSave | 저장 |
| `v2-table-list` | `v2-unified-repeater` | DataProvidable/DataReceivable | 데이터 전달 |
| `v2-table-list` | `v2-aggregation-widget` | tableListDataChange | 집계 |
| `v2-unified-repeater` | `v2-aggregation-widget` | repeaterDataChange | 집계 |
| `v2-tabs-widget` | `v2-table-search-widget` | ActiveTabContext | 탭 필터링 |
| `related-data-buttons` | `v2-table-list` | related-button-select | 연관 데이터 |
| `v2-button-primary` | `v2-table-list` | refreshTable | 새로고침 |
| `v2-card-display` | `v2-table-list` | SplitPanelContext | 선택 연동 |
---
## 관련 파일 참조
| 파일 | 역할 |
|------|------|
| `frontend/lib/utils/buttonActions.ts` | 버튼 액션 실행, 이벤트 발행 |
| `frontend/contexts/TableOptionsContext.tsx` | 테이블 관리 Context |
| `frontend/contexts/SplitPanelContext.tsx` | 분할 패널 Context |
| `frontend/contexts/ScreenContext.tsx` | 화면 Context |
| `frontend/contexts/ActiveTabContext.tsx` | 활성 탭 Context |
| `frontend/components/unified/UnifiedFormContext.tsx` | 폼 Context |
| `frontend/types/data-transfer.ts` | 데이터 전달 타입 |