+
⚙️ 차트 설정
+
+ {/* 쿼리 결과가 없을 때 */}
+ {!queryResult && (
+
+
+ 💡 먼저 SQL 쿼리를 실행하여 데이터를 가져온 후 차트를 설정할 수 있습니다.
+
+
+ )}
+
+ {/* 데이터 필드 매핑 */}
+ {queryResult && (
+ <>
+ {/* 차트 제목 */}
+
+ 차트 제목
+ updateConfig({ title: e.target.value })}
+ placeholder="차트 제목을 입력하세요"
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
+ />
+
+
+ {/* X축 설정 */}
+
+
+ X축 (카테고리)
+ *
+
+ updateConfig({ xAxis: e.target.value })}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
+ >
+ 선택하세요
+ {availableColumns.map((col) => (
+
+ {col} {sampleData[col] && `(예: ${sampleData[col]})`}
+
+ ))}
+
+
+
+ {/* Y축 설정 */}
+
+
+ Y축 (값)
+ *
+
+ updateConfig({ yAxis: e.target.value })}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
+ >
+ 선택하세요
+ {availableColumns.map((col) => (
+
+ {col} {sampleData[col] && `(예: ${sampleData[col]})`}
+
+ ))}
+
+
+
+ {/* 집계 함수 */}
+
+ 집계 함수
+ updateConfig({ aggregation: e.target.value as any })}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
+ >
+ 합계 (SUM)
+ 평균 (AVG)
+ 개수 (COUNT)
+ 최대값 (MAX)
+ 최소값 (MIN)
+
+
+
+ {/* 그룹핑 필드 (선택사항) */}
+
+
+ 그룹핑 필드 (선택사항)
+
+ updateConfig({ groupBy: e.target.value })}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
+ >
+ 없음
+ {availableColumns.map((col) => (
+
+ {col}
+
+ ))}
+
+
+
+ {/* 차트 색상 */}
+
+
차트 색상
+
+ {[
+ ['#3B82F6', '#EF4444', '#10B981', '#F59E0B'], // 기본
+ ['#8B5CF6', '#EC4899', '#06B6D4', '#84CC16'], // 밝은
+ ['#1F2937', '#374151', '#6B7280', '#9CA3AF'], // 회색
+ ['#DC2626', '#EA580C', '#CA8A04', '#65A30D'], // 따뜻한
+ ].map((colorSet, setIdx) => (
+
updateConfig({ colors: colorSet })}
+ className={`
+ h-8 rounded border-2 flex
+ ${JSON.stringify(currentConfig.colors) === JSON.stringify(colorSet)
+ ? 'border-gray-800' : 'border-gray-300'}
+ `}
+ >
+ {colorSet.map((color, idx) => (
+
+ ))}
+
+ ))}
+
+
+
+ {/* 범례 표시 */}
+
+ updateConfig({ showLegend: e.target.checked })}
+ className="rounded"
+ />
+
+ 범례 표시
+
+
+
+ {/* 설정 미리보기 */}
+
+
📋 설정 미리보기
+
+
X축: {currentConfig.xAxis || '미설정'}
+
Y축: {currentConfig.yAxis || '미설정'}
+
집계: {currentConfig.aggregation || 'sum'}
+ {currentConfig.groupBy && (
+
그룹핑: {currentConfig.groupBy}
+ )}
+
데이터 행 수: {queryResult.rows.length}개
+
+
+
+ {/* 필수 필드 확인 */}
+ {(!currentConfig.xAxis || !currentConfig.yAxis) && (
+
+
+ ⚠️ X축과 Y축을 모두 설정해야 차트가 표시됩니다.
+
+
+ )}
+ >
+ )}
+
+ );
+}
diff --git a/frontend/components/admin/dashboard/DashboardCanvas.tsx b/frontend/components/admin/dashboard/DashboardCanvas.tsx
new file mode 100644
index 00000000..d86765a3
--- /dev/null
+++ b/frontend/components/admin/dashboard/DashboardCanvas.tsx
@@ -0,0 +1,109 @@
+'use client';
+
+import React, { forwardRef, useState, useCallback } from 'react';
+import { DashboardElement, ElementType, ElementSubtype, DragData } from './types';
+import { CanvasElement } from './CanvasElement';
+
+interface DashboardCanvasProps {
+ elements: DashboardElement[];
+ selectedElement: string | null;
+ onCreateElement: (type: ElementType, subtype: ElementSubtype, x: number, y: number) => void;
+ onUpdateElement: (id: string, updates: Partial