"use client"; import React, { useState, useEffect } from "react"; import { CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Separator } from "@/components/ui/separator"; import { ChevronDown, ChevronRight, Plus, Trash2, Copy, Settings2, ArrowLeft, Save, Play, AlertTriangle, } from "lucide-react"; import { toast } from "sonner"; // API import import { getColumnsFromConnection } from "@/lib/api/multiConnection"; // 타입 import import { ColumnInfo, Connection, TableInfo } from "@/lib/types/multiConnection"; import { ActionGroup, SingleAction, FieldMapping } from "../types/redesigned"; // 컴포넌트 import import ActionConditionBuilder from "./ActionConfig/ActionConditionBuilder"; import FieldMappingCanvas from "./VisualMapping/FieldMappingCanvas"; interface MultiActionConfigStepProps { fromTable?: TableInfo; toTable?: TableInfo; fromConnection?: Connection; toConnection?: Connection; // 제어 조건 관련 controlConditions: any[]; onUpdateControlCondition: (index: number, condition: any) => void; onDeleteControlCondition: (index: number) => void; onAddControlCondition: () => void; // 액션 그룹 관련 actionGroups: ActionGroup[]; onUpdateActionGroup: (groupId: string, updates: Partial) => void; onDeleteActionGroup: (groupId: string) => void; onAddActionGroup: () => void; onAddActionToGroup: (groupId: string) => void; onUpdateActionInGroup: (groupId: string, actionId: string, updates: Partial) => void; onDeleteActionFromGroup: (groupId: string, actionId: string) => void; // 필드 매핑 관련 fieldMappings: FieldMapping[]; onCreateMapping: (fromField: ColumnInfo, toField: ColumnInfo) => void; onDeleteMapping: (mappingId: string) => void; // 네비게이션 onNext: () => void; onBack: () => void; } /** * 🎯 4단계: 통합된 멀티 액션 설정 * - 제어 조건 설정 * - 여러 액션 그룹 관리 * - AND/OR 논리 연산자 * - 액션별 조건 설정 * - INSERT 액션 시 컬럼 매핑 */ const MultiActionConfigStep: React.FC = ({ fromTable, toTable, fromConnection, toConnection, controlConditions, onUpdateControlCondition, onDeleteControlCondition, onAddControlCondition, actionGroups, onUpdateActionGroup, onDeleteActionGroup, onAddActionGroup, onAddActionToGroup, onUpdateActionInGroup, onDeleteActionFromGroup, fieldMappings, onCreateMapping, onDeleteMapping, onNext, onBack, }) => { const [fromColumns, setFromColumns] = useState([]); const [toColumns, setToColumns] = useState([]); const [isLoading, setIsLoading] = useState(false); const [expandedGroups, setExpandedGroups] = useState>(new Set(["group_1"])); // 첫 번째 그룹은 기본 열림 const [activeTab, setActiveTab] = useState<"control" | "actions" | "mapping">("control"); // 현재 활성 탭 // 컬럼 정보 로드 useEffect(() => { const loadColumns = async () => { if (!fromConnection || !toConnection || !fromTable || !toTable) { return; } try { setIsLoading(true); const [fromCols, toCols] = await Promise.all([ getColumnsFromConnection(fromConnection.id, fromTable.tableName), getColumnsFromConnection(toConnection.id, toTable.tableName), ]); setFromColumns(Array.isArray(fromCols) ? fromCols : []); setToColumns(Array.isArray(toCols) ? toCols : []); } catch (error) { console.error("❌ 컬럼 정보 로드 실패:", error); toast.error("필드 정보를 불러오는데 실패했습니다."); } finally { setIsLoading(false); } }; loadColumns(); }, [fromConnection, toConnection, fromTable, toTable]); // 그룹 확장/축소 토글 const toggleGroupExpansion = (groupId: string) => { setExpandedGroups((prev) => { const newSet = new Set(prev); if (newSet.has(groupId)) { newSet.delete(groupId); } else { newSet.add(groupId); } return newSet; }); }; // 액션 타입별 아이콘 const getActionTypeIcon = (actionType: string) => { switch (actionType) { case "insert": return "➕"; case "update": return "✏️"; case "delete": return "🗑️"; case "upsert": return "🔄"; default: return "⚙️"; } }; // 논리 연산자별 색상 const getLogicalOperatorColor = (operator: string) => { switch (operator) { case "AND": return "bg-blue-100 text-blue-800"; case "OR": return "bg-orange-100 text-orange-800"; default: return "bg-gray-100 text-gray-800"; } }; // INSERT 액션이 있는지 확인 const hasInsertActions = actionGroups.some((group) => group.actions.some((action) => action.actionType === "insert" && action.isEnabled), ); // 탭 정보 const tabs = [ { id: "control" as const, label: "제어 조건", icon: "🎯", description: "전체 제어 실행 조건" }, { id: "actions" as const, label: "액션 설정", icon: "⚙️", description: "액션 그룹 및 실행 조건" }, ...(hasInsertActions ? [{ id: "mapping" as const, label: "컬럼 매핑", icon: "🔗", description: "INSERT 액션 필드 매핑" }] : []), ]; return ( <> 4단계: 액션 및 매핑 설정

