/** * 플로우 조건 빌더 * 동적 조건 생성 UI */ import { useState, useEffect } from "react"; import { Plus, Trash2, Check, ChevronsUpDown } from "lucide-react"; import { Button } from "@/components/ui/button"; 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 { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { FlowConditionGroup, FlowCondition, ConditionOperator } from "@/types/flow"; import { getTableColumns } from "@/lib/api/tableManagement"; import { cn } from "@/lib/utils"; interface FlowConditionBuilderProps { flowId: number; tableName?: string; // 조회할 테이블명 dbSourceType?: "internal" | "external"; // DB 소스 타입 dbConnectionId?: number; // 외부 DB 연결 ID condition?: FlowConditionGroup; onChange: (condition: FlowConditionGroup | undefined) => void; } const OPERATORS: { value: ConditionOperator; label: string }[] = [ { value: "equals", label: "같음 (=)" }, { value: "not_equals", label: "같지 않음 (!=)" }, { value: "greater_than", label: "보다 큼 (>)" }, { value: "less_than", label: "보다 작음 (<)" }, { value: "greater_than_or_equal", label: "이상 (>=)" }, { value: "less_than_or_equal", label: "이하 (<=)" }, { value: "in", label: "포함 (IN)" }, { value: "not_in", label: "제외 (NOT IN)" }, { value: "like", label: "유사 (LIKE)" }, { value: "not_like", label: "유사하지 않음 (NOT LIKE)" }, { value: "is_null", label: "NULL" }, { value: "is_not_null", label: "NOT NULL" }, ]; export function FlowConditionBuilder({ flowId, tableName, dbSourceType = "internal", dbConnectionId, condition, onChange, }: FlowConditionBuilderProps) { const [columns, setColumns] = useState([]); const [loadingColumns, setLoadingColumns] = useState(false); const [conditionType, setConditionType] = useState<"AND" | "OR">(condition?.type || "AND"); const [conditions, setConditions] = useState(condition?.conditions || []); const [columnComboboxOpen, setColumnComboboxOpen] = useState>({}); // condition prop이 변경될 때 상태 동기화 useEffect(() => { if (condition) { setConditionType(condition.type || "AND"); setConditions(condition.conditions || []); } else { setConditionType("AND"); setConditions([]); } }, [condition]); // 테이블 컬럼 로드 - 내부/외부 DB 모두 지원 useEffect(() => { if (!tableName) { setColumns([]); return; } const loadColumns = async () => { try { setLoadingColumns(true); console.log("🔍 [FlowConditionBuilder] Loading columns:", { tableName, dbSourceType, dbConnectionId, }); // 외부 DB인 경우 if (dbSourceType === "external" && dbConnectionId) { const token = localStorage.getItem("authToken"); if (!token) { console.warn("토큰이 없습니다. 외부 DB 컬럼 목록을 조회할 수 없습니다."); setColumns([]); return; } const response = await fetch( `/api/multi-connection/connections/${dbConnectionId}/tables/${tableName}/columns`, { credentials: "include", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }, ).catch((err) => { console.warn("외부 DB 컬럼 fetch 실패:", err); return null; }); if (response && response.ok) { const result = await response.json(); console.log("✅ [FlowConditionBuilder] External columns response:", result); if (result.success && result.data) { const columnList = Array.isArray(result.data) ? result.data.map((col: any) => ({ column_name: col.column_name || col.columnName || col.name, data_type: col.data_type || col.dataType || col.type, })) : []; console.log("✅ Setting external columns:", columnList.length, "items"); setColumns(columnList); } else { console.warn("❌ No data in external columns response"); setColumns([]); } } else { console.warn(`외부 DB 컬럼 조회 실패: ${response?.status}`); setColumns([]); } } else { // 내부 DB인 경우 (기존 로직) const response = await getTableColumns(tableName); console.log("📦 [FlowConditionBuilder] Internal columns response:", response); if (response.success && response.data?.columns) { const columnArray = Array.isArray(response.data.columns) ? response.data.columns : []; console.log("✅ Setting internal columns:", columnArray.length, "items"); setColumns(columnArray); } else { console.error("❌ Failed to load internal columns:", response.message); setColumns([]); } } } catch (error) { console.error("❌ Exception loading columns:", error); setColumns([]); } finally { setLoadingColumns(false); } }; loadColumns(); }, [tableName, dbSourceType, dbConnectionId]); // 조건 변경 시 부모에 전달 useEffect(() => { if (conditions.length === 0) { onChange(undefined); } else { onChange({ type: conditionType, conditions, }); } }, [conditionType, conditions]); // 조건 추가 const addCondition = () => { setConditions([ ...conditions, { column: "", operator: "equals", value: "", }, ]); }; // 조건 수정 const updateCondition = (index: number, field: keyof FlowCondition, value: any) => { const newConditions = [...conditions]; newConditions[index] = { ...newConditions[index], [field]: value, }; setConditions(newConditions); }; // 조건 삭제 const removeCondition = (index: number) => { setConditions(conditions.filter((_, i) => i !== index)); }; // value가 필요 없는 연산자 체크 const needsValue = (operator: ConditionOperator) => { return operator !== "is_null" && operator !== "is_not_null"; }; return (
{/* 조건 타입 선택 */}
{/* 조건 목록 */}
{conditions.length === 0 ? (
조건이 없습니다
조건을 추가하여 데이터를 필터링하세요
) : ( conditions.map((cond, index) => (
{/* 조건 번호 및 삭제 버튼 */}
조건 {index + 1}
{/* 컬럼 선택 */}
{loadingColumns ? ( ) : !Array.isArray(columns) || columns.length === 0 ? ( updateCondition(index, "column", e.target.value)} placeholder="테이블을 먼저 선택하세요" className="h-8" /> ) : ( setColumnComboboxOpen({ ...columnComboboxOpen, [index]: open })} > 컬럼을 찾을 수 없습니다. {columns.map((col, idx) => { const columnName = col.column_name || col.columnName || ""; const dataType = col.data_type || col.dataType || ""; const displayName = col.displayName || col.display_name || columnName; return ( { updateCondition(index, "column", currentValue); setColumnComboboxOpen({ ...columnComboboxOpen, [index]: false }); }} className="text-xs" >
{displayName} ({dataType})
); })}
)}
{/* 연산자 선택 */}
{/* 값 입력 */} {needsValue(cond.operator) && (
updateCondition(index, "value", e.target.value)} placeholder="값 입력" className="h-8" /> {(cond.operator === "in" || cond.operator === "not_in") && (

쉼표(,)로 구분하여 여러 값 입력

)}
)}
)) )}
{/* 조건 추가 버튼 */} {/* 조건 요약 */} {conditions.length > 0 && (
조건 요약:
{conditions.map((cond, index) => (
{cond.column} {OPERATORS.find((op) => op.value === cond.operator)?.label} {needsValue(cond.operator) && {cond.value}} {index < conditions.length - 1 && {conditionType}}
))}
)}
); }