Compare commits
3 Commits
a42db5f15a
...
bc34cded95
| Author | SHA1 | Date |
|---|---|---|
|
|
bc34cded95 | |
|
|
4787a8b177 | |
|
|
2f78c83ef6 |
|
|
@ -870,6 +870,17 @@ export async function addTableData(
|
|||
|
||||
const tableManagementService = new TableManagementService();
|
||||
|
||||
// 🆕 멀티테넌시: company_code 자동 추가 (테이블에 company_code 컬럼이 있는 경우)
|
||||
const companyCode = req.user?.companyCode;
|
||||
if (companyCode && !data.company_code) {
|
||||
// 테이블에 company_code 컬럼이 있는지 확인
|
||||
const hasCompanyCodeColumn = await tableManagementService.hasColumn(tableName, "company_code");
|
||||
if (hasCompanyCodeColumn) {
|
||||
data.company_code = companyCode;
|
||||
logger.info(`🔒 멀티테넌시: company_code 자동 추가 - ${companyCode}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 데이터 추가
|
||||
await tableManagementService.addTableData(tableName, data);
|
||||
|
||||
|
|
|
|||
|
|
@ -1516,6 +1516,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 {
|
||||
|
|
@ -4070,4 +4090,22 @@ export class TableManagementService {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블에 특정 컬럼이 존재하는지 확인
|
||||
*/
|
||||
async hasColumn(tableName: string, columnName: string): Promise<boolean> {
|
||||
try {
|
||||
const result = await query<any>(
|
||||
`SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = $1 AND column_name = $2`,
|
||||
[tableName, columnName]
|
||||
);
|
||||
return result.length > 0;
|
||||
} catch (error) {
|
||||
logger.error(`컬럼 존재 여부 확인 실패: ${tableName}.${columnName}`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4291,8 +4291,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")}
|
||||
|
|
@ -4301,7 +4301,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">
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 분리
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -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,181 @@ 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; // 행 삭제 허용
|
||||
|
||||
// 🆕 v3.5: 테이블 영역 저장 버튼
|
||||
allowSave?: boolean; // 테이블 영역에 저장 버튼 표시
|
||||
saveButtonLabel?: string; // 저장 버튼 라벨 (기본: "저장")
|
||||
|
||||
// 신규 행 기본값
|
||||
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 +306,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 +314,10 @@ export interface TableColumnConfig {
|
|||
editable: boolean; // 편집 가능 여부
|
||||
required?: boolean; // 필수 입력 여부
|
||||
|
||||
// 🆕 v3.3: 컬럼 소스 테이블 지정 (조인 테이블 컬럼 사용 시)
|
||||
fromTable?: string; // 컬럼이 속한 테이블 (비어있으면 소스 테이블)
|
||||
fromJoinId?: string; // additionalJoins의 id 참조 (조인 테이블 컬럼일 때)
|
||||
|
||||
// Select 타입 옵션
|
||||
selectOptions?: { value: string; label: string }[];
|
||||
|
||||
|
|
|
|||
|
|
@ -25,3 +25,4 @@ if (process.env.NODE_ENV === "development") {
|
|||
SectionPaperRenderer.enableHotReload();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -65,3 +65,4 @@ export function useCalculation(calculationRules: CalculationRule[] = []) {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue