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

1040 lines
39 KiB
Markdown
Raw Normal View History

# V2 컴포넌트 분석 가이드
## 개요
V2 컴포넌트는 **화면관리 시스템 전용**으로 개발된 컴포넌트 세트입니다. 기존 컴포넌트와의 충돌을 방지하고, 새로운 기능(엔티티 조인, 다국어 지원, 커스텀 테이블 등)을 지원합니다.
### 핵심 원칙
- 모든 V2 컴포넌트는 `v2-` 접두사를 사용
- 원본 컴포넌트는 기존 화면 호환성 유지용으로 보존
- 새로운 화면 개발 시 반드시 V2 컴포넌트만 사용
- Definition 이름에 `V2` 접두사 사용 (예: `V2TableListDefinition`)
### 파일 경로
```
frontend/lib/registry/components/
├── v2-button-primary/ ← V2 컴포넌트 (수정 대상)
├── v2-table-list/ ← V2 컴포넌트 (수정 대상)
├── v2-split-panel-layout/ ← V2 컴포넌트 (수정 대상)
├── ...
├── button-primary/ ← 원본 (수정 금지)
├── table-list/ ← 원본 (수정 금지)
└── ...
```
---
## V2 컴포넌트 목록 (17개)
| 컴포넌트 ID | 이름 | 카테고리 | 용도 |
|------------|------|----------|------|
| `v2-table-list` | 테이블 리스트 | DISPLAY | 데이터 목록 표시 (테이블/카드 모드) |
| `v2-split-panel-layout` | 분할 패널 | DISPLAY | 마스터-디테일 레이아웃 |
| `v2-unified-repeater` | 통합 리피터 | UNIFIED | 반복 데이터 관리 (인라인/모달/버튼) |
| `v2-pivot-grid` | 피벗 그리드 | DISPLAY | 다차원 데이터 분석 피벗 테이블 |
| `v2-button-primary` | 기본 버튼 | ACTION | 저장/삭제 등 액션 버튼 |
| `v2-text-display` | 텍스트 표시 | DISPLAY | 텍스트/라벨 표시 |
| `v2-divider-line` | 구분선 | DISPLAY | 시각적 구분선 |
| `v2-card-display` | 카드 디스플레이 | DISPLAY | 카드 형태 데이터 표시 |
| `v2-numbering-rule` | 채번 규칙 | DISPLAY | 코드 자동 채번 설정 |
| `v2-table-search-widget` | 검색 필터 | DISPLAY | 테이블 검색/필터 위젯 |
| `v2-section-paper` | 섹션 페이퍼 | LAYOUT | 섹션 구분 컨테이너 |
| `v2-section-card` | 섹션 카드 | LAYOUT | 카드형 섹션 컨테이너 |
| `v2-tabs-widget` | 탭 위젯 | LAYOUT | 탭 기반 콘텐츠 전환 |
| `v2-location-swap-selector` | 위치 선택 | INPUT | 출발지/도착지 스왑 선택 |
| `v2-rack-structure` | 렉 구조 | DISPLAY | 창고 렉 시각화 |
| `v2-aggregation-widget` | 집계 위젯 | DISPLAY | 데이터 집계 (합계/평균/개수) |
| `v2-repeat-container` | 리피터 컨테이너 | LAYOUT | 데이터 수만큼 반복 렌더링 |
---
## 주요 컴포넌트 상세 분석
### 1. v2-table-list (테이블 리스트)
**용도**: 데이터베이스 테이블 데이터를 테이블/카드 형태로 표시
#### 주요 특징
- 테이블 모드 / 카드 모드 전환 가능
- 페이지네이션, 정렬, 필터링 지원
- 체크박스 선택 (단일/다중)
- 가로 스크롤 및 컬럼 고정
- 엔티티 조인 컬럼 지원
- 인라인 편집 기능
- Excel 내보내기
#### 데이터 흐름
```
┌─────────────────────────────────────────────────────────────┐
│ v2-table-list │
├─────────────────────────────────────────────────────────────┤
│ ① config.selectedTable / customTableName 확인 │
│ ↓ │
│ ② tableTypeApi.getData() 호출 │
│ ↓ │
│ ③ entityJoinApi.getEntityJoinColumns() 조인 컬럼 로드 │
│ ↓ │
│ ④ 데이터 + 조인 데이터 병합 │
│ ↓ │
│ ⑤ 테이블/카드 모드로 렌더링 │
│ ↓ │
│ ⑥ onRowClick / onSelectionChange 이벤트 발생 │
└─────────────────────────────────────────────────────────────┘
```
#### 주요 설정 인터페이스
```typescript
interface TableListConfig {
// 표시 모드
displayMode: "table" | "card";
// 커스텀 테이블 설정
customTableName?: string; // 커스텀 테이블
useCustomTable?: boolean; // 커스텀 테이블 사용 여부
isReadOnly?: boolean; // 읽기전용
// 컬럼 설정
columns: ColumnConfig[];
// 페이지네이션
pagination: {
enabled: boolean;
pageSize: number;
showSizeSelector: boolean;
pageSizeOptions: number[];
};
// 체크박스
checkbox: {
enabled: boolean;
multiple: boolean; // true: 체크박스, false: 라디오
position: "left" | "right";
selectAll: boolean;
};
// 필터
filter: {
enabled: boolean;
filters: FilterConfig[];
};
// 연결된 필터 (다른 컴포넌트 값으로 필터링)
linkedFilters?: LinkedFilterConfig[];
// 제외 필터 (다른 테이블에 존재하는 데이터 제외)
excludeFilter?: ExcludeFilterConfig;
// 가로 스크롤 설정
horizontalScroll: {
enabled: boolean;
maxVisibleColumns?: number;
minColumnWidth?: number;
maxColumnWidth?: number;
};
}
```
#### 컬럼 설정
```typescript
interface ColumnConfig {
columnName: string; // 컬럼명
displayName: string; // 표시명
visible: boolean; // 표시 여부
sortable: boolean; // 정렬 가능
searchable: boolean; // 검색 가능
width?: number; // 너비
align: "left" | "center" | "right"; // 정렬
format?: "text" | "number" | "date" | "currency" | "boolean";
// 엔티티 조인
isEntityJoin?: boolean; // 조인 컬럼 여부
entityJoinInfo?: {
sourceTable: string;
sourceColumn: string;
joinAlias: string;
};
// 컬럼 고정
fixed?: "left" | "right" | false;
// 자동생성
autoGeneration?: {
type: "uuid" | "current_user" | "current_time" | "sequence" | "numbering_rule";
enabled: boolean;
};
// 편집 가능 여부
editable?: boolean;
}
```
---
### 2. v2-split-panel-layout (분할 패널)
**용도**: 마스터-디테일 패턴의 좌우 분할 레이아웃
#### 주요 특징
- 좌측: 마스터 목록 (리스트/테이블 모드)
- 우측: 디테일 정보 (연관 데이터)
- 좌우 비율 조절 가능 (드래그 리사이즈)
- 다중 탭 지원 (우측 패널)
- N:M 관계 데이터 지원
- 중복 제거 기능
#### 데이터 흐름
```
┌─────────────────────────────────────────────────────────────┐
│ v2-split-panel-layout │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ 좌측 패널 │ ───────→ │ 우측 패널 │ │
│ │ (마스터) │ 선택 이벤트│ (디테일) │ │
│ └──────────────┘ └──────────────────────┘ │
│ │ │ │
│ ↓ ↓ │
│ leftPanel.tableName rightPanel.tableName │
│ leftPanel.columns rightPanel.relation │
│ │ │ │
│ ↓ ↓ │
│ 좌측 데이터 조회 ─────────→ 관계 설정에 따라 우측 필터링 │
│ (독립 API 호출) (FK/조인 키 기반) │
└─────────────────────────────────────────────────────────────┘
```
#### 주요 설정 인터페이스
```typescript
interface SplitPanelLayoutConfig {
// 좌측 패널
leftPanel: {
title: string;
tableName?: string;
useCustomTable?: boolean;
customTableName?: string;
displayMode?: "list" | "table";
showSearch?: boolean;
showAdd?: boolean;
showEdit?: boolean;
showDelete?: boolean;
columns?: ColumnConfig[];
tableConfig?: TableDisplayConfig;
dataFilter?: DataFilterConfig;
};
// 우측 패널
rightPanel: {
title: string;
tableName?: string;
displayMode?: "list" | "table";
columns?: ColumnConfig[];
// 관계 설정
relation?: {
type?: "join" | "detail";
leftColumn?: string; // 좌측 조인 컬럼
rightColumn?: string; // 우측 조인 컬럼
foreignKey?: string; // FK 컬럼
keys?: Array<{ // 복합키 지원
leftColumn: string;
rightColumn: string;
}>;
};
// 추가 설정 (N:M 관계)
addConfig?: {
targetTable?: string; // 실제 INSERT 테이블
autoFillColumns?: Record<string, any>;
leftPanelColumn?: string;
targetColumn?: string;
};
// 중복 제거
deduplication?: {
enabled: boolean;
groupByColumn: string;
keepStrategy: "latest" | "earliest" | "base_price" | "current_date";
};
// 추가 탭
additionalTabs?: AdditionalTabConfig[];
};
// 레이아웃
splitRatio?: number; // 좌우 비율 (0-100)
resizable?: boolean; // 크기 조절 가능
minLeftWidth?: number;
minRightWidth?: number;
// 동작
autoLoad?: boolean;
syncSelection?: boolean;
}
```
---
### 3. v2-unified-repeater (통합 리피터)
**용도**: 반복 데이터 관리 (기존 여러 리피터 통합)
#### 주요 특징
- 3가지 렌더링 모드: 인라인/모달/버튼
- 마스터-디테일 FK 자동 연결
- 저장 테이블 분리 가능
- 행 추가/삭제, 드래그 정렬
- 선택 기능 (단일/다중)
#### 데이터 흐름
```
┌─────────────────────────────────────────────────────────────┐
│ v2-unified-repeater │
├─────────────────────────────────────────────────────────────┤
│ ① 마스터 저장 이벤트 수신 (repeaterSave) │
│ ↓ │
│ ② masterRecordId 전달받음 │
│ ↓ │
│ ③ foreignKeyColumn에 masterRecordId 자동 설정 │
│ ↓ │
│ ④ dataSource.tableName으로 데이터 저장 │
│ ↓ │
│ ⑤ 저장 완료 후 onDataChange 이벤트 발생 │
└─────────────────────────────────────────────────────────────┘
```
#### 주요 설정 인터페이스
```typescript
interface UnifiedRepeaterConfig {
// 렌더링 모드
renderMode: "inline" | "modal" | "button" | "mixed";
// 데이터 소스
dataSource: {
tableName: string; // 저장 테이블
foreignKey: string; // FK 컬럼
referenceKey: string; // 참조할 PK 컬럼
};
// 컬럼 설정
columns: ColumnConfig[];
// 모달 설정
modal: {
size: "sm" | "md" | "lg" | "xl";
};
// 버튼 설정
button: {
sourceType: "manual" | "auto";
manualButtons: ButtonConfig[];
layout: "horizontal" | "vertical";
style: "outline" | "solid";
};
// 기능 설정
features: {
showAddButton: boolean;
showDeleteButton: boolean;
inlineEdit: boolean;
dragSort: boolean;
showRowNumber: boolean;
selectable: boolean;
multiSelect: boolean;
};
}
```
#### 데이터 전달 인터페이스
v2-unified-repeater는 **DataProvidable**과 **DataReceivable** 인터페이스를 구현하여 다른 컴포넌트와 데이터를 주고받을 수 있습니다.
**DataProvidable 구현**:
```typescript
// 다른 컴포넌트에서 이 리피터의 데이터를 가져갈 수 있음
const dataProvider: DataProvidable = {
componentId: parentId || config.fieldName || "unified-repeater",
componentType: "unified-repeater",
// 선택된 행 데이터 반환
getSelectedData: () => {
return Array.from(selectedRows).map((idx) => data[idx]).filter(Boolean);
},
// 전체 데이터 반환
getAllData: () => {
return [...data];
},
// 선택 초기화
clearSelection: () => {
setSelectedRows(new Set());
},
};
```
**DataReceivable 구현**:
```typescript
// 외부에서 이 리피터로 데이터를 전달받을 수 있음
const dataReceiver: DataReceivable = {
componentId: parentId || config.fieldName || "unified-repeater",
componentType: "repeater",
// 데이터 수신 (append, replace, merge 모드 지원)
receiveData: async (incomingData: any[], config: DataReceiverConfig) => {
// 매핑 규칙 적용 후 모드에 따라 처리
switch (config.mode) {
case "replace": setData(mappedData); break;
case "merge": /* 중복 제거 후 병합 */ break;
case "append": /* 기존 데이터에 추가 */ break;
}
},
// 현재 데이터 반환
getData: () => [...data],
};
```
**ScreenContext 자동 등록**:
```typescript
// 컴포넌트 마운트 시 ScreenContext에 자동 등록
useEffect(() => {
if (screenContext && componentId) {
screenContext.registerDataProvider(componentId, dataProvider);
screenContext.registerDataReceiver(componentId, dataReceiver);
return () => {
screenContext.unregisterDataProvider(componentId);
screenContext.unregisterDataReceiver(componentId);
};
}
}, [screenContext, componentId]);
```
#### V2 이벤트 시스템
**발행 이벤트**:
| 이벤트 | 발행 시점 | 데이터 |
|--------|----------|--------|
| `repeaterDataChange` | 데이터 변경 시 | `{ componentId, tableName, data, selectedData }` |
```typescript
// 데이터 변경 시 V2 표준 이벤트 발행
import { V2_EVENTS, dispatchV2Event } from "@/types/component-events";
useEffect(() => {
if (data.length !== prevDataLengthRef.current) {
dispatchV2Event(V2_EVENTS.REPEATER_DATA_CHANGE, {
componentId: parentId || config.fieldName || "unified-repeater",
tableName: config.dataSource?.tableName || "",
data: data,
selectedData: Array.from(selectedRows).map((idx) => data[idx]).filter(Boolean),
});
}
}, [data, selectedRows]);
```
**구독 이벤트**:
| 이벤트 | 용도 |
|--------|------|
| `beforeFormSave` | 저장 전 데이터 수집 |
| `repeaterSave` | 마스터 저장 후 FK 설정 |
| `componentDataTransfer` | 컴포넌트 간 데이터 전달 수신 |
| `splitPanelDataTransfer` | 분할 패널 간 데이터 전달 수신 |
---
### 4. v2-pivot-grid (피벗 그리드)
**용도**: 다차원 데이터 분석용 피벗 테이블
#### 주요 특징
- 행/열/데이터/필터 영역 드래그앤드롭
- 다양한 집계 함수 (합계, 평균, 개수, 최대, 최소, 고유 개수)
- 소계/총계 표시 (위치 설정 가능)
- 조건부 서식 (색상 스케일, 데이터 바, 아이콘)
- 차트 연동
- Excel 내보내기
- 날짜 그룹화 (연/분기/월/주/일)
#### 데이터 흐름
```
┌─────────────────────────────────────────────────────────────┐
│ v2-pivot-grid │
├─────────────────────────────────────────────────────────────┤
│ ① dataSource 설정 (테이블/API/정적 데이터) │
│ ↓ │
│ ② fields 설정 (행/열/데이터 필드 배치) │
│ ↓ │
│ ③ processPivotData() 로 피벗 계산 │
│ ↓ │
│ ④ 집계 함수 적용 (sum, avg, count 등) │
│ ↓ │
│ ⑤ PivotResult 생성 (rowHeaders, columnHeaders, dataMatrix)│
│ ↓ │
│ ⑥ 조건부 서식 적용 후 렌더링 │
└─────────────────────────────────────────────────────────────┘
```
#### 주요 설정 인터페이스
```typescript
interface PivotGridComponentConfig {
// 데이터 소스
dataSource?: {
type: "table" | "api" | "static";
tableName?: string;
apiEndpoint?: string;
staticData?: any[];
filterConditions?: FilterCondition[];
joinConfigs?: JoinConfig[];
};
// 필드 설정
fields?: Array<{
field: string; // 데이터 필드명
caption: string; // 표시 라벨
area: "row" | "column" | "data" | "filter";
areaIndex?: number; // 영역 내 순서
// 집계 (data 영역용)
summaryType?: "sum" | "count" | "avg" | "min" | "max" | "countDistinct";
// 날짜 그룹화
groupInterval?: "year" | "quarter" | "month" | "week" | "day";
// 포맷
format?: {
type: "number" | "currency" | "percent" | "date" | "text";
precision?: number;
thousandSeparator?: boolean;
prefix?: string;
suffix?: string;
};
}>;
// 총합계 설정
totals?: {
showRowGrandTotals?: boolean;
showRowTotals?: boolean;
showColumnGrandTotals?: boolean;
showColumnTotals?: boolean;
rowGrandTotalPosition?: "top" | "bottom";
columnGrandTotalPosition?: "left" | "right";
};
// 스타일
style?: {
theme: "default" | "compact" | "modern";
alternateRowColors?: boolean;
highlightTotals?: boolean;
conditionalFormats?: ConditionalFormatRule[];
};
// 필드 선택기
fieldChooser?: {
enabled: boolean;
allowSearch?: boolean;
};
// 차트 연동
chart?: {
enabled: boolean;
type: "bar" | "line" | "area" | "pie" | "stackedBar";
position: "top" | "bottom" | "left" | "right";
};
}
```
---
### 5. v2-aggregation-widget (집계 위젯)
**용도**: 데이터 집계 결과 표시 (합계, 평균, 개수 등)
#### 주요 특징
- 다양한 집계 타입 (SUM, AVG, COUNT, MIN, MAX)
- 필터링 지원 (폼 데이터 연동)
- 가로/세로 레이아웃
- 아이콘 표시
- 폼 변경 시 자동 새로고침
#### 데이터 흐름
```
┌─────────────────────────────────────────────────────────────┐
│ v2-aggregation-widget │
├─────────────────────────────────────────────────────────────┤
│ ① dataSourceType 확인 (table / repeater) │
│ ↓ │
│ ② filters 적용 (필터 조건 구성) │
│ ↓ │
│ ③ items 순회하며 각 집계 함수 실행 │
│ ↓ │
│ ④ 집계 결과 포맷팅 (천단위 구분, 접두사/접미사) │
│ ↓ │
│ ⑤ layout에 따라 렌더링 (horizontal / vertical) │
└─────────────────────────────────────────────────────────────┘
```
#### 주요 설정 인터페이스
```typescript
interface AggregationWidgetConfig {
// 데이터 소스
dataSourceType: "table" | "repeater";
// 집계 항목
items: Array<{
id: string;
label: string;
columnName: string;
aggregationType: "sum" | "avg" | "count" | "min" | "max";
format?: {
prefix?: string;
suffix?: string;
thousandSeparator?: boolean;
decimalPlaces?: number;
};
icon?: string;
color?: string;
}>;
// 필터 조건
filters: Array<{
column: string;
operator: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE" | "IN";
value?: any;
valueSource?: "static" | "formData" | "url";
valueField?: string;
}>;
filterLogic: "AND" | "OR";
// 레이아웃
layout: "horizontal" | "vertical";
gap: string;
// 스타일
showLabels: boolean;
showIcons: boolean;
backgroundColor: string;
borderRadius: string;
padding: string;
// 동작
autoRefresh: boolean;
refreshOnFormChange: boolean;
}
```
#### V2 이벤트 시스템
v2-aggregation-widget은 V2 표준 이벤트 시스템을 사용하여 다른 컴포넌트의 데이터 변경을 감지합니다.
**구독 이벤트**:
| 이벤트 | 용도 | 발행자 |
|--------|------|--------|
| `tableListDataChange` | 테이블 데이터 변경 시 집계 갱신 | v2-table-list |
| `repeaterDataChange` | 리피터 데이터 변경 시 집계 갱신 | v2-unified-repeater |
```typescript
import { V2_EVENTS, subscribeV2Event, type TableListDataChangeDetail, type RepeaterDataChangeDetail } from "@/types/component-events";
useEffect(() => {
// 테이블 리스트 데이터 변경 이벤트 구독
const unsubscribeTableList = subscribeV2Event(
V2_EVENTS.TABLE_LIST_DATA_CHANGE,
(event: CustomEvent<TableListDataChangeDetail>) => {
const { data } = event.detail;
// 필터 적용 후 집계 재계산
const filteredData = applyFilters(data, filters, filterLogic, formData, selectedRows);
setData(filteredData);
}
);
// 리피터 데이터 변경 이벤트 구독
const unsubscribeRepeater = subscribeV2Event(
V2_EVENTS.REPEATER_DATA_CHANGE,
(event: CustomEvent<RepeaterDataChangeDetail>) => {
const { data, selectedData } = event.detail;
const rows = selectedData || data || [];
const filteredData = applyFilters(rows, filters, filterLogic, formData, selectedRows);
setData(filteredData);
}
);
return () => {
unsubscribeTableList();
unsubscribeRepeater();
};
}, [dataSourceType, isDesignMode, filterLogic]);
```
**참고**: 이전에 사용하던 중복 이벤트(`selectionChange`, `tableSelectionChange`, `rowSelectionChange` 등)는 제거되었습니다. V2 표준 이벤트만 사용합니다.
---
### 6. v2-table-search-widget (검색 필터)
**용도**: 테이블 데이터 검색 및 필터링
#### 주요 특징
- 동적/고정 필터 모드
- 다중 테이블 지원
- 탭별 필터 값 저장
- 텍스트/숫자/날짜/셀렉트 필터 타입
- 다중선택 지원
- 대상 패널 지정 가능
#### 데이터 흐름
```
┌─────────────────────────────────────────────────────────────┐
│ v2-table-search-widget │
├─────────────────────────────────────────────────────────────┤
│ ① TableOptionsContext에서 등록된 테이블 목록 조회 │
│ ↓ │
│ ② targetPanelPosition에 따라 대상 테이블 필터링 │
│ ↓ │
│ ③ 활성 필터 목록 로드 (localStorage에서 복원) │
│ ↓ │
│ ④ 필터 값 입력 → handleFilterChange() │
│ ↓ │
│ ⑤ currentTable.onFilterChange(filters) 호출 │
│ ↓ │
│ ⑥ 연결된 테이블이 자동으로 데이터 재조회 │
└─────────────────────────────────────────────────────────────┘
```
#### 주요 설정 인터페이스
```typescript
interface TableSearchWidgetConfig {
// 자동 선택
autoSelectFirstTable?: boolean;
showTableSelector?: boolean;
// 필터 모드
filterMode?: "dynamic" | "preset";
// 고정 필터 (preset 모드)
presetFilters?: Array<{
id: string;
columnName: string;
columnLabel: string;
filterType: "text" | "number" | "date" | "select";
width?: number;
multiSelect?: boolean;
}>;
// 대상 패널 위치
targetPanelPosition?: "left" | "right" | "auto";
}
```
---
### 7. v2-repeat-container (리피터 컨테이너)
**용도**: 데이터 수만큼 내부 컴포넌트를 반복 렌더링
#### 주요 특징
- 수동/테이블/리피터 데이터 소스
- 세로/가로/그리드 레이아웃
- 페이징 지원
- 클릭 이벤트 (단일/다중 선택)
- 아이템 제목 템플릿
#### 데이터 흐름
```
┌─────────────────────────────────────────────────────────────┐
│ v2-repeat-container │
├─────────────────────────────────────────────────────────────┤
│ ① dataSourceType에 따라 데이터 로드 │
│ - manual: 수동 입력 데이터 │
│ - table: DB 테이블에서 조회 │
│ - repeater: 리피터 컴포넌트 데이터 │
│ ↓ │
│ ② layout에 따라 배치 (vertical / horizontal / grid) │
│ ↓ │
│ ③ 각 아이템에 대해 children 렌더링 │
│ (RepeatItemContext로 현재 아이템 데이터 전달) │
│ ↓ │
│ ④ 클릭 시 선택 상태 관리 (selectionMode: single/multi) │
└─────────────────────────────────────────────────────────────┘
```
#### 주요 설정 인터페이스
```typescript
interface RepeatContainerConfig {
// 데이터 소스
dataSourceType: "manual" | "table" | "repeater";
tableName?: string;
repeaterComponentId?: string;
manualData?: any[];
// 레이아웃
layout: "vertical" | "horizontal" | "grid";
gridColumns: number;
gap: string;
// 스타일
showBorder: boolean;
showShadow: boolean;
borderRadius: string;
backgroundColor: string;
padding: string;
// 아이템 제목
showItemTitle: boolean;
itemTitleTemplate: string; // 예: "${name} - ${code}"
titleFontSize: string;
titleColor: string;
titleFontWeight: string;
// 빈 상태
emptyMessage: string;
// 페이징
usePaging: boolean;
pageSize: number;
// 선택
clickable: boolean;
showSelectedState: boolean;
selectionMode: "single" | "multi";
}
```
#### V2 이벤트 시스템
v2-repeat-container는 V2 표준 이벤트 시스템을 사용하여 다른 컴포넌트의 데이터 변경을 감지하고 반복 렌더링합니다.
**구독 이벤트**:
| 이벤트 | 용도 | 발행자 |
|--------|------|--------|
| `tableListDataChange` | 테이블 데이터 변경 시 반복 항목 갱신 | v2-table-list |
| `repeaterDataChange` | 리피터 데이터 변경 시 반복 항목 갱신 | v2-unified-repeater |
```typescript
import { V2_EVENTS, subscribeV2Event, type TableListDataChangeDetail, type RepeaterDataChangeDetail } from "@/types/component-events";
useEffect(() => {
// 공통 데이터 처리 함수
const processIncomingData = (componentId: string | undefined, tableName: string | undefined, eventData: any[]) => {
// dataSourceComponentId가 설정된 경우 해당 컴포넌트만 매칭
if (dataSourceComponentId && componentId === dataSourceComponentId) {
setData(eventData);
setCurrentPage(1);
setSelectedIndices([]);
}
// 테이블명으로 매칭
else if (effectiveTableName && tableName === effectiveTableName) {
setData(eventData);
setCurrentPage(1);
setSelectedIndices([]);
}
};
// V2 표준 이벤트 구독
const unsubscribeTableList = subscribeV2Event(V2_EVENTS.TABLE_LIST_DATA_CHANGE, (event) => {
const { componentId, tableName, data } = event.detail;
processIncomingData(componentId, tableName, data);
});
const unsubscribeRepeater = subscribeV2Event(V2_EVENTS.REPEATER_DATA_CHANGE, (event) => {
const { componentId, tableName, data } = event.detail;
processIncomingData(componentId, tableName, data);
});
return () => {
unsubscribeTableList();
unsubscribeRepeater();
};
}, [dataSourceComponentId, effectiveTableName, isDesignMode]);
```
---
## 공통 데이터 흐름 패턴
### 1. 엔티티 조인 데이터 로드
모든 테이블 기반 V2 컴포넌트는 엔티티 조인을 지원합니다.
```
┌─────────────────────────────────────────────────────────────┐
│ 엔티티 조인 흐름 │
├─────────────────────────────────────────────────────────────┤
│ ① entityJoinApi.getEntityJoinColumns(tableName) │
│ ↓ │
│ ② 응답: { joinTables, availableColumns } │
│ ↓ │
│ ③ ConfigPanel에서 조인 컬럼 선택 │
│ ↓ │
│ ④ entityJoinApi.getTableDataWithJoins() 데이터 조회 │
│ ↓ │
│ ⑤ "테이블명.컬럼명" 형식으로 조인 데이터 포함 │
└─────────────────────────────────────────────────────────────┘
```
### 2. 폼 데이터 관리
V2 컴포넌트는 통합 폼 시스템(UnifiedFormContext)을 사용합니다.
```
┌─────────────────────────────────────────────────────────────┐
│ 폼 데이터 흐름 │
├─────────────────────────────────────────────────────────────┤
│ ① 컴포넌트에서 useFormCompatibility() 훅 사용 │
│ ↓ │
│ ② getValue(fieldName) - 값 읽기 │
│ ③ setValue(fieldName, value) - 값 설정 │
│ ↓ │
│ ④ 값 변경이 전체 폼 시스템에 전파 │
│ ↓ │
│ ⑤ 저장 버튼 클릭 시 beforeFormSave 이벤트 발생 │
│ ↓ │
│ ⑥ 모든 컴포넌트가 현재 값을 formData에 추가 │
│ ↓ │
│ ⑦ API 호출하여 저장 │
└─────────────────────────────────────────────────────────────┘
```
### 3. 컴포넌트 간 통신
```
┌─────────────────────────────────────────────────────────────┐
│ 컴포넌트 간 통신 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 버튼 컴포넌트 │ ──────→ │ 리피터 컴포넌트│ │
│ │ │ repeater│ │ │
│ │ v2-button │ Save │ v2-unified- │ │
│ │ -primary │ 이벤트 │ repeater │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ │ masterRecordId │ │
│ └────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 검색 위젯 │ ──────→ │ 테이블 리스트 │ │
│ │ │ onFilter│ │ │
│ │ v2-table- │ Change │ v2-table- │ │
│ │ search-widget│ │ list │ │
│ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## 컴포넌트 등록 구조
```typescript
// frontend/lib/registry/components/index.ts
// V2 컴포넌트들 (화면관리 전용)
import "./v2-unified-repeater/UnifiedRepeaterRenderer";
import "./v2-button-primary/ButtonPrimaryRenderer";
import "./v2-split-panel-layout/SplitPanelLayoutRenderer";
import "./v2-aggregation-widget/AggregationWidgetRenderer";
import "./v2-card-display/CardDisplayRenderer";
import "./v2-numbering-rule/NumberingRuleRenderer";
import "./v2-table-list/TableListRenderer";
import "./v2-text-display/TextDisplayRenderer";
import "./v2-pivot-grid/PivotGridRenderer";
import "./v2-divider-line/DividerLineRenderer";
import "./v2-repeat-container/RepeatContainerRenderer";
import "./v2-section-card/SectionCardRenderer";
import "./v2-section-paper/SectionPaperRenderer";
import "./v2-rack-structure/RackStructureRenderer";
import "./v2-location-swap-selector/LocationSwapSelectorRenderer";
import "./v2-table-search-widget";
import "./v2-tabs-widget/tabs-component";
```
---
## 파일 구조 표준
각 V2 컴포넌트 폴더는 다음 구조를 따릅니다:
```
v2-{component-name}/
├── index.ts # 컴포넌트 Definition (V2 접두사)
├── types.ts # TypeScript 타입 정의
├── {Component}Component.tsx # 실제 컴포넌트 구현
├── {Component}ConfigPanel.tsx # 설정 패널
├── {Component}Renderer.tsx # 레지스트리 등록 및 래퍼
├── config.ts # 기본 설정값 (선택)
└── README.md # 사용 가이드 (선택)
```
---
## 개발 가이드라인
### 새 V2 컴포넌트 생성 시
1. `v2-` 접두사로 폴더 생성
2. Definition 이름에 `V2` 접두사 사용 (예: `V2NewComponentDefinition`)
3. `index.ts`에서 import 추가
4. 엔티티 조인 지원 필수 구현
5. 다국어 키 필드 추가 (`langKeyId`, `langKey`)
### 체크리스트
- [ ] V2 폴더에서 작업 중인지 확인
- [ ] 원본 폴더는 수정하지 않음
- [ ] 컴포넌트 ID에 `v2-` 접두사 사용
- [ ] Definition 이름에 `V2` 접두사 사용
- [ ] 엔티티 조인 컬럼 지원
- [ ] 커스텀 테이블 설정 지원
- [ ] 다국어 필드 추가
---
## 관련 파일 목록
| 파일 | 역할 |
|------|------|
| `frontend/lib/api/entityJoin.ts` | 엔티티 조인 API |
| `frontend/hooks/useFormCompatibility.ts` | 폼 호환성 브릿지 |
| `frontend/components/unified/UnifiedFormContext.tsx` | 통합 폼 Context |
| `frontend/lib/utils/multilangLabelExtractor.ts` | 다국어 라벨 추출/매핑 |
| `frontend/contexts/ScreenMultiLangContext.tsx` | 다국어 번역 Context |
| `frontend/lib/registry/components/index.ts` | 컴포넌트 등록 |
---
## 참고 문서
- [component-development-guide.mdc](.cursor/rules/component-development-guide.mdc) - 컴포넌트 개발 상세 가이드
- [table-list-component-guide.mdc](.cursor/rules/table-list-component-guide.mdc) - 테이블 리스트 가이드