diff --git a/frontend/components/dataflow/node-editor/FlowEditor.tsx b/frontend/components/dataflow/node-editor/FlowEditor.tsx index f74d35aa..333a70c1 100644 --- a/frontend/components/dataflow/node-editor/FlowEditor.tsx +++ b/frontend/components/dataflow/node-editor/FlowEditor.tsx @@ -18,7 +18,6 @@ import { ValidationNotification } from "./ValidationNotification"; import { FlowToolbar } from "./FlowToolbar"; import { TableSourceNode } from "./nodes/TableSourceNode"; import { ExternalDBSourceNode } from "./nodes/ExternalDBSourceNode"; -import { ReferenceLookupNode } from "./nodes/ReferenceLookupNode"; import { ConditionNode } from "./nodes/ConditionNode"; import { InsertActionNode } from "./nodes/InsertActionNode"; import { UpdateActionNode } from "./nodes/UpdateActionNode"; @@ -38,7 +37,6 @@ const nodeTypes = { tableSource: TableSourceNode, externalDBSource: ExternalDBSourceNode, restAPISource: RestAPISourceNode, - referenceLookup: ReferenceLookupNode, // 변환/조건 condition: ConditionNode, dataTransform: DataTransformNode, diff --git a/frontend/components/dataflow/node-editor/nodes/ReferenceLookupNode.tsx b/frontend/components/dataflow/node-editor/nodes/ReferenceLookupNode.tsx deleted file mode 100644 index 181d7dad..00000000 --- a/frontend/components/dataflow/node-editor/nodes/ReferenceLookupNode.tsx +++ /dev/null @@ -1,108 +0,0 @@ -"use client"; - -/** - * 참조 테이블 조회 노드 (내부 DB 전용) - * 다른 테이블에서 데이터를 조회하여 조건 비교나 필드 매핑에 사용 - */ - -import { memo } from "react"; -import { Handle, Position, NodeProps } from "reactflow"; -import { Link2, Database } from "lucide-react"; -import type { ReferenceLookupNodeData } from "@/types/node-editor"; - -export const ReferenceLookupNode = memo(({ data, selected }: NodeProps) => { - return ( -
- {/* 헤더 */} -
- -
-
{data.displayName || "참조 조회"}
-
-
- - {/* 본문 */} -
-
- - 내부 DB 참조 -
- - {/* 참조 테이블 */} - {data.referenceTable && ( -
-
📋 참조 테이블
-
- {data.referenceTableLabel || data.referenceTable} -
-
- )} - - {/* 조인 조건 */} - {data.joinConditions && data.joinConditions.length > 0 && ( -
-
🔗 조인 조건:
-
- {data.joinConditions.map((join, idx) => ( -
- {join.sourceFieldLabel || join.sourceField} - - {join.referenceFieldLabel || join.referenceField} -
- ))} -
-
- )} - - {/* WHERE 조건 */} - {data.whereConditions && data.whereConditions.length > 0 && ( -
-
⚡ WHERE 조건:
-
{data.whereConditions.length}개 조건
-
- )} - - {/* 출력 필드 */} - {data.outputFields && data.outputFields.length > 0 && ( -
-
📤 출력 필드:
-
- {data.outputFields.slice(0, 3).map((field, idx) => ( -
-
- {field.alias} - ← {field.fieldLabel || field.fieldName} -
- ))} - {data.outputFields.length > 3 && ( -
... 외 {data.outputFields.length - 3}개
- )} -
-
- )} -
- - {/* 입력 핸들 (왼쪽) */} - - - {/* 출력 핸들 (오른쪽) */} - -
- ); -}); - -ReferenceLookupNode.displayName = "ReferenceLookupNode"; - - diff --git a/frontend/components/dataflow/node-editor/panels/PropertiesPanel.tsx b/frontend/components/dataflow/node-editor/panels/PropertiesPanel.tsx index cf7c7e6e..67483e03 100644 --- a/frontend/components/dataflow/node-editor/panels/PropertiesPanel.tsx +++ b/frontend/components/dataflow/node-editor/panels/PropertiesPanel.tsx @@ -8,7 +8,6 @@ import { X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useFlowEditorStore } from "@/lib/stores/flowEditorStore"; import { TableSourceProperties } from "./properties/TableSourceProperties"; -import { ReferenceLookupProperties } from "./properties/ReferenceLookupProperties"; import { InsertActionProperties } from "./properties/InsertActionProperties"; import { ConditionProperties } from "./properties/ConditionProperties"; import { UpdateActionProperties } from "./properties/UpdateActionProperties"; @@ -99,9 +98,6 @@ function NodePropertiesRenderer({ node }: { node: any }) { case "tableSource": return ; - case "referenceLookup": - return ; - case "insertAction": return ; @@ -161,7 +157,6 @@ function getNodeTypeLabel(type: NodeType): string { tableSource: "테이블 소스", externalDBSource: "외부 DB 소스", restAPISource: "REST API 소스", - referenceLookup: "참조 조회", condition: "조건 분기", fieldMapping: "필드 매핑", dataTransform: "데이터 변환", diff --git a/frontend/components/dataflow/node-editor/panels/properties/ReferenceLookupProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/ReferenceLookupProperties.tsx deleted file mode 100644 index b2bb51e0..00000000 --- a/frontend/components/dataflow/node-editor/panels/properties/ReferenceLookupProperties.tsx +++ /dev/null @@ -1,706 +0,0 @@ -"use client"; - -/** - * 참조 테이블 조회 노드 속성 편집 - */ - -import { useEffect, useState, useCallback } from "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 { cn } from "@/lib/utils"; -import { useFlowEditorStore } from "@/lib/stores/flowEditorStore"; -import type { ReferenceLookupNodeData } from "@/types/node-editor"; -import { tableTypeApi } from "@/lib/api/screen"; - -// 필드 정의 -interface FieldDefinition { - name: string; - label?: string; - type?: string; -} - -interface ReferenceLookupPropertiesProps { - nodeId: string; - data: ReferenceLookupNodeData; -} - -const OPERATORS = [ - { value: "=", label: "같음 (=)" }, - { value: "!=", label: "같지 않음 (≠)" }, - { value: ">", label: "보다 큼 (>)" }, - { value: "<", label: "보다 작음 (<)" }, - { value: ">=", label: "크거나 같음 (≥)" }, - { value: "<=", label: "작거나 같음 (≤)" }, - { value: "LIKE", label: "포함 (LIKE)" }, - { value: "IN", label: "IN" }, -] as const; - -export function ReferenceLookupProperties({ nodeId, data }: ReferenceLookupPropertiesProps) { - const { updateNode, nodes, edges } = useFlowEditorStore(); - - // 상태 - const [displayName, setDisplayName] = useState(data.displayName || "참조 조회"); - const [referenceTable, setReferenceTable] = useState(data.referenceTable || ""); - const [referenceTableLabel, setReferenceTableLabel] = useState(data.referenceTableLabel || ""); - const [joinConditions, setJoinConditions] = useState(data.joinConditions || []); - const [whereConditions, setWhereConditions] = useState(data.whereConditions || []); - const [outputFields, setOutputFields] = useState(data.outputFields || []); - - // 소스 필드 수집 - const [sourceFields, setSourceFields] = useState([]); - - // 참조 테이블 관련 - const [tables, setTables] = useState([]); - const [tablesLoading, setTablesLoading] = useState(false); - const [tablesOpen, setTablesOpen] = useState(false); - const [referenceColumns, setReferenceColumns] = useState([]); - const [columnsLoading, setColumnsLoading] = useState(false); - - // Combobox 열림 상태 관리 - const [whereFieldOpenState, setWhereFieldOpenState] = useState([]); - - // 데이터 변경 시 로컬 상태 동기화 - useEffect(() => { - setDisplayName(data.displayName || "참조 조회"); - setReferenceTable(data.referenceTable || ""); - setReferenceTableLabel(data.referenceTableLabel || ""); - setJoinConditions(data.joinConditions || []); - setWhereConditions(data.whereConditions || []); - 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); - const fields: FieldDefinition[] = []; - - for (const edge of incomingEdges) { - const sourceNode = nodes.find((n) => n.id === edge.source); - if (!sourceNode) continue; - - const sourceData = sourceNode.data as any; - - if (sourceNode.type === "tableSource" && sourceData.fields) { - fields.push(...sourceData.fields); - } else if (sourceNode.type === "externalDBSource" && sourceData.outputFields) { - fields.push(...sourceData.outputFields); - } - } - - setSourceFields(fields); - }, [nodeId, nodes, edges]); - - // 📊 테이블 목록 로드 - useEffect(() => { - loadTables(); - }, []); - - const loadTables = async () => { - setTablesLoading(true); - try { - const data = await tableTypeApi.getTables(); - setTables(data); - } catch (error) { - console.error("테이블 로드 실패:", error); - } finally { - setTablesLoading(false); - } - }; - - // 📋 참조 테이블 컬럼 로드 - useEffect(() => { - if (referenceTable) { - loadReferenceColumns(); - } else { - setReferenceColumns([]); - } - }, [referenceTable]); - - const loadReferenceColumns = async () => { - if (!referenceTable) return; - - setColumnsLoading(true); - try { - const cols = await tableTypeApi.getColumns(referenceTable); - const formatted = cols.map((col: any) => ({ - name: col.columnName, - type: col.dataType, - label: col.displayName || col.columnName, - })); - setReferenceColumns(formatted); - } catch (error) { - console.error("컬럼 로드 실패:", error); - setReferenceColumns([]); - } finally { - setColumnsLoading(false); - } - }; - - // 테이블 선택 핸들러 - const handleTableSelect = (tableName: string) => { - const selectedTable = tables.find((t) => t.tableName === tableName); - if (selectedTable) { - setReferenceTable(tableName); - setReferenceTableLabel(selectedTable.label); - setTablesOpen(false); - - // 기존 설정 초기화 - setJoinConditions([]); - setWhereConditions([]); - setOutputFields([]); - } - }; - - // 조인 조건 추가 - const handleAddJoinCondition = () => { - setJoinConditions([ - ...joinConditions, - { - sourceField: "", - referenceField: "", - }, - ]); - }; - - const handleRemoveJoinCondition = (index: number) => { - setJoinConditions(joinConditions.filter((_, i) => i !== index)); - }; - - const handleJoinConditionChange = (index: number, field: string, value: any) => { - const newConditions = [...joinConditions]; - newConditions[index] = { ...newConditions[index], [field]: value }; - - // 라벨도 함께 저장 - if (field === "sourceField") { - const sourceField = sourceFields.find((f) => f.name === value); - newConditions[index].sourceFieldLabel = sourceField?.label || value; - } else if (field === "referenceField") { - const refField = referenceColumns.find((f) => f.name === value); - newConditions[index].referenceFieldLabel = refField?.label || value; - } - - setJoinConditions(newConditions); - }; - - // WHERE 조건 추가 - const handleAddWhereCondition = () => { - const newConditions = [ - ...whereConditions, - { - field: "", - operator: "=", - value: "", - valueType: "static", - }, - ]; - setWhereConditions(newConditions); - setWhereFieldOpenState(new Array(newConditions.length).fill(false)); - }; - - const handleRemoveWhereCondition = (index: number) => { - const newConditions = whereConditions.filter((_, i) => i !== index); - setWhereConditions(newConditions); - setWhereFieldOpenState(new Array(newConditions.length).fill(false)); - }; - - const handleWhereConditionChange = (index: number, field: string, value: any) => { - const newConditions = [...whereConditions]; - newConditions[index] = { ...newConditions[index], [field]: value }; - - // 라벨도 함께 저장 - if (field === "field") { - const refField = referenceColumns.find((f) => f.name === value); - newConditions[index].fieldLabel = refField?.label || value; - } - - setWhereConditions(newConditions); - }; - - // 출력 필드 추가 - const handleAddOutputField = () => { - setOutputFields([ - ...outputFields, - { - fieldName: "", - alias: "", - }, - ]); - }; - - const handleRemoveOutputField = (index: number) => { - setOutputFields(outputFields.filter((_, i) => i !== index)); - }; - - const handleOutputFieldChange = (index: number, field: string, value: any) => { - const newFields = [...outputFields]; - newFields[index] = { ...newFields[index], [field]: value }; - - // 라벨도 함께 저장 - if (field === "fieldName") { - const refField = referenceColumns.find((f) => f.name === value); - newFields[index].fieldLabel = refField?.label || value; - // alias 자동 설정 - if (!newFields[index].alias) { - newFields[index].alias = `ref_${value}`; - } - } - - setOutputFields(newFields); - }; - - const handleSave = () => { - updateNode(nodeId, { - displayName, - referenceTable, - referenceTableLabel, - joinConditions, - whereConditions, - outputFields, - }); - }; - - const selectedTableLabel = tables.find((t) => t.tableName === referenceTable)?.label || referenceTable; - - return ( -
-
- {/* 기본 정보 */} -
-

기본 정보

- -
-
- - setDisplayName(e.target.value)} - className="mt-1" - placeholder="노드 표시 이름" - /> -
- - {/* 참조 테이블 선택 */} -
- - - - - - - - - - 검색 결과가 없습니다. - - - {tables.map((table) => ( - handleTableSelect(table.tableName)} - className="cursor-pointer" - > - -
- {table.label} - {table.label !== table.tableName && ( - {table.tableName} - )} -
-
- ))} -
-
-
-
-
-
-
-
-
- - {/* 조인 조건 */} -
-
-

