1040 lines
39 KiB
Markdown
1040 lines
39 KiB
Markdown
# 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) - 테이블 리스트 가이드
|