"use client"; /** * 참조 테이블 조회 노드 속성 편집 */ import { useEffect, useState, useCallback } from "react"; import { Plus, Trash2, Search } 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 { 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"; 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); // 데이터 변경 시 로컬 상태 동기화 useEffect(() => { setDisplayName(data.displayName || "참조 조회"); setReferenceTable(data.referenceTable || ""); setReferenceTableLabel(data.referenceTableLabel || ""); setJoinConditions(data.joinConditions || []); setWhereConditions(data.whereConditions || []); setOutputFields(data.outputFields || []); }, [data]); // 🔍 소스 필드 수집 (업스트림 노드에서) 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 = () => { setWhereConditions([ ...whereConditions, { field: "", operator: "=", value: "", valueType: "static", }, ]); }; const handleRemoveWhereCondition = (index: number) => { setWhereConditions(whereConditions.filter((_, i) => i !== index)); }; 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}
{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')
📤 출력 필드: 참조 테이블에서 가져올 필드 선택 (별칭으로 결과에 추가됨)
); }