- Deleted the following files as they are no longer relevant to the current project structure: - 결재 시스템 구현 현황 - 결재 시스템 v2 사용 가이드 - WACE 시스템 문제점 분석 및 개선 계획 - Agent Pipeline 한계점 분석 - AI 기반 화면 자동 생성 시스템 설계서 - WACE ERP Backend - 분석 문서 인덱스 These deletions help streamline the documentation and remove obsolete information, ensuring that only current and relevant resources are maintained.
74 KiB
V2 컴포넌트 연동 가이드
목차
1. 개요
V2 컴포넌트들은 세 가지 메커니즘을 통해 상호 통신합니다:
| 메커니즘 | 용도 | 특징 |
|---|---|---|
| 이벤트 시스템 | 비동기 통신, 느슨한 결합 | V2 표준 이벤트 타입 사용 |
| Context 시스템 | 상태 공유, 동기 통신 | React Context API |
| 데이터 전달 인터페이스 | 명시적 데이터 전송 | DataProvidable / DataReceivable |
2. V2 표준 이벤트 시스템
2.1 이벤트 타입 정의 파일
파일 위치: frontend/types/component-events.ts
모든 V2 컴포넌트는 이 파일에 정의된 타입 안전한 이벤트 시스템을 사용해야 합니다.
2.2 이벤트 이름 상수
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 유틸리티 함수
타입 안전한 이벤트 발행
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", // 타입 에러!
});
타입 안전한 이벤트 구독
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 이벤트 상세 타입
// 테이블 리스트 데이터 변경
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 마이그레이션 가이드
이전 방식 (사용 금지):
// ❌ 타입 안전하지 않음
window.addEventListener("tableListDataChange" as any, handler);
window.dispatchEvent(new CustomEvent("repeaterDataChange", { detail }));
새로운 방식 (권장):
// ✅ 타입 안전함
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 } |
// 발행 예시
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 } |
// 마스터-디테일 저장 흐름
// 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[] } |
// 테이블 리스트 → 집계 위젯 연동
// 테이블 데이터 변경 시 자동으로 집계 갱신
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 |
// 저장 후 테이블 새로고침
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 구조
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 인터페이스
데이터를 제공하는 컴포넌트가 구현하는 인터페이스
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 인터페이스
데이터를 수신하는 컴포넌트가 구현하는 인터페이스
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
데이터 전달 시 설정
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 구현:
getSelectedData(): any[] // 체크된 행 데이터
getAllData(): any[] // 전체 데이터
clearSelection(): void // 선택 초기화
DataReceivable 구현:
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- 활성 탭 기반 테이블 필터링
동작:
TableOptionsContext.registeredTables에서 테이블 목록 조회- 사용자가 필터 입력
currentTable.onFilterChange(filters)호출- 해당 테이블이 자동으로 재조회
v2-aggregation-widget
구독 이벤트:
tableListDataChange- 테이블 데이터 변경 시 집계 갱신repeaterDataChange- 리피터 데이터 변경 시 집계 갱신
v2-split-panel-layout
Provider 제공:
SplitPanelContext- 좌우 패널 데이터 전달
발행 이벤트:
openScreenModal- 화면 모달 열기
구독 이벤트:
refreshTable- 내부 테이블 갱신
커스텀 모드 (displayMode: "custom"):
- 패널 내부에 자유롭게 컴포넌트 배치 가능 (v2-tabs-widget과 동일 구조)
- 컴포넌트 클릭 시 좌측 설정 패널에서 속성 편집
- 드래그앤드롭으로 컴포넌트 이동, 리사이즈 핸들로 크기 조절
- 디자인 모드에서 실제 컴포넌트 렌더링 (미리보기)
// 커스텀 모드 설정 예시
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 설정 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
설정 방법:
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 발행 → 테이블 갱신 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
설정 방법:
// 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 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
설정 방법:
// 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" │
│ │
└─────────────────────────────────────────────────────────────────────────┘
설정 방법:
// 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-widgetv2-table-list
설정:
// 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
설정:
{
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-repeaterv2-button-primary
설정:
// 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(타겟)
설정:
// 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-repeaterv2-aggregation-widget
설정:
// 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 |
데이터 전달 타입 |