ERP-node/frontend/lib/registry/components/repeat-screen-modal
kjs 28ef7e1226 fix: Enhance error handling and validation messages in form data operations
- Integrated `formatPgError` utility to provide user-friendly error messages based on PostgreSQL error codes during form data operations.
- Updated error responses in `saveFormData`, `saveFormDataEnhanced`, `updateFormData`, and `updateFormDataPartial` to include specific messages based on the company context.
- Improved error handling in the frontend components to display relevant error messages from the server response, ensuring users receive clear feedback on save operations.
- Enhanced the required field validation by incorporating NOT NULL metadata checks across various components, improving the accuracy of form submissions.

These changes improve the overall user experience by providing clearer error messages and ensuring that required fields are properly validated based on both manual settings and database constraints.
2026-03-10 14:47:05 +09:00
..
README.md feat(repeat-screen-modal): 외부 테이블 조인, 필터링, CRUD 및 실시간 집계 기능 추가 2025-12-01 18:50:26 +09:00
RepeatScreenModalComponent.tsx fix: Enhance error handling and validation messages in form data operations 2026-03-10 14:47:05 +09:00
RepeatScreenModalConfigPanel.tsx feat(modal-repeater-table): 체크박스 기반 일괄 삭제 기능 추가 2025-12-16 11:39:30 +09:00
RepeatScreenModalRenderer.tsx 로그 제거 2026-01-06 13:08:33 +09:00
index.ts V2 컴포넌트로의 전환 및 기존 컴포넌트 숨김 처리: ComponentsPanel에서 기존 컴포넌트를 V2 버전으로 대체하고, 관련 컴포넌트들을 패널에서 숨김 처리하여 관리 효율성을 높였습니다. 각 컴포넌트의 정의에 'hidden' 속성을 추가하여 V2 컴포넌트 사용을 명시하였습니다. 2026-01-19 16:51:08 +09:00
types.ts feat(repeat-screen-modal): 연동 저장, 자동 채번, SUM_EXT 참조 제한 기능 추가 2025-12-11 09:24:47 +09:00

README.md

RepeatScreenModal 컴포넌트 v3.1

개요

RepeatScreenModal은 선택한 데이터를 기반으로 여러 개의 카드를 생성하고, 각 카드의 내부 레이아웃을 자유롭게 구성할 수 있는 컴포넌트입니다.

v3.1 주요 변경사항 (2025-11-28)

1. 외부 테이블 데이터 소스

테이블 행에서 외부 테이블의 데이터를 조회하여 표시할 수 있습니다.

예시: 수주 관리에서 출하 계획 이력 조회
┌─────────────────────────────────────────────────────────────────┐
│ 카드: 품목 A                                                     │
├─────────────────────────────────────────────────────────────────┤
│ [행 1] 헤더: 품목코드, 품목명                                    │
├─────────────────────────────────────────────────────────────────┤
│ [행 2] 테이블: shipment_plan 테이블에서 조회                     │
│        → sales_order_id로 조인하여 출하 계획 이력 표시           │
└─────────────────────────────────────────────────────────────────┘

2. 테이블 행 CRUD

테이블 행에서 행 추가/수정/삭제 기능을 지원합니다.

  • 추가: 새 행 추가 버튼으로 빈 행 생성
  • 수정: 편집 가능한 컬럼 직접 수정
  • 삭제: 행 삭제 (확인 팝업 옵션)

모달 하단에 커스터마이징 가능한 버튼 영역을 제공합니다.

┌─────────────────────────────────────────────────────────────────┐
│ 카드 내용...                                                     │
├─────────────────────────────────────────────────────────────────┤
│                                    [초기화] [취소] [저장]        │
└─────────────────────────────────────────────────────────────────┘

4. 집계 연산식 지원

집계 행에서 컬럼 간 사칙연산을 지원합니다.

// 예: 미출하 수량 = 수주수량 - 출하수량
{
  sourceType: "formula",
  formula: "{order_qty} - {ship_qty}",
  label: "미출하 수량"
}

v3 주요 변경사항 (기존)

자유 레이아웃 시스템

