"use client"; import React, { useState, useCallback, useEffect } from "react"; import { ChartConfig, QueryResult, isCircularChart, isAxisBasedChart } 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 { 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 { AlertCircle } from "lucide-react"; import { DateFilterPanel } from "./DateFilterPanel"; import { extractTableNameFromQuery } from "./utils/queryHelpers"; import { dashboardApi } from "@/lib/api/dashboard"; interface ChartConfigPanelProps { config?: ChartConfig; queryResult?: QueryResult; onConfigChange: (config: ChartConfig) => void; chartType?: string; dataSourceType?: "database" | "api"; // 데이터 소스 타입 query?: string; // SQL 쿼리 (테이블명 추출용) } /** * 차트 설정 패널 컴포넌트 * - 데이터 필드 매핑 설정 * - 차트 스타일 설정 * - 실시간 미리보기 */ export function ChartConfigPanel({ config, queryResult, onConfigChange, chartType, dataSourceType, query, }: ChartConfigPanelProps) { const [currentConfig, setCurrentConfig] = useState(config || {}); const [dateColumns, setDateColumns] = useState([]); // 원형 차트 또는 REST API는 Y축이 필수가 아님 const isPieChart = chartType ? isCircularChart(chartType as any) : false; 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"; }); // 테이블 스키마에서 실제 날짜 컬럼 가져오기 useEffect(() => { if (!query || !queryResult || dataSourceType === "api") { // API 소스는 스키마 조회 불가 setDateColumns([]); return; } const tableName = extractTableNameFromQuery(query); if (!tableName) { setDateColumns([]); return; } dashboardApi .getTableSchema(tableName) .then((schema) => { // 원본 테이블의 모든 날짜 컬럼을 표시 // (SELECT에 없어도 WHERE 절에 사용 가능) setDateColumns(schema.dateColumns); }) .catch(() => { // 실패 시 빈 배열 (날짜 필터 비활성화) setDateColumns([]); }); }, [query, queryResult, dataSourceType]); return (
{/* 데이터 필드 매핑 */} {queryResult && ( <> {/* 복잡한 타입 경고 */} {complexColumns.length > 0 && (
차트에 사용할 수 없는 컬럼 감지
다음 컬럼은 객체 또는 배열 타입이라서 차트 축으로 선택할 수 없습니다:
{complexColumns.map((col) => ( {col} ({columnTypes[col]}) ))}
해결 방법: JSON Path를 사용하여 중첩된 객체 내부의 값을 직접 추출하세요.
예: main 또는{" "} data.items
)} {/* 차트 제목 */}
updateConfig({ title: e.target.value })} placeholder="차트 제목을 입력하세요" className="h-8 text-xs" />
{/* 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 })} />
{/* 날짜 필터 */} {dateColumns.length > 0 && ( )} {/* 필수 필드 확인 */} {!currentConfig.xAxis && ( X축은 필수입니다. )} {!isPieChart && !isApiSource && !currentConfig.yAxis && ( Y축을 설정해야 차트가 표시됩니다. )} {(isPieChart || isApiSource) && !currentConfig.yAxis && !currentConfig.aggregation && ( Y축 또는 집계 함수(COUNT 등)를 설정해야 차트가 표시됩니다. )} )}
); }