diff --git a/frontend/lib/registry/pop-components/pop-dashboard/PopDashboardConfig.tsx b/frontend/lib/registry/pop-components/pop-dashboard/PopDashboardConfig.tsx index 66a56876..b75f9b66 100644 --- a/frontend/lib/registry/pop-components/pop-dashboard/PopDashboardConfig.tsx +++ b/frontend/lib/registry/pop-components/pop-dashboard/PopDashboardConfig.tsx @@ -1289,51 +1289,10 @@ function ItemEditor({ - {/* X축 컬럼 */} -
- - - onUpdate({ - ...item, - chartConfig: { - ...item.chartConfig, - chartType: item.chartConfig?.chartType ?? "bar", - xAxisColumn: e.target.value || undefined, - }, - }) - } - placeholder="groupBy 컬럼명 (비우면 자동)" - className="h-8 text-xs" - /> -

- 그룹핑 컬럼명과 동일하게 입력. 비우면 첫 번째 groupBy 컬럼 사용 -

-
- - {/* Y축 컬럼 */} -
- - - onUpdate({ - ...item, - chartConfig: { - ...item.chartConfig, - chartType: item.chartConfig?.chartType ?? "bar", - yAxisColumn: e.target.value || undefined, - }, - }) - } - placeholder="집계 결과 컬럼명 (비우면 자동)" - className="h-8 text-xs" - /> -

- 집계 결과 컬럼. 비우면 집계 함수 결과를 자동 사용 -

-
+ {/* X축/Y축 자동 안내 */} +

+ X축: 그룹핑(X축)에서 선택한 컬럼 자동 적용 / Y축: 집계 결과(value) 자동 적용 +

)} diff --git a/frontend/lib/registry/pop-components/pop-dashboard/items/ChartItem.tsx b/frontend/lib/registry/pop-components/pop-dashboard/items/ChartItem.tsx index c1fbd6b6..93f29b1c 100644 --- a/frontend/lib/registry/pop-components/pop-dashboard/items/ChartItem.tsx +++ b/frontend/lib/registry/pop-components/pop-dashboard/items/ChartItem.tsx @@ -19,6 +19,7 @@ import { XAxis, YAxis, Tooltip, + Legend, ResponsiveContainer, } from "recharts"; import type { DashboardItem } from "../../types"; @@ -124,7 +125,7 @@ export function ChartItemComponent({ /> ) : ( - /* pie */ + /* pie - 카테고리명 + 값 라벨 표시 */ []} @@ -132,8 +133,14 @@ export function ChartItemComponent({ nameKey={xKey} cx="50%" cy="50%" - outerRadius="80%" - label={containerWidth > 250} + outerRadius={containerWidth > 400 ? "70%" : "80%"} + label={ + containerWidth > 250 + ? ({ name, value, percent }: { name: string; value: number; percent: number }) => + `${name} ${value} (${(percent * 100).toFixed(0)}%)` + : false + } + labelLine={containerWidth > 250} > {rows.map((_, index) => ( ))} - + [value, name]} + /> + {containerWidth > 300 && ( + + )} )} diff --git a/frontend/lib/registry/pop-components/pop-dashboard/utils/dataFetcher.ts b/frontend/lib/registry/pop-components/pop-dashboard/utils/dataFetcher.ts index 4746b69b..c2baaa55 100644 --- a/frontend/lib/registry/pop-components/pop-dashboard/utils/dataFetcher.ts +++ b/frontend/lib/registry/pop-components/pop-dashboard/utils/dataFetcher.ts @@ -10,6 +10,7 @@ * - fetch 직접 사용 금지: 반드시 dashboardApi/dataApi 사용 */ +import { apiClient } from "@/lib/api/client"; import { dashboardApi } from "@/lib/api/dashboard"; import { dataApi } from "@/lib/api/data"; import { tableManagementApi } from "@/lib/api/tableManagement"; @@ -238,19 +239,46 @@ export async function fetchAggregatedData( // 집계 또는 조인이 있으면 SQL 직접 실행 if (config.aggregation || (config.joins && config.joins.length > 0)) { const sql = buildAggregationSQL(config); - const result = await dashboardApi.executeQuery(sql); - if (result.rows.length === 0) { + // API 호출: apiClient(axios) 우선, dashboardApi(fetch) 폴백 + let queryResult: { columns: string[]; rows: any[] }; + try { + // 1차: apiClient (axios 기반, 인증/세션 안정적) + const response = await apiClient.post("/dashboards/execute-query", { query: sql }); + if (response.data?.success && response.data?.data) { + queryResult = response.data.data; + } else { + throw new Error(response.data?.message || "쿼리 실행 실패"); + } + } catch { + // 2차: dashboardApi (fetch 기반, 폴백) + queryResult = await dashboardApi.executeQuery(sql); + } + + if (queryResult.rows.length === 0) { return { value: 0, rows: [] }; } + // PostgreSQL bigint/numeric는 JS에서 문자열로 반환됨 + // Recharts PieChart 등은 숫자 타입이 필수이므로 변환 처리 + const processedRows = queryResult.rows.map((row: Record) => { + const converted: Record = { ...row }; + for (const key of Object.keys(converted)) { + const val = converted[key]; + if (typeof val === "string" && val !== "" && !isNaN(Number(val))) { + converted[key] = Number(val); + } + } + return converted; + }); + // 첫 번째 행의 value 컬럼 추출 - const firstRow = result.rows[0]; - const numericValue = parseFloat(firstRow.value ?? firstRow[result.columns[0]] ?? 0); + const firstRow = processedRows[0]; + const numericValue = parseFloat(String(firstRow.value ?? firstRow[queryResult.columns[0]] ?? 0)); return { value: Number.isFinite(numericValue) ? numericValue : 0, - rows: result.rows, + rows: processedRows, }; }