From 270c322dafa8e4e6328589645f0deb4b059e7bf7 Mon Sep 17 00:00:00 2001 From: dohyeons Date: Mon, 27 Oct 2025 15:19:48 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=EA=B8=B0=ED=83=80=20=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/dashboard/[dashboardId]/page.tsx | 2 +- .../admin/dashboard/charts/ChartRenderer.tsx | 3 +- .../admin/dashboard/charts/PieChart.tsx | 29 ++++---- .../admin/dashboard/widgets/ClockWidget.tsx | 28 ++++---- .../admin/dashboard/widgets/ListWidget.tsx | 4 +- .../dashboard/widgets/CustomMetricWidget.tsx | 69 ++++++++++--------- 6 files changed, 72 insertions(+), 63 deletions(-) diff --git a/frontend/app/(main)/dashboard/[dashboardId]/page.tsx b/frontend/app/(main)/dashboard/[dashboardId]/page.tsx index 7639abc6..54d61f77 100644 --- a/frontend/app/(main)/dashboard/[dashboardId]/page.tsx +++ b/frontend/app/(main)/dashboard/[dashboardId]/page.tsx @@ -105,7 +105,7 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) { } return ( -
+
{/* 대시보드 헤더 - 보기 모드에서는 숨김 */} {/*
diff --git a/frontend/components/admin/dashboard/charts/ChartRenderer.tsx b/frontend/components/admin/dashboard/charts/ChartRenderer.tsx index 88e90cae..94efd190 100644 --- a/frontend/components/admin/dashboard/charts/ChartRenderer.tsx +++ b/frontend/components/admin/dashboard/charts/ChartRenderer.tsx @@ -246,7 +246,8 @@ export function ChartRenderer({ element, data, width, height = 200 }: ChartRende const isCircularChart = element.subtype === "pie" || element.subtype === "donut"; const minWidth = isCircularChart ? 400 : 200; const finalWidth = Math.max(actualWidth - 20, minWidth); - const finalHeight = Math.max(height - 20, 300); + // 원형 차트는 범례 공간을 위해 더 많은 여백 필요 + const finalHeight = Math.max(height - (isCircularChart ? 60 : 20), 300); console.log("🎨 ChartRenderer:", { elementSubtype: element.subtype, diff --git a/frontend/components/admin/dashboard/charts/PieChart.tsx b/frontend/components/admin/dashboard/charts/PieChart.tsx index affa2928..ab24219f 100644 --- a/frontend/components/admin/dashboard/charts/PieChart.tsx +++ b/frontend/components/admin/dashboard/charts/PieChart.tsx @@ -24,12 +24,17 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 150, bottom: 40, left: 120 }; + // 범례를 위한 여백 확보 (아래 80px) + const legendHeight = config.showLegend !== false ? 80 : 0; + const margin = { top: 20, right: 20, bottom: 20 + legendHeight, left: 20 }; const chartWidth = width - margin.left - margin.right; - const chartHeight = height - margin.top - margin.bottom; + const chartHeight = height - margin.top - margin.bottom - legendHeight; const radius = Math.min(chartWidth, chartHeight) / 2; - const g = svg.append("g").attr("transform", `translate(${width / 2},${height / 2})`); + // 차트를 위쪽에 배치 (범례 공간 확보) + const centerX = width / 2; + const centerY = margin.top + radius + 20; + const g = svg.append("g").attr("transform", `translate(${centerX},${centerY})`); // 색상 팔레트 const colors = config.colors || ["#3B82F6", "#EF4444", "#10B981", "#F59E0B", "#8B5CF6", "#EC4899"]; @@ -138,10 +143,10 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa // 범례 (차트 아래, 가로 배치, 중앙 정렬) if (config.showLegend !== false) { - const itemSpacing = 140; // 각 범례 항목 사이 간격 + const itemSpacing = 100; // 각 범례 항목 사이 간격 (줄임) const totalWidth = pieData.length * itemSpacing; const legendStartX = (width - totalWidth) / 2; // 시작 위치 - const legendY = height - 40; // 차트 아래 (여백 확보) + const legendY = centerY + radius + 40; // 차트 아래 40px const legend = svg.append("g").attr("class", "legend"); @@ -152,19 +157,19 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa legendItem .append("rect") - .attr("x", -7.5) // 사각형을 중앙 기준으로 - .attr("y", -7.5) - .attr("width", 15) - .attr("height", 15) + .attr("x", -6) // 사각형을 중앙 기준으로 + .attr("y", -6) + .attr("width", 12) + .attr("height", 12) .attr("fill", colors[i % colors.length]) - .attr("rx", 3); + .attr("rx", 2); legendItem .append("text") .attr("x", 0) - .attr("y", 20) + .attr("y", 18) .attr("text-anchor", "middle") // 텍스트 중앙 정렬 - .style("font-size", "11px") + .style("font-size", "10px") .style("fill", "#333") .text(`${d.label} (${d.value})`); }); diff --git a/frontend/components/admin/dashboard/widgets/ClockWidget.tsx b/frontend/components/admin/dashboard/widgets/ClockWidget.tsx index e85623f8..fff65bc4 100644 --- a/frontend/components/admin/dashboard/widgets/ClockWidget.tsx +++ b/frontend/components/admin/dashboard/widgets/ClockWidget.tsx @@ -111,19 +111,21 @@ export function ClockWidget({ element, onConfigUpdate }: ClockWidgetProps) { {/* 시계 콘텐츠 */} {renderClockContent()} - {/* 설정 버튼 - 우측 상단 */} -
- - - - - - setSettingsOpen(false)} /> - - -
+ {/* 설정 버튼 - 우측 상단 (디자이너 모드에서만 표시) */} + {onConfigUpdate && ( +
+ + + + + + setSettingsOpen(false)} /> + + +
+ )}
); } diff --git a/frontend/components/admin/dashboard/widgets/ListWidget.tsx b/frontend/components/admin/dashboard/widgets/ListWidget.tsx index 252831c5..6d3e6929 100644 --- a/frontend/components/admin/dashboard/widgets/ListWidget.tsx +++ b/frontend/components/admin/dashboard/widgets/ListWidget.tsx @@ -216,7 +216,7 @@ export function ListWidget({ element }: ListWidgetProps) { const paginatedRows = config.enablePagination ? data.rows.slice(startIdx, endIdx) : data.rows; return ( -
+
{/* 테이블 뷰 */} {config.viewMode === "table" && (
@@ -306,7 +306,7 @@ export function ListWidget({ element }: ListWidgetProps) { {/* 페이지네이션 */} {config.enablePagination && totalPages > 1 && ( -
+
{startIdx + 1}-{Math.min(endIdx, data.rows.length)} / {data.rows.length}개
diff --git a/frontend/components/dashboard/widgets/CustomMetricWidget.tsx b/frontend/components/dashboard/widgets/CustomMetricWidget.tsx index 893ab6b0..0dba9473 100644 --- a/frontend/components/dashboard/widgets/CustomMetricWidget.tsx +++ b/frontend/components/dashboard/widgets/CustomMetricWidget.tsx @@ -374,45 +374,46 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps) } return ( -
- {/* 스크롤 가능한 콘텐츠 영역 */} -
-
- {/* 그룹별 카드 (활성화 시) */} - {isGroupByMode && - groupedCards.map((card, index) => { - // 색상 순환 (6가지 색상) - const colorKeys = Object.keys(colorMap) as Array; - const colorKey = colorKeys[index % colorKeys.length]; - const colors = colorMap[colorKey]; - - return ( -
-
{card.label}
-
{card.value.toLocaleString()}
-
- ); - })} - - {/* 일반 지표 카드 (항상 표시) */} - {metrics.map((metric) => { - const colors = colorMap[metric.color as keyof typeof colorMap] || colorMap.gray; - const formattedValue = metric.calculatedValue.toFixed(metric.decimals); +
+ {/* 콘텐츠 영역 - 스크롤 없이 자동으로 크기 조정 */} +
+ {/* 그룹별 카드 (활성화 시) */} + {isGroupByMode && + groupedCards.map((card, index) => { + // 색상 순환 (6가지 색상) + const colorKeys = Object.keys(colorMap) as Array; + const colorKey = colorKeys[index % colorKeys.length]; + const colors = colorMap[colorKey]; return ( -
-
{metric.label}
-
- {formattedValue} - {metric.unit} -
+
+
{card.label}
+
{card.value.toLocaleString()}
); })} -
+ + {/* 일반 지표 카드 (항상 표시) */} + {metrics.map((metric) => { + const colors = colorMap[metric.color as keyof typeof colorMap] || colorMap.gray; + const formattedValue = metric.calculatedValue.toFixed(metric.decimals); + + return ( +
+
{metric.label}
+
+ {formattedValue} + {metric.unit} +
+
+ ); + })}
);