조인 조건 (FK 매핑)

- -
- - {joinConditions.length > 0 ? ( -
- {joinConditions.map((condition, index) => ( -
-
- 조인 #{index + 1} - -
- -
-
- - -
- -
- - -
-
-
- ))} -
- ) : ( -
- 조인 조건을 추가하세요 (필수) -
- )} -
- - {/* WHERE 조건 */} -
-
-

WHERE 조건 (선택사항)

- -
- - {whereConditions.length > 0 && ( -
- {whereConditions.map((condition, index) => ( -
-
- WHERE #{index + 1} - -
- -
- {/* 필드 - Combobox */} -
- - { - const newState = [...whereFieldOpenState]; - newState[index] = open; - setWhereFieldOpenState(newState); - }} - > - - - - - - - - 필드를 찾을 수 없습니다. - - {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} - )} -
-
- ))} -
-
-
-
-
-
- -
- - -
- -
- - -
- -
- - {condition.valueType === "field" ? ( - - ) : ( - handleWhereConditionChange(index, "value", e.target.value)} - placeholder="비교할 값" - className="mt-1 h-8 text-xs" - /> - )} -
-
-
- ))} -
- )} -
- - {/* 출력 필드 */} -
-
-

출력 필드

- -
- - {outputFields.length > 0 ? ( -
- {outputFields.map((field, index) => ( -
-
- 필드 #{index + 1} - -
- -
-
- - -
- -
- - handleOutputFieldChange(index, "alias", e.target.value)} - placeholder="ref_field_name" - className="mt-1 h-8 text-xs" - /> -
-
-
- ))} -
- ) : ( -
- 출력 필드를 추가하세요 (필수) -
- )} -
- - {/* 저장 버튼 */} - - {/* 안내 */} -
-
- 🔗 조인 조건: 소스 데이터와 참조 테이블을 연결하는 키 (예: customer_id → id) -
-
- ⚡ WHERE 조건: 참조 테이블에서 특정 조건의 데이터만 가져오기 (예: grade = 'VIP') -
-
- 📤 출력 필드: 참조 테이블에서 가져올 필드 선택 (별칭으로 결과에 추가됨) -
-
-
-
- ); -} diff --git a/frontend/components/dataflow/node-editor/sidebar/nodePaletteConfig.ts b/frontend/components/dataflow/node-editor/sidebar/nodePaletteConfig.ts index 2ff31689..0cc77705 100644 --- a/frontend/components/dataflow/node-editor/sidebar/nodePaletteConfig.ts +++ b/frontend/components/dataflow/node-editor/sidebar/nodePaletteConfig.ts @@ -32,14 +32,6 @@ export const NODE_PALETTE: NodePaletteItem[] = [ category: "source", color: "#10B981", // 초록색 }, - { - type: "referenceLookup", - label: "참조 조회", - icon: "", - description: "다른 테이블에서 데이터를 조회합니다 (내부 DB 전용)", - category: "source", - color: "#A855F7", // 보라색 - }, // ======================================================================== // 변환/조건 diff --git a/frontend/types/node-editor.ts b/frontend/types/node-editor.ts index fc5adb89..9b6ce969 100644 --- a/frontend/types/node-editor.ts +++ b/frontend/types/node-editor.ts @@ -12,7 +12,6 @@ export type NodeType = | "tableSource" // 테이블 소스 | "externalDBSource" // 외부 DB 소스 | "restAPISource" // REST API 소스 - | "referenceLookup" // 참조 테이블 조회 (내부 DB 전용) | "condition" // 조건 분기 | "dataTransform" // 데이터 변환 | "aggregate" // 집계 노드 (SUM, COUNT, AVG 등) @@ -92,35 +91,6 @@ export interface RestAPISourceNodeData { displayName?: string; } -// 참조 테이블 조회 노드 (내부 DB 전용) -export interface ReferenceLookupNodeData { - type: "referenceLookup"; - referenceTable: string; // 참조할 테이블명 - referenceTableLabel?: string; // 테이블 라벨 - joinConditions: Array<{ - // 조인 조건 (FK 매핑) - sourceField: string; // 소스 데이터의 필드 - sourceFieldLabel?: string; - referenceField: string; // 참조 테이블의 필드 - referenceFieldLabel?: string; - }>; - whereConditions?: Array<{ - // 추가 WHERE 조건 - field: string; - fieldLabel?: string; - operator: string; - value: any; - valueType?: "static" | "field"; // 고정값 또는 소스 필드 참조 - }>; - outputFields: Array<{ - // 가져올 필드들 - fieldName: string; // 참조 테이블의 컬럼명 - fieldLabel?: string; - alias: string; // 결과 데이터에서 사용할 이름 - }>; - displayName?: string; -} - // 조건 분기 노드 export interface ConditionNodeData { conditions: Array<{ @@ -431,7 +401,6 @@ export type NodeData = | TableSourceNodeData | ExternalDBSourceNodeData | RestAPISourceNodeData - | ReferenceLookupNodeData | ConditionNodeData | FieldMappingNodeData | DataTransformNodeData