From 7acea0b2725cc479ea01edaf2f3f65efa75dba21 Mon Sep 17 00:00:00 2001 From: hyeonsu Date: Tue, 16 Sep 2025 15:43:18 +0900 Subject: [PATCH] =?UTF-8?q?ConnectionSetupModal=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dataflow/ConnectionSetupModal.tsx | 1958 ++--------------- .../components/dataflow/DataFlowDesigner.tsx | 147 +- .../dataflow/condition/ConditionRenderer.tsx | 215 ++ .../condition/ConditionalSettings.tsx | 67 + .../connection/ActionConditionRenderer.tsx | 206 ++ .../connection/ActionConditionsSection.tsx | 132 ++ .../connection/ActionFieldMappings.tsx | 214 ++ .../dataflow/connection/ActionSplitConfig.tsx | 131 ++ .../connection/ConnectionTypeSelector.tsx | 59 + .../dataflow/connection/DataSaveSettings.tsx | 158 ++ .../connection/ExternalCallSettings.tsx | 115 + .../dataflow/connection/SimpleKeySettings.tsx | 188 ++ frontend/hooks/useConditionManager.ts | 182 ++ frontend/types/connectionTypes.ts | 83 + frontend/utils/connectionUtils.ts | 102 + 15 files changed, 2146 insertions(+), 1811 deletions(-) create mode 100644 frontend/components/dataflow/condition/ConditionRenderer.tsx create mode 100644 frontend/components/dataflow/condition/ConditionalSettings.tsx create mode 100644 frontend/components/dataflow/connection/ActionConditionRenderer.tsx create mode 100644 frontend/components/dataflow/connection/ActionConditionsSection.tsx create mode 100644 frontend/components/dataflow/connection/ActionFieldMappings.tsx create mode 100644 frontend/components/dataflow/connection/ActionSplitConfig.tsx create mode 100644 frontend/components/dataflow/connection/ConnectionTypeSelector.tsx create mode 100644 frontend/components/dataflow/connection/DataSaveSettings.tsx create mode 100644 frontend/components/dataflow/connection/ExternalCallSettings.tsx create mode 100644 frontend/components/dataflow/connection/SimpleKeySettings.tsx create mode 100644 frontend/hooks/useConditionManager.ts create mode 100644 frontend/types/connectionTypes.ts create mode 100644 frontend/utils/connectionUtils.ts diff --git a/frontend/components/dataflow/ConnectionSetupModal.tsx b/frontend/components/dataflow/ConnectionSetupModal.tsx index 9b0bf788..16a2676f 100644 --- a/frontend/components/dataflow/ConnectionSetupModal.tsx +++ b/frontend/components/dataflow/ConnectionSetupModal.tsx @@ -1,98 +1,28 @@ "use client"; -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Badge } from "@/components/ui/badge"; -import { Textarea } from "@/components/ui/textarea"; -import { Link, Key, Save, Globe, Plus, Zap, Trash2 } from "lucide-react"; +import { Link } from "lucide-react"; import { DataFlowAPI, TableRelationship, TableInfo, ColumnInfo, ConditionNode } from "@/lib/api/dataflow"; +import { + ConnectionConfig, + SimpleKeySettings, + DataSaveSettings, + ExternalCallSettings, + ConnectionSetupModalProps, +} from "@/types/connectionTypes"; +import { isConditionalConnection } from "@/utils/connectionUtils"; +import { useConditionManager } from "@/hooks/useConditionManager"; +import { ConditionalSettings } from "./condition/ConditionalSettings"; +import { ConnectionTypeSelector } from "./connection/ConnectionTypeSelector"; +import { SimpleKeySettings as SimpleKeySettingsComponent } from "./connection/SimpleKeySettings"; +import { DataSaveSettings as DataSaveSettingsComponent } from "./connection/DataSaveSettings"; +import { ExternalCallSettings as ExternalCallSettingsComponent } from "./connection/ExternalCallSettings"; import toast from "react-hot-toast"; -// 연결 정보 타입 -interface ConnectionInfo { - fromNode: { - id: string; - tableName: string; - displayName: string; - }; - toNode: { - id: string; - tableName: string; - displayName: string; - }; - fromColumn?: string; - toColumn?: string; - selectedColumnsData?: { - [tableName: string]: { - displayName: string; - columns: string[]; - }; - }; - existingRelationship?: { - relationshipName: string; - connectionType: string; - settings?: Record; - }; -} - -// 연결 설정 타입 -interface ConnectionConfig { - relationshipName: string; - connectionType: "simple-key" | "data-save" | "external-call"; - fromColumnName: string; - toColumnName: string; - settings?: Record; -} - -// 단순 키값 연결 설정 -interface SimpleKeySettings { - notes: string; -} - -// 데이터 저장 설정 -interface DataSaveSettings { - actions: Array<{ - id: string; - name: string; - actionType: "insert" | "update" | "delete" | "upsert"; - conditions?: ConditionNode[]; - fieldMappings: Array<{ - sourceTable?: string; - sourceField: string; - targetTable?: string; - targetField: string; - defaultValue?: string; - transformFunction?: string; - }>; - splitConfig?: { - sourceField: string; // 분할할 소스 필드 - delimiter: string; // 구분자 (예: ",") - targetField: string; // 분할된 값이 들어갈 필드 - }; - }>; -} - -// 외부 호출 설정 -interface ExternalCallSettings { - callType: "rest-api" | "email" | "webhook" | "ftp" | "queue"; - apiUrl?: string; - httpMethod?: "GET" | "POST" | "PUT" | "DELETE"; - headers?: string; - bodyTemplate?: string; -} - -interface ConnectionSetupModalProps { - isOpen: boolean; - connection: ConnectionInfo | null; - companyCode: string; - onConfirm: (relationship: TableRelationship) => void; - onCancel: () => void; -} - export const ConnectionSetupModal: React.FC = ({ isOpen, connection, @@ -125,7 +55,7 @@ export const ConnectionSetupModal: React.FC = ({ bodyTemplate: "{}", }); - // 테이블 및 컬럼 선택을 위한 새로운 상태들 + // 테이블 및 컬럼 선택을 위한 상태들 const [availableTables, setAvailableTables] = useState([]); const [selectedFromTable, setSelectedFromTable] = useState(""); const [selectedToTable, setSelectedToTable] = useState(""); @@ -133,11 +63,80 @@ export const ConnectionSetupModal: React.FC = ({ const [toTableColumns, setToTableColumns] = useState([]); const [selectedFromColumns, setSelectedFromColumns] = useState([]); const [selectedToColumns, setSelectedToColumns] = useState([]); - // 필요시 로드하는 테이블 컬럼 캐시 const [tableColumnsCache, setTableColumnsCache] = useState<{ [tableName: string]: ColumnInfo[] }>({}); - // 조건부 연결을 위한 새로운 상태들 - const [conditions, setConditions] = useState([]); + // 조건 관리 훅 사용 + const { + conditions, + setConditions, + addCondition, + addGroupStart, + addGroupEnd, + updateCondition, + removeCondition, + getCurrentGroupLevel, + } = useConditionManager(); + + // 기존 설정 로드 함수 + const loadExistingSettings = useCallback( + (settings: Record, connectionType: string) => { + if (connectionType === "simple-key" && settings.notes) { + setSimpleKeySettings({ + notes: settings.notes as string, + }); + } else if (connectionType === "data-save" && settings.actions) { + // data-save 설정 로드 - 안전하게 처리 + const actionsData = Array.isArray(settings.actions) ? settings.actions : []; + setDataSaveSettings({ + actions: actionsData.map((action: Record) => ({ + id: (action.id as string) || `action-${Date.now()}`, + name: (action.name as string) || "새 액션", + actionType: (action.actionType as "insert" | "update" | "delete" | "upsert") || "insert", + conditions: Array.isArray(action.conditions) + ? (action.conditions as ConditionNode[]).map((condition) => ({ + ...condition, + operator: condition.operator || "=", // 기본값 보장 + })) + : [], + fieldMappings: Array.isArray(action.fieldMappings) + ? action.fieldMappings.map((mapping: Record) => ({ + sourceTable: (mapping.sourceTable as string) || "", + sourceField: (mapping.sourceField as string) || "", + targetTable: (mapping.targetTable as string) || "", + targetField: (mapping.targetField as string) || "", + defaultValue: (mapping.defaultValue as string) || "", + transformFunction: (mapping.transformFunction as string) || "", + })) + : [], + splitConfig: action.splitConfig + ? { + sourceField: ((action.splitConfig as Record).sourceField as string) || "", + delimiter: ((action.splitConfig as Record).delimiter as string) || ",", + targetField: ((action.splitConfig as Record).targetField as string) || "", + } + : undefined, + })), + }); + + // 전체 실행 조건 로드 + if (settings.control) { + const controlSettings = settings.control as { conditionTree?: ConditionNode[] }; + if (Array.isArray(controlSettings.conditionTree)) { + setConditions(controlSettings.conditionTree || []); + } + } + } else if (connectionType === "external-call") { + setExternalCallSettings({ + callType: (settings.callType as "rest-api" | "webhook") || "rest-api", + apiUrl: (settings.apiUrl as string) || "", + httpMethod: (settings.httpMethod as "GET" | "POST" | "PUT" | "DELETE") || "POST", + headers: (settings.headers as string) || "{}", + bodyTemplate: (settings.bodyTemplate as string) || "{}", + }); + } + }, + [setConditions, setSimpleKeySettings, setDataSaveSettings, setExternalCallSettings], + ); // 테이블 목록 로드 useEffect(() => { @@ -181,103 +180,21 @@ export const ConnectionSetupModal: React.FC = ({ settings: existingRel?.settings || {}, }); - // 🔥 기존 설정 데이터 로드 + // 기존 설정 데이터 로드 if (existingRel?.settings) { - const settings = existingRel.settings; - - if (connectionType === "simple-key" && settings.notes) { - setSimpleKeySettings({ - notes: settings.notes as string, - }); - } else if (connectionType === "data-save" && settings.actions) { - // data-save 설정 로드 - 안전하게 처리 - const actionsData = Array.isArray(settings.actions) ? settings.actions : []; - setDataSaveSettings({ - actions: actionsData.map((action: Record) => ({ - id: (action.id as string) || `action-${Date.now()}`, - name: (action.name as string) || "새 액션", - actionType: (action.actionType as "insert" | "update" | "delete" | "upsert") || "insert", - conditions: Array.isArray(action.conditions) - ? action.conditions.map( - (condition: Record) => - ({ - ...condition, - operator: condition.operator || "=", // 기본값 보장 - }) as ConditionNode, - ) - : [], - fieldMappings: Array.isArray(action.fieldMappings) - ? action.fieldMappings.map((mapping: Record) => ({ - sourceTable: (mapping.sourceTable as string) || "", - sourceField: (mapping.sourceField as string) || "", - targetTable: (mapping.targetTable as string) || "", - targetField: (mapping.targetField as string) || "", - defaultValue: (mapping.defaultValue as string) || "", - transformFunction: (mapping.transformFunction as string) || "", - })) - : [], - splitConfig: action.splitConfig - ? { - sourceField: ((action.splitConfig as Record).sourceField as string) || "", - delimiter: ((action.splitConfig as Record).delimiter as string) || ",", - targetField: ((action.splitConfig as Record).targetField as string) || "", - } - : undefined, - })), - }); - - // 전체 실행 조건 로드 - 안전하게 처리 - if (settings.control) { - const controlSettings = settings.control as { conditionTree?: ConditionNode[] }; - if (Array.isArray(controlSettings.conditionTree)) { - // 기존 조건이 없을 때만 로드 (사용자가 추가한 조건 보존) - setConditions((prevConditions) => { - if (prevConditions.length === 0) { - return controlSettings.conditionTree || []; - } - return prevConditions; - }); - } else { - // 기존 조건이 없을 때만 초기화 - setConditions((prevConditions) => (prevConditions.length === 0 ? [] : prevConditions)); - } - } else { - // 기존 조건이 없을 때만 초기화 - setConditions((prevConditions) => (prevConditions.length === 0 ? [] : prevConditions)); - } - } else if (connectionType === "external-call") { - setExternalCallSettings({ - callType: (settings.callType as "rest-api" | "webhook") || "rest-api", - apiUrl: (settings.apiUrl as string) || "", - httpMethod: (settings.httpMethod as "GET" | "POST" | "PUT" | "DELETE") || "POST", - headers: (settings.headers as string) || "{}", - bodyTemplate: (settings.bodyTemplate as string) || "{}", - }); - } + loadExistingSettings(existingRel.settings, connectionType); } else { // 기본값 설정 setSimpleKeySettings({ notes: `${fromDisplayName}과 ${toDisplayName} 간의 키값 연결`, }); - - setDataSaveSettings({ - actions: [], - }); + setDataSaveSettings({ actions: [] }); } - // 🔥 필드 선택 상태 초기화 + // 필드 선택 상태 초기화 setSelectedFromColumns([]); setSelectedToColumns([]); - // 외부 호출 기본값 설정 - setExternalCallSettings({ - callType: "rest-api", - apiUrl: "https://api.example.com/webhook", - httpMethod: "POST", - headers: "{}", - bodyTemplate: "{}", - }); - // 선택된 컬럼 정보가 있다면 설정 if (connection.selectedColumnsData) { const fromColumns = connection.selectedColumnsData[fromTableName]?.columns || []; @@ -293,7 +210,7 @@ export const ConnectionSetupModal: React.FC = ({ })); } } - }, [isOpen, connection]); + }, [isOpen, connection, setConditions, loadExistingSettings]); // From 테이블 선택 시 컬럼 로드 useEffect(() => { @@ -363,8 +280,8 @@ export const ConnectionSetupModal: React.FC = ({ const tablesToLoad = new Set(); // 필드 매핑에서 사용되는 모든 테이블 수집 - (dataSaveSettings.actions || []).forEach((action) => { - (action.fieldMappings || []).forEach((mapping) => { + dataSaveSettings.actions?.forEach((action) => { + action.fieldMappings?.forEach((mapping) => { if (mapping.sourceTable && !tableColumnsCache[mapping.sourceTable]) { tablesToLoad.add(mapping.sourceTable); } @@ -417,7 +334,7 @@ export const ConnectionSetupModal: React.FC = ({ const toTableName = selectedToTable || connection.toNode.tableName; // 조건부 연결 설정 데이터 준비 - const conditionalSettings = isConditionalConnection() + const conditionalSettings = isConditionalConnection(config.connectionType) ? { control: { triggerType: "insert", @@ -474,8 +391,6 @@ export const ConnectionSetupModal: React.FC = ({ settings: { ...settings, ...conditionalSettings, // 조건부 연결 설정 추가 - // 중복 제거: multiColumnMapping, isMultiColumn, columnCount, description 제거 - // 필요시 from_column_name, to_column_name에서 split으로 추출 가능 }, }; @@ -496,1543 +411,41 @@ export const ConnectionSetupModal: React.FC = ({ onCancel(); }; - if (!connection) return null; - - // 선택된 컬럼 데이터 가져오기 (현재 사용되지 않음 - 향후 확장을 위해 유지) - // const selectedColumnsData = connection.selectedColumnsData || {}; - - // 조건부 연결인지 확인하는 헬퍼 함수 - const isConditionalConnection = () => { - return config.connectionType === "data-save" || config.connectionType === "external-call"; - }; - - // 고유 ID 생성 헬퍼 - const generateId = () => `cond_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - - // 조건 관리 헬퍼 함수들 - const addCondition = () => { - const newCondition: ConditionNode = { - id: generateId(), - type: "condition" as const, - field: "", - operator: "=", - value: "", - dataType: "string", - // 첫 번째 조건이 아니고, 바로 앞이 group-start가 아니면 logicalOperator 추가 - ...(conditions.length > 0 && - conditions[conditions.length - 1]?.type !== "group-start" && { logicalOperator: "AND" as const }), - }; - - setConditions([...conditions, newCondition]); - }; - - // 그룹 시작 추가 - const addGroupStart = () => { - const groupId = `group_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - const groupLevel = getNextGroupLevel(); - - const groupStart: ConditionNode = { - id: generateId(), - type: "group-start" as const, - groupId, - groupLevel, - // 첫 번째 그룹이 아니면 logicalOperator 추가 - ...(conditions.length > 0 && { logicalOperator: "AND" as const }), - }; - - setConditions([...conditions, groupStart]); - }; - - // 그룹 끝 추가 - const addGroupEnd = () => { - // 가장 최근에 열린 그룹 찾기 - const openGroups = findOpenGroups(); - if (openGroups.length === 0) { - toast.error("닫을 그룹이 없습니다."); - return; - } - - const lastOpenGroup = openGroups[openGroups.length - 1]; - const groupEnd: ConditionNode = { - id: generateId(), - type: "group-end" as const, - groupId: lastOpenGroup.groupId, - groupLevel: lastOpenGroup.groupLevel, - }; - - setConditions([...conditions, groupEnd]); - }; - - // 다음 그룹 레벨 계산 - const getNextGroupLevel = (): number => { - const openGroups = findOpenGroups(); - return openGroups.length; - }; - - // 열린 그룹 찾기 - const findOpenGroups = () => { - const openGroups: Array<{ groupId: string; groupLevel: number }> = []; - - for (const condition of conditions) { - if (condition.type === "group-start") { - openGroups.push({ - groupId: condition.groupId!, - groupLevel: condition.groupLevel!, - }); - } else if (condition.type === "group-end") { - // 해당 그룹 제거 - const groupIndex = openGroups.findIndex((g) => g.groupId === condition.groupId); - if (groupIndex !== -1) { - openGroups.splice(groupIndex, 1); - } - } - } - - return openGroups; - }; - - const updateCondition = (index: number, field: keyof ConditionNode, value: string) => { - const updatedConditions = [...conditions]; - updatedConditions[index] = { ...updatedConditions[index], [field]: value }; - setConditions(updatedConditions); - }; - - const removeCondition = (index: number) => { - const conditionToRemove = conditions[index]; - - // 그룹 시작/끝을 삭제하는 경우 해당 그룹 전체 삭제 - if (conditionToRemove.type === "group-start" || conditionToRemove.type === "group-end") { - removeGroup(conditionToRemove.groupId!); - } else { - const updatedConditions = conditions.filter((_, i) => i !== index); - setConditions(updatedConditions); - } - }; - - // 그룹 전체 삭제 - const removeGroup = (groupId: string) => { - const updatedConditions = conditions.filter((c) => c.groupId !== groupId); - setConditions(updatedConditions); - }; - - // 현재 조건의 그룹 레벨 계산 - const getCurrentGroupLevel = (conditionIndex: number): number => { - let level = 0; - for (let i = 0; i < conditionIndex; i++) { - const condition = conditions[i]; - if (condition.type === "group-start") { - level++; - } else if (condition.type === "group-end") { - level--; - } - } - return level; - }; - - // 액션별 조건 그룹 관리 함수들 - const addActionGroupStart = (actionIndex: number) => { - const groupId = `action_group_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - const currentConditions = dataSaveSettings.actions[actionIndex].conditions || []; - const groupLevel = getActionNextGroupLevel(currentConditions); - - const groupStart: ConditionNode = { - id: generateId(), - type: "group-start" as const, - groupId, - groupLevel, - // 첫 번째 그룹이 아니면 logicalOperator 추가 - ...(currentConditions.length > 0 && { logicalOperator: "AND" as const }), - }; - - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions = [...currentConditions, groupStart]; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }; - - const addActionGroupEnd = (actionIndex: number) => { - const currentConditions = dataSaveSettings.actions[actionIndex].conditions || []; - const openGroups = findActionOpenGroups(currentConditions); - - if (openGroups.length === 0) { - toast.error("닫을 그룹이 없습니다."); - return; - } - - const lastOpenGroup = openGroups[openGroups.length - 1]; - const groupEnd: ConditionNode = { - id: generateId(), - type: "group-end" as const, - groupId: lastOpenGroup.groupId, - groupLevel: lastOpenGroup.groupLevel, - }; - - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions = [...currentConditions, groupEnd]; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }; - - // 액션별 다음 그룹 레벨 계산 - const getActionNextGroupLevel = (conditions: ConditionNode[]): number => { - const openGroups = findActionOpenGroups(conditions); - return openGroups.length; - }; - - // 액션별 열린 그룹 찾기 - const findActionOpenGroups = (conditions: ConditionNode[]) => { - const openGroups: Array<{ groupId: string; groupLevel: number }> = []; - - for (const condition of conditions) { - if (condition.type === "group-start") { - openGroups.push({ - groupId: condition.groupId!, - groupLevel: condition.groupLevel!, - }); - } else if (condition.type === "group-end") { - const groupIndex = openGroups.findIndex((g) => g.groupId === condition.groupId); - if (groupIndex !== -1) { - openGroups.splice(groupIndex, 1); - } - } - } - - return openGroups; - }; - - // 액션별 현재 조건의 그룹 레벨 계산 - const getActionCurrentGroupLevel = (conditions: ConditionNode[], conditionIndex: number): number => { - let level = 0; - for (let i = 0; i < conditionIndex; i++) { - const condition = conditions[i]; - if (condition.type === "group-start") { - level++; - } else if (condition.type === "group-end") { - level--; - } - } - return level; - }; - - // 액션별 조건 렌더링 함수 - const renderActionCondition = (condition: ConditionNode, condIndex: number, actionIndex: number) => { - // 그룹 시작 렌더링 - if (condition.type === "group-start") { - return ( -
- {/* 그룹 시작 앞의 논리 연산자 */} - {condIndex > 0 && ( - - )} -
- ( - 그룹 시작 - -
-
- ); - } - - // 그룹 끝 렌더링 - if (condition.type === "group-end") { - return ( -
-
- ) - 그룹 끝 - -
-
- ); - } - - // 일반 조건 렌더링 (기존 로직 간소화) - return ( -
- {/* 그룹 내 첫 번째 조건이 아닐 때만 논리 연산자 표시 */} - {condIndex > 0 && dataSaveSettings.actions[actionIndex].conditions![condIndex - 1]?.type !== "group-start" && ( - - )} -
- - - {/* 데이터 타입에 따른 동적 입력 컴포넌트 */} - {(() => { - const selectedColumn = fromTableColumns.find((col) => col.columnName === condition.field); - const dataType = selectedColumn?.dataType?.toLowerCase() || "string"; - - if (dataType.includes("timestamp") || dataType.includes("datetime") || dataType.includes("date")) { - return ( - { - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions![condIndex].value = e.target.value; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }} - className="h-6 flex-1 text-xs" - /> - ); - } else if (dataType.includes("time")) { - return ( - { - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions![condIndex].value = e.target.value; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }} - className="h-6 flex-1 text-xs" - /> - ); - } else if (dataType.includes("date")) { - return ( - { - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions![condIndex].value = e.target.value; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }} - className="h-6 flex-1 text-xs" - /> - ); - } else if ( - dataType.includes("int") || - dataType.includes("numeric") || - dataType.includes("decimal") || - dataType.includes("float") || - dataType.includes("double") - ) { - return ( - { - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions![condIndex].value = e.target.value; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }} - className="h-6 flex-1 text-xs" - /> - ); - } else if (dataType.includes("bool")) { - return ( - - ); - } else { - return ( - { - const newActions = [...dataSaveSettings.actions]; - newActions[actionIndex].conditions![condIndex].value = e.target.value; - setDataSaveSettings({ ...dataSaveSettings, actions: newActions }); - }} - className="h-6 flex-1 text-xs" - /> - ); - } - })()} - -
-
- ); - }; - - // 조건부 연결 설정 UI 렌더링 - const renderConditionalSettings = () => { - return ( -
-
- - 전체 실행 조건 (언제 이 연결이 동작할지) -
- - {/* 실행 조건 설정 */} -
-
- -
- - - -
-
- - {/* 조건 목록 */} -
- {conditions.length === 0 ? ( -
- 조건을 추가하면 해당 조건을 만족할 때만 실행됩니다. -
- 조건이 없으면 항상 실행됩니다. -
- ) : ( - - {conditions.map((condition, index) => { - // 그룹 시작 렌더링 - if (condition.type === "group-start") { - return ( -
- {/* 그룹 시작 앞의 논리 연산자 - 이전 요소가 group-end가 아닌 경우에만 표시 */} - {index > 0 && conditions[index - 1]?.type !== "group-end" && ( - - )} - {/* 그룹 레벨에 따른 들여쓰기 */} -
- ( - 그룹 시작 - -
-
- ); - } - - // 그룹 끝 렌더링 - if (condition.type === "group-end") { - return ( -
-
- ) - 그룹 끝 - -
- - {/* 그룹 끝 다음에 다른 조건이나 그룹이 있으면 논리 연산자 표시 */} - {index < conditions.length - 1 && ( - - )} -
- ); - } - - // 일반 조건 렌더링 - return ( -
- {/* 일반 조건 앞의 논리 연산자 - 이전 요소가 group-end가 아닌 경우에만 표시 */} - {index > 0 && - conditions[index - 1]?.type !== "group-start" && - conditions[index - 1]?.type !== "group-end" && ( - - )} - - {/* 그룹 레벨에 따른 들여쓰기와 조건 필드들 */} -
- {/* 조건 필드 선택 */} - - - {/* 연산자 선택 */} - - - {/* 데이터 타입에 따른 동적 입력 컴포넌트 */} - {(() => { - const selectedColumn = fromTableColumns.find((col) => col.columnName === condition.field); - const dataType = selectedColumn?.dataType?.toLowerCase() || "string"; - - if ( - dataType.includes("timestamp") || - dataType.includes("datetime") || - dataType.includes("date") - ) { - return ( - updateCondition(index, "value", e.target.value)} - className="h-8 flex-1 text-xs" - /> - ); - } else if (dataType.includes("time")) { - return ( - updateCondition(index, "value", e.target.value)} - className="h-8 flex-1 text-xs" - /> - ); - } else if (dataType.includes("date")) { - return ( - updateCondition(index, "value", e.target.value)} - className="h-8 flex-1 text-xs" - /> - ); - } else if ( - dataType.includes("int") || - dataType.includes("numeric") || - dataType.includes("decimal") || - dataType.includes("float") || - dataType.includes("double") - ) { - return ( - updateCondition(index, "value", e.target.value)} - className="h-8 flex-1 text-xs" - /> - ); - } else if (dataType.includes("bool")) { - return ( - - ); - } else { - return ( - updateCondition(index, "value", e.target.value)} - className="h-8 flex-1 text-xs" - /> - ); - } - })()} - - {/* 삭제 버튼 */} - -
-
- ); - })} -
- )} -
-
-
- ); - }; - // 연결 종류별 설정 패널 렌더링 const renderConnectionTypeSettings = () => { switch (config.connectionType) { case "simple-key": return ( -
- {/* 테이블 및 컬럼 선택 */} -
-
테이블 및 컬럼 선택
- - {/* 현재 선택된 테이블 표시 */} -
-
- -
- - {availableTables.find((t) => t.tableName === selectedFromTable)?.displayName || selectedFromTable} - - ({selectedFromTable}) -
-
- -
- -
- - {availableTables.find((t) => t.tableName === selectedToTable)?.displayName || selectedToTable} - - ({selectedToTable}) -
-
-
- - {/* 컬럼 선택 */} -
-
- -
- {fromTableColumns.map((column) => ( - - ))} - {fromTableColumns.length === 0 && ( -
- {selectedFromTable ? "컬럼을 불러오는 중..." : "테이블을 먼저 선택해주세요"} -
- )} -
-
- -
- -
- {toTableColumns.map((column) => ( - - ))} - {toTableColumns.length === 0 && ( -
- {selectedToTable ? "컬럼을 불러오는 중..." : "테이블을 먼저 선택해주세요"} -
- )} -
-
-
- - {/* 선택된 컬럼 미리보기 */} - {(selectedFromColumns.length > 0 || selectedToColumns.length > 0) && ( -
-
- -
- {selectedFromColumns.length > 0 ? ( - selectedFromColumns.map((column) => ( - - {column} - - )) - ) : ( - 선택된 컬럼 없음 - )} -
-
- -
- -
- {selectedToColumns.length > 0 ? ( - selectedToColumns.map((column) => ( - - {column} - - )) - ) : ( - 선택된 컬럼 없음 - )} -
-
-
- )} -
- - {/* 단순 키값 연결 설정 */} -
-
- - 단순 키값 연결 설정 -
-
-
- -