ERP-node/frontend/components/admin/dashboard/data-sources/MultiDatabaseConfig.tsx

230 lines
7.6 KiB
TypeScript

"use client";
import React, { useState, useEffect } from "react";
import { ChartDataSource } from "@/components/admin/dashboard/types";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Loader2, CheckCircle, XCircle } from "lucide-react";
interface MultiDatabaseConfigProps {
dataSource: ChartDataSource;
onChange: (updates: Partial<ChartDataSource>) => void;
}
interface ExternalConnection {
id: string;
name: string;
type: string;
}
export default function MultiDatabaseConfig({ dataSource, onChange }: MultiDatabaseConfigProps) {
const [testing, setTesting] = useState(false);
const [testResult, setTestResult] = useState<{ success: boolean; message: string; rowCount?: number } | null>(null);
const [externalConnections, setExternalConnections] = useState<ExternalConnection[]>([]);
const [loadingConnections, setLoadingConnections] = useState(false);
// 외부 DB 커넥션 목록 로드
useEffect(() => {
if (dataSource.connectionType === "external") {
loadExternalConnections();
}
}, [dataSource.connectionType]);
const loadExternalConnections = async () => {
setLoadingConnections(true);
try {
const response = await fetch("/api/admin/reports/external-connections", {
credentials: "include",
});
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);
}
}
} catch (error) {
console.error("외부 DB 커넥션 로드 실패:", error);
} finally {
setLoadingConnections(false);
}
};
// 쿼리 테스트
const handleTestQuery = async () => {
if (!dataSource.query) {
setTestResult({ success: false, message: "SQL 쿼리를 입력해주세요" });
return;
}
setTesting(true);
setTestResult(null);
try {
// dashboardApi 사용 (인증 토큰 자동 포함)
const { dashboardApi } = await import("@/lib/api/dashboard");
if (dataSource.connectionType === "external" && dataSource.externalConnectionId) {
// 외부 DB
const { ExternalDbConnectionAPI } = await import("@/lib/api/externalDbConnection");
const result = await ExternalDbConnectionAPI.executeQuery(
parseInt(dataSource.externalConnectionId),
dataSource.query
);
if (result.success && result.data) {
const rowCount = Array.isArray(result.data.rows) ? result.data.rows.length : 0;
setTestResult({
success: true,
message: "쿼리 실행 성공",
rowCount,
});
} else {
setTestResult({ success: false, message: result.message || "쿼리 실행 실패" });
}
} else {
// 현재 DB
const result = await dashboardApi.executeQuery(dataSource.query);
setTestResult({
success: true,
message: "쿼리 실행 성공",
rowCount: result.rowCount || 0,
});
}
} catch (error: any) {
setTestResult({ success: false, message: error.message || "네트워크 오류" });
} finally {
setTesting(false);
}
};
return (
<div className="space-y-4 rounded-lg border p-4">
<h5 className="text-sm font-semibold">Database </h5>
{/* 커넥션 타입 */}
<div className="space-y-2">
<Label className="text-xs"> </Label>
<RadioGroup
value={dataSource.connectionType || "current"}
onValueChange={(value: "current" | "external") =>
onChange({ connectionType: value })
}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="current" id={`current-\${dataSource.id}`} />
<Label
htmlFor={`current-\${dataSource.id}`}
className="text-xs font-normal"
>
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="external" id={`external-\${dataSource.id}`} />
<Label
htmlFor={`external-\${dataSource.id}`}
className="text-xs font-normal"
>
</Label>
</div>
</RadioGroup>
</div>
{/* 외부 DB 선택 */}
{dataSource.connectionType === "external" && (
<div className="space-y-2">
<Label htmlFor={`external-conn-\${dataSource.id}`} className="text-xs">
*
</Label>
{loadingConnections ? (
<div className="flex h-10 items-center justify-center rounded-md border">
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
) : (
<Select
value={dataSource.externalConnectionId || ""}
onValueChange={(value) => onChange({ externalConnectionId: value })}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="외부 DB 선택" />
</SelectTrigger>
<SelectContent>
{externalConnections.map((conn) => (
<SelectItem key={conn.id} value={conn.id} className="text-xs">
{conn.name} ({conn.type})
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
)}
{/* SQL 쿼리 */}
<div className="space-y-2">
<Label htmlFor={`query-\${dataSource.id}`} className="text-xs">
SQL *
</Label>
<Textarea
id={`query-\${dataSource.id}`}
value={dataSource.query || ""}
onChange={(e) => onChange({ query: e.target.value })}
placeholder="SELECT * FROM table_name WHERE ..."
className="min-h-[120px] font-mono text-xs"
/>
<p className="text-[10px] text-muted-foreground">
SELECT
</p>
</div>
{/* 테스트 버튼 */}
<div className="space-y-2 border-t pt-4">
<Button
variant="outline"
size="sm"
onClick={handleTestQuery}
disabled={testing || !dataSource.query}
className="h-8 w-full gap-2 text-xs"
>
{testing ? (
<>
<Loader2 className="h-3 w-3 animate-spin" />
...
</>
) : (
"쿼리 테스트"
)}
</Button>
{testResult && (
<div
className={`flex items-center gap-2 rounded-md p-2 text-xs \${
testResult.success
? "bg-green-50 text-green-700"
: "bg-red-50 text-red-700"
}`}
>
{testResult.success ? (
<CheckCircle className="h-3 w-3" />
) : (
<XCircle className="h-3 w-3" />
)}
<div>
{testResult.message}
{testResult.rowCount !== undefined && (
<span className="ml-1">({testResult.rowCount})</span>
)}
</div>
</div>
)}
</div>
</div>
);
}