import React, { useEffect, useState, useCallback } from "react"; import templateVersionsService, { CreateWorkflowTemplateVersion, TaskDefinition, TaskMetadata, } from "../services/WorkflowTemplateService"; import TaskList from "./TaskList"; import { TaskEditor } from "./TasksEditor"; interface TasksTabProps { data: CreateWorkflowTemplateVersion; errors: Record; isEditMode: boolean; onTasksChange: (name: string, value: TaskDefinition[]) => void; onValidate: (taskId: string, isValid: boolean) => void; taskValidation: Record; } const TasksTab: React.FC = ({ data, errors, isEditMode, onTasksChange, onValidate, taskValidation, }) => { const tasks = data.tasks; const [selectedTask, setSelectedTask] = useState(null); const [pendingSelectGuid, setPendingSelectGuid] = useState( null, ); useEffect(() => { // If we have a pending task GUID to select, find it and select it if (pendingSelectGuid) { const { task: foundTask } = findTaskAndSiblings( pendingSelectGuid, tasks, null, ); if (foundTask) { setSelectedTask(foundTask); setPendingSelectGuid(null); } return; } // Don't override user selection if (selectedTask) return; if (tasks.length === 0) { setSelectedTask(null); return; } // Find first invalid task const firstInvalid = tasks.find( (t) => taskValidation[t.config.guid as string] === false, ); if (firstInvalid) { setSelectedTask(firstInvalid); return; } // Otherwise select first task setSelectedTask(tasks[0]); }, [tasks, taskValidation, selectedTask, pendingSelectGuid]); const handleTasksChange = React.useCallback( (newTasks: TaskDefinition[]) => { // Update the parent form state onTasksChange("tasks", newTasks); }, [onTasksChange], ); const findTaskAndSiblings = ( targetGuid: string, source: TaskDefinition[], parent: TaskDefinition | null = null, ): { task: TaskDefinition | null; siblings: TaskDefinition[]; parent: TaskDefinition | null; } => { for (const task of source) { if (task.config.guid === targetGuid) { return { task, siblings: source, parent }; } const childTasks = (task.config.tasks as TaskDefinition[]) ?? []; if (childTasks.length === 0) continue; const result = findTaskAndSiblings(targetGuid, childTasks, task); if (result.task) { return result; } } return { task: null, siblings: [], parent: null }; }; const handleTaskEditorChange = React.useCallback( (updatedTask: TaskDefinition) => { const { siblings } = findTaskAndSiblings( updatedTask.config.guid as string, tasks, null, ); if (siblings.length === 0) { return; } const updateTaskRecursive = ( source: TaskDefinition[], ): TaskDefinition[] => { return source.map((t) => { if (t.config.guid === updatedTask.config.guid) { return updatedTask; } const childTasks = (t.config.tasks as TaskDefinition[]) ?? []; if (childTasks.length === 0) { return t; } const updatedChildren = updateTaskRecursive(childTasks); if (updatedChildren === childTasks) { return t; } return { ...t, config: { ...t.config, tasks: updatedChildren, }, }; }); }; const newTasks = updateTaskRecursive(tasks); handleTasksChange(newTasks); // Use the updated object from the array, not the raw updatedTask const { task: updatedFromArray } = findTaskAndSiblings( updatedTask.config.guid as string, newTasks, ); if (updatedFromArray) { setSelectedTask(updatedFromArray); } }, [tasks, handleTasksChange], ); const handleTaskDelete = React.useCallback( (taskId: string) => { const removeTaskRecursive = ( source: TaskDefinition[], ): { tasks: TaskDefinition[]; removed: boolean } => { let removed = false; const updated = source.flatMap((task) => { if (task.config.guid === taskId) { removed = true; return []; } const childTasks = (task.config.tasks as TaskDefinition[]) ?? []; if (childTasks.length === 0) { return [task]; } const childResult = removeTaskRecursive(childTasks); if (!childResult.removed) { return [task]; } removed = true; return [ { ...task, config: { ...task.config, tasks: childResult.tasks, }, }, ]; }); return { tasks: updated, removed }; }; const removePredecessorRecursive = ( source: TaskDefinition[], ): TaskDefinition[] => { return source.map((task) => { const childTasks = (task.config.tasks as TaskDefinition[]) ?? []; const updatedChildren = childTasks.length > 0 ? removePredecessorRecursive(childTasks) : childTasks; const predecessors = task.config.predecessors as string[] | undefined; const updatedPredecessors = predecessors ? predecessors.filter((d) => d !== taskId) : predecessors; const childrenChanged = updatedChildren !== childTasks; const predecessorsChanged = updatedPredecessors !== predecessors; if (!childrenChanged && !predecessorsChanged) { return task; } const updatedConfig = { ...task.config } as Record; if (childrenChanged) { updatedConfig.tasks = updatedChildren; } if (predecessorsChanged) { updatedConfig.predecessors = updatedPredecessors; } return { ...task, config: updatedConfig, }; }); }; const deleteResult = removeTaskRecursive(tasks); if (!deleteResult.removed) { return; } const newTasks = removePredecessorRecursive(deleteResult.tasks); onValidate(taskId, true); // Clear validation state for deleted task setSelectedTask(null); handleTasksChange(newTasks); }, [tasks, handleTasksChange], ); return (
{selectedTask && (
{ setPendingSelectGuid(taskGuid); }} />
)}
); }; export default TasksTab;