ERP-node/frontend/components/dataflow/connection/ConnectionSelectionPanel.tsx

287 lines
10 KiB
TypeScript
Raw Normal View History

/**
*
* FROM/TO
*/
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { AlertCircle, Database, ExternalLink } from "lucide-react";
import { getActiveConnections, ConnectionInfo } from "@/lib/api/multiConnection";
import { useToast } from "@/hooks/use-toast";
export interface ConnectionSelectionPanelProps {
fromConnectionId?: number;
toConnectionId?: number;
onFromConnectionChange: (connectionId: number) => void;
onToConnectionChange: (connectionId: number) => void;
actionType: "insert" | "update" | "delete";
// 🆕 자기 자신 테이블 작업 지원
allowSameConnection?: boolean;
currentConnectionId?: number; // 현재 메인 DB 커넥션
disabled?: boolean;
}
export const ConnectionSelectionPanel: React.FC<ConnectionSelectionPanelProps> = ({
fromConnectionId,
toConnectionId,
onFromConnectionChange,
onToConnectionChange,
actionType,
allowSameConnection = true,
currentConnectionId = 0,
disabled = false,
}) => {
const { toast } = useToast();
const [availableConnections, setAvailableConnections] = useState<ConnectionInfo[]>([]);
const [loading, setLoading] = useState(true);
// 커넥션 목록 로드 (한 번만 실행)
useEffect(() => {
let isMounted = true;
const loadConnections = async () => {
try {
setLoading(true);
console.log("🔍 커넥션 목록 로드 시작...");
const connections = await getActiveConnections();
if (isMounted) {
console.log("✅ 커넥션 목록 로드 성공:", connections);
setAvailableConnections(Array.isArray(connections) ? connections : []);
}
} catch (error) {
if (isMounted) {
console.error("❌ 커넥션 목록 로드 실패:", error);
console.error("Error details:", {
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
response: error && typeof error === "object" && "response" in error ? error.response : undefined,
});
toast({
title: "커넥션 로드 실패",
description: `활성 커넥션 목록을 불러오는데 실패했습니다. ${error instanceof Error ? error.message : String(error)}`,
variant: "destructive",
});
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
// 컴포넌트가 마운트된 후 1초 딜레이로 API 호출 (Rate Limiting 방지)
const timeoutId = setTimeout(() => {
if (isMounted) {
loadConnections();
}
}, 1000);
return () => {
isMounted = false;
clearTimeout(timeoutId);
};
}, []); // 의존성 배열을 빈 배열로 수정
// 액션 타입별 라벨 정의
const getConnectionLabels = () => {
switch (actionType) {
case "insert":
return {
from: {
title: "소스 데이터베이스 연결",
desc: "데이터를 가져올 데이터베이스 연결을 선택하세요",
},
to: {
title: "대상 데이터베이스 연결",
desc: "데이터를 저장할 데이터베이스 연결을 선택하세요",
},
};
case "update":
return {
from: {
title: "조건 확인 데이터베이스",
desc: "업데이트 조건을 확인할 데이터베이스 연결을 선택하세요 (자기 자신 가능)",
},
to: {
title: "업데이트 대상 데이터베이스",
desc: "데이터를 업데이트할 데이터베이스 연결을 선택하세요 (자기 자신 가능)",
},
};
case "delete":
return {
from: {
title: "조건 확인 데이터베이스",
desc: "삭제 조건을 확인할 데이터베이스 연결을 선택하세요 (자기 자신 가능)",
},
to: {
title: "삭제 대상 데이터베이스",
desc: "데이터를 삭제할 데이터베이스 연결을 선택하세요 (자기 자신 가능)",
},
};
}
};
// 🆕 자기 자신 테이블 작업 시 경고 메시지
const getSameConnectionWarning = () => {
if (fromConnectionId === toConnectionId && fromConnectionId !== undefined) {
switch (actionType) {
case "update":
return "⚠️ 같은 데이터베이스에서 UPDATE 작업을 수행합니다. 조건을 신중히 설정하세요.";
case "delete":
return "🚨 같은 데이터베이스에서 DELETE 작업을 수행합니다. 데이터 손실에 주의하세요.";
}
}
return null;
};
const labels = getConnectionLabels();
const warningMessage = getSameConnectionWarning();
// 커넥션 선택 핸들러
const handleFromConnectionChange = (value: string) => {
const connectionId = parseInt(value);
onFromConnectionChange(connectionId);
};
const handleToConnectionChange = (value: string) => {
const connectionId = parseInt(value);
onToConnectionChange(connectionId);
};
// 커넥션 아이템 렌더링
const renderConnectionItem = (connection: ConnectionInfo) => (
<SelectItem key={connection.id} value={connection.id.toString()}>
<div className="flex items-center gap-2">
{connection.id === 0 ? (
<Badge variant="default" className="text-xs">
<Database className="mr-1 h-3 w-3" />
DB
</Badge>
) : (
<Badge variant="outline" className="text-xs">
<ExternalLink className="mr-1 h-3 w-3" />
{connection.db_type?.toUpperCase()}
</Badge>
)}
<span className="truncate">{connection.connection_name}</span>
</div>
</SelectItem>
);
if (loading) {
return (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="h-4 w-4 animate-pulse rounded bg-gray-300" />
<div className="h-4 w-32 animate-pulse rounded bg-gray-300" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="h-10 w-full animate-pulse rounded bg-gray-200" />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="h-4 w-4 animate-pulse rounded bg-gray-300" />
<div className="h-4 w-32 animate-pulse rounded bg-gray-300" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="h-10 w-full animate-pulse rounded bg-gray-200" />
</CardContent>
</Card>
</div>
</div>
);
}
return (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-6">
{/* FROM 커넥션 선택 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-4 w-4" />
{labels.from.title}
</CardTitle>
<CardDescription>{labels.from.desc}</CardDescription>
</CardHeader>
<CardContent>
<Select
value={fromConnectionId?.toString() || ""}
onValueChange={handleFromConnectionChange}
disabled={disabled}
>
<SelectTrigger>
<SelectValue placeholder="커넥션을 선택하세요" />
</SelectTrigger>
<SelectContent>
{Array.isArray(availableConnections) ? availableConnections.map(renderConnectionItem) : []}
</SelectContent>
</Select>
</CardContent>
</Card>
{/* TO 커넥션 선택 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-4 w-4" />
{labels.to.title}
</CardTitle>
<CardDescription>{labels.to.desc}</CardDescription>
</CardHeader>
<CardContent>
<Select
value={toConnectionId?.toString() || ""}
onValueChange={handleToConnectionChange}
disabled={disabled}
>
<SelectTrigger>
<SelectValue placeholder="커넥션을 선택하세요" />
</SelectTrigger>
<SelectContent>
{Array.isArray(availableConnections) ? availableConnections.map(renderConnectionItem) : []}
</SelectContent>
</Select>
</CardContent>
</Card>
</div>
{/* 🆕 자기 자신 테이블 작업 시 경고 */}
{warningMessage && (
<Alert variant={actionType === "delete" ? "destructive" : "default"}>
<AlertCircle className="h-4 w-4" />
<AlertTitle></AlertTitle>
<AlertDescription>{warningMessage}</AlertDescription>
</Alert>
)}
{/* 연결 상태 표시 */}
{fromConnectionId !== undefined && toConnectionId !== undefined && (
<div className="text-muted-foreground text-sm">
<div className="flex items-center gap-2">
<span> :</span>
<Badge variant="secondary">
{availableConnections.find((c) => c.id === fromConnectionId)?.connection_name || "Unknown"}
</Badge>
<span></span>
<Badge variant="secondary">
{availableConnections.find((c) => c.id === toConnectionId)?.connection_name || "Unknown"}
</Badge>
</div>
</div>
)}
</div>
);
};