628 lines
21 KiB
Markdown
628 lines
21 KiB
Markdown
# POP 개발 계획
|
|
|
|
---
|
|
|
|
## 현재 상태 (2026-02-12)
|
|
|
|
**대시보드 집계 함수 설정 유효성 검증 강화 (v2 구현 완료, 브라우저 확인 대기)**
|
|
|
|
---
|
|
|
|
## 작업 순서
|
|
|
|
```
|
|
[Phase 1~3] [Phase 5] [정의서] [Phase 0~6]
|
|
v4 Flexbox → v5 CSS Grid → 컴포넌트 설계 → 실제 구현
|
|
완료 완료 (v5.2) 완료 (v8.0) 다음
|
|
```
|
|
|
|
---
|
|
|
|
## 완료된 Phase
|
|
|
|
### Phase 1~3: v4 Flexbox 시스템 (완료, 레거시 삭제됨)
|
|
|
|
v4 Flexbox 기반 시스템은 v5 CSS Grid로 완전히 대체되었습니다.
|
|
v4 관련 파일은 모두 삭제되었습니다.
|
|
|
|
- [x] v4 기본 구조, 렌더러, 디자이너 통합
|
|
- [x] Undo/Redo, 드래그 리사이즈, Flexbox 가로 배치
|
|
- [x] 비율 스케일링 시스템
|
|
- [x] 오버라이드 기능 (모드별 배치 고정)
|
|
- [x] 컴포넌트 표시/숨김, 줄바꿈
|
|
|
|
### Phase 5: v5 CSS Grid 시스템 (완료)
|
|
|
|
#### Phase 5.1: 타입 정의 (완료)
|
|
- [x] `PopLayoutDataV5` 인터페이스
|
|
- [x] `PopGridConfig`, `PopGridPosition` 타입
|
|
- [x] `GridMode`, `GRID_BREAKPOINTS` 상수
|
|
- [x] `createEmptyPopLayoutV5()`, `isV5Layout()`, `detectGridMode()`
|
|
|
|
#### Phase 5.2: 그리드 렌더러 (완료)
|
|
- [x] `PopRenderer.tsx` - CSS Grid 기반 렌더링
|
|
- [x] 격자 셀 렌더링 (CSS Grid 동일 좌표계)
|
|
- [x] 위치 변환 (12칸 -> 4/6/8칸)
|
|
|
|
#### Phase 5.3: 디자이너 UI (완료)
|
|
- [x] `PopCanvas.tsx` - 그리드 캔버스 + 행/열 라벨
|
|
- [x] 드래그 스냅 (칸에 맞춤)
|
|
- [x] `ComponentEditorPanel.tsx` - 위치 편집
|
|
|
|
#### Phase 5.4: 반응형 자동화 (완료)
|
|
- [x] 자동 변환 알고리즘 (12칸 -> 4칸)
|
|
- [x] 겹침 감지 및 재배치
|
|
- [x] 모드별 오버라이드 저장
|
|
|
|
#### v5.1 추가 기능 (완료)
|
|
- [x] 자동 줄바꿈 (col > maxCol -> 맨 아래 배치)
|
|
- [x] "검토 필요" 알림 시스템
|
|
- [x] Gap 프리셋 (좁게/보통/넓게)
|
|
- [x] 숨김 기능 (모드별)
|
|
|
|
#### v5.2 브레이크포인트 재설계 + 세로 자동 확장 (완료)
|
|
- [x] 기기 기반 브레이크포인트 (479/767/1023px)
|
|
- [x] 세로 자동 확장 (dynamicCanvasHeight)
|
|
- [x] 뷰어 반응형 일관성 (detectGridMode 사용)
|
|
- [x] VIEWPORT_PRESETS에서 height 제거
|
|
|
|
### 컴포넌트 설계 (완료)
|
|
|
|
- [x] 9개 컴포넌트 정의 (POPUPDATE_2.md v8.0)
|
|
- [x] POP 헌법 9조 작성
|
|
- [x] 공통 인프라 설계 (DataSourceConfig, ColumnBinding, JoinConfig, useDataSource, usePopEvent, PopActionConfig)
|
|
- [x] 모달 화면 설계 방식 확정 (인라인 + 외부 참조)
|
|
- [x] 기존 시스템 호환성 검증 (DB/백엔드/프론트 변경 불필요 확인)
|
|
|
|
---
|
|
|
|
## 다음 작업
|
|
|
|
### Phase 0: 공통 인프라
|
|
|
|
모든 데이터 연동 컴포넌트가 공유하는 기반 시스템:
|
|
|
|
- [x] ColumnBinding 타입 정의 (read/write/readwrite/hidden) -- types.ts에 추가 완료
|
|
- [x] JoinConfig 타입 정의 (테이블 조인) -- types.ts에 추가 완료
|
|
- [x] DataSourceConfig 타입 정의 (데이터 소스 설정) -- types.ts에 추가 완료
|
|
- [x] PopActionConfig 타입 정의 (액션 설정) -- types.ts에 추가 완료
|
|
- [x] usePopEvent 훅 구현 (이벤트 버스, 데이터 전달, 화면 단위 격리) -- 완료
|
|
- [x] useDataSource 훅 구현 (CRUD 포함, 기존 dataApi 활용) -- 완료
|
|
|
|
### Phase 1: pop-dashboard -- 완료
|
|
|
|
> **2026-02-10**: 17단계 코딩 + 검수 + 팔레트 등록 완료
|
|
|
|
- [x] PopDashboardConfig, DashboardItem 타입 정의
|
|
- [x] 멀티 아이템 컨테이너 구현 (여러 아이템 묶음)
|
|
- [x] 4개 서브타입: kpi-card, chart, gauge, stat-card
|
|
- [x] 4개 표시 모드: arrows, auto-slide, grid, scroll
|
|
- [x] 계산식(formula) 지원: "생산량/총재고량" 같은 복합 표현
|
|
- [x] 설정 패널: 드롭다운 기반 쉬운 집계 설정 (SQL 불필요)
|
|
- [x] PopComponentRegistry 등록 + 디자이너 팔레트 등록
|
|
- [ ] 이벤트: filter_changed 수신, kpi_clicked 발행 -- usePopEvent 완성 후
|
|
- [ ] 기존 `components/pop/dashboard/` 폴더 폐기 (모든 기능 대체 확인 후)
|
|
|
|
### Phase 2: pop-button, pop-icon
|
|
|
|
- [ ] pop-button: 저장/삭제/API 호출 액션
|
|
- [ ] pop-icon: 화면 이동/URL/새로고침
|
|
|
|
### Phase 3: pop-table (테이블형 우선)
|
|
|
|
- [ ] table-list 서브타입: 행/열 장부형
|
|
- [ ] ColumnBinding 기반 컬럼별 read/write
|
|
- [ ] card-list 서브타입: 카드 템플릿 (후순위)
|
|
|
|
### Phase 4: pop-search, pop-field, pop-lookup
|
|
|
|
- [ ] pop-search: 필터 조건 입력 (text/date/select/combo)
|
|
- [ ] pop-field: 저장용 입력 (text/number/date/select/numpad)
|
|
- [ ] pop-lookup: 모달 값 선택 (인라인 + 외부 참조)
|
|
|
|
### Phase 5: 고도화
|
|
|
|
- [ ] pop-table 카드 템플릿 디자이너
|
|
|
|
### Phase 6: pop-system
|
|
|
|
- [ ] 프로필, 테마, 대시보드 보이기/숨기기 통합
|
|
|
|
### 후속 작업
|
|
|
|
- [ ] 워크플로우 연동 (버튼 액션, 화면 전환)
|
|
- [ ] 실기기 테스트 (아이폰 SE, iPad Mini 등)
|
|
|
|
**참고 문서**: [POPUPDATE_2.md](../POPUPDATE_2.md) (컴포넌트 정의서 v8.0)
|
|
|
|
---
|
|
|
|
## 현재 구현 계획
|
|
|
|
> **용도**: 이 섹션은 "지금 바로 실행할 구체적 계획"입니다.
|
|
> 새 세션에서 이 섹션만 읽으면 코딩을 시작할 수 있어야 합니다.
|
|
> 완료되면 다음 기능의 계획으로 **교체**합니다.
|
|
|
|
### 대상: 대시보드 집계 함수 설정 유효성 검증 강화 (v2 - 시뮬레이션 검증 완료)
|
|
|
|
#### 배경 (2026-02-12)
|
|
|
|
팀원이 브랜치를 pull 받은 뒤 대시보드에서 500 에러가 발생.
|
|
원인: `batch_mappings` 테이블의 `to_table_name`(문자열) 컬럼에 `SUM` 집계를 설정한 대시보드 아이템이 존재.
|
|
PostgreSQL이 `SELECT SUM(to_table_name)` 실행 시 `function sum(character varying) does not exist` 에러 반환.
|
|
|
|
**근본 원인**: 설정 UI(`PopDashboardConfig.tsx`)에서 아이템 타입(subType)에 관계없이 동일한 5개 집계 함수를 모두 보여주고, 컬럼 선택에서도 타입 구분 없이 모든 컬럼이 표시됨.
|
|
|
|
#### 시뮬레이션 결과 (2026-02-12)
|
|
|
|
8개 시나리오로 가상 코딩 → 데이터 흐름 추적 수행. 원래 7단계 계획에서 **1건의 심각한 결함 발견**:
|
|
|
|
> **chart(sum) → stat-card로 subType 변경 시**, Select UI에서 "sum"이 목록에 없어도
|
|
> 내부 value는 "sum"으로 유지되어 SQL 생성 시 그대로 적용됨.
|
|
|
|
이를 해결하기 위해 **STEP 7.5 추가**: ItemEditor의 subType 변경 핸들러에서 비호환 aggregation 자동 전환.
|
|
|
|
---
|
|
|
|
#### 수정 대상
|
|
|
|
| 파일 | 경로 | 변경 유형 |
|
|
|------|------|-----------|
|
|
| PopDashboardConfig.tsx | `frontend/lib/registry/pop-components/pop-dashboard/PopDashboardConfig.tsx` | 수정 (신규 파일 없음) |
|
|
|
|
---
|
|
|
|
#### 구현 순서 (의존성 기반, 8단계)
|
|
|
|
| 순서 | 작업 | 수정 위치 (원본 라인) | 의존성 | 상태 |
|
|
|------|------|---------------------|--------|------|
|
|
| 1 | import에 `AggregationType` 추가 + 유틸 상수/함수 6개 추가 | 라인 48~63, 라인 133 뒤 | 없음 | 완료 |
|
|
| 2 | `DataSourceEditor` props에 `subType` 추가 | 라인 149~155 | 없음 | 완료 |
|
|
| 3 | 집계 함수 Select를 동적 생성으로 교체 | 라인 288~294 | 1, 2 | 완료 |
|
|
| 4 | 집계 함수 변경 시 컬럼 자동 초기화 + groupBy 보존 | 라인 271~283 | 1 | 완료 |
|
|
| 5 | 대상 컬럼 Combobox 숫자 필터링 + 안내 메시지 | 라인 333~366 | 1 | 완료 |
|
|
| 6 | chart 모드 groupBy 미설정 경고 | 라인 445~447 | 2 | 완료 |
|
|
| 7 | `DataSourceEditor` 호출부에 `subType` 전달 (라인 1212) | 라인 1212~1215 | 2 | 완료 |
|
|
| **7.5** | **ItemEditor subType 변경 시 비호환 aggregation 자동 전환** | **라인 1147~1148** | **1** | **완료** |
|
|
|
|
---
|
|
|
|
#### STEP 1: import 추가 + 유틸 상수/함수
|
|
|
|
**1-A. import 추가** (라인 48~63)
|
|
|
|
현재:
|
|
|
|
```typescript
|
|
import type {
|
|
PopDashboardConfig,
|
|
DashboardItem,
|
|
DashboardSubType,
|
|
DashboardDisplayMode,
|
|
DataSourceConfig,
|
|
DataSourceFilter,
|
|
FilterOperator,
|
|
FormulaConfig,
|
|
ItemVisibility,
|
|
DashboardCell,
|
|
DashboardPage,
|
|
JoinConfig,
|
|
JoinType,
|
|
ItemStyleConfig,
|
|
} from "../types";
|
|
```
|
|
|
|
변경: `ItemStyleConfig,` 뒤에 `AggregationType,` 추가:
|
|
|
|
```typescript
|
|
import type {
|
|
PopDashboardConfig,
|
|
DashboardItem,
|
|
DashboardSubType,
|
|
DashboardDisplayMode,
|
|
DataSourceConfig,
|
|
DataSourceFilter,
|
|
FilterOperator,
|
|
FormulaConfig,
|
|
ItemVisibility,
|
|
DashboardCell,
|
|
DashboardPage,
|
|
JoinConfig,
|
|
JoinType,
|
|
ItemStyleConfig,
|
|
AggregationType,
|
|
} from "../types";
|
|
```
|
|
|
|
**1-B. 유틸 상수/함수 추가** (라인 133 `};` 직후, 라인 135 `const FILTER_OPERATOR_LABELS` 직전)
|
|
|
|
```typescript
|
|
// ===== 집계 함수 유효성 검증 유틸 =====
|
|
|
|
// 아이템 타입별 사용 가능한 집계 함수
|
|
const SUBTYPE_AGGREGATION_MAP: Record<DashboardSubType, AggregationType[]> = {
|
|
"kpi-card": ["count", "sum", "avg", "min", "max"],
|
|
chart: ["count", "sum", "avg", "min", "max"],
|
|
gauge: ["count", "sum", "avg", "min", "max"],
|
|
"stat-card": ["count"],
|
|
};
|
|
|
|
// 집계 함수 라벨
|
|
const AGGREGATION_LABELS: Record<AggregationType, string> = {
|
|
count: "건수 (COUNT)",
|
|
sum: "합계 (SUM)",
|
|
avg: "평균 (AVG)",
|
|
min: "최소 (MIN)",
|
|
max: "최대 (MAX)",
|
|
};
|
|
|
|
// 숫자 전용 집계 함수 (숫자 컬럼에만 사용 가능)
|
|
const NUMERIC_ONLY_AGGREGATIONS: AggregationType[] = ["sum", "avg"];
|
|
|
|
// PostgreSQL 숫자 타입 판별용 패턴
|
|
const NUMERIC_TYPE_PATTERNS = [
|
|
"int", "integer", "bigint", "smallint",
|
|
"numeric", "decimal", "real", "double",
|
|
"float", "serial", "bigserial", "smallserial",
|
|
"money", "number",
|
|
];
|
|
|
|
/** 컬럼이 숫자 타입인지 판별 */
|
|
function isNumericColumn(col: ColumnInfo): boolean {
|
|
const t = (col.type || "").toLowerCase();
|
|
const u = (col.udtName || "").toLowerCase();
|
|
return NUMERIC_TYPE_PATTERNS.some(
|
|
(pattern) => t.includes(pattern) || u.includes(pattern)
|
|
);
|
|
}
|
|
|
|
/** 현재 집계 함수가 숫자 전용(sum/avg)인지 판별 */
|
|
function isNumericOnlyAggregation(aggType: string | undefined): boolean {
|
|
return !!aggType && NUMERIC_ONLY_AGGREGATIONS.includes(aggType as AggregationType);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### STEP 2: `DataSourceEditor` props에 `subType` 추가
|
|
|
|
라인 149~155 교체:
|
|
|
|
```typescript
|
|
// 현재
|
|
function DataSourceEditor({
|
|
dataSource,
|
|
onChange,
|
|
}: {
|
|
dataSource: DataSourceConfig;
|
|
onChange: (ds: DataSourceConfig) => void;
|
|
}) {
|
|
|
|
// 변경
|
|
function DataSourceEditor({
|
|
dataSource,
|
|
onChange,
|
|
subType,
|
|
}: {
|
|
dataSource: DataSourceConfig;
|
|
onChange: (ds: DataSourceConfig) => void;
|
|
subType?: DashboardSubType;
|
|
}) {
|
|
```
|
|
|
|
`subType`은 optional. FormulaEditor 내부 호출(라인 969)에서는 미전달 → undefined → 5개 전부 표시.
|
|
|
|
---
|
|
|
|
#### STEP 3: 집계 함수 Select 동적 생성
|
|
|
|
라인 288~294 교체:
|
|
|
|
```tsx
|
|
// 현재
|
|
<SelectContent>
|
|
<SelectItem value="count">건수 (COUNT)</SelectItem>
|
|
<SelectItem value="sum">합계 (SUM)</SelectItem>
|
|
<SelectItem value="avg">평균 (AVG)</SelectItem>
|
|
<SelectItem value="min">최소 (MIN)</SelectItem>
|
|
<SelectItem value="max">최대 (MAX)</SelectItem>
|
|
</SelectContent>
|
|
|
|
// 변경
|
|
<SelectContent>
|
|
{(subType
|
|
? SUBTYPE_AGGREGATION_MAP[subType]
|
|
: (Object.keys(AGGREGATION_LABELS) as AggregationType[])
|
|
).map((aggType) => (
|
|
<SelectItem key={aggType} value={aggType}>
|
|
{AGGREGATION_LABELS[aggType]}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
```
|
|
|
|
---
|
|
|
|
#### STEP 4: 집계 함수 변경 시 컬럼 자동 초기화 + groupBy 보존
|
|
|
|
라인 271~283 교체 (onValueChange 핸들러 전체):
|
|
|
|
```typescript
|
|
// 현재
|
|
onValueChange={(val) =>
|
|
onChange({
|
|
...dataSource,
|
|
aggregation: val
|
|
? {
|
|
type: val as NonNullable<
|
|
DataSourceConfig["aggregation"]
|
|
>["type"],
|
|
column: dataSource.aggregation?.column ?? "",
|
|
}
|
|
: undefined,
|
|
})
|
|
}
|
|
|
|
// 변경
|
|
onValueChange={(val) => {
|
|
// 숫자 전용 집계로 변경 시, 기존 컬럼이 숫자가 아니면 초기화
|
|
let currentColumn = dataSource.aggregation?.column ?? "";
|
|
if (val && isNumericOnlyAggregation(val) && currentColumn) {
|
|
const selectedCol = columns.find((c) => c.name === currentColumn);
|
|
if (selectedCol && !isNumericColumn(selectedCol)) {
|
|
currentColumn = "";
|
|
}
|
|
}
|
|
onChange({
|
|
...dataSource,
|
|
aggregation: val
|
|
? {
|
|
type: val as NonNullable<
|
|
DataSourceConfig["aggregation"]
|
|
>["type"],
|
|
column: currentColumn,
|
|
groupBy: dataSource.aggregation?.groupBy,
|
|
}
|
|
: undefined,
|
|
});
|
|
}}
|
|
```
|
|
|
|
기존 대비 변경점 2가지:
|
|
1. 숫자 전용 집계 + 문자열 컬럼 → `currentColumn = ""`
|
|
2. `groupBy` 보존 (기존 코드에서 누락되던 버그 수정)
|
|
|
|
---
|
|
|
|
#### STEP 5: 대상 컬럼 Combobox 필터링
|
|
|
|
**5-A. CommandEmpty 메시지 교체** (라인 333~335):
|
|
|
|
```tsx
|
|
// 현재
|
|
<CommandEmpty className="py-2 text-center text-xs">
|
|
컬럼을 찾을 수 없습니다.
|
|
</CommandEmpty>
|
|
|
|
// 변경
|
|
<CommandEmpty className="py-2 text-center text-xs">
|
|
{isNumericOnlyAggregation(dataSource.aggregation?.type)
|
|
? "숫자 타입 컬럼이 없습니다."
|
|
: "컬럼을 찾을 수 없습니다."}
|
|
</CommandEmpty>
|
|
```
|
|
|
|
**5-B. columns.map을 필터링 후 map으로 교체** (라인 337):
|
|
|
|
```tsx
|
|
// 현재
|
|
{columns.map((col) => (
|
|
|
|
// 변경
|
|
{(isNumericOnlyAggregation(dataSource.aggregation?.type)
|
|
? columns.filter(isNumericColumn)
|
|
: columns
|
|
).map((col) => (
|
|
```
|
|
|
|
나머지 CommandItem 내부는 변경 없음.
|
|
|
|
---
|
|
|
|
#### STEP 6: chart 모드 groupBy 경고
|
|
|
|
라인 445~447 뒤에 추가 (기존 `<p>` 태그는 유지):
|
|
|
|
```tsx
|
|
// 기존 유지
|
|
<p className="mt-0.5 text-[10px] text-muted-foreground">
|
|
차트에서 X축 카테고리로 사용됩니다
|
|
</p>
|
|
|
|
// 아래에 추가
|
|
{subType === "chart" && !dataSource.aggregation?.groupBy?.length && (
|
|
<p className="mt-0.5 text-[10px] text-destructive">
|
|
차트 모드에서는 그룹핑(X축)을 설정해야 의미 있는 차트가 표시됩니다
|
|
</p>
|
|
)}
|
|
```
|
|
|
|
---
|
|
|
|
#### STEP 7: `DataSourceEditor` 호출부에 `subType` 전달
|
|
|
|
**라인 1212~1215** (ItemEditor 내부, 단일 집계 모드):
|
|
|
|
```tsx
|
|
// 현재
|
|
<DataSourceEditor
|
|
dataSource={item.dataSource}
|
|
onChange={(ds) => onUpdate({ ...item, dataSource: ds })}
|
|
/>
|
|
|
|
// 변경
|
|
<DataSourceEditor
|
|
dataSource={item.dataSource}
|
|
onChange={(ds) => onUpdate({ ...item, dataSource: ds })}
|
|
subType={item.subType}
|
|
/>
|
|
```
|
|
|
|
**라인 969** (FormulaEditor 내부): **수정하지 않음** (subType=undefined → 모든 집계 표시).
|
|
|
|
---
|
|
|
|
#### STEP 7.5: ItemEditor subType 변경 시 비호환 aggregation 자동 전환
|
|
|
|
> **시뮬레이션에서 발견**: chart(aggregation=sum) → stat-card로 subType 변경 시,
|
|
> Select 목록에 "sum"이 없지만 내부 value는 "sum" 유지 → SQL 생성 시 SUM 실행됨.
|
|
> radix Select는 목록에 없는 value를 자동으로 초기화하지 않음.
|
|
|
|
**라인 1147~1148** (ItemEditor 내부, subType Select onValueChange):
|
|
|
|
```typescript
|
|
// 현재
|
|
onValueChange={(val) =>
|
|
onUpdate({ ...item, subType: val as DashboardSubType })
|
|
}
|
|
|
|
// 변경
|
|
onValueChange={(val) => {
|
|
const newSubType = val as DashboardSubType;
|
|
const allowedAggs = SUBTYPE_AGGREGATION_MAP[newSubType];
|
|
const currentAggType = item.dataSource.aggregation?.type;
|
|
// 새 subType에서 현재 집계 함수가 허용 안 되면 첫 번째 허용 함수로 전환
|
|
let newDataSource = item.dataSource;
|
|
if (currentAggType && !allowedAggs.includes(currentAggType)) {
|
|
newDataSource = {
|
|
...item.dataSource,
|
|
aggregation: item.dataSource.aggregation
|
|
? {
|
|
...item.dataSource.aggregation,
|
|
type: allowedAggs[0],
|
|
column: allowedAggs[0] === "count" ? "" : item.dataSource.aggregation.column,
|
|
}
|
|
: undefined,
|
|
};
|
|
}
|
|
onUpdate({ ...item, subType: newSubType, dataSource: newDataSource });
|
|
}}
|
|
```
|
|
|
|
핵심: `allowedAggs[0]`이 "count"면 column도 빈 문자열로 (count는 column 불필요).
|
|
|
|
---
|
|
|
|
#### 사전 충돌 검사 결과 (2026-02-12)
|
|
|
|
| 이름 | 유형 | 검색 범위 | 검색 결과 | 판정 |
|
|
|------|------|-----------|-----------|------|
|
|
| `SUBTYPE_AGGREGATION_MAP` | 상수 | frontend 전체 | 0건 | **충돌 없음** |
|
|
| `AGGREGATION_LABELS` | 상수 | frontend 전체 | 0건 | **충돌 없음** |
|
|
| `NUMERIC_ONLY_AGGREGATIONS` | 상수 | frontend 전체 | 0건 | **충돌 없음** |
|
|
| `NUMERIC_TYPE_PATTERNS` | 상수 | frontend 전체 | 0건 | **충돌 없음** |
|
|
| `isNumericColumn` | 함수 | frontend 전체 | 0건 | **충돌 없음** |
|
|
| `isNumericOnlyAggregation` | 함수 | frontend 전체 | 0건 | **충돌 없음** |
|
|
| `AggregationType` | type import | PopDashboardConfig.tsx | 0건 (현재 미import) | **충돌 없음** (추가 필요) |
|
|
|
|
---
|
|
|
|
#### 정의-사용 매핑
|
|
|
|
| 정의 | 정의 위치 | 사용 위치 |
|
|
|------|-----------|-----------|
|
|
| `SUBTYPE_AGGREGATION_MAP` | STEP 1 (상수) | STEP 3, STEP 7.5 |
|
|
| `AGGREGATION_LABELS` | STEP 1 (상수) | STEP 3 |
|
|
| `NUMERIC_ONLY_AGGREGATIONS` | STEP 1 (상수) | `isNumericOnlyAggregation` 내부 |
|
|
| `NUMERIC_TYPE_PATTERNS` | STEP 1 (상수) | `isNumericColumn` 내부 |
|
|
| `isNumericColumn` | STEP 1 (함수) | STEP 4, STEP 5 |
|
|
| `isNumericOnlyAggregation` | STEP 1 (함수) | STEP 4, STEP 5 |
|
|
| `subType` (prop) | STEP 2 (DataSourceEditor) | STEP 3, STEP 6 |
|
|
| `AggregationType` | `types.ts` L123 (기존) | STEP 1 import 추가 |
|
|
| `ColumnInfo` | `dataFetcher.ts` (기존, L71에서 import) | STEP 1 `isNumericColumn` 파라미터 |
|
|
| `DashboardSubType` | `types.ts` (기존, L48에서 이미 import) | STEP 2 prop 타입, STEP 7.5 |
|
|
|
|
**누락 검사**: 모든 신규 정의에 사용처 있음. 모든 사용처에 정의 존재. 누락 없음.
|
|
|
|
---
|
|
|
|
#### 함정 경고
|
|
|
|
| # | 심각도 | 위험 | 설명 | 해결 방안 |
|
|
|---|--------|------|------|-----------|
|
|
| W1 | **높음** | **import 누락** | `AggregationType`을 import에 추가 안 하면 컴파일 에러 | STEP 1-A에서 반드시 추가 |
|
|
| W2 | **높음** | **subType 변경 시 비호환 aggregation 잔류** | chart(sum) → stat-card 시 Select 목록에 없는 값이 내부에 남음 | STEP 7.5에서 해결 (시뮬레이션 발견) |
|
|
| W3 | **중간** | **groupBy 소실 (기존 버그)** | 기존 코드에서 집계 함수 변경 시 groupBy가 누락됨 | STEP 4에서 `groupBy` 명시적 보존 |
|
|
| W4 | **낮음** | **FormulaEditor 호출 미수정** | FormulaEditor 내부 DataSourceEditor에는 subType 미전달 | 의도적 결정 (수식 모드는 모든 집계 허용). 절대 수정하지 말 것 (연쇄 변경 발생) |
|
|
| W5 | **낮음** | **columns 비동기 로드** | 테이블 방금 선택 → columns=[] → STEP 4 초기화 안 됨 | 허용. 컬럼 로드 후 재변경 시 정상 동작 |
|
|
| W6 | **정보** | **MIN/MAX 문자열 허용** | PostgreSQL MIN/MAX는 문자열에도 작동 (사전순) | 의도적. `NUMERIC_ONLY`에 포함 안 함 |
|
|
| W7 | **정보** | **STEP 7.5에서 column 초기화 조건** | `allowedAggs[0] === "count"`이면 column="" (count는 * 사용) | stat-card → count 전환 시 올바른 동작 |
|
|
|
|
---
|
|
|
|
#### 작업 완료 후 확인 체크리스트
|
|
|
|
##### 코드 레벨
|
|
|
|
- [x] `AggregationType` import 추가 확인
|
|
- [x] stat-card: 집계 함수 Select에 "건수 (COUNT)"만 표시
|
|
- [x] kpi-card/chart/gauge: 5개 집계 함수 전부 표시
|
|
- [x] SUM 선택 시 컬럼 목록에 숫자 타입만 표시
|
|
- [x] COUNT 선택 시 컬럼 목록에 모든 타입 표시
|
|
- [x] COUNT→SUM 변경 시 문자열 컬럼 자동 초기화
|
|
- [x] SUM→COUNT 변경 시 숫자 컬럼 유지
|
|
- [x] 집계 함수 변경 시 groupBy 보존
|
|
- [x] chart→stat-card subType 변경 시 sum→count 자동 전환 (STEP 7.5)
|
|
- [x] chart 모드에서 groupBy 미설정 시 빨간 경고 표시
|
|
- [x] FormulaEditor 내부에서 5개 전부 표시 (subType 미전달 → 기본 전체 목록)
|
|
- [x] TypeScript 컴파일 에러 0건
|
|
- [x] 린트 에러 0건
|
|
- [ ] 기존 저장된 대시보드 데이터 조회 동작에 영향 없음 (브라우저 확인 필요)
|
|
|
|
##### 구조 레벨
|
|
|
|
- [x] 수정 파일 1개: `PopDashboardConfig.tsx`
|
|
- [x] 신규 파일 0개
|
|
- [x] 기존 파일 삭제 0개
|
|
|
|
---
|
|
|
|
#### 이전 완료된 계획 (보관)
|
|
|
|
**Phase 0 공통 인프라 (2026-02-11, 완료)**:
|
|
usePopEvent + useDataSource + popSqlBuilder 구현.
|
|
|
|
**대시보드 스타일 정리 (2026-02-11, 완료)**:
|
|
글자 크기 커스텀 제거, 라벨 정렬만 유지, stale closure 수정, .next 캐시 해결.
|
|
|
|
**브라우저 확인 체크리스트 (대기)**:
|
|
- [ ] 라벨 정렬, 페이지 미리보기, 차트 디자인, 게이지/통계카드 동작 확인
|
|
|
|
---
|
|
|
|
## 브레이크포인트 (v5.2 현재)
|
|
|
|
| 모드 | 화면 너비 | 칸 수 | 대상 기기 |
|
|
|------|----------|-------|----------|
|
|
| mobile_portrait | ~479px | 4칸 | 아이폰 SE ~ 갤럭시 S |
|
|
| mobile_landscape | 480~767px | 6칸 | 스마트폰 가로 |
|
|
| tablet_portrait | 768~1023px | 8칸 | 8~10인치 태블릿 세로 |
|
|
| tablet_landscape | 1024px~ | 12칸 | 10~14인치 태블릿 가로 |
|
|
|
|
---
|
|
|
|
## 관련 문서
|
|
|
|
| 문서 | 내용 |
|
|
|------|------|
|
|
| [STATUS.md](./STATUS.md) | 현재 진행 상태 |
|
|
| [SPEC.md](./SPEC.md) | 기술 스펙 |
|
|
| [ARCHITECTURE.md](./ARCHITECTURE.md) | 코드 구조 |
|
|
| [POPUPDATE_2.md](../POPUPDATE_2.md) | 컴포넌트 정의서 v8.0 (최신) |
|
|
| [components-spec.md](./components-spec.md) | 컴포넌트 상세 설계 (v4 기준, 갱신 필요) |
|
|
| [decisions/005](./decisions/005-breakpoint-redesign.md) | 브레이크포인트 재설계 ADR |
|
|
|
|
---
|
|
|
|
*최종 업데이트: 2026-02-12 (대시보드 집계 함수 유효성 검증 v2 - 시뮬레이션 검증 완료, STEP 7.5 추가)*
|