"use client"; /** * DELETE 액션 노드 속성 편집 */ import { useEffect, useState } from "react"; import { Plus, Trash2, AlertTriangle, Database, Globe, Link2, Check, ChevronsUpDown } from "lucide-react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; import { useFlowEditorStore } from "@/lib/stores/flowEditorStore"; import { tableTypeApi } from "@/lib/api/screen"; import { getTestedExternalConnections, getExternalTables, getExternalColumns } from "@/lib/api/nodeExternalConnections"; import type { DeleteActionNodeData } from "@/types/node-editor"; import type { ExternalConnection, ExternalTable, ExternalColumn } from "@/lib/api/nodeExternalConnections"; interface DeleteActionPropertiesProps { nodeId: string; data: DeleteActionNodeData; } const OPERATORS = [ { value: "EQUALS", label: "=" }, { value: "NOT_EQUALS", label: "≠" }, { value: "GREATER_THAN", label: ">" }, { value: "LESS_THAN", label: "<" }, { value: "IN", label: "IN" }, { value: "NOT_IN", label: "NOT IN" }, ] as const; export function DeleteActionProperties({ nodeId, data }: DeleteActionPropertiesProps) { const { updateNode, getExternalConnectionsCache } = useFlowEditorStore(); // 🔥 타겟 타입 상태 const [targetType, setTargetType] = useState<"internal" | "external" | "api">(data.targetType || "internal"); const [displayName, setDisplayName] = useState(data.displayName || `${data.targetTable} 삭제`); const [targetTable, setTargetTable] = useState(data.targetTable); const [whereConditions, setWhereConditions] = useState(data.whereConditions || []); // 🔥 외부 DB 관련 상태 const [externalConnections, setExternalConnections] = useState([]); const [externalConnectionsLoading, setExternalConnectionsLoading] = useState(false); const [selectedExternalConnectionId, setSelectedExternalConnectionId] = useState( data.externalConnectionId, ); const [externalTables, setExternalTables] = useState([]); const [externalTablesLoading, setExternalTablesLoading] = useState(false); const [externalTargetTable, setExternalTargetTable] = useState(data.externalTargetTable); const [externalColumns, setExternalColumns] = useState([]); const [externalColumnsLoading, setExternalColumnsLoading] = useState(false); // 🔥 REST API 관련 상태 (DELETE는 요청 바디 없음) const [apiEndpoint, setApiEndpoint] = useState(data.apiEndpoint || ""); const [apiAuthType, setApiAuthType] = useState<"none" | "basic" | "bearer" | "apikey">(data.apiAuthType || "none"); const [apiAuthConfig, setApiAuthConfig] = useState(data.apiAuthConfig || {}); const [apiHeaders, setApiHeaders] = useState>(data.apiHeaders || {}); // 🔥 내부 DB 테이블 관련 상태 const [tables, setTables] = useState([]); const [tablesLoading, setTablesLoading] = useState(false); const [tablesOpen, setTablesOpen] = useState(false); const [selectedTableLabel, setSelectedTableLabel] = useState(data.targetTable); useEffect(() => { setDisplayName(data.displayName || `${data.targetTable} 삭제`); setTargetTable(data.targetTable); setWhereConditions(data.whereConditions || []); }, [data]); // 🔥 내부 DB 테이블 목록 로딩 useEffect(() => { if (targetType === "internal") { loadTables(); } }, [targetType]); // 🔥 외부 커넥션 로딩 useEffect(() => { if (targetType === "external") { loadExternalConnections(); } }, [targetType]); // 🔥 외부 테이블 로딩 useEffect(() => { if (targetType === "external" && selectedExternalConnectionId) { loadExternalTables(selectedExternalConnectionId); } }, [targetType, selectedExternalConnectionId]); // 🔥 외부 컬럼 로딩 useEffect(() => { if (targetType === "external" && selectedExternalConnectionId && externalTargetTable) { loadExternalColumns(selectedExternalConnectionId, externalTargetTable); } }, [targetType, selectedExternalConnectionId, externalTargetTable]); const loadExternalConnections = async () => { try { setExternalConnectionsLoading(true); const cached = getExternalConnectionsCache(); if (cached) { setExternalConnections(cached); return; } const data = await getTestedExternalConnections(); setExternalConnections(data); } catch (error) { console.error("외부 커넥션 로딩 실패:", error); } finally { setExternalConnectionsLoading(false); } }; const loadExternalTables = async (connectionId: number) => { try { setExternalTablesLoading(true); const data = await getExternalTables(connectionId); setExternalTables(data); } catch (error) { console.error("외부 테이블 로딩 실패:", error); } finally { setExternalTablesLoading(false); } }; const loadExternalColumns = async (connectionId: number, tableName: string) => { try { setExternalColumnsLoading(true); const data = await getExternalColumns(connectionId, tableName); setExternalColumns(data); } catch (error) { console.error("외부 컬럼 로딩 실패:", error); } finally { setExternalColumnsLoading(false); } }; const handleTargetTypeChange = (newType: "internal" | "external" | "api") => { setTargetType(newType); updateNode(nodeId, { targetType: newType, targetTable: newType === "internal" ? targetTable : undefined, externalConnectionId: newType === "external" ? selectedExternalConnectionId : undefined, externalTargetTable: newType === "external" ? externalTargetTable : undefined, apiEndpoint: newType === "api" ? apiEndpoint : undefined, apiAuthType: newType === "api" ? apiAuthType : undefined, apiAuthConfig: newType === "api" ? apiAuthConfig : undefined, apiHeaders: newType === "api" ? apiHeaders : undefined, }); }; // 🔥 테이블 목록 로딩 const loadTables = async () => { try { setTablesLoading(true); const tableList = await tableTypeApi.getTables(); setTables(tableList); } catch (error) { console.error("테이블 목록 로딩 실패:", error); } finally { setTablesLoading(false); } }; const handleTableSelect = (tableName: string) => { const selectedTable = tables.find((t: any) => t.tableName === tableName); const label = (selectedTable as any)?.tableLabel || selectedTable?.displayName || tableName; setTargetTable(tableName); setSelectedTableLabel(label); setTablesOpen(false); updateNode(nodeId, { targetTable: tableName, displayName: label, }); }; const handleAddCondition = () => { setWhereConditions([ ...whereConditions, { field: "", operator: "EQUALS", value: "", }, ]); }; const handleRemoveCondition = (index: number) => { setWhereConditions(whereConditions.filter((_, i) => i !== index)); }; const handleConditionChange = (index: number, field: string, value: any) => { const newConditions = [...whereConditions]; newConditions[index] = { ...newConditions[index], [field]: value }; setWhereConditions(newConditions); }; const handleSave = () => { updateNode(nodeId, { displayName, targetTable, whereConditions, }); }; return (
{/* 경고 */}

