diff --git a/src/modules/manager/workflowTemplates/components/VisualisetTab.tsx b/src/modules/manager/workflowTemplates/components/VisualisetTab.tsx index debb0fc..ddeb5ad 100644 --- a/src/modules/manager/workflowTemplates/components/VisualisetTab.tsx +++ b/src/modules/manager/workflowTemplates/components/VisualisetTab.tsx @@ -89,6 +89,24 @@ const VisualiserTab: React.FC = ({ return 0; } + // If multiple predecessors, position task at the average of their columns + if (predecessors.length > 1) { + const predColumns = predecessors + .map((predGuid) => { + const predTask = byGuid.get(predGuid); + return predTask ? getColumn(predTask) : 0; + }) + .filter((col) => col !== undefined); + + if (predColumns.length > 0) { + const avgColumn = + predColumns.reduce((sum, col) => sum + col, 0) / predColumns.length; + const column = Math.round(avgColumn); + columnByGuid.set(guid, column); + return column; + } + } + // Get predecessor column and find which child index this is const predTask = byGuid.get(predecessors[0]); if (!predTask) { @@ -231,19 +249,130 @@ const VisualiserTab: React.FC = ({ if (numColumns <= 1) return [50]; return levelTasks.map((task) => { + const predecessors = (task.config.predecessors as string[]) ?? []; const taskColumn = taskColumnMap.current.get(task.config.guid as string) ?? 0; + + if (predecessors.length > 1) { + const predColumns = predecessors + .map((predGuid) => taskColumnMap.current.get(predGuid)) + .filter((col) => col !== undefined) as number[]; + + if (predColumns.length > 0) { + const avgPredColumn = + predColumns.reduce((sum, col) => sum + col, 0) / predColumns.length; + const gridCol = avgPredColumn - columnRange.min; + // Map averaged column to percentage for centered merges + return ((gridCol + 0.5) / numColumns) * 100; + } + } + const gridCol = taskColumn - columnRange.min; // Map grid column to percentage (e.g., 2 columns: col 0 = 25%, col 1 = 75%) return ((gridCol + 0.5) / numColumns) * 100; }); }; + const getPredecessorPositionsByColumn = ( + levelTasks: CreateWorkflowTemplateVersion["tasks"][], + ): number[] => { + const numColumns = columnRange.max - columnRange.min + 1; + if (numColumns <= 1) return [50]; + + const predColumns = new Set(); + + levelTasks.forEach((task) => { + const preds = (task.config.predecessors as string[]) ?? []; + preds.forEach((predGuid) => { + const predColumn = taskColumnMap.current.get(predGuid); + if (predColumn !== undefined) { + predColumns.add(predColumn); + } + }); + }); + + return Array.from(predColumns).map((predColumn) => { + const gridCol = predColumn - columnRange.min; + return ((gridCol + 0.5) / numColumns) * 100; + }); + }; + const renderConnector = ( count: number, positions?: number[], predecessorPositions?: number[], + currentLevelTasks?: CreateWorkflowTemplateVersion["tasks"][], ) => { + // Check if any task in this level has multiple predecessors from different columns + const hasMultiplePredecessorColumns = + currentLevelTasks && + currentLevelTasks.some((task) => { + const preds = (task.config.predecessors as string[]) ?? []; + if (preds.length <= 1) return false; + + const predColumns = preds + .map((predGuid) => taskColumnMap.current.get(predGuid)) + .filter((col) => col !== undefined) as number[]; + + return new Set(predColumns).size > 1; // Multiple different columns + }); + + // If we have a merging scenario (multiple predecessors converging) + if ( + hasMultiplePredecessorColumns && + predecessorPositions && + predecessorPositions.length > 1 && + positions + ) { + const minPredX = Math.min(...predecessorPositions); + const maxPredX = Math.max(...predecessorPositions); + + return ( + + ); + } + if (count <= 1) { // For a single child, use predecessor position if available let startX = 50; @@ -431,7 +560,8 @@ const VisualiserTab: React.FC = ({ renderConnector( levels[0].length, getConnectorPositionsByColumn(levels[0]), - undefined, + getPredecessorPositionsByColumn(levels[0]), + levels[0], )} {levels.map((level, index) => ( @@ -461,6 +591,10 @@ const VisualiserTab: React.FC = ({ )?.length; const numColumns = columnRange.max - columnRange.min + 1; + const predecessors = task.config.predecessors as + | string[] + | undefined; + let styleObj: React.CSSProperties = {}; if (isRootTask && numColumns > 1) { @@ -470,6 +604,28 @@ const VisualiserTab: React.FC = ({ display: "flex", justifyContent: "center", }; + } else if (predecessors && predecessors.length > 1) { + // Tasks with multiple predecessors - span across predecessor columns + const predColumns = predecessors + .map((predGuid) => taskColumnMap.current.get(predGuid)) + .filter((col) => col !== undefined) as number[]; + + if (predColumns.length > 1) { + const minPredCol = Math.min(...predColumns); + const maxPredCol = Math.max(...predColumns); + const startGridCol = minPredCol - columnRange.min + 1; + const endGridCol = maxPredCol - columnRange.min + 2; + + styleObj = { + gridColumn: `${startGridCol} / ${endGridCol}`, + display: "flex", + justifyContent: "center", + }; + } else { + // Fallback to normal positioning + const gridColumn = taskColumn - columnRange.min + 1; + styleObj = { gridColumn }; + } } else { // Child tasks positioned in their specific column const gridColumn = taskColumn - columnRange.min + 1; @@ -503,12 +659,14 @@ const VisualiserTab: React.FC = ({ ? renderConnector( levels[index + 1].length, getConnectorPositionsByColumn(levels[index + 1]), - getConnectorPositionsByColumn(levels[index]), + getPredecessorPositionsByColumn(levels[index + 1]), + levels[index + 1], ) - : renderConnector(1, undefined, undefined)} + : renderConnector(1, undefined, undefined, undefined)} ))} - {levels.length === 0 && renderConnector(1)} + {levels.length === 0 && + renderConnector(1, undefined, undefined, undefined)}
End