"use client"; import React, { useState, useCallback } from "react"; import { ChartDataSource, QueryResult } from "./types"; import { ExternalDbConnectionAPI } from "@/lib/api/externalDbConnection"; import { dashboardApi } from "@/lib/api/dashboard"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Play, Loader2, Database, Code } from "lucide-react"; interface QueryEditorProps { dataSource?: ChartDataSource; onDataSourceChange: (dataSource: ChartDataSource) => void; onQueryTest?: (result: QueryResult) => void; } /** * SQL 쿼리 에디터 컴포넌트 * - SQL 쿼리 작성 및 편집 * - 쿼리 실행 및 결과 미리보기 * - 현재 DB / 외부 DB 분기 처리 */ export function QueryEditor({ dataSource, onDataSourceChange, onQueryTest }: QueryEditorProps) { const [query, setQuery] = useState(dataSource?.query || ""); const [isExecuting, setIsExecuting] = useState(false); const [queryResult, setQueryResult] = useState(null); const [error, setError] = useState(null); // 쿼리 실행 const executeQuery = useCallback(async () => { console.log("🚀 executeQuery 호출됨!"); console.log("📝 현재 쿼리:", query); console.log("✅ query.trim():", query.trim()); if (!query.trim()) { setError("쿼리를 입력해주세요."); return; } // 외부 DB인 경우 커넥션 ID 확인 if (dataSource?.connectionType === "external" && !dataSource?.externalConnectionId) { setError("외부 DB 커넥션을 선택해주세요."); console.log("❌ 쿼리가 비어있음!"); return; } setIsExecuting(true); setError(null); console.log("🔄 쿼리 실행 시작..."); try { let apiResult: { columns: string[]; rows: any[]; rowCount: number }; // 현재 DB vs 외부 DB 분기 if (dataSource?.connectionType === "external" && dataSource?.externalConnectionId) { // 외부 DB 쿼리 실행 const result = await ExternalDbConnectionAPI.executeQuery( parseInt(dataSource.externalConnectionId), query.trim(), ); if (!result.success) { throw new Error(result.message || "외부 DB 쿼리 실행에 실패했습니다."); } // ExternalDbConnectionAPI의 응답을 통일된 형식으로 변환 apiResult = { columns: result.data?.[0] ? Object.keys(result.data[0]) : [], rows: result.data || [], rowCount: result.data?.length || 0, }; } else { // 현재 DB 쿼리 실행 apiResult = await dashboardApi.executeQuery(query.trim()); } // 결과를 QueryResult 형식으로 변환 const result: QueryResult = { columns: apiResult.columns, rows: apiResult.rows, totalRows: apiResult.rowCount, executionTime: 0, }; setQueryResult(result); onQueryTest?.(result); // 데이터 소스 업데이트 onDataSourceChange({ ...dataSource, type: "database", query: query.trim(), refreshInterval: dataSource?.refreshInterval ?? 0, lastExecuted: new Date().toISOString(), }); } catch (err) { const errorMessage = err instanceof Error ? err.message : "쿼리 실행 중 오류가 발생했습니다."; setError(errorMessage); } finally { setIsExecuting(false); } }, [query, dataSource, onDataSourceChange, onQueryTest]); // 샘플 쿼리 삽입 const insertSampleQuery = useCallback((sampleType: string) => { const samples = { comparison: `-- 제품별 월별 매출 비교 (다중 시리즈) -- 갤럭시(Galaxy) vs 아이폰(iPhone) 매출 비교 SELECT DATE_TRUNC('month', order_date) as month, SUM(CASE WHEN product_category = '갤럭시' THEN amount ELSE 0 END) as galaxy_sales, SUM(CASE WHEN product_category = '아이폰' THEN amount ELSE 0 END) as iphone_sales, SUM(CASE WHEN product_category = '기타' THEN amount ELSE 0 END) as other_sales FROM orders WHERE order_date >= CURRENT_DATE - INTERVAL '12 months' GROUP BY DATE_TRUNC('month', order_date) ORDER BY month;`, sales: `-- 월별 매출 데이터 SELECT DATE_TRUNC('month', order_date) as month, SUM(total_amount) as sales, COUNT(*) as order_count FROM orders WHERE order_date >= CURRENT_DATE - INTERVAL '12 months' GROUP BY DATE_TRUNC('month', order_date) ORDER BY month;`, users: `-- 사용자 가입 추이 SELECT DATE_TRUNC('week', created_at) as week, COUNT(*) as new_users FROM users WHERE created_at >= CURRENT_DATE - INTERVAL '3 months' GROUP BY DATE_TRUNC('week', created_at) ORDER BY week;`, products: `-- 상품별 판매량 SELECT product_name, SUM(quantity) as total_sold, SUM(quantity * price) as revenue FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.created_at >= CURRENT_DATE - INTERVAL '1 month' GROUP BY product_name ORDER BY total_sold DESC LIMIT 10;`, regional: `-- 지역별 매출 비교 SELECT region as 지역, SUM(CASE WHEN quarter = 'Q1' THEN sales ELSE 0 END) as Q1, SUM(CASE WHEN quarter = 'Q2' THEN sales ELSE 0 END) as Q2, SUM(CASE WHEN quarter = 'Q3' THEN sales ELSE 0 END) as Q3, SUM(CASE WHEN quarter = 'Q4' THEN sales ELSE 0 END) as Q4 FROM regional_sales WHERE year = EXTRACT(YEAR FROM CURRENT_DATE) GROUP BY region ORDER BY Q4 DESC;`, }; setQuery(samples[sampleType as keyof typeof samples] || ""); }, []); return (
{/* 쿼리 에디터 헤더 */}

SQL 쿼리 에디터

{/* 샘플 쿼리 버튼들 */}
{/* SQL 쿼리 입력 영역 */}