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- 내부 테이블 갱신
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 |
데이터 전달 타입 |