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,
};
}