diff --git a/frontend/lib/registry/pop-components/pop-dashboard/PopDashboardConfig.tsx b/frontend/lib/registry/pop-components/pop-dashboard/PopDashboardConfig.tsx index 52f18d27..f64e09ae 100644 --- a/frontend/lib/registry/pop-components/pop-dashboard/PopDashboardConfig.tsx +++ b/frontend/lib/registry/pop-components/pop-dashboard/PopDashboardConfig.tsx @@ -60,6 +60,7 @@ import type { JoinConfig, JoinType, ItemStyleConfig, + AggregationType, } from "../types"; import { TEXT_ALIGN_LABELS, @@ -132,6 +133,50 @@ const JOIN_TYPE_LABELS: Record = { right: "RIGHT JOIN", }; +// ===== 집계 함수 유효성 검증 유틸 ===== + +// 아이템 타입별 사용 가능한 집계 함수 +const SUBTYPE_AGGREGATION_MAP: Record = { + "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 = { + 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); +} + const FILTER_OPERATOR_LABELS: Record = { "=": "같음 (=)", "!=": "다름 (!=)", @@ -149,9 +194,11 @@ const FILTER_OPERATOR_LABELS: Record = { function DataSourceEditor({ dataSource, onChange, + subType, }: { dataSource: DataSourceConfig; onChange: (ds: DataSourceConfig) => void; + subType?: DashboardSubType; }) { // 테이블 목록 (Combobox용) const [tables, setTables] = useState([]); @@ -268,7 +315,15 @@ function DataSourceEditor({ @@ -331,10 +390,15 @@ function DataSourceEditor({ /> - 컬럼을 찾을 수 없습니다. + {isNumericOnlyAggregation(dataSource.aggregation?.type) + ? "숫자 타입 컬럼이 없습니다." + : "컬럼을 찾을 수 없습니다."} - {columns.map((col) => ( + {(isNumericOnlyAggregation(dataSource.aggregation?.type) + ? columns.filter(isNumericColumn) + : columns + ).map((col) => ( 차트에서 X축 카테고리로 사용됩니다

+ {subType === "chart" && !dataSource.aggregation?.groupBy?.length && ( +

+ 차트 모드에서는 그룹핑(X축)을 설정해야 의미 있는 차트가 표시됩니다 +

+ )} )} @@ -1144,9 +1213,30 @@ function ItemEditor({