제어 조건, 액션 그룹, 필드 매핑을 설정하세요

{/* 탭 헤더 */}
{tabs.map((tab) => ( ))}
{/* 탭 설명 */}

{tabs.find((tab) => tab.id === activeTab)?.description}

{/* 탭별 컨텐츠 */}
{activeTab === "control" && (
{/* 제어 조건 섹션 */}

제어 조건

{controlConditions.length === 0 ? (

제어 조건이 없습니다

조건을 추가하면 해당 조건이 충족될 때만 액션이 실행됩니다

) : (
{controlConditions.map((condition, index) => (
조건 {index + 1}
{/* 여기에 조건 편집 컴포넌트 추가 */}
조건 설정: {JSON.stringify(condition)}
))}
)}
)} {activeTab === "actions" && (
{/* 액션 그룹 헤더 */}

액션 그룹

{actionGroups.filter((g) => g.isEnabled).length}개 활성화
{/* 액션 그룹 목록 */}
{actionGroups.map((group, groupIndex) => (
{/* 그룹 헤더 */} toggleGroupExpansion(group.id)} >
{expandedGroups.has(group.id) ? ( ) : ( )}
onUpdateActionGroup(group.id, { name: e.target.value })} className="h-8 w-40" onClick={(e) => e.stopPropagation()} /> {group.logicalOperator} {group.actions.length}개 액션
{/* 그룹 논리 연산자 선택 */} {/* 그룹 활성화/비활성화 */} onUpdateActionGroup(group.id, { isEnabled: checked })} onClick={(e) => e.stopPropagation()} /> {/* 그룹 삭제 */} {actionGroups.length > 1 && ( )}
{/* 그룹 내용 */}
{/* 액션 추가 버튼 */}
{/* 액션 목록 */}
{group.actions.map((action, actionIndex) => (
{/* 액션 헤더 */}
{getActionTypeIcon(action.actionType)} onUpdateActionInGroup(group.id, action.id, { name: e.target.value }) } className="h-8 w-32" />
onUpdateActionInGroup(group.id, action.id, { isEnabled: checked }) } /> {group.actions.length > 1 && ( )}
{/* 액션 조건 설정 */} onUpdateActionInGroup(group.id, action.id, { conditions }) } onFieldMappingsChange={(fieldMappings) => onUpdateActionInGroup(group.id, action.id, { fieldMappings }) } />
))}
{/* 그룹 로직 설명 */}
{group.logicalOperator} 조건 그룹
{group.logicalOperator === "AND" ? "이 그룹의 모든 액션이 실행 가능한 조건일 때만 실행됩니다." : "이 그룹의 액션 중 하나라도 실행 가능한 조건이면 해당 액션만 실행됩니다."}
{/* 그룹 간 연결선 (마지막 그룹이 아닌 경우) */} {groupIndex < actionGroups.length - 1 && (
다음 그룹
)}
))}
)} {activeTab === "mapping" && hasInsertActions && (
{/* 컬럼 매핑 헤더 */}

컬럼 매핑

{fieldMappings.length}개 매핑
INSERT 액션에 필요한 필드들을 매핑하세요
{/* 컬럼 매핑 캔버스 */} {isLoading ? (
컬럼 정보를 불러오는 중...
) : fromColumns.length > 0 && toColumns.length > 0 ? (
) : (
컬럼 정보를 찾을 수 없습니다.
FROM 컬럼: {fromColumns.length}개, TO 컬럼: {toColumns.length}개
)} {/* 매핑되지 않은 필드 처리 옵션 */}

매핑되지 않은 필드 처리

)}
{/* 하단 네비게이션 */}
{actionGroups.filter((g) => g.isEnabled).length}개 그룹, 총{" "} {actionGroups.reduce((sum, g) => sum + g.actions.filter((a) => a.isEnabled).length, 0)}개 액션
); }; export default MultiActionConfigStep;