"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 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; // 컬럼 정보 (중앙에서 관리) 🔧 추가 fromColumns?: ColumnInfo[]; toColumns?: ColumnInfo[]; // 제어 조건 관련 controlConditions: any[]; onUpdateControlCondition: (index: number, condition: any) => void; onDeleteControlCondition: (index: number) => void; onAddControlCondition: () => void; // 액션 그룹 관련 actionGroups: ActionGroup[]; groupsLogicalOperator?: "AND" | "OR"; 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; onSetGroupsLogicalOperator?: (operator: "AND" | "OR") => void; // 필드 매핑 관련 fieldMappings: FieldMapping[]; onCreateMapping: (fromField: ColumnInfo, toField: ColumnInfo) => void; onDeleteMapping: (mappingId: string) => void; // 네비게이션 onNext: () => void; onBack: () => void; // 컬럼 로드 액션 onLoadColumns?: () => Promise; } /** * 🎯 4단계: 통합된 멀티 액션 설정 * - 제어 조건 설정 * - 여러 액션 그룹 관리 * - AND/OR 논리 연산자 * - 액션별 조건 설정 * - INSERT 액션 시 컬럼 매핑 */ const MultiActionConfigStep: React.FC = ({ fromTable, toTable, fromConnection, toConnection, fromColumns = [], // 🔧 중앙에서 관리되는 컬럼 정보 toColumns = [], // 🔧 중앙에서 관리되는 컬럼 정보 controlConditions, onUpdateControlCondition, onDeleteControlCondition, onAddControlCondition, actionGroups, groupsLogicalOperator = "AND", onUpdateActionGroup, onDeleteActionGroup, onAddActionGroup, onAddActionToGroup, onUpdateActionInGroup, onDeleteActionFromGroup, onSetGroupsLogicalOperator, fieldMappings, onCreateMapping, onDeleteMapping, onNext, onBack, onLoadColumns, }) => { const [expandedGroups, setExpandedGroups] = useState>(new Set(["group_1"])); // 첫 번째 그룹은 기본 열림 const [activeTab, setActiveTab] = useState<"control" | "actions" | "mapping">("control"); // 현재 활성 탭 // 컬럼 로딩 상태 확인 const isColumnsLoaded = fromColumns.length > 0 && toColumns.length > 0; // 컴포넌트 마운트 시 컬럼 로드 useEffect(() => { if (!isColumnsLoaded && fromConnection && toConnection && fromTable && toTable && onLoadColumns) { console.log("🔄 MultiActionConfigStep: 컬럼 로드 시작"); onLoadColumns(); } }, [isColumnsLoaded, fromConnection?.id, toConnection?.id, fromTable?.tableName, toTable?.tableName]); // 그룹 확장/축소 토글 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-primary/20 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: "액션 그룹 및 실행 조건" }, ]; 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.length > 1 && (

그룹 간 실행 조건

onSetGroupsLogicalOperator?.("AND")} className="h-4 w-4" />
onSetGroupsLogicalOperator?.("OR")} className="h-4 w-4" />
)} {/* 액션 그룹 목록 */}
{actionGroups.map((group, groupIndex) => (
{/* 그룹 간 논리 연산자 표시 (첫 번째 그룹 제외) */} {groupIndex > 0 && (
{groupsLogicalOperator}
)}
{/* 그룹 헤더 */} 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 && ( )}
{/* 액션 조건 설정 */} {isColumnsLoaded ? ( { // 필드값 설정용: FieldValueMapping 타입만 필터링 const fieldValueMappings = (action.fieldMappings || []).filter( (mapping) => mapping.valueType && // valueType이 있고 !mapping.fromField && // fromField가 없고 !mapping.toField, // toField가 없으면 FieldValueMapping ); console.log("📋 ActionConditionBuilder에 전달되는 필드값 설정:", { allMappings: action.fieldMappings, filteredFieldValueMappings: fieldValueMappings, }); return fieldValueMappings; })()} columnMappings={ // 컬럼 매핑용: FieldMapping 타입만 필터링 (action.fieldMappings || []).filter( (mapping) => mapping.fromField && mapping.toField && mapping.fromField.columnName && mapping.toField.columnName, ) } onConditionsChange={(conditions) => onUpdateActionInGroup(group.id, action.id, { conditions }) } onFieldMappingsChange={(newFieldMappings) => { // 필드값 설정만 업데이트, 컬럼 매핑은 유지 const existingColumnMappings = (action.fieldMappings || []).filter( (mapping) => mapping.fromField && mapping.toField && mapping.fromField.columnName && mapping.toField.columnName, ); console.log("🔄 필드값 설정 업데이트:", { existingColumnMappings, newFieldMappings, combined: [...existingColumnMappings, ...newFieldMappings], }); onUpdateActionInGroup(group.id, action.id, { fieldMappings: [...existingColumnMappings, ...newFieldMappings], }); }} /> ) : (
컬럼 정보를 불러오는 중...
)} {/* INSERT 액션일 때만 필드 매핑 UI 표시 */} {action.actionType === "insert" && isColumnsLoaded && (
필드 매핑
{action.fieldMappings?.length || 0}개 매핑
{/* 컬럼 매핑 캔버스 */}
mapping.fromField && mapping.toField && mapping.fromField.columnName && mapping.toField.columnName, ) } onCreateMapping={(fromField, toField) => { const newMapping = { id: `${fromField.columnName}_to_${toField.columnName}_${Date.now()}`, fromField, toField, isValid: true, validationMessage: undefined, }; // 기존 필드값 설정은 유지하고 새 컬럼 매핑만 추가 const existingFieldValueMappings = (action.fieldMappings || []).filter( (mapping) => mapping.valueType && // valueType이 있고 !mapping.fromField && // fromField가 없고 !mapping.toField, // toField가 없으면 FieldValueMapping ); const existingColumnMappings = (action.fieldMappings || []).filter( (mapping) => mapping.fromField && mapping.toField && mapping.fromField.columnName && mapping.toField.columnName, ); onUpdateActionInGroup(group.id, action.id, { fieldMappings: [ ...existingFieldValueMappings, ...existingColumnMappings, newMapping, ], }); }} onDeleteMapping={(mappingId) => { // 컬럼 매핑만 삭제하고 필드값 설정은 유지 const remainingMappings = (action.fieldMappings || []).filter( (mapping) => mapping.id !== mappingId, ); onUpdateActionInGroup(group.id, action.id, { fieldMappings: remainingMappings, }); }} />
{/* 매핑되지 않은 필드 처리 옵션 */}
매핑되지 않은 필드 처리
)}
))}
{/* 그룹 로직 설명 */}
{group.logicalOperator} 조건 그룹
{group.logicalOperator === "AND" ? "이 그룹의 모든 액션이 실행 가능한 조건일 때만 실행됩니다." : "이 그룹의 액션 중 하나라도 실행 가능한 조건이면 해당 액션만 실행됩니다."}
))}
)}
{/* 하단 네비게이션 */}
{actionGroups.filter((g) => g.isEnabled).length}개 그룹, 총{" "} {actionGroups.reduce((sum, g) => sum + g.actions.filter((a) => a.isEnabled).length, 0)}개 액션
); }; export default MultiActionConfigStep;