기존의 "simple 모드 / withTable 모드" 구분을 없애고, 행(Row)을 추가하고 각 행마다 타입을 선택하는 방식으로 변경되었습니다.

┌─────────────────────────────────────────────────────────────────┐
│ 카드                                                            │
├─────────────────────────────────────────────────────────────────┤
│ [행 1] 타입: 헤더   → 품목코드, 품목명, 규격                     │
├─────────────────────────────────────────────────────────────────┤
│ [행 2] 타입: 집계   → 총수주잔량, 현재고, 가용재고               │
├─────────────────────────────────────────────────────────────────┤
│ [행 3] 타입: 테이블 → 수주번호, 거래처, 납기일, 출하계획         │
├─────────────────────────────────────────────────────────────────┤
│ [행 4] 타입: 테이블 → 또 다른 테이블도 가능!                     │
└─────────────────────────────────────────────────────────────────┘

행 타입

타입 설명 사용 시나리오
헤더 (header) 필드들을 가로/세로로 나열 품목정보, 거래처정보 표시
필드 (fields) 헤더와 동일, 편집 가능 폼 입력 영역
집계 (aggregation) 그룹 내 데이터 집계값 표시 총수량, 합계금액 등
테이블 (table) 그룹 내 각 행을 테이블로 표시 수주목록, 품목목록 등

설정 방법

1. 기본 설정 탭

  • 카드 제목 표시: 카드 상단에 제목을 표시할지 여부
  • 카드 제목 템플릿: {field_name} 형식으로 동적 제목 생성
  • 카드 간격: 카드 사이의 간격 (8px ~ 32px)
  • 테두리: 카드 테두리 표시 여부
  • 저장 모드: 전체 저장 / 개별 저장

2. 데이터 소스 탭

  • 소스 테이블: 데이터를 조회할 테이블
  • 필터 필드: formData에서 필터링할 필드 (예: selectedIds)

3. 그룹 탭

  • 그룹핑 활성화: 여러 행을 하나의 카드로 묶을지 여부
  • 그룹 기준 필드: 그룹핑할 필드 (예: part_code)
  • 집계 설정:
    • 원본 필드: 합계할 필드 (예: balance_qty)
    • 집계 타입: sum, count, avg, min, max
    • 결과 필드명: 집계 결과를 저장할 필드명
    • 라벨: 표시될 라벨

4. 레이아웃 탭

행 추가

4가지 타입의 행을 추가할 수 있습니다:

  • 헤더: 필드 정보 표시 (읽기전용)
  • 집계: 그룹 집계값 표시
  • 테이블: 그룹 내 행들을 테이블로 표시
  • 필드: 입력 필드 (편집가능)

헤더/필드 행 설정

  • 방향: 가로 / 세로
  • 배경색: 없음, 파랑, 초록, 보라, 주황
  • 컬럼: 필드명, 라벨, 타입, 너비, 편집 가능, 필수
  • 소스 설정: 직접 / 조인 / 수동
  • 저장 설정: 저장할 테이블과 컬럼

집계 행 설정

  • 레이아웃: 가로 나열 / 그리드
  • 그리드 컬럼 수: 2, 3, 4개
  • 집계 필드: 그룹 탭에서 정의한 집계 결과 선택
  • 스타일: 배경색, 폰트 크기

테이블 행 설정 (v3.1 확장)

  • 테이블 제목: 선택사항
  • 헤더 표시: 테이블 헤더 표시 여부
  • 외부 테이블 데이터 소스: (v3.1 신규)
    • 소스 테이블: 조회할 외부 테이블
    • 조인 조건: 외부 테이블 키 ↔ 카드 데이터 키
    • 정렬: 정렬 컬럼 및 방향
  • CRUD 설정: (v3.1 신규)
    • 추가: 새 행 추가 허용
    • 수정: 행 수정 허용
    • 삭제: 행 삭제 허용 (확인 팝업 옵션)
  • 테이블 컬럼: 필드명, 라벨, 타입, 너비, 편집 가능
  • 저장 설정: 편집 가능한 컬럼의 저장 위치
  • Footer 사용: Footer 영역 활성화
  • 위치: 컨텐츠 아래 / 하단 고정 (sticky)
  • 정렬: 왼쪽 / 가운데 / 오른쪽
  • 버튼 설정:
    • 라벨: 버튼 텍스트
    • 액션: 저장 / 취소 / 닫기 / 초기화 / 커스텀
    • 스타일: 기본 / 보조 / 외곽선 / 삭제 / 고스트
    • 아이콘: 저장 / X / 초기화 / 없음