위험한 작업입니다!

DELETE 작업은 되돌릴 수 없습니다. WHERE 조건을 반드시 설정하세요.

{/* 기본 정보 */}

기본 정보

setDisplayName(e.target.value)} className="mt-1" />
{/* 🔥 타겟 타입 선택 */}
{/* 내부 DB: 타겟 테이블 Combobox */} {targetType === "internal" && (
테이블을 찾을 수 없습니다. {tables.map((table: any) => ( handleTableSelect(table.tableName)} >
{table.tableLabel || table.displayName} {table.tableName}
))}
)} {/* 🔥 외부 DB 설정 */} {targetType === "external" && ( <>
{selectedExternalConnectionId && (
)} {externalTargetTable && externalColumns.length > 0 && (
{externalColumns.map((col) => (
{col.column_name} {col.data_type}
))}
)} )} {/* 🔥 REST API 설정 (DELETE는 간단함) */} {targetType === "api" && (
{ setApiEndpoint(e.target.value); updateNode(nodeId, { apiEndpoint: e.target.value }); }} className="h-8 text-xs" />
{apiAuthType !== "none" && (
{apiAuthType === "bearer" && ( { const newConfig = { token: e.target.value }; setApiAuthConfig(newConfig); updateNode(nodeId, { apiAuthConfig: newConfig }); }} className="h-8 text-xs" /> )} {apiAuthType === "basic" && (
{ const newConfig = { ...(apiAuthConfig as any), username: e.target.value }; setApiAuthConfig(newConfig); updateNode(nodeId, { apiAuthConfig: newConfig }); }} className="h-8 text-xs" /> { const newConfig = { ...(apiAuthConfig as any), password: e.target.value }; setApiAuthConfig(newConfig); updateNode(nodeId, { apiAuthConfig: newConfig }); }} className="h-8 text-xs" />
)} {apiAuthType === "apikey" && (
{ const newConfig = { ...(apiAuthConfig as any), headerName: e.target.value }; setApiAuthConfig(newConfig); updateNode(nodeId, { apiAuthConfig: newConfig }); }} className="h-8 text-xs" /> { const newConfig = { ...(apiAuthConfig as any), apiKey: e.target.value }; setApiAuthConfig(newConfig); updateNode(nodeId, { apiAuthConfig: newConfig }); }} className="h-8 text-xs" />
)}
)}
{Object.entries(apiHeaders).map(([key, value], index) => (
{ const newHeaders = { ...apiHeaders }; delete newHeaders[key]; newHeaders[e.target.value] = value; setApiHeaders(newHeaders); updateNode(nodeId, { apiHeaders: newHeaders }); }} className="h-7 flex-1 text-xs" /> { const newHeaders = { ...apiHeaders, [key]: e.target.value }; setApiHeaders(newHeaders); updateNode(nodeId, { apiHeaders: newHeaders }); }} className="h-7 flex-1 text-xs" />
))}
)}
{/* WHERE 조건 */}

WHERE 조건 (필수)

{whereConditions.length > 0 ? (
{whereConditions.map((condition, index) => (
조건 #{index + 1}
handleConditionChange(index, "field", e.target.value)} placeholder="조건 필드명" className="mt-1 h-8 text-xs" />
handleConditionChange(index, "value", e.target.value)} placeholder="비교 값" className="mt-1 h-8 text-xs" />
))}
) : (
⚠️ WHERE 조건이 없습니다! 모든 데이터가 삭제됩니다!
)}
🚨 WHERE 조건 없이 삭제하면 테이블의 모든 데이터가 영구 삭제됩니다!
💡 실행 전 WHERE 조건을 꼭 확인하세요.
); }