From 6d54a4c9ea40066f3b68a12407d23cfd3b44f55b Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 24 Oct 2025 18:05:11 +0900 Subject: [PATCH] =?UTF-8?q?=EB=85=B8=EB=93=9C=EB=B3=84=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EA=B2=80=EC=83=89=EC=84=A0=ED=83=9D=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../properties/DeleteActionProperties.tsx | 263 ++- .../properties/InsertActionProperties.tsx | 312 +++- .../properties/ReferenceLookupProperties.tsx | 107 +- .../properties/UpdateActionProperties.tsx | 1594 ++++++++++------- .../properties/UpsertActionProperties.tsx | 316 +++- 5 files changed, 1723 insertions(+), 869 deletions(-) diff --git a/frontend/components/dataflow/node-editor/panels/properties/DeleteActionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/DeleteActionProperties.tsx index 3c5995d7..16eca3cd 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/DeleteActionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/DeleteActionProperties.tsx @@ -67,6 +67,19 @@ export function DeleteActionProperties({ nodeId, data }: DeleteActionPropertiesP const [tablesOpen, setTablesOpen] = useState(false); const [selectedTableLabel, setSelectedTableLabel] = useState(data.targetTable); + // 내부 DB 컬럼 관련 상태 + interface ColumnInfo { + columnName: string; + columnLabel?: string; + dataType: string; + isNullable: boolean; + } + const [targetColumns, setTargetColumns] = useState([]); + const [columnsLoading, setColumnsLoading] = useState(false); + + // Combobox 열림 상태 관리 + const [fieldOpenState, setFieldOpenState] = useState([]); + useEffect(() => { setDisplayName(data.displayName || `${data.targetTable} 삭제`); setTargetTable(data.targetTable); @@ -101,6 +114,18 @@ export function DeleteActionProperties({ nodeId, data }: DeleteActionPropertiesP } }, [targetType, selectedExternalConnectionId, externalTargetTable]); + // 🔥 내부 DB 컬럼 로딩 + useEffect(() => { + if (targetType === "internal" && targetTable) { + loadColumns(targetTable); + } + }, [targetType, targetTable]); + + // whereConditions 변경 시 fieldOpenState 초기화 + useEffect(() => { + setFieldOpenState(new Array(whereConditions.length).fill(false)); + }, [whereConditions.length]); + const loadExternalConnections = async () => { try { setExternalConnectionsLoading(true); @@ -171,6 +196,28 @@ export function DeleteActionProperties({ nodeId, data }: DeleteActionPropertiesP } }; + // 🔥 내부 DB 컬럼 로딩 + const loadColumns = async (tableName: string) => { + try { + setColumnsLoading(true); + const response = await tableTypeApi.getColumns(tableName); + if (response && Array.isArray(response)) { + const columnInfos: ColumnInfo[] = response.map((col: any) => ({ + columnName: col.columnName || col.column_name, + columnLabel: col.columnLabel || col.column_label, + dataType: col.dataType || col.data_type || "text", + isNullable: col.isNullable !== undefined ? col.isNullable : true, + })); + setTargetColumns(columnInfos); + } + } catch (error) { + console.error("컬럼 로딩 실패:", error); + setTargetColumns([]); + } finally { + setColumnsLoading(false); + } + }; + const handleTableSelect = (tableName: string) => { const selectedTable = tables.find((t: any) => t.tableName === tableName); const label = (selectedTable as any)?.tableLabel || selectedTable?.displayName || tableName; @@ -186,18 +233,22 @@ export function DeleteActionProperties({ nodeId, data }: DeleteActionPropertiesP }; const handleAddCondition = () => { - setWhereConditions([ + const newConditions = [ ...whereConditions, { field: "", operator: "EQUALS", value: "", }, - ]); + ]; + setWhereConditions(newConditions); + setFieldOpenState(new Array(newConditions.length).fill(false)); }; const handleRemoveCondition = (index: number) => { - setWhereConditions(whereConditions.filter((_, i) => i !== index)); + const newConditions = whereConditions.filter((_, i) => i !== index); + setWhereConditions(newConditions); + setFieldOpenState(new Array(newConditions.length).fill(false)); }; const handleConditionChange = (index: number, field: string, value: any) => { @@ -639,64 +690,169 @@ export function DeleteActionProperties({ nodeId, data }: DeleteActionPropertiesP + {/* 컬럼 로딩 상태 */} + {targetType === "internal" && targetTable && columnsLoading && ( +
+ 컬럼 정보를 불러오는 중... +
+ )} + + {/* 테이블 미선택 안내 */} + {targetType === "internal" && !targetTable && ( +
+ 먼저 타겟 테이블을 선택하세요 +
+ )} + {whereConditions.length > 0 ? (
- {whereConditions.map((condition, index) => ( -
-
- 조건 #{index + 1} - -
+ {whereConditions.map((condition, index) => { + // 현재 타입에 따라 사용 가능한 컬럼 리스트 결정 + const availableColumns = + targetType === "internal" + ? targetColumns + : targetType === "external" + ? externalColumns.map((col) => ({ + columnName: col.column_name, + columnLabel: col.column_name, + dataType: col.data_type, + isNullable: true, + })) + : []; -
-
- - 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" - /> +
+ {/* 필드 - Combobox */} +
+ + {availableColumns.length > 0 ? ( + { + const newState = [...fieldOpenState]; + newState[index] = open; + setFieldOpenState(newState); + }} + > + + + + + + + + 필드를 찾을 수 없습니다. + + {availableColumns.map((col) => ( + { + handleConditionChange(index, "field", currentValue); + const newState = [...fieldOpenState]; + newState[index] = false; + setFieldOpenState(newState); + }} + className="text-xs sm:text-sm" + > + +
+ + {col.columnLabel || col.columnName} + + {col.dataType} +
+
+ ))} +
+
+
+
+
+ ) : ( + 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" + /> +
-
- ))} + ); + })}
) : (
@@ -705,7 +861,6 @@ export function DeleteActionProperties({ nodeId, data }: DeleteActionPropertiesP )}
-
🚨 WHERE 조건 없이 삭제하면 테이블의 모든 데이터가 영구 삭제됩니다! diff --git a/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx index 44a3b7e2..5f3b3220 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx @@ -13,6 +13,7 @@ import { Checkbox } from "@/components/ui/checkbox"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; import { useFlowEditorStore } from "@/lib/stores/flowEditorStore"; import { tableTypeApi } from "@/lib/api/screen"; @@ -37,6 +38,7 @@ interface ColumnInfo { columnLabel?: string; dataType: string; isNullable: boolean; + columnDefault?: string | null; } export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesProps) { @@ -63,6 +65,10 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP // REST API 소스 노드 연결 여부 const [hasRestAPISource, setHasRestAPISource] = useState(false); + // Combobox 열림 상태 관리 (필드 매핑) + const [mappingSourceFieldsOpenState, setMappingSourceFieldsOpenState] = useState([]); + const [mappingTargetFieldsOpenState, setMappingTargetFieldsOpenState] = useState([]); + // 🔥 외부 DB 관련 상태 const [externalConnections, setExternalConnections] = useState([]); const [externalConnectionsLoading, setExternalConnectionsLoading] = useState(false); @@ -118,6 +124,12 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP } }, [targetType, selectedExternalConnectionId]); + // fieldMappings 변경 시 Combobox 열림 상태 초기화 + useEffect(() => { + setMappingSourceFieldsOpenState(new Array(fieldMappings.length).fill(false)); + setMappingTargetFieldsOpenState(new Array(fieldMappings.length).fill(false)); + }, [fieldMappings.length]); + // 🔥 외부 테이블 변경 시 컬럼 로드 useEffect(() => { if (targetType === "external" && selectedExternalConnectionId && externalTargetTable) { @@ -340,12 +352,27 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP const columns = await tableTypeApi.getColumns(tableName); - const columnInfo: ColumnInfo[] = columns.map((col: any) => ({ - columnName: col.column_name || col.columnName, - columnLabel: col.label_ko || col.columnLabel, - dataType: col.data_type || col.dataType || "unknown", - isNullable: col.is_nullable === "YES" || col.isNullable === true, - })); + const columnInfo: ColumnInfo[] = columns.map((col: any) => { + // is_nullable 파싱: "YES", true, 1 등을 true로, "NO", false, 0 등을 false로 변환 + const isNullableValue = col.is_nullable ?? col.isNullable; + let isNullable = true; // 기본값: nullable + + if (typeof isNullableValue === "boolean") { + isNullable = isNullableValue; + } else if (typeof isNullableValue === "string") { + isNullable = isNullableValue.toUpperCase() === "YES" || isNullableValue.toUpperCase() === "TRUE"; + } else if (typeof isNullableValue === "number") { + isNullable = isNullableValue !== 0; + } + + return { + columnName: col.column_name || col.columnName, + columnLabel: col.label_ko || col.columnLabel, + dataType: col.data_type || col.dataType || "unknown", + isNullable, + columnDefault: col.column_default ?? col.columnDefault ?? null, + }; + }); setTargetColumns(columnInfo); console.log(`✅ 컬럼 ${columnInfo.length}개 로딩 완료`); @@ -449,12 +476,20 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP ]; setFieldMappings(newMappings); updateNode(nodeId, { fieldMappings: newMappings }); + + // Combobox 열림 상태 배열 초기화 + setMappingSourceFieldsOpenState(new Array(newMappings.length).fill(false)); + setMappingTargetFieldsOpenState(new Array(newMappings.length).fill(false)); }; const handleRemoveMapping = (index: number) => { const newMappings = fieldMappings.filter((_, i) => i !== index); setFieldMappings(newMappings); updateNode(nodeId, { fieldMappings: newMappings }); + + // Combobox 열림 상태 배열도 업데이트 + setMappingSourceFieldsOpenState(new Array(newMappings.length).fill(false)); + setMappingTargetFieldsOpenState(new Array(newMappings.length).fill(false)); }; const handleMappingChange = (index: number, field: string, value: any) => { @@ -1077,35 +1112,87 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP className="mt-1 h-8 text-xs" /> ) : ( - // 일반 소스인 경우: 드롭다운 선택 - + + + + + + + + + 필드를 찾을 수 없습니다. + + + {sourceFields.map((field) => ( + { + handleMappingChange(index, "sourceField", currentValue || null); + const newState = [...mappingSourceFieldsOpenState]; + newState[index] = false; + setMappingSourceFieldsOpenState(newState); + }} + className="text-xs sm:text-sm" + > + +
+ {field.label || field.name} + {field.label && field.label !== field.name && ( + + {field.name} + + )} +
+
+ ))} +
+
+
+
+ )} {hasRestAPISource && (

API 응답 JSON의 필드명을 입력하세요

@@ -1116,43 +1203,134 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
- {/* 타겟 필드 드롭다운 (🔥 타입별 컬럼 사용) */} + {/* 타겟 필드 Combobox (🔥 타입별 컬럼 사용) */}
- + {/* 🔥 외부 DB 컬럼 */} + {targetType === "external" && + externalColumns.map((col) => ( + { + handleMappingChange(index, "targetField", currentValue); + const newState = [...mappingTargetFieldsOpenState]; + newState[index] = false; + setMappingTargetFieldsOpenState(newState); + }} + className="text-xs sm:text-sm" + > + +
+ {col.column_name} + {col.data_type} +
+
+ ))} + + + + +
{/* 정적 값 */} diff --git a/frontend/components/dataflow/node-editor/panels/properties/ReferenceLookupProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/ReferenceLookupProperties.tsx index 2f553608..b2bb51e0 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/ReferenceLookupProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/ReferenceLookupProperties.tsx @@ -5,14 +5,13 @@ */ import { useEffect, useState, useCallback } from "react"; -import { Plus, Trash2, Search } from "lucide-react"; +import { Plus, Trash2, Search, Check, ChevronsUpDown } from "lucide-react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; -import { Check } from "lucide-react"; import { cn } from "@/lib/utils"; import { useFlowEditorStore } from "@/lib/stores/flowEditorStore"; import type { ReferenceLookupNodeData } from "@/types/node-editor"; @@ -62,6 +61,9 @@ export function ReferenceLookupProperties({ nodeId, data }: ReferenceLookupPrope const [referenceColumns, setReferenceColumns] = useState([]); const [columnsLoading, setColumnsLoading] = useState(false); + // Combobox 열림 상태 관리 + const [whereFieldOpenState, setWhereFieldOpenState] = useState([]); + // 데이터 변경 시 로컬 상태 동기화 useEffect(() => { setDisplayName(data.displayName || "참조 조회"); @@ -72,6 +74,11 @@ export function ReferenceLookupProperties({ nodeId, data }: ReferenceLookupPrope setOutputFields(data.outputFields || []); }, [data]); + // whereConditions 변경 시 whereFieldOpenState 초기화 + useEffect(() => { + setWhereFieldOpenState(new Array(whereConditions.length).fill(false)); + }, [whereConditions.length]); + // 🔍 소스 필드 수집 (업스트림 노드에서) useEffect(() => { const incomingEdges = edges.filter((e) => e.target === nodeId); @@ -187,7 +194,7 @@ export function ReferenceLookupProperties({ nodeId, data }: ReferenceLookupPrope // WHERE 조건 추가 const handleAddWhereCondition = () => { - setWhereConditions([ + const newConditions = [ ...whereConditions, { field: "", @@ -195,11 +202,15 @@ export function ReferenceLookupProperties({ nodeId, data }: ReferenceLookupPrope value: "", valueType: "static", }, - ]); + ]; + setWhereConditions(newConditions); + setWhereFieldOpenState(new Array(newConditions.length).fill(false)); }; const handleRemoveWhereCondition = (index: number) => { - setWhereConditions(whereConditions.filter((_, i) => i !== index)); + const newConditions = whereConditions.filter((_, i) => i !== index); + setWhereConditions(newConditions); + setWhereFieldOpenState(new Array(newConditions.length).fill(false)); }; const handleWhereConditionChange = (index: number, field: string, value: any) => { @@ -455,23 +466,81 @@ export function ReferenceLookupProperties({ nodeId, data }: ReferenceLookupPrope
+ {/* 필드 - Combobox */}
- + + + + + + + + 필드를 찾을 수 없습니다. + + {referenceColumns.map((field) => ( + { + handleWhereConditionChange(index, "field", currentValue); + const newState = [...whereFieldOpenState]; + newState[index] = false; + setWhereFieldOpenState(newState); + }} + className="text-xs sm:text-sm" + > + +
+ {field.label || field.name} + {field.type && ( + {field.type} + )} +
+
+ ))} +
+
+
+
+
diff --git a/frontend/components/dataflow/node-editor/panels/properties/UpdateActionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/UpdateActionProperties.tsx index d39ed167..7d6d2e5a 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/UpdateActionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/UpdateActionProperties.tsx @@ -37,6 +37,7 @@ interface ColumnInfo { columnLabel?: string; dataType: string; isNullable: boolean; + columnDefault?: string | null; } const OPERATORS = [ @@ -79,6 +80,14 @@ export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesP // REST API 소스 노드 연결 여부 const [hasRestAPISource, setHasRestAPISource] = useState(false); + // Combobox 열림 상태 관리 (WHERE 조건) + const [sourceFieldsOpenState, setSourceFieldsOpenState] = useState([]); + const [targetFieldsOpenState, setTargetFieldsOpenState] = useState([]); + + // Combobox 열림 상태 관리 (필드 매핑) + const [mappingSourceFieldsOpenState, setMappingSourceFieldsOpenState] = useState([]); + const [mappingTargetFieldsOpenState, setMappingTargetFieldsOpenState] = useState([]); + // 🔥 외부 DB 관련 상태 const [externalConnections, setExternalConnections] = useState([]); const [externalConnectionsLoading, setExternalConnectionsLoading] = useState(false); @@ -142,6 +151,18 @@ export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesP } }, [targetType, selectedExternalConnectionId, externalTargetTable]); + // whereConditions 변경 시 Combobox 열림 상태 초기화 + useEffect(() => { + setSourceFieldsOpenState(new Array(whereConditions.length).fill(false)); + setTargetFieldsOpenState(new Array(whereConditions.length).fill(false)); + }, [whereConditions.length]); + + // fieldMappings 변경 시 Combobox 열림 상태 초기화 + useEffect(() => { + setMappingSourceFieldsOpenState(new Array(fieldMappings.length).fill(false)); + setMappingTargetFieldsOpenState(new Array(fieldMappings.length).fill(false)); + }, [fieldMappings.length]); + // 연결된 소스 노드에서 필드 가져오기 (재귀적으로 모든 상위 노드 탐색) useEffect(() => { const getAllSourceFields = ( @@ -331,12 +352,27 @@ export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesP const columns = await tableTypeApi.getColumns(tableName); - const columnInfo: ColumnInfo[] = columns.map((col: any) => ({ - columnName: col.column_name || col.columnName, - columnLabel: col.label_ko || col.columnLabel, - dataType: col.data_type || col.dataType || "unknown", - isNullable: col.is_nullable === "YES" || col.isNullable === true, - })); + const columnInfo: ColumnInfo[] = columns.map((col: any) => { + // is_nullable 파싱: "YES", true, 1 등을 true로, "NO", false, 0 등을 false로 변환 + const isNullableValue = col.is_nullable ?? col.isNullable; + let isNullable = true; // 기본값: nullable + + if (typeof isNullableValue === "boolean") { + isNullable = isNullableValue; + } else if (typeof isNullableValue === "string") { + isNullable = isNullableValue.toUpperCase() === "YES" || isNullableValue.toUpperCase() === "TRUE"; + } else if (typeof isNullableValue === "number") { + isNullable = isNullableValue !== 0; + } + + return { + columnName: col.column_name || col.columnName, + columnLabel: col.label_ko || col.columnLabel, + dataType: col.data_type || col.dataType || "unknown", + isNullable, + columnDefault: col.column_default ?? col.columnDefault ?? null, + }; + }); setTargetColumns(columnInfo); console.log(`✅ UPDATE 노드 - 컬럼 ${columnInfo.length}개 로딩 완료`); @@ -379,12 +415,20 @@ export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesP ]; setFieldMappings(newMappings); updateNode(nodeId, { fieldMappings: newMappings }); + + // Combobox 열림 상태 배열 초기화 + setMappingSourceFieldsOpenState(new Array(newMappings.length).fill(false)); + setMappingTargetFieldsOpenState(new Array(newMappings.length).fill(false)); }; const handleRemoveMapping = (index: number) => { const newMappings = fieldMappings.filter((_, i) => i !== index); setFieldMappings(newMappings); updateNode(nodeId, { fieldMappings: newMappings }); + + // Combobox 열림 상태 배열도 업데이트 + setMappingSourceFieldsOpenState(new Array(newMappings.length).fill(false)); + setMappingTargetFieldsOpenState(new Array(newMappings.length).fill(false)); }; const handleMappingChange = (index: number, field: string, value: any) => { @@ -452,12 +496,20 @@ export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesP ]; setWhereConditions(newConditions); updateNode(nodeId, { whereConditions: newConditions }); + + // Combobox 열림 상태 배열 초기화 + setSourceFieldsOpenState(new Array(newConditions.length).fill(false)); + setTargetFieldsOpenState(new Array(newConditions.length).fill(false)); }; const handleRemoveCondition = (index: number) => { const newConditions = whereConditions.filter((_, i) => i !== index); setWhereConditions(newConditions); updateNode(nodeId, { whereConditions: newConditions }); + + // Combobox 열림 상태 배열도 업데이트 + setSourceFieldsOpenState(new Array(newConditions.length).fill(false)); + setTargetFieldsOpenState(new Array(newConditions.length).fill(false)); }; const handleConditionChange = (index: number, field: string, value: any) => { @@ -506,177 +558,207 @@ export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesP return (
- {/* 기본 정보 */} -
-

기본 정보

+ {/* 기본 정보 */} +
+

기본 정보

-
-
- - setDisplayName(e.target.value)} - className="mt-1" - placeholder="노드 표시 이름" - /> +
+
+ + setDisplayName(e.target.value)} + className="mt-1" + placeholder="노드 표시 이름" + /> +
+ + {/* 🔥 타겟 타입 선택 */} +
+ +
+ {/* 내부 데이터베이스 */} + + + {/* 외부 데이터베이스 */} + + + {/* REST API */} +
+
- {/* 🔥 타겟 타입 선택 */} + {/* 내부 DB: 타겟 테이블 Combobox */} + {targetType === "internal" && (
- -
- {/* 내부 데이터베이스 */} - - - {/* 외부 데이터베이스 */} - - - {/* REST API */} - -
+ + + + + + + 테이블을 찾을 수 없습니다. + + + {tables.map((table) => ( + handleTableSelect(table.tableName)} + > + +
+ {table.label || table.displayName} + {table.tableName} +
+
+ ))} +
+
+
+
+
+ )} - {/* 내부 DB: 타겟 테이블 Combobox */} - {targetType === "internal" && ( + {/* 🔥 외부 DB 설정 (INSERT 노드와 동일 패턴) */} + {targetType === "external" && ( + <> + {/* 외부 커넥션 선택 */}
- - - - - - - - - 테이블을 찾을 수 없습니다. - - - {tables.map((table) => ( - handleTableSelect(table.tableName)} - > - -
- {table.label || table.displayName} - {table.tableName} -
-
- ))} -
-
-
-
-
+ +
- )} - {/* 🔥 외부 DB 설정 (INSERT 노드와 동일 패턴) */} - {targetType === "external" && ( - <> - {/* 외부 커넥션 선택 */} + {/* 외부 테이블 선택 */} + {selectedExternalConnectionId && (
- +
+ )} - {/* 외부 테이블 선택 */} - {selectedExternalConnectionId && ( -
- - { + setApiEndpoint(e.target.value); + updateNode(nodeId, { apiEndpoint: e.target.value }); + }} + className="h-8 text-xs" + /> +
+ + {/* HTTP 메서드 */} +
+ + +
+ + {/* 인증 타입 */} +
+ + +
+ + {/* 인증 설정 */} + {apiAuthType !== "none" && ( +
+ + + {apiAuthType === "bearer" && ( + { + const newConfig = { token: e.target.value }; + setApiAuthConfig(newConfig); + updateNode(nodeId, { apiAuthConfig: newConfig }); }} - > - - - - - {externalTablesLoading ? ( -
로딩 중...
- ) : externalTables.length === 0 ? ( -
테이블이 없습니다
- ) : ( - externalTables.map((table) => ( - -
- {table.table_name} - {table.schema && ({table.schema})} -
-
- )) - )} -
- -
- )} + className="h-8 text-xs" + /> + )} - {/* 외부 컬럼 표시 */} - {externalTargetTable && externalColumns.length > 0 && ( -
- -
- {externalColumns.map((col) => ( -
- {col.column_name} - {col.data_type} -
- ))} -
-
- )} - - )} - - {/* 🔥 REST API 설정 */} - {targetType === "api" && ( -
- {/* API 엔드포인트 */} -
- - { - setApiEndpoint(e.target.value); - updateNode(nodeId, { apiEndpoint: e.target.value }); - }} - className="h-8 text-xs" - /> -
- - {/* HTTP 메서드 */} -
- - -
- - {/* 인증 타입 */} -
- - -
- - {/* 인증 설정 */} - {apiAuthType !== "none" && ( -
- - - {apiAuthType === "bearer" && ( + {apiAuthType === "basic" && ( +
{ - const newConfig = { token: e.target.value }; + 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 === "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" - /> - -
- ))} - -
+ {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" + /> +
+ )}
+ )} - {/* 요청 바디 설정 */} -
- -