데이터 흐름

1. formData에서 selectedIds 가져오기
   ↓
2. 소스 테이블에서 해당 ID들의 데이터 조회
   ↓
3. 그룹핑 활성화 시 groupByField 기준으로 그룹화
   ↓
4. 각 그룹에 대해 집계값 계산
   ↓
5. 외부 테이블 데이터 소스가 설정된 테이블 행의 데이터 로드 (v3.1)
   ↓
6. 카드 렌더링 (contentRows 기반)
   ↓
7. 사용자 편집 (CRUD 포함)
   ↓
8. Footer 버튼 또는 기본 저장 버튼으로 저장
   ↓
9. 기본 데이터 + 외부 테이블 데이터 일괄 저장

사용 예시

출하계획 등록 (v3.1 - 외부 테이블 + CRUD)

{
  showCardTitle: true,
  cardTitle: "{part_code} - {part_name}",
  dataSource: {
    sourceTable: "sales_order_mng",
    filterField: "selectedIds"
  },
  grouping: {
    enabled: true,
    groupByField: "part_code",
    aggregations: [
      { sourceField: "balance_qty", type: "sum", resultField: "total_balance", label: "총수주잔량" },
      { sourceField: "id", type: "count", resultField: "order_count", label: "수주건수" }
    ]
  },
  contentRows: [
    {
      id: "row-1",
      type: "header",
      columns: [
        { id: "c1", field: "part_code", label: "품목코드", type: "text", editable: false },
        { id: "c2", field: "part_name", label: "품목명", type: "text", editable: false }
      ],
      layout: "horizontal"
    },
    {
      id: "row-2",
      type: "aggregation",
      aggregationLayout: "horizontal",
      aggregationFields: [
        { sourceType: "aggregation", aggregationResultField: "total_balance", label: "총수주잔량", backgroundColor: "blue" },
        { sourceType: "formula", formula: "{order_qty} - {ship_qty}", label: "미출하 수량", backgroundColor: "orange" }
      ]
    },
    {
      id: "row-3",
      type: "table",
      tableTitle: "출하 계획 이력",
      showTableHeader: true,
      // 외부 테이블에서 데이터 조회
      tableDataSource: {
        enabled: true,
        sourceTable: "shipment_plan",
        joinConditions: [
          { sourceKey: "sales_order_id", referenceKey: "id" }
        ],
        orderBy: { column: "created_date", direction: "desc" }
      },
      // CRUD 설정
      tableCrud: {
        allowCreate: true,
        allowUpdate: true,
        allowDelete: true,
        newRowDefaults: {
          sales_order_id: "{id}",
          status: "READY"
        },
        deleteConfirm: { enabled: true }
      },
      tableColumns: [
        { id: "tc1", field: "plan_date", label: "계획일", type: "date", editable: true },
        { id: "tc2", field: "plan_qty", label: "계획수량", type: "number", editable: true },
        { id: "tc3", field: "status", label: "상태", type: "text", editable: false },
        { id: "tc4", field: "memo", label: "비고", type: "text", editable: true }
      ]
    }
  ],
  // Footer 설정
  footerConfig: {
    enabled: true,
    position: "sticky",
    alignment: "right",
    buttons: [
      { id: "btn-cancel", label: "취소", action: "cancel", variant: "outline" },
      { id: "btn-save", label: "저장", action: "save", variant: "default", icon: "save" }
    ]
  }
}

타입 정의 (v3.1)

TableDataSourceConfig

