"use client"; import React, { useState } from "react"; import { ChartDataSource, QueryResult, KeyValuePair } from "../types"; import { Card } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Plus, X, Play, AlertCircle } from "lucide-react"; interface ApiConfigProps { dataSource: ChartDataSource; onChange: (updates: Partial) => void; onTestResult?: (result: QueryResult) => void; } /** * REST API 설정 컴포넌트 * - API 엔드포인트 설정 * - 헤더 및 쿼리 파라미터 추가 * - JSON Path 설정 */ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps) { const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState(null); const [testError, setTestError] = useState(null); // 헤더를 배열로 정규화 (객체 형식 호환) const normalizeHeaders = (): KeyValuePair[] => { if (!dataSource.headers) return []; if (Array.isArray(dataSource.headers)) return dataSource.headers; // 객체 형식이면 배열로 변환 return Object.entries(dataSource.headers as Record).map(([key, value]) => ({ id: `header_${Date.now()}_${Math.random()}`, key, value, })); }; // 헤더 추가 const addHeader = () => { const headers = normalizeHeaders(); onChange({ headers: [...headers, { id: `header_${Date.now()}`, key: "", value: "" }], }); }; // 헤더 제거 const removeHeader = (id: string) => { const headers = normalizeHeaders(); onChange({ headers: headers.filter((h) => h.id !== id) }); }; // 헤더 업데이트 const updateHeader = (id: string, updates: Partial) => { const headers = normalizeHeaders(); onChange({ headers: headers.map((h) => (h.id === id ? { ...h, ...updates } : h)), }); }; // 쿼리 파라미터를 배열로 정규화 (객체 형식 호환) const normalizeQueryParams = (): KeyValuePair[] => { if (!dataSource.queryParams) return []; if (Array.isArray(dataSource.queryParams)) return dataSource.queryParams; // 객체 형식이면 배열로 변환 return Object.entries(dataSource.queryParams as Record).map(([key, value]) => ({ id: `param_${Date.now()}_${Math.random()}`, key, value, })); }; // 쿼리 파라미터 추가 const addQueryParam = () => { const queryParams = normalizeQueryParams(); onChange({ queryParams: [...queryParams, { id: `param_${Date.now()}`, key: "", value: "" }], }); }; // 쿼리 파라미터 제거 const removeQueryParam = (id: string) => { const queryParams = normalizeQueryParams(); onChange({ queryParams: queryParams.filter((p) => p.id !== id) }); }; // 쿼리 파라미터 업데이트 const updateQueryParam = (id: string, updates: Partial) => { const queryParams = normalizeQueryParams(); onChange({ queryParams: queryParams.map((p) => (p.id === id ? { ...p, ...updates } : p)), }); }; // API 테스트 const testApi = async () => { if (!dataSource.endpoint) { setTestError("API URL을 입력하세요"); return; } setTesting(true); setTestError(null); setTestResult(null); try { // 쿼리 파라미터 구성 const params: Record = {}; const normalizedQueryParams = normalizeQueryParams(); normalizedQueryParams.forEach(({ key, value }) => { if (key && value) { params[key] = value; } }); // 헤더 구성 const headers: Record = {}; const normalizedHeaders = normalizeHeaders(); normalizedHeaders.forEach(({ key, value }) => { if (key && value) { headers[key] = value; } }); // 백엔드 프록시를 통한 외부 API 호출 (CORS 우회) const response = await fetch("http://localhost:8080/api/dashboards/fetch-external-api", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ url: dataSource.endpoint, method: "GET", headers: headers, queryParams: params, }), }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const apiResponse = await response.json(); if (!apiResponse.success) { throw new Error(apiResponse.message || "외부 API 호출 실패"); } const apiData = apiResponse.data; // JSON Path 처리 let data = apiData; if (dataSource.jsonPath) { const paths = dataSource.jsonPath.split("."); for (const path of paths) { if (data && typeof data === "object" && path in data) { data = data[path]; } else { throw new Error(`JSON Path "${dataSource.jsonPath}"에서 데이터를 찾을 수 없습니다`); } } } // 배열이 아니면 배열로 변환 const rows = Array.isArray(data) ? data : [data]; if (rows.length === 0) { throw new Error("API 응답에 데이터가 없습니다"); } // 컬럼 추출 및 타입 분석 const firstRow = rows[0]; const columns = Object.keys(firstRow); // 각 컬럼의 타입 분석 const columnTypes: Record = {}; columns.forEach((col) => { const value = firstRow[col]; if (value === null || value === undefined) { columnTypes[col] = "null"; } else if (Array.isArray(value)) { columnTypes[col] = "array"; } else if (typeof value === "object") { columnTypes[col] = "object"; } else if (typeof value === "number") { columnTypes[col] = "number"; } else if (typeof value === "boolean") { columnTypes[col] = "boolean"; } else { columnTypes[col] = "string"; } }); const queryResult: QueryResult = { columns, rows, totalRows: rows.length, executionTime: 0, columnTypes, // 타입 정보 추가 }; setTestResult(queryResult); onTestResult?.(queryResult); } catch (err) { const errorMessage = err instanceof Error ? err.message : "알 수 없는 오류가 발생했습니다"; setTestError(errorMessage); } finally { setTesting(false); } }; return (

2단계: REST API 설정

외부 API에서 데이터를 가져올 설정을 입력하세요

{/* API URL */}
onChange({ endpoint: e.target.value })} className="mt-2" />

GET 요청을 보낼 API 엔드포인트

{/* HTTP 메서드 (고정) */}
GET (고정)

데이터 조회는 GET 메서드만 지원합니다

{/* 쿼리 파라미터 */}
{(() => { const params = normalizeQueryParams(); return params.length > 0 ? (
{params.map((param) => (
updateQueryParam(param.id, { key: e.target.value })} className="flex-1" /> updateQueryParam(param.id, { value: e.target.value })} className="flex-1" />
))}
) : (

추가된 파라미터가 없습니다

); })()}

예: category=electronics, limit=10

{/* 헤더 */}
{/* 빠른 헤더 템플릿 */}
{(() => { const headers = normalizeHeaders(); return headers.length > 0 ? (
{headers.map((header) => (
updateHeader(header.id, { key: e.target.value })} className="flex-1" /> updateHeader(header.id, { value: e.target.value })} className="flex-1" type={header.key.toLowerCase().includes("auth") ? "password" : "text"} />
))}
) : (

추가된 헤더가 없습니다

); })()}
{/* JSON Path */} onChange({ jsonPath: e.target.value })} />

JSON 응답에서 데이터 배열의 경로 (예: data.results, items, response.data)
비워두면 전체 응답을 사용합니다

{/* 테스트 버튼 */}
{/* 테스트 오류 */} {testError && (
API 호출 실패
{testError}
)} {/* 테스트 결과 */} {testResult && (
API 호출 성공
총 {testResult.rows.length}개의 데이터를 불러왔습니다
컬럼: {testResult.columns.join(", ")}
)}
); }