diff --git a/frontend/components/admin/dashboard/charts/ChartRenderer.tsx b/frontend/components/admin/dashboard/charts/ChartRenderer.tsx index 8ab0d9af..8b94ec50 100644 --- a/frontend/components/admin/dashboard/charts/ChartRenderer.tsx +++ b/frontend/components/admin/dashboard/charts/ChartRenderer.tsx @@ -36,40 +36,100 @@ export function ChartRenderer({ element, data, width = 250, height = 200 }: Char } // 데이터 소스가 설정되어 있으면 페칭 - if (element.dataSource && element.dataSource.query && element.chartConfig) { + if (element.dataSource && element.chartConfig) { setIsLoading(true); setError(null); try { let queryResult: QueryResult; - // 현재 DB vs 외부 DB 분기 - if (element.dataSource.connectionType === "external" && element.dataSource.externalConnectionId) { - // 외부 DB - const result = await ExternalDbConnectionAPI.executeQuery( - parseInt(element.dataSource.externalConnectionId), - element.dataSource.query, - ); - - if (!result.success) { - throw new Error(result.message || "외부 DB 쿼리 실행 실패"); + // REST API vs Database 분기 + if (element.dataSource.type === "api" && element.dataSource.endpoint) { + // REST API + const params = new URLSearchParams(); + if (element.dataSource.queryParams) { + Object.entries(element.dataSource.queryParams).forEach(([key, value]) => { + if (key && value) { + params.append(key, value); + } + }); } + let url = element.dataSource.endpoint; + const queryString = params.toString(); + if (queryString) { + url += (url.includes("?") ? "&" : "?") + queryString; + } + + const headers: Record = { + "Content-Type": "application/json", + ...element.dataSource.headers, + }; + + const response = await fetch(url, { + method: "GET", + headers, + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const apiData = await response.json(); + + // JSON Path 처리 + let processedData = apiData; + if (element.dataSource.jsonPath) { + const paths = element.dataSource.jsonPath.split("."); + for (const path of paths) { + if (processedData && typeof processedData === "object" && path in processedData) { + processedData = processedData[path]; + } else { + throw new Error(`JSON Path "${element.dataSource.jsonPath}"에서 데이터를 찾을 수 없습니다`); + } + } + } + + const rows = Array.isArray(processedData) ? processedData : [processedData]; + const columns = rows.length > 0 ? Object.keys(rows[0]) : []; + queryResult = { - columns: result.data?.[0] ? Object.keys(result.data[0]) : [], - rows: result.data || [], - totalRows: result.data?.length || 0, + columns, + rows, + totalRows: rows.length, executionTime: 0, }; + } else if (element.dataSource.query) { + // Database (현재 DB 또는 외부 DB) + if (element.dataSource.connectionType === "external" && element.dataSource.externalConnectionId) { + // 외부 DB + const result = await ExternalDbConnectionAPI.executeQuery( + parseInt(element.dataSource.externalConnectionId), + element.dataSource.query, + ); + + if (!result.success) { + throw new Error(result.message || "외부 DB 쿼리 실행 실패"); + } + + queryResult = { + columns: result.data?.[0] ? Object.keys(result.data[0]) : [], + rows: result.data || [], + totalRows: result.data?.length || 0, + executionTime: 0, + }; + } else { + // 현재 DB + const result = await dashboardApi.executeQuery(element.dataSource.query); + queryResult = { + columns: result.columns, + rows: result.rows, + totalRows: result.rowCount, + executionTime: 0, + }; + } } else { - // 현재 DB - const result = await dashboardApi.executeQuery(element.dataSource.query); - queryResult = { - columns: result.columns, - rows: result.rows, - totalRows: result.rowCount, - executionTime: 0, - }; + throw new Error("데이터 소스가 올바르게 설정되지 않았습니다"); } // ChartData로 변환 diff --git a/frontend/components/admin/dashboard/data-sources/ApiConfig.tsx b/frontend/components/admin/dashboard/data-sources/ApiConfig.tsx index 27377ba2..ae7e8294 100644 --- a/frontend/components/admin/dashboard/data-sources/ApiConfig.tsx +++ b/frontend/components/admin/dashboard/data-sources/ApiConfig.tsx @@ -91,33 +91,59 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps }); } - const url = `http://localhost:8080/api/dashboards/fetch-api?${params.toString()}`; + // URL 구성 + let url = dataSource.endpoint; + const queryString = params.toString(); + if (queryString) { + url += (url.includes("?") ? "&" : "?") + queryString; + } + // 헤더 구성 + const headers: Record = { + "Content-Type": "application/json", + ...dataSource.headers, + }; + + // 외부 API 직접 호출 const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${localStorage.getItem("token") || "test-token"}`, - }, - body: JSON.stringify({ - endpoint: dataSource.endpoint, - headers: dataSource.headers || {}, - jsonPath: dataSource.jsonPath || "", - }), + method: "GET", + headers, }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } - const result: ApiResponse = await response.json(); + const apiData = await response.json(); - if (!result.success) { - throw new Error(result.message || "API 호출에 실패했습니다"); + // 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}"에서 데이터를 찾을 수 없습니다`); + } + } } - setTestResult(result.data); - onTestResult?.(result.data); + // 배열이 아니면 배열로 변환 + const rows = Array.isArray(data) ? data : [data]; + + // 컬럼 추출 + const columns = rows.length > 0 ? Object.keys(rows[0]) : []; + + const result: QueryResult = { + columns, + rows, + totalRows: rows.length, + executionTime: 0, + }; + + setTestResult(result); + onTestResult?.(result); } catch (err) { const errorMessage = err instanceof Error ? err.message : "알 수 없는 오류가 발생했습니다"; setTestError(errorMessage);