/** * 테이블 선택 패널 * 선택된 커넥션에서 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 } from "@/components/ui/alert"; import { Table, AlertCircle, Info, Search } from "lucide-react"; import { Input } from "@/components/ui/input"; import { getTablesFromConnection, MultiConnectionTableInfo } from "@/lib/api/multiConnection"; import { useToast } from "@/hooks/use-toast"; export interface TableSelectionPanelProps { fromConnectionId?: number; toConnectionId?: number; selectedFromTable?: string; selectedToTable?: string; onFromTableChange: (tableName: string) => void; onToTableChange: (tableName: string) => void; actionType: "insert" | "update" | "delete"; // 🆕 자기 자신 테이블 작업 지원 allowSameTable?: boolean; showSameTableWarning?: boolean; disabled?: boolean; } export const TableSelectionPanel: React.FC = ({ fromConnectionId, toConnectionId, selectedFromTable, selectedToTable, onFromTableChange, onToTableChange, actionType, allowSameTable = true, showSameTableWarning = true, disabled = false, }) => { const { toast } = useToast(); const [fromTables, setFromTables] = useState([]); const [toTables, setToTables] = useState([]); const [fromLoading, setFromLoading] = useState(false); const [toLoading, setToLoading] = useState(false); const [fromSearchTerm, setFromSearchTerm] = useState(""); const [toSearchTerm, setToSearchTerm] = useState(""); // FROM 커넥션 변경 시 테이블 목록 로딩 useEffect(() => { if (fromConnectionId !== undefined && fromConnectionId !== null) { console.log(`🔍 FROM 테이블 로딩 시작: connectionId=${fromConnectionId}`); loadTablesFromConnection(fromConnectionId, setFromTables, setFromLoading, "FROM"); } else { setFromTables([]); } }, [fromConnectionId]); // TO 커넥션 변경 시 테이블 목록 로딩 useEffect(() => { if (toConnectionId !== undefined && toConnectionId !== null) { console.log(`🔍 TO 테이블 로딩 시작: connectionId=${toConnectionId}`); loadTablesFromConnection(toConnectionId, setToTables, setToLoading, "TO"); } else { setToTables([]); } }, [toConnectionId]); // 테이블 목록 로딩 함수 const loadTablesFromConnection = async ( connectionId: number, setTables: React.Dispatch>, setLoading: React.Dispatch>, type: "FROM" | "TO", ) => { try { setLoading(true); console.log(`🔍 ${type} 테이블 API 호출 시작: connectionId=${connectionId}`); const tables = await getTablesFromConnection(connectionId); console.log(`✅ ${type} 테이블 로딩 성공: ${tables.length}개`); setTables(tables); } catch (error) { console.error(`❌ ${type} 테이블 목록 로드 실패:`, error); toast({ title: "테이블 로드 실패", description: `${type} 테이블 목록을 불러오는데 실패했습니다. ${error instanceof Error ? error.message : String(error)}`, variant: "destructive", }); setTables([]); // 에러 시 빈 배열로 설정 } finally { setLoading(false); } }; // 테이블 필터링 const getFilteredTables = (tables: MultiConnectionTableInfo[], searchTerm: string) => { if (!searchTerm.trim()) return tables; const term = searchTerm.toLowerCase(); return tables.filter( (table) => table.tableName.toLowerCase().includes(term) || table.displayName?.toLowerCase().includes(term), ); }; // 액션 타입별 라벨 정의 const getTableLabels = () => { 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 getSameTableWarning = () => { if ( showSameTableWarning && fromConnectionId === toConnectionId && selectedFromTable === selectedToTable && selectedFromTable ) { switch (actionType) { case "update": return "⚠️ 같은 테이블에서 UPDATE 작업을 수행합니다. 무한 루프에 주의하세요."; case "delete": return "🚨 같은 테이블에서 DELETE 작업을 수행합니다. 매우 위험할 수 있습니다."; } } return null; }; const labels = getTableLabels(); const warningMessage = getSameTableWarning(); const filteredFromTables = getFilteredTables(fromTables, fromSearchTerm); const filteredToTables = getFilteredTables(toTables, toSearchTerm); // 테이블 아이템 렌더링 const renderTableItem = (table: MultiConnectionTableInfo) => (
{table.displayName && table.displayName !== table.tableName ? table.displayName : table.tableName} {table.columnCount}개 컬럼 ); // 로딩 상태 렌더링 const renderLoadingState = (title: string) => (
{title}
); return (
{/* FROM 테이블 선택 */} {fromLoading ? ( renderLoadingState(labels.from.title) ) : (
{labels.from.title} {labels.from.desc} {/* 검색 필드 */}
setFromSearchTerm(e.target.value)} className="pl-9" disabled={disabled || fromConnectionId === undefined || fromConnectionId === null} />
{/* 테이블 선택 */} {/* 테이블 정보 */} {selectedFromTable && fromTables.find((t) => t.tableName === selectedFromTable) && (
{fromTables.find((t) => t.tableName === selectedFromTable)?.columnCount}개 컬럼, 커넥션:{" "} {fromTables.find((t) => t.tableName === selectedFromTable)?.connectionName}
)}
)} {/* TO 테이블 선택 */} {toLoading ? ( renderLoadingState(labels.to.title) ) : (
{labels.to.title} {labels.to.desc} {/* 검색 필드 */}
setToSearchTerm(e.target.value)} className="pl-9" disabled={disabled || toConnectionId === undefined || toConnectionId === null} />
{/* 테이블 선택 */} {/* 테이블 정보 */} {selectedToTable && toTables.find((t) => t.tableName === selectedToTable) && (
{toTables.find((t) => t.tableName === selectedToTable)?.columnCount}개 컬럼, 커넥션:{" "} {toTables.find((t) => t.tableName === selectedToTable)?.connectionName}
)}
)} {/* 🆕 자기 자신 테이블 작업 시 경고 */} {warningMessage && ( {warningMessage} )} {/* 선택 상태 표시 */} {selectedFromTable && selectedToTable && (
테이블 매핑: {(() => { const fromTable = fromTables.find((t) => t.tableName === selectedFromTable); return fromTable?.displayName && fromTable.displayName !== fromTable.tableName ? fromTable.displayName : selectedFromTable; })()} {(() => { const toTable = toTables.find((t) => t.tableName === selectedToTable); return toTable?.displayName && toTable.displayName !== toTable.tableName ? toTable.displayName : selectedToTable; })()} {selectedFromTable === selectedToTable && ( 자기 자신 )}
)} ); };