+ )}
);
}
diff --git a/frontend/components/admin/dashboard/data-sources/MultiDataSourceConfig.tsx b/frontend/components/admin/dashboard/data-sources/MultiDataSourceConfig.tsx
index e0b39220..e24dc42a 100644
--- a/frontend/components/admin/dashboard/data-sources/MultiDataSourceConfig.tsx
+++ b/frontend/components/admin/dashboard/data-sources/MultiDataSourceConfig.tsx
@@ -78,15 +78,28 @@ export default function MultiDataSourceConfig({
여러 데이터 소스를 연결하여 데이터를 통합할 수 있습니다
-
+
+
+
+
+
+ handleAddDataSource("api")}>
+
+ REST API 추가
+
+ handleAddDataSource("database")}>
+
+ Database 추가
+
+
+
{/* 데이터 소스가 없는 경우 */}
@@ -95,15 +108,28 @@ export default function MultiDataSourceConfig({
연결된 데이터 소스가 없습니다
-
+
+
+
+
+
+ handleAddDataSource("api")}>
+
+ REST API 추가
+
+ handleAddDataSource("database")}>
+
+ Database 추가
+
+
+
) : (
/* 탭 UI */
diff --git a/frontend/components/admin/dashboard/data-sources/MultiDatabaseConfig.tsx b/frontend/components/admin/dashboard/data-sources/MultiDatabaseConfig.tsx
index cf1efaa5..62a38701 100644
--- a/frontend/components/admin/dashboard/data-sources/MultiDatabaseConfig.tsx
+++ b/frontend/components/admin/dashboard/data-sources/MultiDatabaseConfig.tsx
@@ -3,6 +3,7 @@
import React, { useState, useEffect } from "react";
import { ChartDataSource } from "@/components/admin/dashboard/types";
import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
@@ -25,6 +26,10 @@ export default function MultiDatabaseConfig({ dataSource, onChange }: MultiDatab
const [testResult, setTestResult] = useState<{ success: boolean; message: string; rowCount?: number } | null>(null);
const [externalConnections, setExternalConnections] = useState([]);
const [loadingConnections, setLoadingConnections] = useState(false);
+ const [availableColumns, setAvailableColumns] = useState([]); // 쿼리 테스트 후 발견된 컬럼 목록
+ const [columnTypes, setColumnTypes] = useState>({}); // 컬럼 타입 정보
+ const [sampleData, setSampleData] = useState([]); // 샘플 데이터 (최대 3개)
+ const [columnSearchTerm, setColumnSearchTerm] = useState(""); // 컬럼 검색어
// 외부 DB 커넥션 목록 로드
useEffect(() => {
@@ -36,19 +41,19 @@ export default function MultiDatabaseConfig({ dataSource, onChange }: MultiDatab
const loadExternalConnections = async () => {
setLoadingConnections(true);
try {
- const response = await fetch("/api/admin/reports/external-connections", {
- credentials: "include",
- });
+ // ExternalDbConnectionAPI 사용 (인증 토큰 자동 포함)
+ const { ExternalDbConnectionAPI } = await import("@/lib/api/externalDbConnection");
+ const connections = await ExternalDbConnectionAPI.getConnections({ is_active: "Y" });
- if (response.ok) {
- const result = await response.json();
- if (result.success && result.data) {
- const connections = Array.isArray(result.data) ? result.data : result.data.data || [];
- setExternalConnections(connections);
- }
- }
+ console.log("✅ 외부 DB 커넥션 로드 성공:", connections.length, "개");
+ setExternalConnections(connections.map((conn: any) => ({
+ id: String(conn.id),
+ name: conn.connection_name,
+ type: conn.db_type,
+ })));
} catch (error) {
- console.error("외부 DB 커넥션 로드 실패:", error);
+ console.error("❌ 외부 DB 커넥션 로드 실패:", error);
+ setExternalConnections([]);
} finally {
setLoadingConnections(false);
}
@@ -77,7 +82,41 @@ export default function MultiDatabaseConfig({ dataSource, onChange }: MultiDatab
);
if (result.success && result.data) {
- const rowCount = Array.isArray(result.data.rows) ? result.data.rows.length : 0;
+ const rows = Array.isArray(result.data.rows) ? result.data.rows : [];
+ const rowCount = rows.length;
+
+ // 컬럼 목록 및 타입 추출
+ if (rows.length > 0) {
+ const columns = Object.keys(rows[0]);
+ setAvailableColumns(columns);
+
+ // 컬럼 타입 분석
+ const types: Record = {};
+ columns.forEach(col => {
+ const value = rows[0][col];
+ if (value === null || value === undefined) {
+ types[col] = "unknown";
+ } else if (typeof value === "number") {
+ types[col] = "number";
+ } else if (typeof value === "boolean") {
+ types[col] = "boolean";
+ } else if (typeof value === "string") {
+ if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
+ types[col] = "date";
+ } else {
+ types[col] = "string";
+ }
+ } else {
+ types[col] = "object";
+ }
+ });
+ setColumnTypes(types);
+ setSampleData(rows.slice(0, 3));
+
+ console.log("📊 발견된 컬럼:", columns);
+ console.log("📊 컬럼 타입:", types);
+ }
+
setTestResult({
success: true,
message: "쿼리 실행 성공",
@@ -89,6 +128,39 @@ export default function MultiDatabaseConfig({ dataSource, onChange }: MultiDatab
} else {
// 현재 DB
const result = await dashboardApi.executeQuery(dataSource.query);
+
+ // 컬럼 목록 및 타입 추출
+ if (result.rows && result.rows.length > 0) {
+ const columns = Object.keys(result.rows[0]);
+ setAvailableColumns(columns);
+
+ // 컬럼 타입 분석
+ const types: Record = {};
+ columns.forEach(col => {
+ const value = result.rows[0][col];
+ if (value === null || value === undefined) {
+ types[col] = "unknown";
+ } else if (typeof value === "number") {
+ types[col] = "number";
+ } else if (typeof value === "boolean") {
+ types[col] = "boolean";
+ } else if (typeof value === "string") {
+ if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
+ types[col] = "date";
+ } else {
+ types[col] = "string";
+ }
+ } else {
+ types[col] = "object";
+ }
+ });
+ setColumnTypes(types);
+ setSampleData(result.rows.slice(0, 3));
+
+ console.log("📊 발견된 컬럼:", columns);
+ console.log("📊 컬럼 타입:", types);
+ }
+
setTestResult({
success: true,
message: "쿼리 실행 성공",
@@ -183,6 +255,34 @@ export default function MultiDatabaseConfig({ dataSource, onChange }: MultiDatab
+ {/* 자동 새로고침 설정 */}
+