# 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; 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) => { const { data } = event.detail; // 필터 적용 후 집계 재계산 const filteredData = applyFilters(data, filters, filterLogic, formData, selectedRows); setData(filteredData); } ); // 리피터 데이터 변경 이벤트 구독 const unsubscribeRepeater = subscribeV2Event( V2_EVENTS.REPEATER_DATA_CHANGE, (event: CustomEvent) => { 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) - 테이블 리스트 가이드