"use client"; import React, { useState, useCallback } from "react"; import { ChartConfig, QueryResult } from "./types"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Label } from "@/components/ui/label"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Checkbox } from "@/components/ui/checkbox"; import { Separator } from "@/components/ui/separator"; import { TrendingUp, AlertCircle } from "lucide-react"; interface ChartConfigPanelProps { config?: ChartConfig; queryResult?: QueryResult; onConfigChange: (config: ChartConfig) => void; chartType?: string; dataSourceType?: "database" | "api"; // 데이터 소스 타입 } /** * 차트 설정 패널 컴포넌트 * - 데이터 필드 매핑 설정 * - 차트 스타일 설정 * - 실시간 미리보기 */ export function ChartConfigPanel({ config, queryResult, onConfigChange, chartType, dataSourceType, }: ChartConfigPanelProps) { const [currentConfig, setCurrentConfig] = useState(config || {}); // 원형/도넛 차트 또는 REST API는 Y축이 필수가 아님 const isPieChart = chartType === "pie" || chartType === "donut"; const isApiSource = dataSourceType === "api"; // 설정 업데이트 const updateConfig = useCallback( (updates: Partial) => { const newConfig = { ...currentConfig, ...updates }; setCurrentConfig(newConfig); onConfigChange(newConfig); }, [currentConfig, onConfigChange], ); // 사용 가능한 컬럼 목록 및 타입 정보 const availableColumns = queryResult?.columns || []; const columnTypes = queryResult?.columnTypes || {}; const sampleData = queryResult?.rows?.[0] || {}; // 차트에 사용 가능한 컬럼 필터링 const simpleColumns = availableColumns.filter((col) => { const type = columnTypes[col]; // number, string, boolean만 허용 (object, array는 제외) return !type || type === "number" || type === "string" || type === "boolean"; }); // 숫자 타입 컬럼만 필터링 (Y축용) const numericColumns = availableColumns.filter((col) => columnTypes[col] === "number"); // 복잡한 타입의 컬럼 (경고 표시용) const complexColumns = availableColumns.filter((col) => { const type = columnTypes[col]; return type === "object" || type === "array"; }); return (
{/* 데이터 필드 매핑 */} {queryResult && ( <> {/* API 응답 미리보기 */} {queryResult.rows && queryResult.rows.length > 0 && (

📋 API 응답 데이터 미리보기

총 {queryResult.totalRows}개 데이터 중 첫 번째 행:
{JSON.stringify(sampleData, null, 2)}
)} {/* 복잡한 타입 경고 */} {complexColumns.length > 0 && (
⚠️ 차트에 사용할 수 없는 컬럼 감지
다음 컬럼은 객체 또는 배열 타입이라서 차트 축으로 선택할 수 없습니다:
{complexColumns.map((col) => ( {col} ({columnTypes[col]}) ))}
💡 해결 방법: JSON Path를 사용하여 중첩된 객체 내부의 값을 직접 추출하세요.
예: main 또는{" "} data.items
)} {/* 차트 제목 */}
updateConfig({ title: e.target.value })} placeholder="차트 제목을 입력하세요" />
{/* X축 설정 */}
{simpleColumns.length === 0 && (

⚠️ 사용 가능한 컬럼이 없습니다. JSON Path를 확인하세요.

)}
{/* Y축 설정 (다중 선택 가능) */}
{/* 숫자 타입 우선 표시 */} {numericColumns.length > 0 && ( <>
✅ 숫자 타입 (권장)
{numericColumns.map((col) => { const isSelected = Array.isArray(currentConfig.yAxis) ? currentConfig.yAxis.includes(col) : currentConfig.yAxis === col; return (
{ const currentYAxis = Array.isArray(currentConfig.yAxis) ? currentConfig.yAxis : currentConfig.yAxis ? [currentConfig.yAxis] : []; let newYAxis: string | string[]; if (checked) { newYAxis = [...currentYAxis, col]; } else { newYAxis = currentYAxis.filter((c) => c !== col); } if (newYAxis.length === 1) { newYAxis = newYAxis[0]; } updateConfig({ yAxis: newYAxis }); }} />
); })} )} {/* 기타 간단한 타입 */} {simpleColumns.filter((col) => !numericColumns.includes(col)).length > 0 && ( <> {numericColumns.length > 0 &&
}
📝 기타 타입
{simpleColumns .filter((col) => !numericColumns.includes(col)) .map((col) => { const isSelected = Array.isArray(currentConfig.yAxis) ? currentConfig.yAxis.includes(col) : currentConfig.yAxis === col; return (
{ const currentYAxis = Array.isArray(currentConfig.yAxis) ? currentConfig.yAxis : currentConfig.yAxis ? [currentConfig.yAxis] : []; let newYAxis: string | string[]; if (checked) { newYAxis = [...currentYAxis, col]; } else { newYAxis = currentYAxis.filter((c) => c !== col); } if (newYAxis.length === 1) { newYAxis = newYAxis[0]; } updateConfig({ yAxis: newYAxis }); }} />
); })} )}
{simpleColumns.length === 0 && (

⚠️ 사용 가능한 컬럼이 없습니다. JSON Path를 확인하세요.

)}

팁: 여러 항목을 선택하면 비교 차트가 생성됩니다 (예: 갤럭시 vs 아이폰)

{/* 집계 함수 */}

💡 그룹핑 필드와 함께 사용하면 자동으로 데이터를 집계합니다. (예: 부서별 개수, 월별 합계)

{/* 그룹핑 필드 (선택사항) */}
{/* 차트 색상 */}
{[ ["#3B82F6", "#EF4444", "#10B981", "#F59E0B"], // 기본 ["#8B5CF6", "#EC4899", "#06B6D4", "#84CC16"], // 밝은 ["#1F2937", "#374151", "#6B7280", "#9CA3AF"], // 회색 ["#DC2626", "#EA580C", "#CA8A04", "#65A30D"], // 따뜻한 ].map((colorSet, setIdx) => ( ))}
{/* 범례 표시 */}
updateConfig({ showLegend: checked as boolean })} />
{/* 설정 미리보기 */}
설정 미리보기
X축: {currentConfig.xAxis || "미설정"}
Y축: {Array.isArray(currentConfig.yAxis) && currentConfig.yAxis.length > 0 ? `${currentConfig.yAxis.length}개 (${currentConfig.yAxis.join(", ")})` : currentConfig.yAxis || "미설정"}
집계: {currentConfig.aggregation || "없음"}
{currentConfig.groupBy && (
그룹핑: {currentConfig.groupBy}
)}
데이터 행 수: {queryResult.rows.length}개
{Array.isArray(currentConfig.yAxis) && currentConfig.yAxis.length > 1 && (
✨ 다중 시리즈 차트가 생성됩니다!
)}
{/* 필수 필드 확인 */} {!currentConfig.xAxis && ( X축은 필수입니다. )} {!isPieChart && !isApiSource && !currentConfig.yAxis && ( Y축을 설정해야 차트가 표시됩니다. )} {(isPieChart || isApiSource) && !currentConfig.yAxis && !currentConfig.aggregation && ( Y축 또는 집계 함수(COUNT 등)를 설정해야 차트가 표시됩니다. )} )}
); }