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

39 KiB

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 이벤트 발생              │
└─────────────────────────────────────────────────────────────┘

주요 설정 인터페이스

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;
  };
}

컬럼 설정

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/조인 키 기반)              │
└─────────────────────────────────────────────────────────────┘

주요 설정 인터페이스

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 이벤트 발생                   │
└─────────────────────────────────────────────────────────────┘

주요 설정 인터페이스

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는 DataProvidableDataReceivable 인터페이스를 구현하여 다른 컴포넌트와 데이터를 주고받을 수 있습니다.

DataProvidable 구현:

// 다른 컴포넌트에서 이 리피터의 데이터를 가져갈 수 있음
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 구현:

// 외부에서 이 리피터로 데이터를 전달받을 수 있음
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 자동 등록:

// 컴포넌트 마운트 시 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 }
// 데이터 변경 시 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)│
│           ↓                                                  │
│  ⑥ 조건부 서식 적용 후 렌더링                              │
└─────────────────────────────────────────────────────────────┘

주요 설정 인터페이스

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)           │
└─────────────────────────────────────────────────────────────┘

주요 설정 인터페이스

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
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) 호출               │
│           ↓                                                  │
│  ⑥ 연결된 테이블이 자동으로 데이터 재조회                  │
└─────────────────────────────────────────────────────────────┘

주요 설정 인터페이스

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)   │
└─────────────────────────────────────────────────────────────┘

주요 설정 인터페이스

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
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         │                  │
│  └──────────────┘         └──────────────┘                  │
│                                                              │
└─────────────────────────────────────────────────────────────┘

컴포넌트 등록 구조

// 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 컴포넌트 등록

참고 문서