feat(repeat-screen-modal): 외부 테이블 조인, 필터링, CRUD 및 실시간 집계 기능 추가

- 외부 테이블 데이터 소스 설정 (TableDataSourceConfig) 추가
- 다중 테이블 조인 지원 (AdditionalJoinConfig)
- 테이블 필터링 (equals/notEquals) 지원
- 테이블 CRUD (행 추가/수정/삭제) 기능 추가
- 데이터 변경 시 집계 실시간 재계산 (recalculateAggregationsWithExternalData)
- 시각적 수식 빌더 (FormulaBuilder) 컴포넌트 추가
- 테이블 컬럼 순서 변경 기능 추가
- 백엔드: 배열 파라미터 IN 절 변환 로직 추가
This commit is contained in:
SeongHyun Kim 2025-12-01 18:50:26 +09:00
parent 0281d3722e
commit 2f78c83ef6
10 changed files with 2716 additions and 171 deletions

View File

@ -1502,6 +1502,26 @@ export class TableManagementService {
columnName
);
// 🆕 배열 처리: IN 절 사용
if (Array.isArray(value)) {
if (value.length === 0) {
// 빈 배열이면 항상 false 조건
return {
whereClause: `1 = 0`,
values: [],
paramCount: 0,
};
}
// IN 절로 여러 값 검색
const placeholders = value.map((_, idx) => `$${paramIndex + idx}`).join(", ");
return {
whereClause: `${columnName} IN (${placeholders})`,
values: value,
paramCount: value.length,
};
}
if (!entityTypeInfo.isEntityType || !entityTypeInfo.referenceTable) {
// 엔티티 타입이 아니면 기본 검색
return {

View File

@ -4245,8 +4245,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
{/* 통합 패널 */}
{panelStates.unified?.isOpen && (
<div className="border-border bg-card flex h-full w-[240px] flex-col border-r shadow-sm">
<div className="border-border flex items-center justify-between border-b px-4 py-3">
<div className="border-border bg-card flex h-full w-[240px] flex-col border-r shadow-sm overflow-hidden">
<div className="border-border flex items-center justify-between border-b px-4 py-3 shrink-0">
<h3 className="text-foreground text-sm font-semibold"></h3>
<button
onClick={() => closePanel("unified")}
@ -4255,7 +4255,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
</button>
</div>
<div className="flex min-h-0 flex-1 flex-col">
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
<Tabs defaultValue="components" className="flex min-h-0 flex-1 flex-col">
<TabsList className="mx-4 mt-2 grid h-8 w-auto grid-cols-2 gap-1">
<TabsTrigger value="components" className="text-xs">

View File

@ -238,9 +238,9 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
// 컴포넌트가 선택되지 않았을 때도 해상도 설정과 격자 설정은 표시
if (!selectedComponent) {
return (
<div className="flex h-full flex-col bg-white overflow-x-auto">
<div className="flex h-full flex-col bg-white overflow-hidden">
{/* 해상도 설정과 격자 설정 표시 */}
<div className="flex-1 overflow-y-auto overflow-x-auto p-2">
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
<div className="space-y-4 text-xs">
{/* 해상도 설정 */}
{currentResolution && onResolutionChange && (
@ -1403,7 +1403,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
};
return (
<div className="flex h-full flex-col bg-white">
<div className="flex h-full flex-col bg-white overflow-hidden">
{/* 헤더 - 간소화 */}
<div className="border-border border-b px-3 py-2">
{selectedComponent.type === "widget" && (
@ -1414,7 +1414,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
</div>
{/* 통합 컨텐츠 (탭 제거) */}
<div className="flex-1 overflow-y-auto overflow-x-auto p-2">
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
<div className="space-y-4 text-xs">
{/* 해상도 설정 - 항상 맨 위에 표시 */}
{currentResolution && onResolutionChange && (

View File

@ -31,7 +31,7 @@ function SelectTrigger({
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-48 items-center justify-between gap-2 rounded-md border bg-transparent text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-10 data-[size=default]:px-3 data-[size=default]:py-2 data-[size=sm]:h-9 data-[size=sm]:px-3 data-[size=sm]:py-1 data-[size=xs]:h-6 data-[size=xs]:px-2 data-[size=xs]:py-0 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-full items-center justify-between gap-2 rounded-md border bg-transparent text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-10 data-[size=default]:px-3 data-[size=default]:py-2 data-[size=sm]:h-9 data-[size=sm]:px-3 data-[size=sm]:py-1 data-[size=xs]:h-6 data-[size=xs]:px-2 data-[size=xs]:py-0 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}

View File

@ -1,10 +1,63 @@
# RepeatScreenModal 컴포넌트 v3
# RepeatScreenModal 컴포넌트 v3.1
## 개요
`RepeatScreenModal`은 선택한 데이터를 기반으로 여러 개의 카드를 생성하고, 각 카드의 내부 레이아웃을 자유롭게 구성할 수 있는 컴포넌트입니다.
## v3 주요 변경사항
## v3.1 주요 변경사항 (2025-11-28)
### 1. 외부 테이블 데이터 소스
테이블 행에서 **외부 테이블의 데이터를 조회**하여 표시할 수 있습니다.
```
예시: 수주 관리에서 출하 계획 이력 조회
┌─────────────────────────────────────────────────────────────────┐
│ 카드: 품목 A │
├─────────────────────────────────────────────────────────────────┤
│ [행 1] 헤더: 품목코드, 품목명 │
├─────────────────────────────────────────────────────────────────┤
│ [행 2] 테이블: shipment_plan 테이블에서 조회 │
│ → sales_order_id로 조인하여 출하 계획 이력 표시 │
└─────────────────────────────────────────────────────────────────┘
```
### 2. 테이블 행 CRUD
테이블 행에서 **행 추가/수정/삭제** 기능을 지원합니다.
- **추가**: 새 행 추가 버튼으로 빈 행 생성
- **수정**: 편집 가능한 컬럼 직접 수정
- **삭제**: 행 삭제 (확인 팝업 옵션)
### 3. Footer 버튼 영역
모달 하단에 **커스터마이징 가능한 버튼 영역**을 제공합니다.
```
┌─────────────────────────────────────────────────────────────────┐
│ 카드 내용... │
├─────────────────────────────────────────────────────────────────┤
│ [초기화] [취소] [저장] │
└─────────────────────────────────────────────────────────────────┘
```
### 4. 집계 연산식 지원
집계 행에서 **컬럼 간 사칙연산**을 지원합니다.
```typescript
// 예: 미출하 수량 = 수주수량 - 출하수량
{
sourceType: "formula",
formula: "{order_qty} - {ship_qty}",
label: "미출하 수량"
}
```
---
## v3 주요 변경사항 (기존)
### 자유 레이아웃 시스템
@ -33,29 +86,7 @@
| **집계 (aggregation)** | 그룹 내 데이터 집계값 표시 | 총수량, 합계금액 등 |
| **테이블 (table)** | 그룹 내 각 행을 테이블로 표시 | 수주목록, 품목목록 등 |
### 자유로운 조합
```
예시 1: 헤더 + 집계 + 테이블 (출하계획)
├── [행 1] 헤더: 품목코드, 품목명
├── [행 2] 집계: 총수주잔량, 현재고
└── [행 3] 테이블: 수주별 출하계획
예시 2: 집계만
└── [행 1] 집계: 총매출, 총비용, 순이익
예시 3: 테이블만
└── [행 1] 테이블: 품목 목록
예시 4: 테이블 2개
├── [행 1] 테이블: 입고 내역
└── [행 2] 테이블: 출고 내역
예시 5: 헤더 + 헤더 + 필드
├── [행 1] 헤더: 기본 정보 (읽기전용)
├── [행 2] 헤더: 상세 정보 (읽기전용)
└── [행 3] 필드: 입력 필드 (편집가능)
```
---
## 설정 방법
@ -107,13 +138,34 @@
- **집계 필드**: 그룹 탭에서 정의한 집계 결과 선택
- **스타일**: 배경색, 폰트 크기
#### 테이블 행 설정
#### 테이블 행 설정 (v3.1 확장)
- **테이블 제목**: 선택사항
- **헤더 표시**: 테이블 헤더 표시 여부
- **외부 테이블 데이터 소스**: (v3.1 신규)
- 소스 테이블: 조회할 외부 테이블
- 조인 조건: 외부 테이블 키 ↔ 카드 데이터 키
- 정렬: 정렬 컬럼 및 방향
- **CRUD 설정**: (v3.1 신규)
- 추가: 새 행 추가 허용
- 수정: 행 수정 허용
- 삭제: 행 삭제 허용 (확인 팝업 옵션)
- **테이블 컬럼**: 필드명, 라벨, 타입, 너비, 편집 가능
- **저장 설정**: 편집 가능한 컬럼의 저장 위치
### 5. Footer 탭 (v3.1 신규)
- **Footer 사용**: Footer 영역 활성화
- **위치**: 컨텐츠 아래 / 하단 고정 (sticky)
- **정렬**: 왼쪽 / 가운데 / 오른쪽
- **버튼 설정**:
- 라벨: 버튼 텍스트
- 액션: 저장 / 취소 / 닫기 / 초기화 / 커스텀
- 스타일: 기본 / 보조 / 외곽선 / 삭제 / 고스트
- 아이콘: 저장 / X / 초기화 / 없음
---
## 데이터 흐름
```
@ -125,16 +177,22 @@
4. 각 그룹에 대해 집계값 계산
5. 카드 렌더링 (contentRows 기반)
5. 외부 테이블 데이터 소스가 설정된 테이블 행의 데이터 로드 (v3.1)
6. 사용자 편집
6. 카드 렌더링 (contentRows 기반)
7. 저장 시 targetConfig에 따라 테이블별로 데이터 분류 후 저장
7. 사용자 편집 (CRUD 포함)
8. Footer 버튼 또는 기본 저장 버튼으로 저장
9. 기본 데이터 + 외부 테이블 데이터 일괄 저장
```
---
## 사용 예시
### 출하계획 등록
### 출하계획 등록 (v3.1 - 외부 테이블 + CRUD)
```typescript
{
@ -167,40 +225,185 @@
type: "aggregation",
aggregationLayout: "horizontal",
aggregationFields: [
{ aggregationResultField: "total_balance", label: "총수주잔량", backgroundColor: "blue" },
{ aggregationResultField: "order_count", label: "수주건수", backgroundColor: "green" }
{ sourceType: "aggregation", aggregationResultField: "total_balance", label: "총수주잔량", backgroundColor: "blue" },
{ sourceType: "formula", formula: "{order_qty} - {ship_qty}", label: "미출하 수량", backgroundColor: "orange" }
]
},
{
id: "row-3",
type: "table",
tableTitle: "수주 목록",
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: "order_no", label: "수주번호", type: "text", editable: false },
{ id: "tc2", field: "partner_name", label: "거래처", type: "text", editable: false },
{ id: "tc3", field: "balance_qty", label: "미출하", type: "number", editable: false },
{
id: "tc4",
field: "plan_qty",
label: "출하계획",
type: "number",
editable: true,
targetConfig: { targetTable: "shipment_plan", targetColumn: "plan_qty", saveEnabled: true }
}
{ 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
```typescript
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
```typescript
interface TableCrudConfig {
allowCreate: boolean; // 행 추가 허용
allowUpdate: boolean; // 행 수정 허용
allowDelete: boolean; // 행 삭제 허용
newRowDefaults?: Record<string, string>; // 신규 행 기본값 ({field} 형식 지원)
deleteConfirm?: {
enabled: boolean; // 삭제 확인 팝업
message?: string; // 확인 메시지
};
targetTable?: string; // 저장 대상 테이블
}
```
### FooterConfig
```typescript
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 확장)
```typescript
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 분리

View File

@ -23,6 +23,9 @@ export interface RepeatScreenModalProps {
// === 🆕 v3: 자유 레이아웃 ===
contentRows?: CardContentRowConfig[]; // 카드 내부 행들 (각 행마다 타입 선택)
// === 🆕 v3.1: Footer 버튼 설정 ===
footerConfig?: FooterConfig; // Footer 영역 설정
// === (레거시 호환) ===
cardMode?: "simple" | "withTable"; // @deprecated - contentRows 사용 권장
cardLayout?: CardRowConfig[]; // @deprecated - contentRows 사용 권장
@ -33,6 +36,34 @@ export interface RepeatScreenModalProps {
onChange?: (newData: any[]) => void;
}
/**
* 🆕 v3.1: Footer
*/
export interface FooterConfig {
enabled: boolean; // Footer 사용 여부
buttons?: FooterButtonConfig[]; // Footer 버튼들
position?: "sticky" | "static"; // sticky: 하단 고정, static: 컨텐츠 아래
alignment?: "left" | "center" | "right"; // 버튼 정렬
}
/**
* 🆕 v3.1: Footer
*/
export interface FooterButtonConfig {
id: string; // 버튼 고유 ID
label: string; // 버튼 라벨
action: "save" | "cancel" | "close" | "reset" | "custom"; // 액션 타입
variant?: "default" | "secondary" | "outline" | "destructive" | "ghost"; // 버튼 스타일
icon?: string; // 아이콘 (lucide 아이콘명)
disabled?: boolean; // 비활성화 여부
// custom 액션일 때
customAction?: {
type: string; // 커스텀 액션 타입
config?: Record<string, any>; // 커스텀 설정
};
}
/**
*
*/
@ -79,26 +110,177 @@ export interface CardContentRowConfig {
tableTitle?: string; // 테이블 제목
showTableHeader?: boolean; // 테이블 헤더 표시 여부
tableMaxHeight?: string; // 테이블 최대 높이
// 🆕 v3.1: 테이블 외부 데이터 소스
tableDataSource?: TableDataSourceConfig; // 외부 테이블에서 데이터 조회
// 🆕 v3.1: 테이블 CRUD 설정
tableCrud?: TableCrudConfig; // 행 추가/수정/삭제 설정
}
/**
* 🆕 v3.1: 테이블
*
*/
export interface TableDataSourceConfig {
enabled: boolean; // 외부 데이터 소스 사용 여부
sourceTable: string; // 조회할 테이블 (예: "shipment_plan")
// 조인 설정
joinConditions: JoinCondition[]; // 조인 조건 (복합 키 지원)
// 🆕 v3.3: 추가 조인 테이블 설정 (소스 테이블에 없는 컬럼 조회)
additionalJoins?: AdditionalJoinConfig[];
// 🆕 v3.4: 필터 조건 설정 (그룹 내 특정 조건으로 필터링)
filterConfig?: TableFilterConfig;
// 정렬 설정
orderBy?: {
column: string; // 정렬 컬럼
direction: "asc" | "desc"; // 정렬 방향
};
// 제한
limit?: number; // 최대 행 수
}
/**
* 🆕 v3.4: 테이블
*
*/
export interface TableFilterConfig {
enabled: boolean; // 필터 사용 여부
filterField: string; // 필터링할 필드 (예: "order_no")
filterType: "equals" | "notEquals"; // equals: 같은 값만, notEquals: 다른 값만
referenceField: string; // 비교 기준 필드 (formData 또는 카드 대표 데이터에서)
referenceSource: "formData" | "representativeData"; // 비교 값 소스
}
/**
* 🆕 v3.3: 추가
*
*/
export interface AdditionalJoinConfig {
id: string; // 조인 설정 고유 ID
joinTable: string; // 조인할 테이블 (예: "sales_order_mng")
joinType: "left" | "inner"; // 조인 타입
sourceKey: string; // 소스 테이블의 조인 키 (예: "sales_order_id")
targetKey: string; // 조인 테이블의 키 (예: "id")
alias?: string; // 테이블 별칭 (예: "so")
selectColumns?: string[]; // 가져올 컬럼 목록 (비어있으면 전체)
}
/**
* 🆕 v3.1: 조인
*/
export interface JoinCondition {
sourceKey: string; // 외부 테이블의 조인 키 (예: "sales_order_id")
referenceKey: string; // 현재 카드 데이터의 참조 키 (예: "id")
referenceType?: "card" | "row"; // card: 카드 대표 데이터, row: 각 행 데이터 (기본: card)
}
/**
* 🆕 v3.1: 테이블 CRUD
*/
export interface TableCrudConfig {
allowCreate: boolean; // 행 추가 허용
allowUpdate: boolean; // 행 수정 허용
allowDelete: boolean; // 행 삭제 허용
// 신규 행 기본값
newRowDefaults?: Record<string, string>; // 기본값 (예: { status: "READY", sales_order_id: "{id}" })
// 삭제 확인
deleteConfirm?: {
enabled: boolean; // 삭제 확인 팝업 표시 여부
message?: string; // 확인 메시지
};
// 저장 대상 테이블 (외부 데이터 소스 사용 시)
targetTable?: string; // 저장할 테이블 (기본: tableDataSource.sourceTable)
}
/**
* 🆕 v3: 집계
*/
export interface AggregationDisplayConfig {
aggregationResultField: string; // 그룹핑 설정의 resultField 참조
// 값 소스 타입
sourceType: "aggregation" | "formula" | "external" | "externalFormula";
// === sourceType: "aggregation" (기존 그룹핑 집계 결과 참조) ===
aggregationResultField?: string; // 그룹핑 설정의 resultField 참조
// === sourceType: "formula" (컬럼 간 연산) ===
formula?: string; // 연산식 (예: "{order_qty} - {ship_qty}")
// === sourceType: "external" (외부 테이블 조회) ===
externalSource?: ExternalValueSource;
// === sourceType: "externalFormula" (외부 테이블 + 연산) ===
externalSources?: ExternalValueSource[]; // 여러 외부 소스
externalFormula?: string; // 외부 값들을 조합한 연산식 (예: "{inv_qty} + {prod_qty}")
// 표시 설정
label: string; // 표시 라벨
icon?: string; // 아이콘 (lucide 아이콘명)
backgroundColor?: string; // 배경색
textColor?: string; // 텍스트 색상
fontSize?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl"; // 폰트 크기
format?: "number" | "currency" | "percent"; // 숫자 포맷
decimalPlaces?: number; // 소수점 자릿수
}
/**
* 🆕 v3.1: 외부
*/
export interface ExternalValueSource {
alias: string; // 연산식에서 사용할 별칭 (예: "inv_qty")
sourceTable: string; // 조회할 테이블
sourceColumn: string; // 조회할 컬럼
aggregationType?: "sum" | "count" | "avg" | "min" | "max" | "first"; // 집계 타입 (기본: first)
// 조인 설정 (다단계 조인 지원)
joins: ChainedJoinConfig[];
}
/**
* 🆕 v3.1: 다단계
*/
export interface ChainedJoinConfig {
step: number; // 조인 순서 (1, 2, 3...)
sourceTable: string; // 조인할 테이블
joinConditions: {
sourceKey: string; // 조인 테이블의 키
referenceKey: string; // 참조 키 (이전 단계 결과 또는 카드 데이터)
referenceFrom?: "card" | "previousStep"; // 참조 소스 (기본: card, step > 1이면 previousStep)
}[];
selectColumns?: string[]; // 이 단계에서 선택할 컬럼
}
/**
*
* 🆕 v3.2: 다중 (formula)
*/
export interface AggregationConfig {
sourceField: string; // 원본 필드 (예: "balance_qty")
type: "sum" | "count" | "avg" | "min" | "max"; // 집계 타입
// === 집계 소스 타입 ===
sourceType: "column" | "formula"; // column: 테이블 컬럼 집계, formula: 연산식 (가상 집계)
// === sourceType: "column" (테이블 컬럼 집계) ===
sourceTable?: string; // 집계할 테이블 (기본: dataSource.sourceTable, 외부 테이블도 가능)
sourceField?: string; // 원본 필드 (예: "balance_qty")
type?: "sum" | "count" | "avg" | "min" | "max"; // 집계 타입
// === sourceType: "formula" (가상 집계 - 연산식) ===
// 연산식 문법:
// - {resultField}: 다른 집계 결과 참조 (예: {total_balance})
// - {테이블.컬럼}: 테이블의 컬럼 직접 참조 (예: {sales_order_mng.order_qty})
// - SUM({컬럼}): 기본 테이블 행들의 합계
// - SUM_EXT({컬럼}): 외부 테이블 행들의 합계 (externalTableData)
// - 산술 연산: +, -, *, /, ()
formula?: string; // 연산식 (예: "{total_balance} - SUM_EXT({plan_qty})")
// === 공통 ===
resultField: string; // 결과 필드명 (예: "total_balance_qty")
label: string; // 표시 라벨 (예: "총수주잔량")
}
@ -120,7 +302,7 @@ export interface TableLayoutConfig {
*/
export interface TableColumnConfig {
id: string; // 컬럼 고유 ID
field: string; // 필드명
field: string; // 필드명 (소스 테이블 컬럼 또는 조인 테이블 컬럼)
label: string; // 헤더 라벨
type: "text" | "number" | "date" | "select" | "badge"; // 타입
width?: string; // 너비 (예: "100px", "20%")
@ -128,6 +310,10 @@ export interface TableColumnConfig {
editable: boolean; // 편집 가능 여부
required?: boolean; // 필수 입력 여부
// 🆕 v3.3: 컬럼 소스 테이블 지정 (조인 테이블 컬럼 사용 시)
fromTable?: string; // 컬럼이 속한 테이블 (비어있으면 소스 테이블)
fromJoinId?: string; // additionalJoins의 id 참조 (조인 테이블 컬럼일 때)
// Select 타입 옵션
selectOptions?: { value: string; label: string }[];

View File

@ -25,3 +25,4 @@ if (process.env.NODE_ENV === "development") {
SectionPaperRenderer.enableHotReload();
}

View File

@ -65,3 +65,4 @@ export function useCalculation(calculationRules: CalculationRule[] = []) {
};
}