2026-01-29 23:20:23 +09:00
|
|
|
# V2 컴포넌트 연동 가이드
|
|
|
|
|
|
|
|
|
|
## 목차
|
|
|
|
|
|
|
|
|
|
1. [개요](#1-개요)
|
|
|
|
|
2. [V2 표준 이벤트 시스템](#2-v2-표준-이벤트-시스템)
|
|
|
|
|
3. [이벤트 시스템 상세](#3-이벤트-시스템-상세)
|
|
|
|
|
4. [Context 시스템](#4-context-시스템)
|
|
|
|
|
5. [데이터 전달 인터페이스](#5-데이터-전달-인터페이스)
|
|
|
|
|
6. [컴포넌트별 연동 능력](#6-컴포넌트별-연동-능력)
|
|
|
|
|
7. [연동 가능한 조합](#7-연동-가능한-조합)
|
|
|
|
|
8. [연동 설정 방법](#8-연동-설정-방법)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 1. 개요
|
|
|
|
|
|
|
|
|
|
V2 컴포넌트들은 세 가지 메커니즘을 통해 상호 통신합니다:
|
|
|
|
|
|
|
|
|
|
| 메커니즘 | 용도 | 특징 |
|
|
|
|
|
|----------|------|------|
|
|
|
|
|
| **이벤트 시스템** | 비동기 통신, 느슨한 결합 | V2 표준 이벤트 타입 사용 |
|
|
|
|
|
| **Context 시스템** | 상태 공유, 동기 통신 | React Context API |
|
|
|
|
|
| **데이터 전달 인터페이스** | 명시적 데이터 전송 | `DataProvidable` / `DataReceivable` |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 2. V2 표준 이벤트 시스템
|
|
|
|
|
|
|
|
|
|
### 2.1 이벤트 타입 정의 파일
|
|
|
|
|
|
|
|
|
|
**파일 위치**: `frontend/types/component-events.ts`
|
|
|
|
|
|
|
|
|
|
모든 V2 컴포넌트는 이 파일에 정의된 **타입 안전한 이벤트 시스템**을 사용해야 합니다.
|
|
|
|
|
|
|
|
|
|
### 2.2 이벤트 이름 상수
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { V2_EVENTS } from "@/types/component-events";
|
|
|
|
|
|
|
|
|
|
// 사용 가능한 이벤트
|
|
|
|
|
V2_EVENTS.TABLE_LIST_DATA_CHANGE // "tableListDataChange"
|
|
|
|
|
V2_EVENTS.REPEATER_DATA_CHANGE // "repeaterDataChange"
|
|
|
|
|
V2_EVENTS.BEFORE_FORM_SAVE // "beforeFormSave"
|
|
|
|
|
V2_EVENTS.AFTER_FORM_SAVE // "afterFormSave"
|
|
|
|
|
V2_EVENTS.REPEATER_SAVE // "repeaterSave"
|
|
|
|
|
V2_EVENTS.REFRESH_TABLE // "refreshTable"
|
|
|
|
|
V2_EVENTS.REFRESH_CARD_DISPLAY // "refreshCardDisplay"
|
|
|
|
|
V2_EVENTS.COMPONENT_DATA_TRANSFER // "componentDataTransfer"
|
|
|
|
|
V2_EVENTS.SPLIT_PANEL_DATA_TRANSFER // "splitPanelDataTransfer"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2.3 유틸리티 함수
|
|
|
|
|
|
|
|
|
|
#### 타입 안전한 이벤트 발행
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { dispatchV2Event, V2_EVENTS } from "@/types/component-events";
|
|
|
|
|
|
|
|
|
|
// 올바른 방법 (타입 안전)
|
|
|
|
|
dispatchV2Event(V2_EVENTS.REPEATER_DATA_CHANGE, {
|
|
|
|
|
componentId: "my-repeater",
|
|
|
|
|
tableName: "order_detail",
|
|
|
|
|
data: rows,
|
|
|
|
|
selectedData: selectedRows,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 잘못된 방법 (타입 오류 발생)
|
|
|
|
|
dispatchV2Event(V2_EVENTS.REPEATER_DATA_CHANGE, {
|
|
|
|
|
wrongField: "value", // 타입 에러!
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 타입 안전한 이벤트 구독
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { subscribeV2Event, V2_EVENTS, type RepeaterDataChangeDetail } from "@/types/component-events";
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
// 구독 (자동 cleanup 함수 반환)
|
|
|
|
|
const unsubscribe = subscribeV2Event(
|
|
|
|
|
V2_EVENTS.REPEATER_DATA_CHANGE,
|
|
|
|
|
(event: CustomEvent<RepeaterDataChangeDetail>) => {
|
|
|
|
|
const { componentId, data } = event.detail;
|
|
|
|
|
// 타입 안전하게 데이터 접근
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return () => unsubscribe();
|
|
|
|
|
}, []);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2.4 이벤트 상세 타입
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 테이블 리스트 데이터 변경
|
|
|
|
|
interface TableListDataChangeDetail {
|
|
|
|
|
componentId: string;
|
|
|
|
|
tableName: string;
|
|
|
|
|
data: any[];
|
|
|
|
|
selectedRows: string[] | number[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 리피터 데이터 변경
|
|
|
|
|
interface RepeaterDataChangeDetail {
|
|
|
|
|
componentId: string;
|
|
|
|
|
tableName: string;
|
|
|
|
|
data: any[];
|
|
|
|
|
selectedData?: any[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 폼 저장 전
|
|
|
|
|
interface BeforeFormSaveDetail {
|
|
|
|
|
formData: Record<string, any>;
|
|
|
|
|
skipDefaultSave?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 리피터 저장 (마스터-디테일 FK 연결용)
|
|
|
|
|
interface RepeaterSaveDetail {
|
|
|
|
|
parentId?: string | number;
|
|
|
|
|
masterRecordId: string | number;
|
|
|
|
|
mainFormData: Record<string, any>;
|
|
|
|
|
tableName: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 컴포넌트 간 데이터 전달
|
|
|
|
|
interface ComponentDataTransferDetail {
|
|
|
|
|
sourceComponentId: string;
|
|
|
|
|
targetComponentId: string;
|
|
|
|
|
data: any[];
|
|
|
|
|
mode: "append" | "replace" | "merge";
|
|
|
|
|
mappingRules?: MappingRule[];
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2.5 마이그레이션 가이드
|
|
|
|
|
|
|
|
|
|
**이전 방식 (사용 금지)**:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ❌ 타입 안전하지 않음
|
|
|
|
|
window.addEventListener("tableListDataChange" as any, handler);
|
|
|
|
|
window.dispatchEvent(new CustomEvent("repeaterDataChange", { detail }));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**새로운 방식 (권장)**:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ✅ 타입 안전함
|
|
|
|
|
import { subscribeV2Event, dispatchV2Event, V2_EVENTS } from "@/types/component-events";
|
|
|
|
|
|
|
|
|
|
const unsubscribe = subscribeV2Event(V2_EVENTS.TABLE_LIST_DATA_CHANGE, handler);
|
|
|
|
|
dispatchV2Event(V2_EVENTS.REPEATER_DATA_CHANGE, detail);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 3. 이벤트 시스템 상세
|
|
|
|
|
|
|
|
|
|
### 3.1 저장 관련 이벤트
|
|
|
|
|
|
|
|
|
|
#### `beforeFormSave`
|
|
|
|
|
|
|
|
|
|
폼 저장 직전에 발생하여 각 컴포넌트가 데이터를 수집할 기회를 제공합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `buttonActions.ts`, `UnifiedFormContext.tsx` |
|
|
|
|
|
| **구독자** | `UnifiedRepeater`, `SimpleRepeaterTable`, `ModalRepeaterTable`, `SelectedItemsDetailInput`, `RepeatScreenModal`, `UniversalFormModal` |
|
|
|
|
|
| **데이터 구조** | `{ formData: Record<string, any>, skipDefaultSave?: boolean }` |
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 발행 예시
|
|
|
|
|
window.dispatchEvent(new CustomEvent("beforeFormSave", {
|
|
|
|
|
detail: { formData: {}, skipDefaultSave: false }
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// 구독 예시
|
|
|
|
|
window.addEventListener("beforeFormSave", (event: CustomEvent) => {
|
|
|
|
|
const { formData } = event.detail;
|
|
|
|
|
formData["myField"] = myValue; // 데이터 추가
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### `afterFormSave`
|
|
|
|
|
|
|
|
|
|
폼 저장 완료 후 발생합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `UnifiedFormContext.tsx` |
|
|
|
|
|
| **데이터 구조** | `{ success: boolean, data?: any }` |
|
|
|
|
|
|
|
|
|
|
#### `repeaterSave`
|
|
|
|
|
|
|
|
|
|
마스터 저장 후 리피터에 FK를 전달하기 위해 발생합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `InteractiveScreenViewerDynamic.tsx` |
|
|
|
|
|
| **구독자** | `UnifiedRepeater.tsx` |
|
|
|
|
|
| **데이터 구조** | `{ parentId, masterRecordId, mainFormData, tableName }` |
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 마스터-디테일 저장 흐름
|
|
|
|
|
// 1. 마스터 저장 완료
|
|
|
|
|
// 2. repeaterSave 이벤트 발행
|
|
|
|
|
window.dispatchEvent(new CustomEvent("repeaterSave", {
|
|
|
|
|
detail: {
|
|
|
|
|
masterRecordId: savedId, // 마스터 ID
|
|
|
|
|
tableName: "receiving_mng",
|
|
|
|
|
mainFormData: formData
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// 3. UnifiedRepeater에서 수신
|
|
|
|
|
// → 모든 행의 foreignKeyColumn에 masterRecordId 설정
|
|
|
|
|
// → 디테일 테이블에 저장
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 3.2 데이터 변경 이벤트
|
|
|
|
|
|
|
|
|
|
#### `tableListDataChange`
|
|
|
|
|
|
|
|
|
|
테이블 리스트의 데이터가 변경될 때 발생합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `v2-table-list`, `table-list` |
|
|
|
|
|
| **구독자** | `v2-repeat-container`, `v2-aggregation-widget`, `repeat-container`, `aggregation-widget` |
|
|
|
|
|
| **데이터 구조** | `{ componentId, tableName, data: any[], selectedRows: string[] }` |
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 테이블 리스트 → 집계 위젯 연동
|
|
|
|
|
// 테이블 데이터 변경 시 자동으로 집계 갱신
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### `repeaterDataChange`
|
|
|
|
|
|
|
|
|
|
리피터 컴포넌트의 데이터가 변경될 때 발생합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **구독자** | `v2-repeat-container`, `v2-aggregation-widget` |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 3.3 UI 갱신 이벤트
|
|
|
|
|
|
|
|
|
|
#### `refreshTable`
|
|
|
|
|
|
|
|
|
|
테이블 데이터를 다시 로드합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `v2-button-primary`, `InteractiveScreenViewerDynamic`, `ScreenModal`, `buttonActions.ts` |
|
|
|
|
|
| **구독자** | `v2-table-list`, `v2-split-panel-layout`, `InteractiveDataTable` |
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 저장 후 테이블 새로고침
|
|
|
|
|
window.dispatchEvent(new CustomEvent("refreshTable"));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### `refreshCardDisplay`
|
|
|
|
|
|
|
|
|
|
카드 디스플레이를 다시 로드합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `InteractiveScreenViewerDynamic`, `buttonActions.ts` |
|
|
|
|
|
| **구독자** | `v2-card-display`, `card-display` |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 3.4 모달 제어 이벤트
|
|
|
|
|
|
|
|
|
|
#### `openEditModal`
|
|
|
|
|
|
|
|
|
|
편집 모달을 엽니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `SplitPanelLayout2`, `InteractiveScreenViewer`, `InteractiveDataTable` |
|
|
|
|
|
| **구독자** | `EditModal.tsx`, 화면 페이지 |
|
|
|
|
|
|
|
|
|
|
#### `closeEditModal`
|
|
|
|
|
|
|
|
|
|
편집 모달을 닫습니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `v2-button-primary`, `buttonActions.ts` |
|
|
|
|
|
| **구독자** | `EditModal.tsx` |
|
|
|
|
|
|
|
|
|
|
#### `saveSuccessInModal`
|
|
|
|
|
|
|
|
|
|
모달 내 저장 성공 시 발생합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `v2-button-primary`, `buttonActions.ts` |
|
|
|
|
|
| **구독자** | `ScreenModal.tsx` |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 3.5 데이터 전달 이벤트
|
|
|
|
|
|
|
|
|
|
#### `componentDataTransfer`
|
|
|
|
|
|
|
|
|
|
컴포넌트 간 데이터 전달 시 발생합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `buttonActions.ts` |
|
|
|
|
|
| **구독자** | `UnifiedRepeater.tsx` |
|
|
|
|
|
|
|
|
|
|
#### `splitPanelDataTransfer`
|
|
|
|
|
|
|
|
|
|
분할 패널 간 데이터 전달 시 발생합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `buttonActions.ts` |
|
|
|
|
|
| **구독자** | `UnifiedRepeater.tsx`, `RepeaterFieldGroupRenderer.tsx` |
|
|
|
|
|
|
|
|
|
|
#### `screenDataTransfer`
|
|
|
|
|
|
|
|
|
|
화면 간 데이터 전달 시 발생합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `buttonActions.ts`, `useScreenDataTransfer.ts` |
|
|
|
|
|
| **구독자** | `useScreenDataTransfer.ts` |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 3.6 연관 데이터 버튼 이벤트
|
|
|
|
|
|
|
|
|
|
#### `related-button-select`
|
|
|
|
|
|
|
|
|
|
연관 데이터 버튼 클릭 시 발생합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `RelatedDataButtonsComponent.tsx` |
|
|
|
|
|
| **구독자** | `v2-table-list`, `table-list`, `InteractiveDataTable` |
|
|
|
|
|
| **데이터 구조** | `{ targetTable, filterColumn, filterValue, selectedData }` |
|
|
|
|
|
|
|
|
|
|
#### `related-button-register` / `related-button-unregister`
|
|
|
|
|
|
|
|
|
|
연관 데이터 버튼이 대상 테이블을 등록/해제합니다.
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| **발행자** | `RelatedDataButtonsComponent.tsx` |
|
|
|
|
|
| **구독자** | `v2-table-list`, `table-list` |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 3.7 이벤트 흐름 다이어그램
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 저장 플로우 │
|
|
|
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
|
|
|
│ │
|
|
|
|
|
│ [저장 버튼 클릭] │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ ▼ │
|
|
|
|
|
│ ┌─────────────────┐ │
|
|
|
|
|
│ │ beforeFormSave │ ────────────────────────────────────────────┐ │
|
|
|
|
|
│ └────────┬────────┘ │ │
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ ▼ ▼ │
|
|
|
|
|
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
|
|
|
|
│ │ UnifiedRepeater │ │ SimpleRepeater │ │ ModalRepeater │ ... │
|
|
|
|
|
│ │ (데이터 수집) │ │ (데이터 수집) │ │ (데이터 수집) │ │
|
|
|
|
|
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ ▼ │
|
|
|
|
|
│ ┌─────────────────┐ │
|
|
|
|
|
│ │ API 저장 │ │
|
|
|
|
|
│ └────────┬────────┘ │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ ▼ │
|
|
|
|
|
│ ┌─────────────────┐ ┌─────────────────┐ │
|
|
|
|
|
│ │ afterFormSave │ │ repeaterSave │ (마스터-디테일 시) │
|
|
|
|
|
│ └────────┬────────┘ └────────┬────────┘ │
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ ▼ ▼ │
|
|
|
|
|
│ ┌─────────────────┐ ┌─────────────────┐ │
|
|
|
|
|
│ │ refreshTable │ │ UnifiedRepeater │ │
|
|
|
|
|
│ └─────────────────┘ │ (FK 설정 후 저장)│ │
|
|
|
|
|
│ └─────────────────┘ │
|
|
|
|
|
│ │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 데이터 변경 플로우 │
|
|
|
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
|
|
|
│ │
|
|
|
|
|
│ ┌─────────────────┐ │
|
|
|
|
|
│ │ v2-table-list │ │
|
|
|
|
|
│ │ (데이터 로드/변경)│ │
|
|
|
|
|
│ └────────┬────────┘ │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ ▼ │
|
|
|
|
|
│ ┌─────────────────────┐ │
|
|
|
|
|
│ │ tableListDataChange │ │
|
|
|
|
|
│ └────────┬────────────┘ │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ ├─────────────────────┬─────────────────────┐ │
|
|
|
|
|
│ ▼ ▼ ▼ │
|
|
|
|
|
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
|
|
|
|
│ │v2-aggregation- │ │v2-repeat- │ │ 기타 구독자 │ │
|
|
|
|
|
│ │widget (집계갱신) │ │container │ │ │ │
|
|
|
|
|
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
|
|
|
|
│ │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 4. Context 시스템
|
|
|
|
|
|
|
|
|
|
### 4.1 TableOptionsContext
|
|
|
|
|
|
|
|
|
|
**역할**: 화면 내 테이블 컴포넌트 등록/관리 및 필터링 연동
|
|
|
|
|
|
|
|
|
|
**파일**: `frontend/contexts/TableOptionsContext.tsx`
|
|
|
|
|
|
|
|
|
|
#### 제공하는 기능
|
|
|
|
|
|
|
|
|
|
| 함수/상태 | 설명 |
|
|
|
|
|
|-----------|------|
|
|
|
|
|
| `registeredTables` | 등록된 테이블 Map |
|
|
|
|
|
| `selectedTableId` | 현재 선택된 테이블 ID |
|
|
|
|
|
| `registerTable(tableId, registration)` | 테이블 등록 |
|
|
|
|
|
| `unregisterTable(tableId)` | 테이블 해제 |
|
|
|
|
|
| `getTable(tableId)` | 테이블 조회 |
|
|
|
|
|
| `setSelectedTableId(id)` | 선택 테이블 설정 |
|
|
|
|
|
| `updateTableDataCount(tableId, count)` | 데이터 건수 업데이트 |
|
|
|
|
|
| `getActiveTabTables()` | 활성 탭의 테이블만 반환 |
|
|
|
|
|
|
|
|
|
|
#### TableRegistration 구조
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
interface TableRegistration {
|
|
|
|
|
tableId: string;
|
|
|
|
|
tableName: string;
|
|
|
|
|
columns: ColumnInfo[];
|
|
|
|
|
dataCount: number;
|
|
|
|
|
parentTabId?: string; // 소속 탭 ID
|
|
|
|
|
onFilterChange: (filters: TableFilter[]) => void;
|
|
|
|
|
getColumnUniqueValues: (columnName: string) => Promise<SelectOption[]>;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 연동 흐름
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ TableOptionsContext 연동 │
|
|
|
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
|
|
|
│ │
|
|
|
|
|
│ ┌─────────────────┐ │
|
|
|
|
|
│ │ v2-table-list │ │
|
|
|
|
|
│ └────────┬────────┘ │
|
|
|
|
|
│ │ registerTable() │
|
|
|
|
|
│ ▼ │
|
|
|
|
|
│ ┌─────────────────────────────────────┐ │
|
|
|
|
|
│ │ TableOptionsContext │ │
|
|
|
|
|
│ │ ┌─────────────────────────────┐ │ │
|
|
|
|
|
│ │ │ registeredTables │ │ │
|
|
|
|
|
│ │ │ - tableId │ │ │
|
|
|
|
|
│ │ │ - onFilterChange() │ │ │
|
|
|
|
|
│ │ │ - columns │ │ │
|
|
|
|
|
│ │ └─────────────────────────────┘ │ │
|
|
|
|
|
│ └────────────────┬────────────────────┘ │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ ▼ │
|
|
|
|
|
│ ┌─────────────────────────────────────┐ │
|
|
|
|
|
│ │ v2-table-search-widget │ │
|
|
|
|
|
│ │ - 등록된 테이블 목록 표시 │ │
|
|
|
|
|
│ │ - 필터 입력 │ │
|
|
|
|
|
│ │ - currentTable.onFilterChange() │ │
|
|
|
|
|
│ └────────────────┬────────────────────┘ │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ ▼ │
|
|
|
|
|
│ ┌─────────────────────────────────────┐ │
|
|
|
|
|
│ │ v2-table-list (자동 재조회) │ │
|
|
|
|
|
│ └─────────────────────────────────────┘ │
|
|
|
|
|
│ │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 사용 컴포넌트
|
|
|
|
|
|
|
|
|
|
| 컴포넌트 | 사용 방식 |
|
|
|
|
|
|----------|----------|
|
|
|
|
|
| `v2-table-list` | 테이블 등록/해제, 데이터 건수 업데이트 |
|
|
|
|
|
| `v2-table-search-widget` | 등록된 테이블 목록 조회, 필터 적용 |
|
|
|
|
|
| `v2-split-panel-layout` | 내부 테이블 등록/해제 |
|
|
|
|
|
| `v2-card-display` | 테이블 등록 (선택적) |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 4.2 SplitPanelContext
|
|
|
|
|
|
|
|
|
|
**역할**: 좌우 분할 패널 간 데이터 전달 및 상태 관리
|
|
|
|
|
|
|
|
|
|
**파일**: `frontend/contexts/SplitPanelContext.tsx`
|
|
|
|
|
|
|
|
|
|
#### 제공하는 기능
|
|
|
|
|
|
|
|
|
|
| 함수/상태 | 설명 |
|
|
|
|
|
|-----------|------|
|
|
|
|
|
| `splitPanelId` | 분할 패널 ID |
|
|
|
|
|
| `leftScreenId`, `rightScreenId` | 좌우 화면 ID |
|
|
|
|
|
| `selectedLeftData` | 좌측 선택 데이터 |
|
|
|
|
|
| `setSelectedLeftData(data)` | 좌측 선택 데이터 설정 |
|
|
|
|
|
| `addedItemIds` | 우측에 추가된 항목 ID Set |
|
|
|
|
|
| `addItemIds(ids)` | 항목 ID 추가 |
|
|
|
|
|
| `registerReceiver(receiver)` | 데이터 수신자 등록 |
|
|
|
|
|
| `transferToOtherSide(data)` | 반대편으로 데이터 전달 |
|
|
|
|
|
| `linkedFilters` | 연결 필터 설정 |
|
|
|
|
|
| `parentDataMapping` | 부모 데이터 매핑 설정 |
|
|
|
|
|
|
|
|
|
|
#### 연동 흐름
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ SplitPanelContext 연동 │
|
|
|
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
|
|
|
│ │
|
|
|
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
|
|
|
│ │ v2-split-panel-layout │ │
|
|
|
|
|
│ │ (SplitPanelProvider) │ │
|
|
|
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ ▼ ▼ │
|
|
|
|
|
│ ┌─────────────────┐ ┌─────────────────┐ │
|
|
|
|
|
│ │ 좌측 패널 │ │ 우측 패널 │ │
|
|
|
|
|
│ │ (CardDisplay) │ │ (TableList) │ │
|
|
|
|
|
│ └────────┬────────┘ └────────┬────────┘ │
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ │ 행 클릭 │ │
|
|
|
|
|
│ ▼ │ │
|
|
|
|
|
│ setSelectedLeftData(rowData) │ │
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ └──────────────────────────────────────────▶│ │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ relation 설정에 따라 │ │
|
|
|
|
|
│ 자동 필터링 (FK 기반) │ │
|
|
|
|
|
│ ▼ │
|
|
|
|
|
│ ┌─────────────────┐ │
|
|
|
|
|
│ │ 필터링된 데이터 │ │
|
|
|
|
|
│ │ 표시 │ │
|
|
|
|
|
│ └─────────────────┘ │
|
|
|
|
|
│ │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 사용 컴포넌트
|
|
|
|
|
|
|
|
|
|
| 컴포넌트 | 사용 방식 |
|
|
|
|
|
|----------|----------|
|
|
|
|
|
| `v2-split-panel-layout` | Provider 제공, 좌우 패널 관리 |
|
|
|
|
|
| `v2-table-list` | 분할 패널 데이터 수신자로 등록 |
|
|
|
|
|
| `v2-card-display` | 분할 패널 위치 확인, 데이터 수신 |
|
|
|
|
|
| `v2-button-primary` | 분할 패널 컨텍스트 확인 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 4.3 ScreenContext
|
|
|
|
|
|
|
|
|
|
**역할**: 같은 화면 내 컴포넌트 간 통신 (데이터 제공자/수신자 등록)
|
|
|
|
|
|
|
|
|
|
**파일**: `frontend/contexts/ScreenContext.tsx`
|
|
|
|
|
|
|
|
|
|
#### 제공하는 기능
|
|
|
|
|
|
|
|
|
|
| 함수/상태 | 설명 |
|
|
|
|
|
|-----------|------|
|
|
|
|
|
| `screenId` | 화면 ID |
|
|
|
|
|
| `tableName` | 테이블명 |
|
|
|
|
|
| `menuObjid` | 메뉴 OBJID (카테고리 값 조회용) |
|
|
|
|
|
| `splitPanelPosition` | 분할 패널 위치 (`left` \| `right`) |
|
|
|
|
|
| `formData` | 폼 데이터 |
|
|
|
|
|
| `updateFormData(field, value)` | 폼 데이터 업데이트 |
|
|
|
|
|
| `registerDataProvider(provider)` | 데이터 제공자 등록 |
|
|
|
|
|
| `registerDataReceiver(receiver)` | 데이터 수신자 등록 |
|
|
|
|
|
| `getDataProvider(id)` | 데이터 제공자 조회 |
|
|
|
|
|
| `getDataReceiver(id)` | 데이터 수신자 조회 |
|
|
|
|
|
| `getAllDataProviders()` | 모든 데이터 제공자 조회 |
|
|
|
|
|
|
|
|
|
|
#### 사용 컴포넌트
|
|
|
|
|
|
|
|
|
|
| 컴포넌트 | 사용 방식 |
|
|
|
|
|
|----------|----------|
|
|
|
|
|
| `v2-table-list` | 데이터 제공자/수신자로 등록 |
|
|
|
|
|
| `v2-card-display` | 화면 컨텍스트 확인 |
|
|
|
|
|
| `v2-button-primary` | 화면 컨텍스트 확인, 데이터 전달 실행 |
|
|
|
|
|
| `repeater-field-group` | 데이터 수신자 등록, formData 사용 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 4.4 UnifiedFormContext
|
|
|
|
|
|
|
|
|
|
**역할**: 폼 상태 관리, 조건부 로직, 저장/검증/초기화
|
|
|
|
|
|
|
|
|
|
**파일**: `frontend/components/unified/UnifiedFormContext.tsx`
|
|
|
|
|
|
|
|
|
|
#### 제공하는 기능
|
|
|
|
|
|
|
|
|
|
| 함수/상태 | 설명 |
|
|
|
|
|
|-----------|------|
|
|
|
|
|
| `formData` | 폼 데이터 |
|
|
|
|
|
| `originalData` | 원본 데이터 (수정 모드) |
|
|
|
|
|
| `status` | 폼 상태 (isSubmitting, isDirty, isValid 등) |
|
|
|
|
|
| `errors` | 필드 에러 배열 |
|
|
|
|
|
| `getValue(field)`, `setValue(field, value)` | 값 관리 |
|
|
|
|
|
| `submit(options)` | 폼 저장 |
|
|
|
|
|
| `reset()` | 폼 초기화 |
|
|
|
|
|
| `validate()` | 폼 검증 |
|
|
|
|
|
| `evaluateCondition(condition)` | 조건 평가 |
|
|
|
|
|
| `getRepeaterData(key)`, `setRepeaterData(key, data)` | 리피터 데이터 관리 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 4.5 ActiveTabContext
|
|
|
|
|
|
|
|
|
|
**역할**: 탭 컴포넌트의 활성 탭 추적
|
|
|
|
|
|
|
|
|
|
**파일**: `frontend/contexts/ActiveTabContext.tsx`
|
|
|
|
|
|
|
|
|
|
#### 제공하는 기능
|
|
|
|
|
|
|
|
|
|
| 함수/상태 | 설명 |
|
|
|
|
|
|-----------|------|
|
|
|
|
|
| `activeTabs` | 활성 탭 정보 Map |
|
|
|
|
|
| `setActiveTab(tabsId, tabId)` | 활성 탭 설정 |
|
|
|
|
|
| `getActiveTabId(tabsId)` | 특정 탭 컴포넌트의 활성 탭 ID |
|
|
|
|
|
| `getAllActiveTabIds()` | 전체 활성 탭 ID 목록 |
|
|
|
|
|
|
|
|
|
|
#### 사용 컴포넌트
|
|
|
|
|
|
|
|
|
|
| 컴포넌트 | 사용 방식 |
|
|
|
|
|
|----------|----------|
|
|
|
|
|
| `v2-table-search-widget` | 활성 탭 기반 테이블 필터링 |
|
|
|
|
|
| `v2-tabs-widget` | 탭 활성화 관리 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 4.6 ScreenPreviewContext
|
|
|
|
|
|
|
|
|
|
**역할**: 디자이너 모드와 실제 화면 모드 구분
|
|
|
|
|
|
|
|
|
|
**파일**: `frontend/contexts/ScreenPreviewContext.tsx`
|
|
|
|
|
|
|
|
|
|
#### 제공하는 기능
|
|
|
|
|
|
|
|
|
|
| 함수/상태 | 설명 |
|
|
|
|
|
|-----------|------|
|
|
|
|
|
| `isPreviewMode` | 미리보기 모드 여부 |
|
|
|
|
|
|
|
|
|
|
#### 사용 컴포넌트
|
|
|
|
|
|
|
|
|
|
| 컴포넌트 | 사용 방식 |
|
|
|
|
|
|----------|----------|
|
|
|
|
|
| `v2-table-search-widget` | 미리보기 모드에서 설정 버튼 비활성화 |
|
|
|
|
|
| `v2-button-primary` | 프리뷰 모드 확인 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 4.7 Context 계층 구조
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ Context 계층 구조 │
|
|
|
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
|
|
|
│ │
|
|
|
|
|
│ ScreenPreviewContext (최상위 - 디자이너/실제 화면 구분) │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ └─── ScreenContext (화면 레벨) │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ ├─── TableOptionsContext (테이블 관리) │
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ │ └─── ActiveTabContext (탭 필터링) │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ └─── SplitPanelContext (분할 패널 - 선택적) │
|
|
|
|
|
│ │
|
|
|
|
|
│ UnifiedFormContext (폼 상태 관리 - 독립적, 선택적 사용) │
|
|
|
|
|
│ │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 5. 데이터 전달 인터페이스
|
|
|
|
|
|
|
|
|
|
### 5.1 DataProvidable 인터페이스
|
|
|
|
|
|
|
|
|
|
**데이터를 제공하는 컴포넌트**가 구현하는 인터페이스
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
interface DataProvidable {
|
|
|
|
|
componentId: string;
|
|
|
|
|
componentType: string;
|
|
|
|
|
|
|
|
|
|
// 선택된 데이터 반환
|
|
|
|
|
getSelectedData(): any[];
|
|
|
|
|
|
|
|
|
|
// 모든 데이터 반환
|
|
|
|
|
getAllData(): any[];
|
|
|
|
|
|
|
|
|
|
// 선택 초기화
|
|
|
|
|
clearSelection(): void;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 구현 컴포넌트
|
|
|
|
|
|
|
|
|
|
| 컴포넌트 | 제공 데이터 |
|
|
|
|
|
|----------|------------|
|
|
|
|
|
| `v2-table-list` | 선택된 행 데이터, 전체 데이터 |
|
|
|
|
|
| `v2-card-display` | 선택된 카드 데이터 |
|
|
|
|
|
| `select-basic` | 선택된 값 |
|
|
|
|
|
| `conditional-container` | 조건부 컨테이너의 선택 값 |
|
|
|
|
|
|
|
|
|
|
### 5.2 DataReceivable 인터페이스
|
|
|
|
|
|
|
|
|
|
**데이터를 수신하는 컴포넌트**가 구현하는 인터페이스
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
interface DataReceivable {
|
|
|
|
|
componentId: string;
|
|
|
|
|
componentType: DataReceivableComponentType;
|
|
|
|
|
|
|
|
|
|
// 데이터 수신
|
|
|
|
|
receiveData(data: any[], config: DataReceiverConfig): Promise<void>;
|
|
|
|
|
|
|
|
|
|
// 현재 데이터 반환
|
|
|
|
|
getData(): any;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type DataReceivableComponentType =
|
|
|
|
|
| "table-list"
|
|
|
|
|
| "unified-repeater"
|
|
|
|
|
| "repeater-field-group"
|
|
|
|
|
| "simple-repeater-table";
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 구현 컴포넌트
|
|
|
|
|
|
|
|
|
|
| 컴포넌트 | 수신 모드 |
|
|
|
|
|
|----------|----------|
|
|
|
|
|
| `v2-table-list` | append, replace, merge |
|
|
|
|
|
| `repeater-field-group` | append |
|
|
|
|
|
| `embedded-screen` | 화면 임베딩 데이터 수신 |
|
|
|
|
|
|
|
|
|
|
### 5.3 DataReceiverConfig
|
|
|
|
|
|
|
|
|
|
데이터 전달 시 설정
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
interface DataReceiverConfig {
|
|
|
|
|
// 타겟 컴포넌트 정보
|
|
|
|
|
targetComponentId: string;
|
|
|
|
|
targetComponentType: DataReceivableComponentType;
|
|
|
|
|
|
|
|
|
|
// 수신 모드
|
|
|
|
|
mode: "append" | "replace" | "merge";
|
|
|
|
|
|
|
|
|
|
// 필드 매핑 규칙
|
|
|
|
|
mappingRules: Array<{
|
|
|
|
|
sourceField: string; // 소스 필드
|
|
|
|
|
targetField: string; // 타겟 필드
|
|
|
|
|
transform?: string; // 변환 함수 (선택)
|
|
|
|
|
defaultValue?: any; // 기본값 (선택)
|
|
|
|
|
}>;
|
|
|
|
|
|
|
|
|
|
// 조건부 전달
|
|
|
|
|
condition?: {
|
|
|
|
|
field: string;
|
|
|
|
|
operator: "=" | "!=" | ">" | "<";
|
|
|
|
|
value: any;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 검증 규칙
|
|
|
|
|
validation?: {
|
|
|
|
|
required: string[]; // 필수 필드
|
|
|
|
|
unique?: string[]; // 중복 불가 필드
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 5.4 데이터 전달 흐름
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
|
|
|
│ 데이터 전달 흐름 (버튼 액션) │
|
|
|
|
|
├─────────────────────────────────────────────────────────────────────────┤
|
|
|
|
|
│ │
|
|
|
|
|
│ ┌─────────────────┐ │
|
|
|
|
|
│ │ v2-button- │ │
|
|
|
|
|
│ │ primary │ action.type = "transferData" │
|
|
|
|
|
│ └────────┬────────┘ │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ ▼ │
|
|
|
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
|
|
|
│ │ handleTransferDataAction() │ │
|
|
|
|
|
│ ├─────────────────────────────────────────────────────────────────┤ │
|
|
|
|
|
│ │ 1. ScreenContext에서 소스 컴포넌트 조회 │ │
|
|
|
|
|
│ │ getDataProvider(sourceComponentId) │ │
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ │ 2. 소스에서 데이터 가져오기 │ │
|
|
|
|
|
│ │ source.getSelectedData() 또는 source.getAllData() │ │
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ │ 3. 매핑 규칙 적용 │ │
|
|
|
|
|
│ │ mappingRules.forEach(rule => ...) │ │
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ │ 4. ScreenContext에서 타겟 컴포넌트 조회 │ │
|
|
|
|
|
│ │ getDataReceiver(targetComponentId) │ │
|
|
|
|
|
│ │ │ │
|
|
|
|
|
│ │ 5. 타겟에 데이터 전달 │ │
|
|
|
|
|
│ │ target.receiveData(mappedData, config) │ │
|
|
|
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
|
|
|
│ │ │
|
|
|
|
|
│ ▼ │
|
|
|
|
|
│ ┌─────────────────┐ │
|
|
|
|
|
│ │ 타겟 컴포넌트 │ │
|
|
|
|
|
│ │ (v2-table-list, │ │
|
|
|
|
|
│ │ repeater 등) │ │
|
|
|
|
|
│ └─────────────────┘ │
|
|
|
|
|
│ │
|
|
|
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 6. 컴포넌트별 연동 능력
|
|
|
|
|
|
|
|
|
|
### 6.1 연동 능력 매트릭스
|
|
|
|
|
|
|
|
|
|
| 컴포넌트 | 이벤트 발행 | 이벤트 구독 | DataProvider | DataReceiver | Context 사용 |
|
|
|
|
|
|----------|:-----------:|:-----------:|:------------:|:------------:|:------------:|
|
|
|
|
|
| `v2-table-list` | ✅ | ✅ | ✅ | ✅ | TableOptions, Screen, SplitPanel |
|
|
|
|
|
| `v2-split-panel-layout` | ✅ | ✅ | ❌ | ❌ | TableOptions (Provider) |
|
|
|
|
|
| `v2-unified-repeater` | ✅ | ✅ | ✅ | ✅ | Screen |
|
|
|
|
|
| `v2-button-primary` | ✅ | ❌ | ❌ | ❌ | Screen, SplitPanel |
|
|
|
|
|
| `v2-table-search-widget` | ❌ | ❌ | ❌ | ❌ | TableOptions, ActiveTab |
|
|
|
|
|
| `v2-aggregation-widget` | ❌ | ✅ | ❌ | ❌ | - |
|
|
|
|
|
| `v2-repeat-container` | ❌ | ✅ | ❌ | ❌ | - |
|
|
|
|
|
| `v2-card-display` | ✅ | ✅ | ✅ | ❌ | TableOptions, Screen, SplitPanel |
|
|
|
|
|
| `v2-pivot-grid` | ❌ | ❌ | ❌ | ❌ | - |
|
|
|
|
|
| `v2-tabs-widget` | ❌ | ❌ | ❌ | ❌ | ActiveTab |
|
|
|
|
|
|
|
|
|
|
### 6.2 컴포넌트별 상세
|
|
|
|
|
|
|
|
|
|
#### v2-table-list
|
|
|
|
|
|
|
|
|
|
**발행 이벤트**:
|
|
|
|
|
- `tableListDataChange` - 데이터 로드/변경 시
|
|
|
|
|
|
|
|
|
|
**구독 이벤트**:
|
|
|
|
|
- `refreshTable` - 테이블 새로고침
|
|
|
|
|
- `related-button-select` - 연관 버튼 선택
|
|
|
|
|
- `related-button-register/unregister` - 연관 버튼 등록/해제
|
|
|
|
|
|
|
|
|
|
**DataProvidable 구현**:
|
|
|
|
|
```typescript
|
|
|
|
|
getSelectedData(): any[] // 체크된 행 데이터
|
|
|
|
|
getAllData(): any[] // 전체 데이터
|
|
|
|
|
clearSelection(): void // 선택 초기화
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**DataReceivable 구현**:
|
|
|
|
|
```typescript
|
|
|
|
|
receiveData(data, config): Promise<void>
|
|
|
|
|
// mode: "append" - 기존 데이터에 추가
|
|
|
|
|
// mode: "replace" - 데이터 교체
|
|
|
|
|
// mode: "merge" - 키 기준 병합
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### v2-unified-repeater
|
|
|
|
|
|
|
|
|
|
**발행 이벤트**:
|
|
|
|
|
- `repeaterDataChange` - 데이터 변경 시 (V2 표준 이벤트)
|
|
|
|
|
|
|
|
|
|
**구독 이벤트**:
|
|
|
|
|
- `beforeFormSave` - 저장 전 데이터 수집
|
|
|
|
|
- `repeaterSave` - 마스터 저장 후 FK 설정
|
|
|
|
|
- `componentDataTransfer` - 컴포넌트 간 데이터 전달
|
|
|
|
|
- `splitPanelDataTransfer` - 분할 패널 간 데이터 전달
|
|
|
|
|
|
|
|
|
|
**DataProvidable 구현**:
|
|
|
|
|
- `getSelectedData()` - 선택된 행 데이터 반환
|
|
|
|
|
- `getAllData()` - 전체 데이터 반환
|
|
|
|
|
- `clearSelection()` - 선택 초기화
|
|
|
|
|
|
|
|
|
|
**DataReceivable 구현**:
|
|
|
|
|
- `receiveData(data, config)` - 데이터 수신 (append, replace, merge 모드 지원)
|
|
|
|
|
- `getData()` - 현재 데이터 반환
|
|
|
|
|
|
|
|
|
|
**Context 등록**:
|
|
|
|
|
- ScreenContext에 DataProvider/DataReceiver 자동 등록
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### v2-button-primary
|
|
|
|
|
|
|
|
|
|
**발행 이벤트**:
|
|
|
|
|
- `refreshTable` - 저장 후 테이블 갱신
|
|
|
|
|
- `closeEditModal` - 모달 닫기
|
|
|
|
|
- `saveSuccessInModal` - 모달 저장 성공
|
|
|
|
|
|
|
|
|
|
**역할**:
|
|
|
|
|
- 저장, 삭제, 데이터 전달 등 액션 실행
|
|
|
|
|
- `buttonActions.ts`의 함수들 호출
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### v2-table-search-widget
|
|
|
|
|
|
|
|
|
|
**Context 의존**:
|
|
|
|
|
- `TableOptionsContext` - 등록된 테이블 조회, 필터 적용
|
|
|
|
|
- `ActiveTabContext` - 활성 탭 기반 테이블 필터링
|
|
|
|
|
|
|
|
|
|
**동작**:
|
|
|
|
|
1. `TableOptionsContext.registeredTables`에서 테이블 목록 조회
|
|
|
|
|
2. 사용자가 필터 입력
|
|
|
|
|
3. `currentTable.onFilterChange(filters)` 호출
|
|
|
|
|
4. 해당 테이블이 자동으로 재조회
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### v2-aggregation-widget
|
|
|
|
|
|
|
|
|
|
**구독 이벤트**:
|
|
|
|
|
- `tableListDataChange` - 테이블 데이터 변경 시 집계 갱신
|
|
|
|
|
- `repeaterDataChange` - 리피터 데이터 변경 시 집계 갱신
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### v2-split-panel-layout
|
|
|
|
|
|
|
|
|
|
**Provider 제공**:
|
|
|
|
|
- `SplitPanelContext` - 좌우 패널 데이터 전달
|
|
|
|
|
|
|
|
|
|
**발행 이벤트**:
|
|
|
|
|
- `openScreenModal` - 화면 모달 열기
|
|
|
|
|
|
|
|
|
|
**구독 이벤트**:
|
|
|
|
|
- `refreshTable` - 내부 테이블 갱신
|
|
|
|
|
|
2026-01-30 16:34:05 +09:00
|
|
|
**커스텀 모드 (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" }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-01-29 23:20:23 +09:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 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` | 데이터 전달 타입 |
|