# 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) => { 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; skipDefaultSave?: boolean; } // 리피터 저장 (마스터-디테일 FK 연결용) interface RepeaterSaveDetail { parentId?: string | number; masterRecordId: string | number; mainFormData: Record; 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, 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; } ``` #### 연동 흐름 ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ 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; // 현재 데이터 반환 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 // 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` - 내부 테이블 갱신 **커스텀 모드 (displayMode: "custom")**: - 패널 내부에 자유롭게 컴포넌트 배치 가능 (v2-tabs-widget과 동일 구조) - 컴포넌트 클릭 시 좌측 설정 패널에서 속성 편집 - 드래그앤드롭으로 컴포넌트 이동, 리사이즈 핸들로 크기 조절 - 디자인 모드에서 실제 컴포넌트 렌더링 (미리보기) ```typescript // 커스텀 모드 설정 예시 leftPanel: { displayMode: "custom", components: [ { id: "btn-1", componentType: "v2-button-primary", label: "저장", position: { x: 10, y: 10 }, size: { width: 100, height: 40 }, componentConfig: { buttonAction: "save" } } ] } ``` --- ## 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` | 데이터 전달 타입 |