diff --git a/frontend/components/admin/dashboard/CanvasElement.tsx b/frontend/components/admin/dashboard/CanvasElement.tsx index 33b1d801..9f723165 100644 --- a/frontend/components/admin/dashboard/CanvasElement.tsx +++ b/frontend/components/admin/dashboard/CanvasElement.tsx @@ -353,9 +353,9 @@ export function CanvasElement({ let newX = resizeStart.elementX; let newY = resizeStart.elementY; - // 최소 크기 설정: 달력은 2x3, 나머지는 2x2 - const minWidthCells = 2; - const minHeightCells = element.type === "widget" && element.subtype === "calendar" ? 3 : 2; + // 최소 크기 설정: 모든 위젯 1x1 + const minWidthCells = 1; + const minHeightCells = 1; const minWidth = cellSize * minWidthCells; const minHeight = cellSize * minHeightCells; @@ -721,7 +721,7 @@ export function CanvasElement({
{/* 헤더 */} -
+
{/* 차트 타입 전환 드롭다운 (차트일 경우만) */} {element.type === "chart" && ( @@ -743,7 +743,7 @@ export function CanvasElement({ }} > e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} > @@ -772,7 +772,7 @@ export function CanvasElement({ )} {/* 제목 */} {!element.type || element.type !== "chart" ? ( - {element.customTitle || element.title} + {element.customTitle || element.title} ) : null}
@@ -780,18 +780,18 @@ export function CanvasElement({
{/* 내용 */} -
+
{element.type === "chart" ? ( // 차트 렌더링
@@ -807,7 +807,7 @@ export function CanvasElement({ element={element} data={chartData || undefined} width={element.size.width} - height={element.size.height - 45} + height={element.size.height - 32} /> )}
diff --git a/frontend/components/admin/dashboard/DashboardDesigner.tsx b/frontend/components/admin/dashboard/DashboardDesigner.tsx index 5b39a8f7..07303e24 100644 --- a/frontend/components/admin/dashboard/DashboardDesigner.tsx +++ b/frontend/components/admin/dashboard/DashboardDesigner.tsx @@ -271,6 +271,10 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D setElements((prev) => [...prev, newElement]); setElementCounter((prev) => prev + 1); setSelectedElement(newElement.id); + + // 새 요소 생성 시 자동으로 설정 사이드바 열기 + setSidebarElement(newElement); + setSidebarOpen(true); }, [elementCounter, canvasConfig], ); diff --git a/frontend/components/admin/dashboard/charts/ChartRenderer.tsx b/frontend/components/admin/dashboard/charts/ChartRenderer.tsx index 94efd190..3cd5afbe 100644 --- a/frontend/components/admin/dashboard/charts/ChartRenderer.tsx +++ b/frontend/components/admin/dashboard/charts/ChartRenderer.tsx @@ -242,12 +242,12 @@ export function ChartRenderer({ element, data, width, height = 200 }: ChartRende // D3 차트 렌더링 const actualWidth = width !== undefined ? width : containerWidth; - // 원형 차트는 더 큰 크기가 필요 (최소 400px) + // 최소 크기 제약 완화 (1x1 위젯 지원) 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 - (isCircularChart ? 60 : 20), 300); + const minWidth = 35; // 최소 너비 35px + const finalWidth = Math.max(actualWidth - 4, minWidth); + // 최소 높이도 35px로 설정 + const finalHeight = Math.max(height - (isCircularChart ? 10 : 4), 35); console.log("🎨 ChartRenderer:", { elementSubtype: element.subtype, @@ -263,7 +263,7 @@ export function ChartRenderer({ element, data, width, height = 200 }: ChartRende }); return ( -
+
- +
+ {/* 시계판 배경 */} @@ -70,7 +70,7 @@ export function AnalogClock({ time, theme, timezone, customColor }: AnalogClockP y={y} textAnchor="middle" dominantBaseline="middle" - fontSize="20" + fontSize="16" fontWeight="bold" fill={colors.number} > @@ -86,7 +86,7 @@ export function AnalogClock({ time, theme, timezone, customColor }: AnalogClockP x2={100 + 40 * Math.cos((hourAngle * Math.PI) / 180)} y2={100 + 40 * Math.sin((hourAngle * Math.PI) / 180)} stroke={colors.hourHand} - strokeWidth="6" + strokeWidth="5" strokeLinecap="round" /> @@ -97,7 +97,7 @@ export function AnalogClock({ time, theme, timezone, customColor }: AnalogClockP x2={100 + 60 * Math.cos((minuteAngle * Math.PI) / 180)} y2={100 + 60 * Math.sin((minuteAngle * Math.PI) / 180)} stroke={colors.minuteHand} - strokeWidth="4" + strokeWidth="3" strokeLinecap="round" /> @@ -108,18 +108,18 @@ export function AnalogClock({ time, theme, timezone, customColor }: AnalogClockP x2={100 + 75 * Math.cos((secondAngle * Math.PI) / 180)} y2={100 + 75 * Math.sin((secondAngle * Math.PI) / 180)} stroke={colors.secondHand} - strokeWidth="2" + strokeWidth="1.5" strokeLinecap="round" /> {/* 중심점 */} - - + + {/* 타임존 표시 */} {timezoneLabel && ( -
+
{timezoneLabel}
)} diff --git a/frontend/components/admin/dashboard/widgets/DigitalClock.tsx b/frontend/components/admin/dashboard/widgets/DigitalClock.tsx index eb8b9cba..5ed506c5 100644 --- a/frontend/components/admin/dashboard/widgets/DigitalClock.tsx +++ b/frontend/components/admin/dashboard/widgets/DigitalClock.tsx @@ -56,21 +56,21 @@ export function DigitalClock({ return (
{/* 날짜 표시 (compact 모드에서는 숨김) */} {!compact && showDate && dateString && ( -
{dateString}
+
{dateString}
)} {/* 시간 표시 */} -
+
{timeString}
{/* 타임존 표시 */} -
+
{timezoneLabel}
diff --git a/frontend/components/admin/dashboard/widgets/ListWidgetConfigSidebar.tsx b/frontend/components/admin/dashboard/widgets/ListWidgetConfigSidebar.tsx index 62db5cef..cd72b044 100644 --- a/frontend/components/admin/dashboard/widgets/ListWidgetConfigSidebar.tsx +++ b/frontend/components/admin/dashboard/widgets/ListWidgetConfigSidebar.tsx @@ -94,24 +94,19 @@ export function ListWidgetConfigSidebar({ element, isOpen, onClose, onApply }: L const handleQueryTest = useCallback((result: QueryResult) => { setQueryResult(result); - // 쿼리 결과의 컬럼을 자동으로 listConfig.columns에 추가 (기존 컬럼은 유지) - setListConfig((prev) => { - const existingFields = prev.columns.map((col) => col.field); - const newColumns = result.columns - .filter((col) => !existingFields.includes(col)) - .map((col, idx) => ({ - id: `col_${Date.now()}_${idx}`, - field: col, - label: col, - visible: true, - align: "left" as const, - })); + // 쿼리 실행 시마다 컬럼 설정 초기화 (새로운 쿼리 결과로 덮어쓰기) + const newColumns = result.columns.map((col, idx) => ({ + id: `col_${Date.now()}_${idx}`, + field: col, + label: col, + visible: true, + align: "left" as const, + })); - return { - ...prev, - columns: [...prev.columns, ...newColumns], - }; - }); + setListConfig((prev) => ({ + ...prev, + columns: newColumns, + })); }, []); // 컬럼 설정 변경 diff --git a/frontend/components/dashboard/DashboardViewer.tsx b/frontend/components/dashboard/DashboardViewer.tsx index 4ad23fa8..9ed55dc4 100644 --- a/frontend/components/dashboard/DashboardViewer.tsx +++ b/frontend/components/dashboard/DashboardViewer.tsx @@ -379,8 +379,8 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi style={{ minHeight: "300px" }} > {element.showHeader !== false && ( -
-

{element.customTitle || element.title}

+
+

{element.customTitle || element.title}

)} -
+
{!isMounted ? (
@@ -441,8 +441,8 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi }} > {element.showHeader !== false && ( -
-

{element.customTitle || element.title}

+
+

{element.customTitle || element.title}

)} -
+
{!isMounted ? (
@@ -475,7 +475,7 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi element={element} data={data} width={undefined} - height={element.showHeader !== false ? element.size.height - 50 : element.size.height} + height={element.showHeader !== false ? element.size.height - 32 : element.size.height} /> ) : ( renderWidget(element) diff --git a/frontend/components/dashboard/widgets/CustomMetricWidget.tsx b/frontend/components/dashboard/widgets/CustomMetricWidget.tsx index 52c8411c..9c23c714 100644 --- a/frontend/components/dashboard/widgets/CustomMetricWidget.tsx +++ b/frontend/components/dashboard/widgets/CustomMetricWidget.tsx @@ -373,48 +373,53 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps) ); } + // 위젯 높이에 따라 레이아웃 결정 (세로 1칸이면 가로, 2칸 이상이면 세로) + // 실제 측정된 1칸 높이: 119px + const isHorizontalLayout = element?.size?.height && element.size.height <= 130; // 1칸 여유 (119px + 약간의 마진) + 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} +
+
+ ); + })}
); }