feat(repeat-screen-modal): 자유 레이아웃 구현 및 데이터 전달 버그 수정
- contentRows 기반 자유 레이아웃 지원 (header/aggregation/table/fields 타입) - aggregationFields, tableColumns 직접 참조하도록 렌더링 로직 수정 - groupByField 없어도 grouping.enabled면 그룹핑 모드로 처리 - buttonActions에서 selectedRowsData를 모달 이벤트로 전달 - ScreenModal에서 selectedData를 groupedData props로 컴포넌트에 전달 - types.ts에 CardContentRowConfig, AggregationDisplayConfig 인터페이스 추가
This commit is contained in:
@@ -120,10 +120,28 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
||||
};
|
||||
};
|
||||
|
||||
// 🆕 선택된 데이터 상태 추가 (RepeatScreenModal 등에서 사용)
|
||||
const [selectedData, setSelectedData] = useState<Record<string, any>[]>([]);
|
||||
|
||||
// 전역 모달 이벤트 리스너
|
||||
useEffect(() => {
|
||||
const handleOpenModal = (event: CustomEvent) => {
|
||||
const { screenId, title, description, size, urlParams } = event.detail;
|
||||
const { screenId, title, description, size, urlParams, selectedData: eventSelectedData, selectedIds } = event.detail;
|
||||
|
||||
console.log("📦 [ScreenModal] 모달 열기 이벤트 수신:", {
|
||||
screenId,
|
||||
title,
|
||||
selectedData: eventSelectedData,
|
||||
selectedIds,
|
||||
});
|
||||
|
||||
// 🆕 선택된 데이터 저장
|
||||
if (eventSelectedData && Array.isArray(eventSelectedData)) {
|
||||
setSelectedData(eventSelectedData);
|
||||
console.log("📦 [ScreenModal] 선택된 데이터 저장:", eventSelectedData.length, "건");
|
||||
} else {
|
||||
setSelectedData([]);
|
||||
}
|
||||
|
||||
// 🆕 URL 파라미터가 있으면 현재 URL에 추가
|
||||
if (urlParams && typeof window !== "undefined") {
|
||||
@@ -164,6 +182,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
||||
});
|
||||
setScreenData(null);
|
||||
setFormData({});
|
||||
setSelectedData([]); // 🆕 선택된 데이터 초기화
|
||||
setContinuousMode(false);
|
||||
localStorage.setItem("screenModal_continuousMode", "false"); // localStorage에 저장
|
||||
console.log("🔄 연속 모드 초기화: false");
|
||||
@@ -605,6 +624,8 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
||||
userId={userId}
|
||||
userName={userName}
|
||||
companyCode={user?.companyCode}
|
||||
// 🆕 선택된 데이터 전달 (RepeatScreenModal 등에서 사용)
|
||||
groupedData={selectedData.length > 0 ? selectedData : undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -408,6 +408,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
value: currentValue,
|
||||
onChange: (value: any) => handleFormDataChange(fieldName, value),
|
||||
onFormDataChange: handleFormDataChange,
|
||||
formData: formData, // 🆕 전체 formData 전달
|
||||
isInteractive: true,
|
||||
readonly: readonly,
|
||||
required: required,
|
||||
@@ -415,6 +416,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
className: "w-full h-full",
|
||||
isInModal: isInModal, // 🆕 EditModal 내부 여부 전달
|
||||
onSave: onSave, // 🆕 EditModal의 handleSave 콜백 전달
|
||||
groupedData: groupedData, // 🆕 그룹 데이터 전달 (RepeatScreenModal용)
|
||||
}}
|
||||
config={widget.webTypeConfig}
|
||||
onEvent={(event: string, data: any) => {
|
||||
|
||||
@@ -863,27 +863,23 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
|
||||
});
|
||||
|
||||
// 래퍼 컴포넌트: 새 ConfigPanel 인터페이스를 기존 패턴에 맞춤
|
||||
const ConfigPanelWrapper = () => {
|
||||
// Section Card, Section Paper 등 신규 컴포넌트는 componentConfig 바로 아래에 설정 저장
|
||||
const config = currentConfig || definition.defaultProps?.componentConfig || {};
|
||||
|
||||
const handleConfigChange = (newConfig: any) => {
|
||||
// componentConfig 전체를 업데이트
|
||||
onUpdateProperty(selectedComponent.id, "componentConfig", newConfig);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<Settings className="h-4 w-4 text-primary" />
|
||||
<h3 className="text-sm font-semibold">{definition.name} 설정</h3>
|
||||
</div>
|
||||
<ConfigPanelComponent config={config} onChange={handleConfigChange} />
|
||||
</div>
|
||||
);
|
||||
// Section Card, Section Paper 등 신규 컴포넌트는 componentConfig 바로 아래에 설정 저장
|
||||
const config = currentConfig || definition.defaultProps?.componentConfig || {};
|
||||
|
||||
const handleConfigChange = (newConfig: any) => {
|
||||
// componentConfig 전체를 업데이트
|
||||
onUpdateProperty(selectedComponent.id, "componentConfig", newConfig);
|
||||
};
|
||||
|
||||
return <ConfigPanelWrapper key={selectedComponent.id} />;
|
||||
return (
|
||||
<div className="space-y-4" key={selectedComponent.id}>
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<Settings className="h-4 w-4 text-primary" />
|
||||
<h3 className="text-sm font-semibold">{definition.name} 설정</h3>
|
||||
</div>
|
||||
<ConfigPanelComponent config={config} onChange={handleConfigChange} />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
console.warn("⚠️ ConfigPanel 없음:", {
|
||||
componentId,
|
||||
|
||||
@@ -326,40 +326,36 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||
});
|
||||
|
||||
// 래퍼 컴포넌트: 새 ConfigPanel 인터페이스를 기존 패턴에 맞춤
|
||||
const ConfigPanelWrapper = () => {
|
||||
// Section Card, Section Paper 등 신규 컴포넌트는 componentConfig 바로 아래에 설정 저장
|
||||
const config = currentConfig || definition.defaultProps?.componentConfig || {};
|
||||
|
||||
const handleConfigChange = (newConfig: any) => {
|
||||
// componentConfig 전체를 업데이트
|
||||
onUpdateProperty(selectedComponent.id, "componentConfig", newConfig);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<Settings className="h-4 w-4 text-primary" />
|
||||
<h3 className="text-sm font-semibold">{definition.name} 설정</h3>
|
||||
</div>
|
||||
<Suspense fallback={
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="text-sm text-muted-foreground">설정 패널 로딩 중...</div>
|
||||
</div>
|
||||
}>
|
||||
<ConfigPanelComponent
|
||||
config={config}
|
||||
onChange={handleConfigChange}
|
||||
tables={tables} // 테이블 정보 전달
|
||||
allTables={allTables} // 🆕 전체 테이블 목록 전달 (selected-items-detail-input 등에서 사용)
|
||||
screenTableName={selectedComponent.tableName || currentTable?.tableName || currentTableName} // 🔧 화면 테이블명 전달
|
||||
tableColumns={currentTable?.columns || []} // 🔧 테이블 컬럼 정보 전달
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
// Section Card, Section Paper 등 신규 컴포넌트는 componentConfig 바로 아래에 설정 저장
|
||||
const config = currentConfig || definition.defaultProps?.componentConfig || {};
|
||||
|
||||
const handleConfigChange = (newConfig: any) => {
|
||||
// componentConfig 전체를 업데이트
|
||||
onUpdateProperty(selectedComponent.id, "componentConfig", newConfig);
|
||||
};
|
||||
|
||||
return <ConfigPanelWrapper key={selectedComponent.id} />;
|
||||
return (
|
||||
<div className="space-y-4" key={selectedComponent.id}>
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<Settings className="h-4 w-4 text-primary" />
|
||||
<h3 className="text-sm font-semibold">{definition.name} 설정</h3>
|
||||
</div>
|
||||
<Suspense fallback={
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="text-sm text-muted-foreground">설정 패널 로딩 중...</div>
|
||||
</div>
|
||||
}>
|
||||
<ConfigPanelComponent
|
||||
config={config}
|
||||
onChange={handleConfigChange}
|
||||
tables={tables} // 테이블 정보 전달
|
||||
allTables={allTables} // 🆕 전체 테이블 목록 전달 (selected-items-detail-input 등에서 사용)
|
||||
screenTableName={selectedComponent.tableName || currentTable?.tableName || currentTableName} // 🔧 화면 테이블명 전달
|
||||
tableColumns={currentTable?.columns || []} // 🔧 테이블 컬럼 정보 전달
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
console.warn("⚠️ ComponentRegistry에서 ConfigPanel을 찾을 수 없음 - switch case로 이동:", {
|
||||
componentId,
|
||||
|
||||
@@ -1,71 +1,145 @@
|
||||
# RepeatScreenModal 컴포넌트 v2
|
||||
# RepeatScreenModal 컴포넌트 v3
|
||||
|
||||
## 개요
|
||||
|
||||
`RepeatScreenModal`은 선택한 데이터를 그룹핑하여 카드 형태로 표시하고, 각 카드 내에서 데이터를 편집할 수 있는 **만능 폼 컴포넌트**입니다.
|
||||
`RepeatScreenModal`은 선택한 데이터를 기반으로 여러 개의 카드를 생성하고, 각 카드의 내부 레이아웃을 자유롭게 구성할 수 있는 컴포넌트입니다.
|
||||
|
||||
**이 컴포넌트 하나로 대부분의 ERP 화면을 설정만으로 구현할 수 있습니다.**
|
||||
## v3 주요 변경사항
|
||||
|
||||
## 핵심 철학
|
||||
### 자유 레이아웃 시스템
|
||||
|
||||
기존의 "simple 모드 / withTable 모드" 구분을 없애고, **행(Row)을 추가하고 각 행마다 타입을 선택**하는 방식으로 변경되었습니다.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ERP 화면 구성의 핵심 │
|
||||
│ 카드 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 1. 어떤 테이블에서 → 어떤 컬럼을 → 어떻게 보여줄 것인가? │
|
||||
│ │
|
||||
│ 2. 보기만 할 것인가? vs 수정 가능하게 할 것인가? │
|
||||
│ │
|
||||
│ 3. 수정한다면 → 어떤 테이블의 → 어떤 컬럼에 저장할 것인가? │
|
||||
│ │
|
||||
│ 4. 데이터를 어떻게 그룹화해서 보여줄 것인가? │
|
||||
│ │
|
||||
│ [행 1] 타입: 헤더 → 품목코드, 품목명, 규격 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ [행 2] 타입: 집계 → 총수주잔량, 현재고, 가용재고 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ [행 3] 타입: 테이블 → 수주번호, 거래처, 납기일, 출하계획 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ [행 4] 타입: 테이블 → 또 다른 테이블도 가능! │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 카드 모드
|
||||
### 행 타입
|
||||
|
||||
### 1. Simple 모드 (단순)
|
||||
| 타입 | 설명 | 사용 시나리오 |
|
||||
|------|------|---------------|
|
||||
| **헤더 (header)** | 필드들을 가로/세로로 나열 | 품목정보, 거래처정보 표시 |
|
||||
| **필드 (fields)** | 헤더와 동일, 편집 가능 | 폼 입력 영역 |
|
||||
| **집계 (aggregation)** | 그룹 내 데이터 집계값 표시 | 총수량, 합계금액 등 |
|
||||
| **테이블 (table)** | 그룹 내 각 행을 테이블로 표시 | 수주목록, 품목목록 등 |
|
||||
|
||||
- **1행 = 1카드**: 선택한 각 행이 독립적인 카드로 표시
|
||||
- 자유로운 레이아웃 구성 (행/컬럼 기반)
|
||||
- 적합한 상황: 단순 데이터 편집, 개별 레코드 수정
|
||||
|
||||
### 2. WithTable 모드 (테이블 포함)
|
||||
|
||||
- **N행 = 1카드**: 그룹핑된 여러 행이 하나의 카드로 표시
|
||||
- 카드 = 헤더 영역 + 테이블 영역
|
||||
- 헤더: 그룹 대표값, 집계값 표시
|
||||
- 테이블: 그룹 내 각 행을 테이블로 표시
|
||||
- 적합한 상황: 출하계획, 구매발주, 생산계획 등 일괄 등록
|
||||
|
||||
## 주요 기능
|
||||
|
||||
| 기능 | 설명 |
|
||||
|------|------|
|
||||
| 그룹핑 | 특정 필드 기준으로 여러 행을 하나의 카드로 묶음 |
|
||||
| 집계 | 그룹 내 데이터의 합계/개수/평균/최소/최대 자동 계산 |
|
||||
| 카드 내 테이블 | 그룹 내 각 행을 테이블 형태로 표시 |
|
||||
| 테이블 내 편집 | 테이블의 특정 컬럼을 편집 가능하게 설정 |
|
||||
| 다중 테이블 저장 | 하나의 카드에서 여러 테이블 동시 저장 |
|
||||
| 컬럼별 소스 설정 | 직접 조회/조인 조회/수동 입력 선택 |
|
||||
| 컬럼별 타겟 설정 | 저장할 테이블과 컬럼 지정 |
|
||||
|
||||
## 사용 시나리오
|
||||
|
||||
### 시나리오 1: 출하계획 동시 등록
|
||||
### 자유로운 조합
|
||||
|
||||
```
|
||||
그룹핑: part_code (품목코드)
|
||||
헤더: 품목정보 + 총수주잔량 + 현재고
|
||||
테이블: 수주별 출하계획 입력
|
||||
예시 1: 헤더 + 집계 + 테이블 (출하계획)
|
||||
├── [행 1] 헤더: 품목코드, 품목명
|
||||
├── [행 2] 집계: 총수주잔량, 현재고
|
||||
└── [행 3] 테이블: 수주별 출하계획
|
||||
|
||||
예시 2: 집계만
|
||||
└── [행 1] 집계: 총매출, 총비용, 순이익
|
||||
|
||||
예시 3: 테이블만
|
||||
└── [행 1] 테이블: 품목 목록
|
||||
|
||||
예시 4: 테이블 2개
|
||||
├── [행 1] 테이블: 입고 내역
|
||||
└── [행 2] 테이블: 출고 내역
|
||||
|
||||
예시 5: 헤더 + 헤더 + 필드
|
||||
├── [행 1] 헤더: 기본 정보 (읽기전용)
|
||||
├── [행 2] 헤더: 상세 정보 (읽기전용)
|
||||
└── [행 3] 필드: 입력 필드 (편집가능)
|
||||
```
|
||||
|
||||
**설정 예시:**
|
||||
## 설정 방법
|
||||
|
||||
### 1. 기본 설정 탭
|
||||
|
||||
- **카드 제목 표시**: 카드 상단에 제목을 표시할지 여부
|
||||
- **카드 제목 템플릿**: `{field_name}` 형식으로 동적 제목 생성
|
||||
- **카드 간격**: 카드 사이의 간격 (8px ~ 32px)
|
||||
- **테두리**: 카드 테두리 표시 여부
|
||||
- **저장 모드**: 전체 저장 / 개별 저장
|
||||
|
||||
### 2. 데이터 소스 탭
|
||||
|
||||
- **소스 테이블**: 데이터를 조회할 테이블
|
||||
- **필터 필드**: formData에서 필터링할 필드 (예: selectedIds)
|
||||
|
||||
### 3. 그룹 탭
|
||||
|
||||
- **그룹핑 활성화**: 여러 행을 하나의 카드로 묶을지 여부
|
||||
- **그룹 기준 필드**: 그룹핑할 필드 (예: part_code)
|
||||
- **집계 설정**:
|
||||
- 원본 필드: 합계할 필드 (예: balance_qty)
|
||||
- 집계 타입: sum, count, avg, min, max
|
||||
- 결과 필드명: 집계 결과를 저장할 필드명
|
||||
- 라벨: 표시될 라벨
|
||||
|
||||
### 4. 레이아웃 탭
|
||||
|
||||
#### 행 추가
|
||||
|
||||
4가지 타입의 행을 추가할 수 있습니다:
|
||||
- **헤더**: 필드 정보 표시 (읽기전용)
|
||||
- **집계**: 그룹 집계값 표시
|
||||
- **테이블**: 그룹 내 행들을 테이블로 표시
|
||||
- **필드**: 입력 필드 (편집가능)
|
||||
|
||||
#### 헤더/필드 행 설정
|
||||
|
||||
- **방향**: 가로 / 세로
|
||||
- **배경색**: 없음, 파랑, 초록, 보라, 주황
|
||||
- **컬럼**: 필드명, 라벨, 타입, 너비, 편집 가능, 필수
|
||||
- **소스 설정**: 직접 / 조인 / 수동
|
||||
- **저장 설정**: 저장할 테이블과 컬럼
|
||||
|
||||
#### 집계 행 설정
|
||||
|
||||
- **레이아웃**: 가로 나열 / 그리드
|
||||
- **그리드 컬럼 수**: 2, 3, 4개
|
||||
- **집계 필드**: 그룹 탭에서 정의한 집계 결과 선택
|
||||
- **스타일**: 배경색, 폰트 크기
|
||||
|
||||
#### 테이블 행 설정
|
||||
|
||||
- **테이블 제목**: 선택사항
|
||||
- **헤더 표시**: 테이블 헤더 표시 여부
|
||||
- **테이블 컬럼**: 필드명, 라벨, 타입, 너비, 편집 가능
|
||||
- **저장 설정**: 편집 가능한 컬럼의 저장 위치
|
||||
|
||||
## 데이터 흐름
|
||||
|
||||
```
|
||||
1. formData에서 selectedIds 가져오기
|
||||
↓
|
||||
2. 소스 테이블에서 해당 ID들의 데이터 조회
|
||||
↓
|
||||
3. 그룹핑 활성화 시 groupByField 기준으로 그룹화
|
||||
↓
|
||||
4. 각 그룹에 대해 집계값 계산
|
||||
↓
|
||||
5. 카드 렌더링 (contentRows 기반)
|
||||
↓
|
||||
6. 사용자 편집
|
||||
↓
|
||||
7. 저장 시 targetConfig에 따라 테이블별로 데이터 분류 후 저장
|
||||
```
|
||||
|
||||
## 사용 예시
|
||||
|
||||
### 출하계획 등록
|
||||
|
||||
```typescript
|
||||
{
|
||||
cardMode: "withTable",
|
||||
showCardTitle: true,
|
||||
cardTitle: "{part_code} - {part_name}",
|
||||
dataSource: {
|
||||
sourceTable: "sales_order_mng",
|
||||
filterField: "selectedIds"
|
||||
@@ -78,159 +152,55 @@
|
||||
{ sourceField: "id", type: "count", resultField: "order_count", label: "수주건수" }
|
||||
]
|
||||
},
|
||||
tableLayout: {
|
||||
headerRows: [
|
||||
{
|
||||
columns: [
|
||||
{ field: "part_code", label: "품목코드", type: "text", editable: false },
|
||||
{ field: "part_name", label: "품목명", type: "text", editable: false },
|
||||
{ field: "total_balance", label: "총수주잔량", type: "aggregation", aggregationField: "total_balance" }
|
||||
]
|
||||
}
|
||||
],
|
||||
tableColumns: [
|
||||
{ field: "order_no", label: "수주번호", type: "text", editable: false },
|
||||
{ field: "partner_id", label: "거래처", type: "text", editable: false },
|
||||
{ field: "due_date", label: "납기일", type: "date", editable: false },
|
||||
{ field: "balance_qty", label: "미출하", type: "number", editable: false },
|
||||
{
|
||||
field: "plan_qty",
|
||||
label: "출하계획",
|
||||
type: "number",
|
||||
editable: true,
|
||||
targetConfig: { targetTable: "shipment_plan", targetColumn: "plan_qty", saveEnabled: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
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: [
|
||||
{ aggregationResultField: "total_balance", label: "총수주잔량", backgroundColor: "blue" },
|
||||
{ aggregationResultField: "order_count", label: "수주건수", backgroundColor: "green" }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "row-3",
|
||||
type: "table",
|
||||
tableTitle: "수주 목록",
|
||||
showTableHeader: 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 }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 시나리오 2: 구매발주 일괄 등록
|
||||
## 레거시 호환
|
||||
|
||||
```
|
||||
그룹핑: supplier_id (공급업체)
|
||||
헤더: 공급업체정보 + 총발주금액
|
||||
테이블: 품목별 발주수량 입력
|
||||
```
|
||||
v2에서 사용하던 `cardMode`, `cardLayout`, `tableLayout` 설정도 계속 지원됩니다.
|
||||
새로운 프로젝트에서는 `contentRows`를 사용하는 것을 권장합니다.
|
||||
|
||||
### 시나리오 3: 생산계획 일괄 등록
|
||||
## 주의사항
|
||||
|
||||
```
|
||||
그룹핑: product_code (제품코드)
|
||||
헤더: 제품정보 + 현재고 + 필요수량
|
||||
테이블: 작업지시별 생산수량 입력
|
||||
```
|
||||
|
||||
### 시나리오 4: 입고검사 일괄 처리
|
||||
|
||||
```
|
||||
그룹핑: po_no (발주번호)
|
||||
헤더: 발주정보 + 공급업체
|
||||
테이블: 품목별 검사결과 입력
|
||||
```
|
||||
|
||||
## ConfigPanel 사용법
|
||||
|
||||
### 1. 기본 설정 탭
|
||||
|
||||
- **카드 제목**: `{field}` 형식으로 동적 제목 설정
|
||||
- **카드 간격**: 카드 사이 간격 (8px ~ 32px)
|
||||
- **테두리**: 카드 테두리 표시 여부
|
||||
- **저장 모드**: 전체 저장 / 개별 저장
|
||||
- **카드 모드**: 단순 / 테이블
|
||||
|
||||
### 2. 데이터 소스 탭
|
||||
|
||||
- **소스 테이블**: 데이터를 조회할 테이블
|
||||
- **필터 필드**: formData에서 가져올 필터 필드명 (예: `selectedIds`)
|
||||
|
||||
### 3. 그룹핑 탭 (테이블 모드에서 활성화)
|
||||
|
||||
- **그룹핑 활성화**: ON/OFF
|
||||
- **그룹 기준 필드**: 그룹핑할 필드 선택
|
||||
- **집계 설정**: 합계/개수/평균 등 집계 추가
|
||||
|
||||
### 4. 레이아웃 탭
|
||||
|
||||
**Simple 모드:**
|
||||
- 행 추가/삭제
|
||||
- 각 행에 컬럼 추가/삭제
|
||||
- 컬럼별 필드명, 라벨, 타입, 너비, 편집 가능 여부 설정
|
||||
|
||||
**WithTable 모드:**
|
||||
- 헤더 영역: 그룹 대표값, 집계값 표시용 행/컬럼 설정
|
||||
- 테이블 영역: 그룹 내 각 행을 표시할 테이블 컬럼 설정
|
||||
|
||||
## 컬럼 설정 상세
|
||||
|
||||
### 소스 설정 (데이터 조회)
|
||||
|
||||
| 타입 | 설명 |
|
||||
|------|------|
|
||||
| direct | 소스 테이블에서 직접 조회 |
|
||||
| join | 다른 테이블과 조인하여 조회 |
|
||||
| manual | 사용자 직접 입력 |
|
||||
|
||||
### 타겟 설정 (데이터 저장)
|
||||
|
||||
- **저장 테이블**: 데이터를 저장할 테이블
|
||||
- **저장 컬럼**: 데이터를 저장할 컬럼
|
||||
- **저장 활성화**: 저장 여부
|
||||
|
||||
## 타입 정의
|
||||
|
||||
```typescript
|
||||
interface RepeatScreenModalProps {
|
||||
// 기본 설정
|
||||
cardTitle?: string;
|
||||
cardSpacing?: string;
|
||||
showCardBorder?: boolean;
|
||||
saveMode?: "all" | "individual";
|
||||
cardMode?: "simple" | "withTable";
|
||||
|
||||
// 데이터 소스
|
||||
dataSource?: DataSourceConfig;
|
||||
|
||||
// 그룹핑 설정
|
||||
grouping?: GroupingConfig;
|
||||
|
||||
// 레이아웃
|
||||
cardLayout?: CardRowConfig[]; // simple 모드
|
||||
tableLayout?: TableLayoutConfig; // withTable 모드
|
||||
}
|
||||
|
||||
interface GroupingConfig {
|
||||
enabled: boolean;
|
||||
groupByField: string;
|
||||
aggregations?: AggregationConfig[];
|
||||
}
|
||||
|
||||
interface AggregationConfig {
|
||||
sourceField: string;
|
||||
type: "sum" | "count" | "avg" | "min" | "max";
|
||||
resultField: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface TableLayoutConfig {
|
||||
headerRows: CardRowConfig[];
|
||||
tableColumns: TableColumnConfig[];
|
||||
}
|
||||
```
|
||||
|
||||
## 파일 구조
|
||||
|
||||
```
|
||||
repeat-screen-modal/
|
||||
├── index.ts # 컴포넌트 정의 및 export
|
||||
├── types.ts # 타입 정의
|
||||
├── RepeatScreenModalComponent.tsx # 메인 컴포넌트
|
||||
├── RepeatScreenModalConfigPanel.tsx # 설정 패널
|
||||
├── RepeatScreenModalRenderer.tsx # 자동 등록
|
||||
└── README.md # 문서
|
||||
```
|
||||
|
||||
## 버전 히스토리
|
||||
|
||||
- **v2.0.0**: 그룹핑, 집계, 테이블 모드 추가
|
||||
- **v1.0.0**: 초기 버전 (Simple 모드)
|
||||
1. **집계는 그룹핑 필수**: 집계 행은 그룹핑이 활성화되어 있어야 의미가 있습니다.
|
||||
2. **테이블은 그룹핑 필수**: 테이블 행도 그룹핑이 활성화되어 있어야 그룹 내 행들을 표시할 수 있습니다.
|
||||
3. **단순 모드**: 그룹핑 없이 사용하면 1행 = 1카드로 동작합니다. 이 경우 헤더/필드 타입만 사용 가능합니다.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -18,18 +18,20 @@ import type {
|
||||
TableColumnConfig,
|
||||
GroupedCardData,
|
||||
CardRowData,
|
||||
CardContentRowConfig,
|
||||
AggregationDisplayConfig,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* RepeatScreenModal 컴포넌트 정의 v2
|
||||
* RepeatScreenModal 컴포넌트 정의 v3
|
||||
* 반복 화면 모달 - 선택한 행 개수만큼 카드를 생성하며, 각 카드는 커스터마이징 가능한 레이아웃
|
||||
*
|
||||
* 주요 기능:
|
||||
* - 🆕 카드 모드: 단순(simple) / 테이블(withTable) 모드 선택
|
||||
* - 🆕 그룹핑: 특정 필드 기준으로 여러 행을 하나의 카드로 묶기
|
||||
* - 🆕 집계: 그룹 내 데이터의 합계/평균/개수 등 자동 계산
|
||||
* - 🆕 카드 내 테이블: 그룹 내 각 행을 테이블 형태로 표시
|
||||
* - 유연한 레이아웃: 행/컬럼 기반 자유로운 구조
|
||||
* - 🆕 v3: 자유 레이아웃 - 행(Row)을 추가하고 각 행마다 타입(헤더/집계/테이블/필드) 선택
|
||||
* - 그룹핑: 특정 필드 기준으로 여러 행을 하나의 카드로 묶기
|
||||
* - 집계: 그룹 내 데이터의 합계/평균/개수 등 자동 계산
|
||||
* - 카드 내 테이블: 그룹 내 각 행을 테이블 형태로 표시
|
||||
* - 유연한 레이아웃: 행 타입 자유 선택, 순서 자유 배치
|
||||
* - 컬럼별 소스 설정: 직접 조회/조인 조회/수동 입력
|
||||
* - 컬럼별 타겟 설정: 어느 테이블의 어느 컬럼에 저장할지 설정
|
||||
* - 다중 테이블 저장: 하나의 카드에서 여러 테이블 동시 저장
|
||||
@@ -45,67 +47,37 @@ export const RepeatScreenModalDefinition = createComponentDefinition({
|
||||
name: "반복 화면 모달",
|
||||
nameEng: "Repeat Screen Modal",
|
||||
description:
|
||||
"선택한 행을 그룹핑하여 카드로 표시하고, 각 카드는 헤더+테이블 구조로 편집 가능한 폼 (출하계획, 구매발주 등)",
|
||||
"선택한 행을 그룹핑하여 카드로 표시하고, 각 카드는 헤더/집계/테이블을 자유롭게 구성 가능한 폼 (출하계획, 구매발주 등)",
|
||||
category: ComponentCategory.DATA,
|
||||
webType: "form",
|
||||
component: RepeatScreenModalComponent,
|
||||
defaultConfig: {
|
||||
// 기본 설정
|
||||
cardTitle: "{part_code} - {part_name}",
|
||||
showCardTitle: true,
|
||||
cardTitle: "카드 {index}",
|
||||
cardSpacing: "24px",
|
||||
showCardBorder: true,
|
||||
saveMode: "all",
|
||||
|
||||
// 카드 모드 (simple: 1행=1카드, withTable: 그룹핑+테이블)
|
||||
cardMode: "simple",
|
||||
|
||||
// 데이터 소스
|
||||
dataSource: {
|
||||
sourceTable: "",
|
||||
filterField: "selectedIds",
|
||||
},
|
||||
|
||||
// 그룹핑 설정 (withTable 모드에서 사용)
|
||||
// 그룹핑 설정
|
||||
grouping: {
|
||||
enabled: false,
|
||||
groupByField: "",
|
||||
aggregations: [],
|
||||
},
|
||||
|
||||
// Simple 모드 레이아웃
|
||||
cardLayout: [
|
||||
{
|
||||
id: "row-1",
|
||||
columns: [
|
||||
{
|
||||
id: "col-1",
|
||||
field: "name",
|
||||
label: "이름",
|
||||
type: "text",
|
||||
width: "50%",
|
||||
editable: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: "col-2",
|
||||
field: "status",
|
||||
label: "상태",
|
||||
type: "select",
|
||||
width: "50%",
|
||||
editable: true,
|
||||
required: false,
|
||||
selectOptions: [
|
||||
{ value: "active", label: "활성" },
|
||||
{ value: "inactive", label: "비활성" },
|
||||
],
|
||||
},
|
||||
],
|
||||
gap: "16px",
|
||||
layout: "horizontal",
|
||||
},
|
||||
],
|
||||
// 🆕 v3: 자유 레이아웃 (행 추가 후 타입 선택)
|
||||
contentRows: [],
|
||||
|
||||
// WithTable 모드 레이아웃
|
||||
// (레거시 호환)
|
||||
cardMode: "simple",
|
||||
cardLayout: [],
|
||||
tableLayout: {
|
||||
headerRows: [],
|
||||
tableColumns: [],
|
||||
@@ -114,8 +86,8 @@ export const RepeatScreenModalDefinition = createComponentDefinition({
|
||||
defaultSize: { width: 1000, height: 800 },
|
||||
configPanel: RepeatScreenModalConfigPanel,
|
||||
icon: "LayoutGrid",
|
||||
tags: ["모달", "폼", "반복", "카드", "그룹핑", "집계", "테이블", "편집", "데이터", "출하계획", "일괄등록"],
|
||||
version: "2.0.0",
|
||||
tags: ["모달", "폼", "반복", "카드", "그룹핑", "집계", "테이블", "편집", "데이터", "출하계획", "일괄등록", "자유레이아웃"],
|
||||
version: "3.0.0",
|
||||
author: "개발팀",
|
||||
});
|
||||
|
||||
@@ -134,6 +106,8 @@ export type {
|
||||
TableColumnConfig,
|
||||
GroupedCardData,
|
||||
CardRowData,
|
||||
CardContentRowConfig,
|
||||
AggregationDisplayConfig,
|
||||
};
|
||||
|
||||
// 컴포넌트 재 export
|
||||
|
||||
@@ -4,10 +4,11 @@ import { ComponentRendererProps } from "@/types/component";
|
||||
* RepeatScreenModal Props
|
||||
* 선택한 행 개수만큼 카드를 생성하며, 각 카드는 커스터마이징 가능한 레이아웃을 가짐
|
||||
*
|
||||
* 🆕 v2: 그룹핑, 집계, 카드 내 테이블 기능 추가
|
||||
* 🆕 v3: 행(Row) 기반 자유 레이아웃 - 각 행마다 타입(헤더/집계/테이블) 선택 가능
|
||||
*/
|
||||
export interface RepeatScreenModalProps {
|
||||
// === 기본 설정 ===
|
||||
showCardTitle?: boolean; // 카드 제목 표시 여부
|
||||
cardTitle?: string; // 카드 제목 템플릿 (예: "{order_no} - {item_code}")
|
||||
cardSpacing?: string; // 카드 간 간격 (기본: 24px)
|
||||
showCardBorder?: boolean; // 카드 테두리 표시 여부
|
||||
@@ -16,17 +17,16 @@ export interface RepeatScreenModalProps {
|
||||
// === 데이터 소스 ===
|
||||
dataSource?: DataSourceConfig; // 데이터 소스 설정
|
||||
|
||||
// === 🆕 그룹핑 설정 ===
|
||||
// === 그룹핑 설정 ===
|
||||
grouping?: GroupingConfig; // 그룹핑 설정
|
||||
|
||||
// === 🆕 카드 모드 ===
|
||||
cardMode?: "simple" | "withTable"; // 단순 필드 vs 테이블 포함
|
||||
// === 🆕 v3: 자유 레이아웃 ===
|
||||
contentRows?: CardContentRowConfig[]; // 카드 내부 행들 (각 행마다 타입 선택)
|
||||
|
||||
// === 레이아웃 (simple 모드) ===
|
||||
cardLayout?: CardRowConfig[]; // 카드 내부 레이아웃 (행/컬럼 구조)
|
||||
|
||||
// === 🆕 레이아웃 (withTable 모드) ===
|
||||
tableLayout?: TableLayoutConfig; // 테이블 포함 레이아웃
|
||||
// === (레거시 호환) ===
|
||||
cardMode?: "simple" | "withTable"; // @deprecated - contentRows 사용 권장
|
||||
cardLayout?: CardRowConfig[]; // @deprecated - contentRows 사용 권장
|
||||
tableLayout?: TableLayoutConfig; // @deprecated - contentRows 사용 권장
|
||||
|
||||
// === 값 ===
|
||||
value?: any[];
|
||||
@@ -43,7 +43,7 @@ export interface DataSourceConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 그룹핑 설정
|
||||
* 그룹핑 설정
|
||||
* 특정 필드 기준으로 여러 행을 하나의 카드로 묶음
|
||||
*/
|
||||
export interface GroupingConfig {
|
||||
@@ -55,7 +55,46 @@ export interface GroupingConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 집계 설정
|
||||
* 🆕 v3: 카드 내부 행 설정
|
||||
* 각 행마다 타입(헤더/집계/테이블)을 선택할 수 있음
|
||||
*/
|
||||
export interface CardContentRowConfig {
|
||||
id: string; // 행 고유 ID
|
||||
type: "header" | "aggregation" | "table" | "fields"; // 행 타입
|
||||
|
||||
// === header/fields 타입일 때 ===
|
||||
columns?: CardColumnConfig[]; // 컬럼 설정
|
||||
layout?: "horizontal" | "vertical"; // 레이아웃 방향
|
||||
gap?: string; // 컬럼 간 간격
|
||||
backgroundColor?: string; // 배경색
|
||||
padding?: string; // 패딩
|
||||
|
||||
// === aggregation 타입일 때 ===
|
||||
aggregationFields?: AggregationDisplayConfig[]; // 표시할 집계 필드들
|
||||
aggregationLayout?: "horizontal" | "grid"; // 집계 레이아웃 (가로 나열 / 그리드)
|
||||
aggregationColumns?: number; // grid일 때 컬럼 수 (기본: 4)
|
||||
|
||||
// === table 타입일 때 ===
|
||||
tableColumns?: TableColumnConfig[]; // 테이블 컬럼 설정
|
||||
tableTitle?: string; // 테이블 제목
|
||||
showTableHeader?: boolean; // 테이블 헤더 표시 여부
|
||||
tableMaxHeight?: string; // 테이블 최대 높이
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 v3: 집계 표시 설정
|
||||
*/
|
||||
export interface AggregationDisplayConfig {
|
||||
aggregationResultField: string; // 그룹핑 설정의 resultField 참조
|
||||
label: string; // 표시 라벨
|
||||
icon?: string; // 아이콘 (lucide 아이콘명)
|
||||
backgroundColor?: string; // 배경색
|
||||
textColor?: string; // 텍스트 색상
|
||||
fontSize?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl"; // 폰트 크기
|
||||
}
|
||||
|
||||
/**
|
||||
* 집계 설정
|
||||
*/
|
||||
export interface AggregationConfig {
|
||||
sourceField: string; // 원본 필드 (예: "balance_qty")
|
||||
@@ -65,24 +104,19 @@ export interface AggregationConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 테이블 포함 레이아웃 설정
|
||||
* 카드 = 헤더 영역 + 테이블 영역
|
||||
* @deprecated v3에서는 contentRows 사용 권장
|
||||
* 테이블 포함 레이아웃 설정
|
||||
*/
|
||||
export interface TableLayoutConfig {
|
||||
// 헤더 영역: 그룹 대표값, 집계값 표시
|
||||
headerRows: CardRowConfig[];
|
||||
|
||||
// 테이블 영역: 그룹 내 각 행을 테이블로 표시
|
||||
tableColumns: TableColumnConfig[];
|
||||
|
||||
// 테이블 설정
|
||||
tableTitle?: string; // 테이블 제목
|
||||
showTableHeader?: boolean; // 테이블 헤더 표시 여부 (기본: true)
|
||||
tableMaxHeight?: string; // 테이블 최대 높이 (스크롤용)
|
||||
tableTitle?: string;
|
||||
showTableHeader?: boolean;
|
||||
tableMaxHeight?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 테이블 컬럼 설정
|
||||
* 테이블 컬럼 설정
|
||||
*/
|
||||
export interface TableColumnConfig {
|
||||
id: string; // 컬럼 고유 ID
|
||||
|
||||
@@ -1,132 +1 @@
|
||||
/**
|
||||
* SimpleRepeaterTable 컴포넌트 타입 정의
|
||||
* 데이터 검색/추가 없이 주어진 데이터를 표시하고 편집하는 경량 테이블
|
||||
*/
|
||||
|
||||
export interface SimpleRepeaterTableProps {
|
||||
// 데이터
|
||||
value?: any[]; // 현재 표시할 데이터
|
||||
onChange?: (newData: any[]) => void; // 데이터 변경 콜백
|
||||
|
||||
// 테이블 설정
|
||||
columns: SimpleRepeaterColumnConfig[]; // 테이블 컬럼 설정
|
||||
|
||||
// 🆕 초기 데이터 로드 설정
|
||||
initialDataConfig?: InitialDataConfig;
|
||||
|
||||
// 계산 규칙
|
||||
calculationRules?: CalculationRule[]; // 자동 계산 규칙 (수량 * 단가 = 금액)
|
||||
|
||||
// 옵션
|
||||
readOnly?: boolean; // 읽기 전용 모드 (편집 불가)
|
||||
showRowNumber?: boolean; // 행 번호 표시 (기본: true)
|
||||
allowDelete?: boolean; // 삭제 버튼 표시 (기본: true)
|
||||
maxHeight?: string; // 테이블 최대 높이 (기본: "240px")
|
||||
|
||||
// 스타일
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface SimpleRepeaterColumnConfig {
|
||||
field: string; // 필드명 (화면에 표시용 임시 키)
|
||||
label: string; // 컬럼 헤더 라벨
|
||||
type?: "text" | "number" | "date" | "select"; // 입력 타입
|
||||
editable?: boolean; // 편집 가능 여부
|
||||
calculated?: boolean; // 계산 필드 여부 (자동 계산되는 필드)
|
||||
width?: string; // 컬럼 너비
|
||||
required?: boolean; // 필수 입력 여부
|
||||
defaultValue?: any; // 기본값
|
||||
selectOptions?: { value: string; label: string }[]; // select일 때 옵션
|
||||
|
||||
// 🆕 데이터 조회 설정 (어디서 가져올지)
|
||||
sourceConfig?: ColumnSourceConfig;
|
||||
|
||||
// 🆕 데이터 저장 설정 (어디에 저장할지)
|
||||
targetConfig?: ColumnTargetConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 데이터 조회 설정
|
||||
* 어떤 테이블에서 어떤 컬럼을 어떤 조건으로 조회할지 정의
|
||||
*/
|
||||
export interface ColumnSourceConfig {
|
||||
/** 조회 타입 */
|
||||
type: "direct" | "join" | "manual";
|
||||
|
||||
// type: "direct" - 직접 조회 (단일 테이블에서 바로 가져오기)
|
||||
sourceTable?: string; // 조회할 테이블 (예: "sales_order_mng")
|
||||
sourceColumn?: string; // 조회할 컬럼 (예: "item_name")
|
||||
|
||||
// type: "join" - 조인 조회 (다른 테이블과 조인하여 가져오기)
|
||||
joinTable?: string; // 조인할 테이블 (예: "customer_item_mapping")
|
||||
joinColumn?: string; // 조인 테이블에서 가져올 컬럼 (예: "basic_price")
|
||||
joinKey?: string; // 🆕 조인 키 (현재 테이블의 컬럼, 예: "sales_order_id")
|
||||
joinRefKey?: string; // 🆕 참조 키 (조인 테이블의 컬럼, 예: "id")
|
||||
joinConditions?: SourceJoinCondition[]; // 조인 조건 (어떤 키로 조인할지)
|
||||
|
||||
// type: "manual" - 사용자 직접 입력 (조회 안 함)
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 데이터 저장 설정
|
||||
* 어떤 테이블의 어떤 컬럼에 저장할지 정의
|
||||
*/
|
||||
export interface ColumnTargetConfig {
|
||||
targetTable?: string; // 저장할 테이블 (예: "shipment_plan")
|
||||
targetColumn?: string; // 저장할 컬럼 (예: "plan_qty")
|
||||
saveEnabled?: boolean; // 저장 활성화 여부 (false면 읽기 전용)
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 소스 조인 조건
|
||||
* 데이터를 조회할 때 어떤 키로 조인할지 정의
|
||||
*/
|
||||
export interface SourceJoinCondition {
|
||||
/** 기준 테이블 */
|
||||
baseTable: string; // 기준이 되는 테이블 (예: "sales_order_mng")
|
||||
/** 기준 컬럼 */
|
||||
baseColumn: string; // 기준 테이블의 컬럼 (예: "item_code")
|
||||
/** 조인 테이블의 컬럼 */
|
||||
joinColumn: string; // 조인 테이블에서 매칭할 컬럼 (예: "item_code")
|
||||
/** 비교 연산자 */
|
||||
operator?: "=" | "!=" | ">" | "<" | ">=" | "<=";
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 초기 데이터 로드 설정
|
||||
* 컴포넌트가 로드될 때 어떤 데이터를 가져올지
|
||||
*/
|
||||
export interface InitialDataConfig {
|
||||
/** 로드할 테이블 */
|
||||
sourceTable: string; // 예: "sales_order_mng"
|
||||
|
||||
/** 필터 조건 */
|
||||
filterConditions?: DataFilterCondition[];
|
||||
|
||||
/** 선택할 컬럼 목록 */
|
||||
selectColumns?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터 필터 조건
|
||||
*/
|
||||
export interface DataFilterCondition {
|
||||
/** 필드명 */
|
||||
field: string;
|
||||
/** 연산자 */
|
||||
operator: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE" | "IN";
|
||||
/** 값 (또는 다른 필드 참조) */
|
||||
value: any;
|
||||
/** 값을 다른 필드에서 가져올지 */
|
||||
valueFromField?: string; // 예: "order_no" (formData에서 가져오기)
|
||||
}
|
||||
|
||||
/**
|
||||
* 계산 규칙 (자동 계산)
|
||||
*/
|
||||
export interface CalculationRule {
|
||||
result: string; // 결과를 저장할 필드 (예: "total_amount")
|
||||
formula: string; // 계산 공식 (예: "quantity * unit_price")
|
||||
dependencies: string[]; // 의존하는 필드들 (예: ["quantity", "unit_price"])
|
||||
}
|
||||
|
||||
|
||||
@@ -942,6 +942,7 @@ export class ButtonActionExecutor {
|
||||
title: config.modalTitle,
|
||||
size: config.modalSize,
|
||||
targetScreenId: config.targetScreenId,
|
||||
selectedRowsData: context.selectedRowsData,
|
||||
});
|
||||
|
||||
if (config.targetScreenId) {
|
||||
@@ -958,6 +959,10 @@ export class ButtonActionExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 선택된 행 데이터 수집
|
||||
const selectedData = context.selectedRowsData || [];
|
||||
console.log("📦 [handleModal] 선택된 데이터:", selectedData);
|
||||
|
||||
// 전역 모달 상태 업데이트를 위한 이벤트 발생
|
||||
const modalEvent = new CustomEvent("openScreenModal", {
|
||||
detail: {
|
||||
@@ -965,6 +970,9 @@ export class ButtonActionExecutor {
|
||||
title: config.modalTitle || "화면",
|
||||
description: description,
|
||||
size: config.modalSize || "md",
|
||||
// 🆕 선택된 행 데이터 전달
|
||||
selectedData: selectedData,
|
||||
selectedIds: selectedData.map((row: any) => row.id).filter(Boolean),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user