interface TableDataSourceConfig {
  enabled: boolean;           // 외부 데이터 소스 사용 여부
  sourceTable: string;        // 조회할 테이블
  joinConditions: JoinCondition[];  // 조인 조건
  orderBy?: {
    column: string;           // 정렬 컬럼
    direction: "asc" | "desc"; // 정렬 방향
  };
  limit?: number;             // 최대 행 수
}

interface JoinCondition {
  sourceKey: string;          // 외부 테이블의 조인 키
  referenceKey: string;       // 카드 데이터의 참조 키
  referenceType?: "card" | "row"; // 참조 소스
}

TableCrudConfig

interface TableCrudConfig {
  allowCreate: boolean;       // 행 추가 허용
  allowUpdate: boolean;       // 행 수정 허용
  allowDelete: boolean;       // 행 삭제 허용
  newRowDefaults?: Record<string, string>; // 신규 행 기본값 ({field} 형식 지원)
  deleteConfirm?: {
    enabled: boolean;         // 삭제 확인 팝업
    message?: string;         // 확인 메시지
  };
  targetTable?: string;       // 저장 대상 테이블
}

FooterConfig

interface FooterConfig {
  enabled: boolean;           // Footer 사용 여부
  buttons?: FooterButtonConfig[];
  position?: "sticky" | "static";
  alignment?: "left" | "center" | "right";
}

interface FooterButtonConfig {
  id: string;
  label: string;
  action: "save" | "cancel" | "close" | "reset" | "custom";
  variant?: "default" | "secondary" | "outline" | "destructive" | "ghost";
  icon?: string;
  disabled?: boolean;
  customAction?: {
    type: string;
    config?: Record<string, any>;
  };
}

AggregationDisplayConfig (v3.1 확장)

interface AggregationDisplayConfig {
  // 값 소스 타입
  sourceType: "aggregation" | "formula" | "external" | "externalFormula";
  
  // aggregation: 기존 집계 결과 참조
  aggregationResultField?: string;
  
  // formula: 컬럼 간 연산
  formula?: string;  // 예: "{order_qty} - {ship_qty}"
  
  // external: 외부 테이블 조회 (향후 구현)
  externalSource?: ExternalValueSource;
  
  // externalFormula: 외부 테이블 + 연산 (향후 구현)
  externalSources?: ExternalValueSource[];
  externalFormula?: string;
  
  // 표시 설정
  label: string;
  icon?: string;
  backgroundColor?: string;
  textColor?: string;
  fontSize?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl";
  format?: "number" | "currency" | "percent";
  decimalPlaces?: number;
}

레거시 호환

v2에서 사용하던 cardMode, cardLayout, tableLayout 설정도 계속 지원됩니다. 새로운 프로젝트에서는 contentRows를 사용하는 것을 권장합니다.


주의사항

  1. 집계는 그룹핑 필수: 집계 행은 그룹핑이 활성화되어 있어야 의미가 있습니다.
  2. 테이블은 그룹핑 필수: 테이블 행도 그룹핑이 활성화되어 있어야 그룹 내 행들을 표시할 수 있습니다.
  3. 단순 모드: 그룹핑 없이 사용하면 1행 = 1카드로 동작합니다. 이 경우 헤더/필드 타입만 사용 가능합니다.
  4. 외부 테이블 CRUD: 외부 테이블 데이터 소스가 설정된 테이블에서만 CRUD가 동작합니다.
  5. 연산식: 사칙연산(+, -, *, /)과 괄호만 지원됩니다. 복잡한 함수는 지원하지 않습니다.

변경 이력

v3.1 (2025-11-28)

  • 외부 테이블 데이터 소스 기능 추가
  • 테이블 행 CRUD (추가/수정/삭제) 기능 추가
  • Footer 버튼 영역 기능 추가
  • 집계 연산식 (formula) 지원 추가
  • 다단계 조인 타입 정의 추가 (향후 구현 예정)

v3.0

  • 자유 레이아웃 시스템 도입
  • contentRows 기반 행 타입 선택 방식
  • 헤더/필드/집계/테이블 4가지 행 타입 지원

v2.0

  • simple 모드 / withTable 모드 구분
  • cardLayout / tableLayout 분리