Compare commits
2 Commits
279ec17623
...
b6ed76f243
| Author | SHA1 | Date |
|---|---|---|
|
|
b6ed76f243 | |
|
|
7f186c509f |
|
|
@ -0,0 +1,210 @@
|
|||
# 집계 위젯 (Aggregation Widget) 개발 진행상황
|
||||
|
||||
## 개요
|
||||
데이터의 합계, 평균, 개수, 최대값, 최소값 등을 집계하여 표시하는 위젯
|
||||
|
||||
## 파일 위치
|
||||
- **V2 버전**: `frontend/lib/registry/components/v2-aggregation-widget/`
|
||||
- `index.ts` - 컴포넌트 정의
|
||||
- `types.ts` - 타입 정의
|
||||
- `AggregationWidgetComponent.tsx` - 메인 컴포넌트
|
||||
- `AggregationWidgetConfigPanel.tsx` - 설정 패널
|
||||
- `AggregationWidgetRenderer.tsx` - 렌더러
|
||||
|
||||
- **기존 버전**: `frontend/lib/registry/components/aggregation-widget/`
|
||||
|
||||
---
|
||||
|
||||
## 완료된 기능
|
||||
|
||||
### 1. 기본 집계 기능
|
||||
- [x] 테이블 데이터 조회 및 집계 (SUM, AVG, COUNT, MAX, MIN)
|
||||
- [x] 숫자형 컬럼 자동 감지 (`inputType` / `webType` 기반)
|
||||
- [x] 집계 결과 포맷팅 (숫자, 통화, 퍼센트)
|
||||
- [x] 가로/세로 레이아웃 지원
|
||||
|
||||
### 2. 데이터 소스 타입
|
||||
- [x] `table` - 테이블에서 직접 조회
|
||||
- [x] `component` - 다른 컴포넌트(리피터 등)에서 데이터 수신
|
||||
- [x] `selection` - 선택된 행 데이터로 집계
|
||||
|
||||
### 3. 필터 조건
|
||||
- [x] 필터 추가/삭제/활성화 UI
|
||||
- [x] 연산자: =, !=, >, >=, <, <=, LIKE, IN, IS NULL, IS NOT NULL
|
||||
- [x] 필터 결합 방식: AND / OR
|
||||
- [x] 값 소스 타입:
|
||||
- [x] `static` - 고정값 입력
|
||||
- [x] `formField` - 폼 필드에서 가져오기
|
||||
- [x] `selection` - 선택된 행에서 가져오기 (부분 완료)
|
||||
- [x] `urlParam` - URL 파라미터에서 가져오기
|
||||
- [x] 카테고리 타입 컬럼 - 콤보박스로 값 선택
|
||||
|
||||
### 4. 자동 새로고침
|
||||
- [x] `autoRefresh` - 주기적 새로고침
|
||||
- [x] `refreshInterval` - 새로고침 간격 (초)
|
||||
- [x] `refreshOnFormChange` - 폼 데이터 변경 시 새로고침
|
||||
|
||||
### 5. 스타일 설정
|
||||
- [x] 배경색, 테두리, 패딩
|
||||
- [x] 폰트 크기, 색상
|
||||
- [x] 라벨/아이콘 표시 여부
|
||||
|
||||
---
|
||||
|
||||
## 미완료 기능
|
||||
|
||||
### 1. 선택 데이터 필터 - 소스 컴포넌트 연동 (진행중)
|
||||
|
||||
**현재 상태**:
|
||||
- `FilterCondition`에 `sourceComponentId` 필드 추가됨
|
||||
- 설정 패널 UI에 소스 컴포넌트 선택 드롭다운 추가됨
|
||||
- 소스 컴포넌트 컬럼 로딩 함수 구현됨
|
||||
|
||||
**문제점**:
|
||||
- `screenComponents`가 빈 배열로 전달되어 소스 컴포넌트 목록이 표시되지 않음
|
||||
- `allComponents` → `screenComponents` 변환이 `getComponentConfigPanel.tsx`에서 수행되지만, 실제 컴포넌트 목록이 비어있음
|
||||
|
||||
**해결 필요 사항**:
|
||||
1. `UnifiedPropertiesPanel`에서 `allComponents`가 제대로 전달되는지 확인
|
||||
2. `getComponentConfigPanel.tsx`에서 `screenComponents` 변환 로직 디버깅
|
||||
3. 필터링 조건 확인 (table-list, v2-table-list, unified-repeater 등)
|
||||
|
||||
**관련 코드**:
|
||||
```typescript
|
||||
// types.ts - FilterCondition
|
||||
export interface FilterCondition {
|
||||
// ...
|
||||
sourceComponentId?: string; // 소스 컴포넌트 ID (NEW)
|
||||
sourceColumnName?: string; // 소스 컬럼명
|
||||
// ...
|
||||
}
|
||||
|
||||
// AggregationWidgetConfigPanel.tsx
|
||||
const selectableComponents = useMemo(() => {
|
||||
return screenComponents.filter(comp =>
|
||||
comp.componentType === "table-list" ||
|
||||
comp.componentType === "v2-table-list" ||
|
||||
// ...
|
||||
);
|
||||
}, [screenComponents]);
|
||||
```
|
||||
|
||||
### 2. 런타임 선택 데이터 연동
|
||||
|
||||
**현재 상태**:
|
||||
- `applyFilters` 함수에서 `selectedRows`를 사용하여 필터링
|
||||
- 하지만 특정 컴포넌트(`sourceComponentId`)의 선택 데이터를 가져오는 로직 미구현
|
||||
|
||||
**해결 필요 사항**:
|
||||
1. 각 컴포넌트별 선택 데이터를 관리하는 글로벌 상태 또는 이벤트 시스템 구현
|
||||
2. `selectionChange` 이벤트에서 `componentId`별로 선택 데이터 저장
|
||||
3. `applyFilters`에서 `sourceComponentId`에 해당하는 선택 데이터 사용
|
||||
|
||||
**예상 구현**:
|
||||
```typescript
|
||||
// 컴포넌트별 선택 데이터 저장 (전역 상태)
|
||||
const componentSelections = useRef<Record<string, any[]>>({});
|
||||
|
||||
// 이벤트 리스너
|
||||
window.addEventListener("selectionChange", (event) => {
|
||||
const { componentId, selectedData } = event.detail;
|
||||
componentSelections.current[componentId] = selectedData;
|
||||
});
|
||||
|
||||
// 필터 적용 시
|
||||
case "selection":
|
||||
const sourceData = componentSelections.current[filter.sourceComponentId];
|
||||
compareValue = sourceData?.[0]?.[filter.sourceColumnName];
|
||||
break;
|
||||
```
|
||||
|
||||
### 3. 리피터 컨테이너 내부 집계
|
||||
|
||||
**시나리오**:
|
||||
- 리피터 컨테이너 내부에 집계 위젯 배치
|
||||
- 각 반복 아이템별로 다른 집계 결과 표시
|
||||
|
||||
**현재 상태**:
|
||||
- 리피터가 `formData`에 현재 아이템 데이터를 전달
|
||||
- 필터에서 `valueSourceType: "formField"`를 사용하면 현재 아이템 기준 필터링 가능
|
||||
- 테스트 미완료
|
||||
|
||||
**테스트 필요 케이스**:
|
||||
1. 카테고리 리스트 리피터 + 집계 위젯 (해당 카테고리 상품 개수)
|
||||
2. 주문 리스트 리피터 + 집계 위젯 (해당 주문의 상품 금액 합계)
|
||||
|
||||
---
|
||||
|
||||
## 사용 예시
|
||||
|
||||
### 기본 사용 (테이블 전체 집계)
|
||||
```
|
||||
데이터 소스: 테이블 → sales_order
|
||||
집계 항목:
|
||||
- 총 금액 (SUM of amount)
|
||||
- 주문 건수 (COUNT)
|
||||
- 평균 금액 (AVG of amount)
|
||||
```
|
||||
|
||||
### 필터 사용 (조건부 집계)
|
||||
```
|
||||
데이터 소스: 테이블 → sales_order
|
||||
필터 조건:
|
||||
- status = '완료'
|
||||
- order_date >= 2026-01-01
|
||||
집계 항목:
|
||||
- 완료 주문 금액 합계
|
||||
```
|
||||
|
||||
### 선택 데이터 연동 (목표)
|
||||
```
|
||||
좌측: 품목 테이블 리스트 (item_mng)
|
||||
우측: 집계 위젯
|
||||
|
||||
데이터 소스: 테이블 → sales_order
|
||||
필터 조건:
|
||||
- 컬럼: item_code
|
||||
- 연산자: 같음 (=)
|
||||
- 값 소스: 선택된 행
|
||||
- 소스 컴포넌트: 품목 리스트
|
||||
- 소스 컬럼: item_code
|
||||
|
||||
→ 품목 선택 시 해당 품목의 수주 금액 합계 표시
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 디버깅 로그
|
||||
|
||||
현재 설정 패널에 다음 로그가 추가되어 있음:
|
||||
```typescript
|
||||
console.log("[AggregationWidget] screenComponents:", screenComponents);
|
||||
console.log("[AggregationWidget] selectableComponents:", filtered);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 다음 단계
|
||||
|
||||
1. **소스 컴포넌트 목록 표시 문제 해결**
|
||||
- `allComponents` 전달 경로 추적
|
||||
- `screenComponents` 변환 로직 확인
|
||||
|
||||
2. **컴포넌트별 선택 데이터 관리 구현**
|
||||
- 글로벌 상태 또는 Context 사용
|
||||
- `selectionChange` 이벤트 표준화
|
||||
|
||||
3. **리피터 내부 집계 테스트**
|
||||
- `formField` 필터로 현재 아이템 기준 집계 확인
|
||||
|
||||
4. **디버깅 로그 제거**
|
||||
- 개발 완료 후 콘솔 로그 정리
|
||||
|
||||
---
|
||||
|
||||
## 관련 파일
|
||||
|
||||
- `frontend/lib/utils/getComponentConfigPanel.tsx` - `screenComponents` 변환
|
||||
- `frontend/components/screen/panels/UnifiedPropertiesPanel.tsx` - `allComponents` 전달
|
||||
- `frontend/components/screen/ScreenDesigner.tsx` - `layout.components` 전달
|
||||
|
||||
|
|
@ -58,11 +58,11 @@ export function ComponentsPanel({
|
|||
// unified-biz 제거 - 개별 컴포넌트(flow-widget, rack-structure, numbering-rule)로 직접 표시
|
||||
// unified-hierarchy 제거 - 현재 미사용
|
||||
{
|
||||
id: "unified-repeater",
|
||||
id: "v2-unified-repeater",
|
||||
name: "리피터 그리드",
|
||||
description: "행 단위로 데이터를 추가/수정/삭제",
|
||||
category: "data" as ComponentCategory,
|
||||
tags: ["repeater", "table", "modal", "button", "unified"],
|
||||
tags: ["repeater", "table", "modal", "button", "unified", "v2"],
|
||||
defaultSize: { width: 600, height: 300 },
|
||||
},
|
||||
] as ComponentDefinition[],
|
||||
|
|
@ -113,6 +113,25 @@ export function ComponentsPanel({
|
|||
"selected-items-detail-input",
|
||||
// 연관 데이터 버튼 - unified-repeater로 대체 가능
|
||||
"related-data-buttons",
|
||||
// ===== V2로 대체된 기존 컴포넌트 (v2 버전만 사용) =====
|
||||
"button-primary", // → v2-button-primary
|
||||
"split-panel-layout", // → v2-split-panel-layout
|
||||
"aggregation-widget", // → v2-aggregation-widget
|
||||
"card-display", // → v2-card-display
|
||||
"table-list", // → v2-table-list
|
||||
"text-display", // → v2-text-display
|
||||
"divider-line", // → v2-divider-line
|
||||
"numbering-rule", // → v2-numbering-rule
|
||||
"section-paper", // → v2-section-paper
|
||||
"section-card", // → v2-section-card
|
||||
"location-swap-selector", // → v2-location-swap-selector
|
||||
"rack-structure", // → v2-rack-structure
|
||||
"unified-repeater", // → v2-unified-repeater (아래 unifiedComponents에서 별도 처리)
|
||||
"repeat-container", // → v2-repeat-container
|
||||
"repeat-screen-modal", // → v2-repeat-screen-modal
|
||||
"pivot-grid", // → v2-pivot-grid
|
||||
"table-search-widget", // → v2-table-search-widget
|
||||
"tabs", // → v2-tabs
|
||||
];
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export const AggregationWidgetDefinition = createComponentDefinition({
|
|||
tags: ["집계", "합계", "평균", "개수", "통계", "데이터"],
|
||||
version: "1.0.0",
|
||||
author: "개발팀",
|
||||
hidden: true, // v2-aggregation-widget 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 타입 내보내기
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export const ButtonPrimaryDefinition = createComponentDefinition({
|
|||
version: "1.0.0",
|
||||
author: "개발팀",
|
||||
documentation: "https://docs.example.com/components/button-primary",
|
||||
hidden: true, // v2-button-primary 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 컴포넌트는 ButtonPrimaryRenderer에서 자동 등록됩니다
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ export const CardDisplayDefinition = createComponentDefinition({
|
|||
author: "개발팀",
|
||||
documentation:
|
||||
"테이블 데이터를 카드 형태로 표시하는 컴포넌트입니다. 레이아웃과 다르게 컴포넌트로서 재사용 가능하며, 다양한 설정이 가능합니다.",
|
||||
hidden: true, // v2-card-display 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 컴포넌트는 CardDisplayRenderer에서 자동 등록됩니다
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export const DividerLineDefinition = createComponentDefinition({
|
|||
version: "1.0.0",
|
||||
author: "Developer",
|
||||
documentation: "https://docs.example.com/components/divider-line",
|
||||
hidden: true, // v2-divider-line 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 타입 내보내기
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export const LocationSwapSelectorDefinition = createComponentDefinition({
|
|||
tags: ["출발지", "도착지", "교환", "스왑", "위치", "모바일"],
|
||||
version: "1.0.0",
|
||||
author: "개발팀",
|
||||
hidden: true, // v2-location-swap-selector 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 컴포넌트 내보내기
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export const NumberingRuleDefinition = createComponentDefinition({
|
|||
version: "1.0.0",
|
||||
author: "개발팀",
|
||||
documentation: "코드 자동 채번 규칙을 설정합니다. 접두사, 날짜, 순번 등을 조합하여 고유한 코드를 생성할 수 있습니다.",
|
||||
hidden: true, // v2-numbering-rule 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 타입 내보내기
|
||||
|
|
|
|||
|
|
@ -267,6 +267,7 @@ const PivotGridDefinition = createComponentDefinition({
|
|||
version: "1.0.0",
|
||||
author: "개발팀",
|
||||
documentation: "",
|
||||
hidden: true, // v2-pivot-grid 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ formData에서 다음 필드를 자동으로 읽어옵니다:
|
|||
- location_type / locationType: 위치 유형
|
||||
- status: 사용 여부
|
||||
`,
|
||||
hidden: true, // v2-rack-structure 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 타입 내보내기
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export const RepeatContainerDefinition = createComponentDefinition({
|
|||
tags: ["리피터", "반복", "컨테이너", "데이터", "레이아웃", "그리드"],
|
||||
version: "1.0.0",
|
||||
author: "개발팀",
|
||||
hidden: true, // v2-repeat-container 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 타입 내보내기
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ export const RepeatScreenModalDefinition = createComponentDefinition({
|
|||
tags: ["모달", "폼", "반복", "카드", "그룹핑", "집계", "테이블", "편집", "데이터", "출하계획", "일괄등록", "자유레이아웃"],
|
||||
version: "3.0.0",
|
||||
author: "개발팀",
|
||||
hidden: true, // v2-repeat-screen-modal 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 타입 재 export
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export const SectionCardDefinition = createComponentDefinition({
|
|||
tags: ["섹션", "그룹", "카드", "컨테이너", "제목", "card"],
|
||||
version: "1.0.0",
|
||||
author: "WACE",
|
||||
hidden: true, // v2-section-card 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 컴포넌트는 SectionCardRenderer에서 자동 등록됩니다
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export const SectionPaperDefinition = createComponentDefinition({
|
|||
tags: ["섹션", "그룹", "배경", "컨테이너", "색종이", "paper"],
|
||||
version: "1.0.0",
|
||||
author: "WACE",
|
||||
hidden: true, // v2-section-paper 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 컴포넌트는 SectionPaperRenderer에서 자동 등록됩니다
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export const SplitPanelLayoutDefinition = createComponentDefinition({
|
|||
version: "1.0.0",
|
||||
author: "개발팀",
|
||||
documentation: "https://docs.example.com/components/split-panel-layout",
|
||||
hidden: true, // v2-split-panel-layout 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 컴포넌트는 SplitPanelLayoutRenderer에서 자동 등록됩니다
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export const TableListDefinition = createComponentDefinition({
|
|||
autoWidth: true,
|
||||
stickyHeader: false,
|
||||
|
||||
hidden: true, // v2-table-list 사용으로 패널에서 숨김
|
||||
// 가로 스크롤 및 컬럼 고정 설정
|
||||
horizontalScroll: {
|
||||
enabled: true,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ ComponentRegistry.registerComponent({
|
|||
configPanel: TableSearchWidgetConfigPanel,
|
||||
version: "1.0.0",
|
||||
author: "WACE",
|
||||
hidden: true, // v2-table-search-widget 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
export { TableSearchWidget } from "./TableSearchWidget";
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ ComponentRegistry.registerComponent({
|
|||
width: 800,
|
||||
height: 600,
|
||||
},
|
||||
|
||||
hidden: true, // v2-tabs-widget 사용으로 패널에서 숨김
|
||||
|
||||
defaultProps: {
|
||||
type: "tabs" as const,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export const TextDisplayDefinition = createComponentDefinition({
|
|||
version: "1.0.0",
|
||||
author: "개발팀",
|
||||
documentation: "https://docs.example.com/components/text-display",
|
||||
hidden: true, // v2-text-display 사용으로 패널에서 숨김
|
||||
});
|
||||
|
||||
// 타입 내보내기
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@ export const UnifiedRepeaterDefinition = createComponentDefinition({
|
|||
|
||||
// 설정 패널
|
||||
configPanel: UnifiedRepeaterConfigPanel,
|
||||
|
||||
// v2-unified-repeater 사용으로 패널에서 숨김
|
||||
hidden: true,
|
||||
});
|
||||
|
||||
export default UnifiedRepeaterDefinition;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ interface AggregationWidgetConfigPanelProps {
|
|||
onChange: (config: Partial<AggregationWidgetConfig>) => void;
|
||||
screenTableName?: string;
|
||||
// 화면 내 컴포넌트 목록 (컴포넌트 연결용)
|
||||
screenComponents?: Array<{ id: string; componentType: string; label?: string }>;
|
||||
screenComponents?: Array<{ id: string; componentType: string; label?: string; tableName?: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -138,9 +138,55 @@ export function AggregationWidgetConfigPanel({
|
|||
// 카테고리 옵션 캐시 (categoryCode -> options)
|
||||
const [categoryOptionsCache, setCategoryOptionsCache] = useState<Record<string, Array<{ value: string; label: string }>>>({});
|
||||
|
||||
// 소스 컴포넌트별 컬럼 캐시 (componentId -> columns)
|
||||
const [sourceComponentColumnsCache, setSourceComponentColumnsCache] = useState<Record<string, Array<{ columnName: string; label?: string }>>>({});
|
||||
|
||||
// 데이터 소스 타입 (기본값: table)
|
||||
const dataSourceType = config.dataSourceType || "table";
|
||||
|
||||
// 선택 가능한 데이터 소스 컴포넌트 (테이블 리스트 등)
|
||||
const selectableComponents = useMemo(() => {
|
||||
console.log("[AggregationWidget] screenComponents:", screenComponents);
|
||||
const filtered = screenComponents.filter(comp =>
|
||||
comp.componentType === "table-list" ||
|
||||
comp.componentType === "v2-table-list" ||
|
||||
comp.componentType === "unified-repeater" ||
|
||||
comp.componentType === "v2-unified-repeater" ||
|
||||
comp.componentType === "repeat-container" ||
|
||||
comp.componentType === "v2-repeat-container"
|
||||
);
|
||||
console.log("[AggregationWidget] selectableComponents:", filtered);
|
||||
return filtered;
|
||||
}, [screenComponents]);
|
||||
|
||||
// 소스 컴포넌트 컬럼 로드
|
||||
const loadSourceComponentColumns = async (componentId: string) => {
|
||||
// 이미 캐시에 있으면 스킵
|
||||
if (sourceComponentColumnsCache[componentId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceComp = screenComponents.find(c => c.id === componentId);
|
||||
if (!sourceComp?.tableName) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await tableManagementApi.getColumns(sourceComp.tableName);
|
||||
const cols = (response.data?.columns || response.data || []).map((col: any) => ({
|
||||
columnName: col.column_name || col.columnName,
|
||||
label: col.column_label || col.columnLabel || col.display_name || col.column_name || col.columnName,
|
||||
}));
|
||||
|
||||
setSourceComponentColumnsCache(prev => ({
|
||||
...prev,
|
||||
[componentId]: cols,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("소스 컴포넌트 컬럼 로드 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 실제 사용할 테이블 이름 계산
|
||||
const targetTableName = useMemo(() => {
|
||||
if (config.useCustomTable && config.customTableName) {
|
||||
|
|
@ -177,6 +223,17 @@ export function AggregationWidgetConfigPanel({
|
|||
fetchTables();
|
||||
}, []);
|
||||
|
||||
// 기존 필터의 소스 컴포넌트 컬럼 미리 로드
|
||||
useEffect(() => {
|
||||
const filters = config.filters || [];
|
||||
filters.forEach((filter) => {
|
||||
if (filter.valueSourceType === "selection" && filter.sourceComponentId) {
|
||||
loadSourceComponentColumns(filter.sourceComponentId);
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [config.filters, screenComponents]);
|
||||
|
||||
// 테이블 컬럼 로드
|
||||
useEffect(() => {
|
||||
const loadColumns = async () => {
|
||||
|
|
@ -748,21 +805,53 @@ export function AggregationWidgetConfigPanel({
|
|||
/>
|
||||
)}
|
||||
{filter.valueSourceType === "selection" && (
|
||||
<Select
|
||||
value={filter.sourceColumnName || ""}
|
||||
onValueChange={(value) => updateFilter(filter.id, { sourceColumnName: value })}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue placeholder="컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{columns.map((col) => (
|
||||
<SelectItem key={col.columnName} value={col.columnName}>
|
||||
{col.label || col.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="space-y-2 col-span-2">
|
||||
{/* 소스 컴포넌트 선택 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">소스 컴포넌트</Label>
|
||||
<Select
|
||||
value={filter.sourceComponentId || ""}
|
||||
onValueChange={(value) => {
|
||||
updateFilter(filter.id, { sourceComponentId: value, sourceColumnName: "" });
|
||||
if (value) {
|
||||
loadSourceComponentColumns(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue placeholder="데이터 소스 컴포넌트 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{selectableComponents.map((comp) => (
|
||||
<SelectItem key={comp.id} value={comp.id}>
|
||||
{comp.label || comp.id} ({comp.tableName || "테이블 미설정"})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{/* 소스 컬럼 선택 */}
|
||||
{filter.sourceComponentId && (
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">소스 컬럼 (선택된 행의 컬럼)</Label>
|
||||
<Select
|
||||
value={filter.sourceColumnName || ""}
|
||||
onValueChange={(value) => updateFilter(filter.id, { sourceColumnName: value })}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue placeholder="컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(sourceComponentColumnsCache[filter.sourceComponentId] || []).map((col) => (
|
||||
<SelectItem key={col.columnName} value={col.columnName}>
|
||||
{col.label || col.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{filter.valueSourceType === "urlParam" && (
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -39,12 +39,13 @@ export type FilterValueSourceType =
|
|||
*/
|
||||
export interface FilterCondition {
|
||||
id: string;
|
||||
columnName: string; // 필터 적용할 컬럼
|
||||
columnName: string; // 필터 적용할 컬럼 (집계 대상 테이블)
|
||||
operator: FilterOperator; // 연산자
|
||||
valueSourceType: FilterValueSourceType; // 값 소스 타입
|
||||
staticValue?: string | number | boolean; // 고정 값 (valueSourceType이 static일 때)
|
||||
formFieldName?: string; // 폼 필드명 (valueSourceType이 formField일 때)
|
||||
sourceColumnName?: string; // 소스 컬럼명 (valueSourceType이 selection일 때)
|
||||
sourceComponentId?: string; // 소스 컴포넌트 ID (valueSourceType이 selection일 때)
|
||||
sourceColumnName?: string; // 소스 컬럼명 - 선택된 데이터의 컬럼 (valueSourceType이 selection일 때)
|
||||
urlParamName?: string; // URL 파라미터명 (valueSourceType이 urlParam일 때)
|
||||
enabled: boolean; // 필터 활성화 여부
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ const CONFIG_PANEL_MAP: Record<string, () => Promise<any>> = {
|
|||
"map": () => import("@/lib/registry/components/map/MapConfigPanel"),
|
||||
"rack-structure": () => import("@/lib/registry/components/rack-structure/RackStructureConfigPanel"),
|
||||
"aggregation-widget": () => import("@/lib/registry/components/aggregation-widget/AggregationWidgetConfigPanel"),
|
||||
"v2-aggregation-widget": () => import("@/lib/registry/components/v2-aggregation-widget/AggregationWidgetConfigPanel"),
|
||||
"numbering-rule": () => import("@/lib/registry/components/numbering-rule/NumberingRuleConfigPanel"),
|
||||
"category-manager": () => import("@/lib/registry/components/category-manager/CategoryManagerConfigPanel"),
|
||||
"universal-form-modal": () => import("@/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel"),
|
||||
|
|
@ -489,6 +490,17 @@ export const DynamicComponentConfigPanel: React.FC<ComponentConfigPanelProps> =
|
|||
);
|
||||
}
|
||||
|
||||
// 🆕 allComponents를 screenComponents 형태로 변환 (집계 위젯 등에서 사용)
|
||||
const screenComponents = React.useMemo(() => {
|
||||
if (!allComponents) return [];
|
||||
return allComponents.map((comp: any) => ({
|
||||
id: comp.id,
|
||||
componentType: comp.componentType || comp.type,
|
||||
label: comp.label || comp.name || comp.id,
|
||||
tableName: comp.componentConfig?.tableName || comp.tableName,
|
||||
}));
|
||||
}, [allComponents]);
|
||||
|
||||
return (
|
||||
<ConfigPanelComponent
|
||||
config={config}
|
||||
|
|
@ -502,6 +514,7 @@ export const DynamicComponentConfigPanel: React.FC<ComponentConfigPanelProps> =
|
|||
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
|
||||
allComponents={allComponents} // 🆕 현재 화면의 모든 컴포넌트 (연쇄 드롭다운 부모 감지용)
|
||||
currentComponent={currentComponent} // 🆕 현재 컴포넌트 정보
|
||||
screenComponents={screenComponents} // 🆕 집계 위젯 등에서 사용하는 컴포넌트 목록
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue