658 lines
21 KiB
JavaScript
658 lines
21 KiB
JavaScript
import path from "path";
|
|
import { log, readJSON, writeJSON } from "../utils.js";
|
|
import { isTaskDependentOn } from "../task-manager.js";
|
|
import generateTaskFiles from "./generate-task-files.js";
|
|
|
|
/**
|
|
* Move one or more tasks/subtasks to new positions
|
|
* @param {string} tasksPath - Path to tasks.json file
|
|
* @param {string} sourceId - ID(s) of the task/subtask to move (e.g., '5' or '5.2' or '5,6,7')
|
|
* @param {string} destinationId - ID(s) of the destination (e.g., '7' or '7.3' or '7,8,9')
|
|
* @param {boolean} generateFiles - Whether to regenerate task files after moving
|
|
* @returns {Object} Result object with moved task details
|
|
*/
|
|
async function moveTask(
|
|
tasksPath,
|
|
sourceId,
|
|
destinationId,
|
|
generateFiles = true
|
|
) {
|
|
// Check if we have comma-separated IDs (multiple moves)
|
|
const sourceIds = sourceId.split(",").map((id) => id.trim());
|
|
const destinationIds = destinationId.split(",").map((id) => id.trim());
|
|
|
|
// If multiple IDs, validate they match in count
|
|
if (sourceIds.length > 1 || destinationIds.length > 1) {
|
|
if (sourceIds.length !== destinationIds.length) {
|
|
throw new Error(
|
|
`Number of source IDs (${sourceIds.length}) must match number of destination IDs (${destinationIds.length})`
|
|
);
|
|
}
|
|
|
|
// Perform multiple moves
|
|
return await moveMultipleTasks(
|
|
tasksPath,
|
|
sourceIds,
|
|
destinationIds,
|
|
generateFiles
|
|
);
|
|
}
|
|
|
|
// Single move - use existing logic
|
|
return await moveSingleTask(
|
|
tasksPath,
|
|
sourceId,
|
|
destinationId,
|
|
generateFiles
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Move multiple tasks/subtasks to new positions
|
|
* @param {string} tasksPath - Path to tasks.json file
|
|
* @param {string[]} sourceIds - Array of source IDs
|
|
* @param {string[]} destinationIds - Array of destination IDs
|
|
* @param {boolean} generateFiles - Whether to regenerate task files after moving
|
|
* @returns {Object} Result object with moved task details
|
|
*/
|
|
async function moveMultipleTasks(
|
|
tasksPath,
|
|
sourceIds,
|
|
destinationIds,
|
|
generateFiles = true
|
|
) {
|
|
try {
|
|
log(
|
|
"info",
|
|
`Moving multiple tasks/subtasks: ${sourceIds.join(", ")} to ${destinationIds.join(", ")}...`
|
|
);
|
|
|
|
const results = [];
|
|
|
|
// Perform moves one by one, but don't regenerate files until the end
|
|
for (let i = 0; i < sourceIds.length; i++) {
|
|
const result = await moveSingleTask(
|
|
tasksPath,
|
|
sourceIds[i],
|
|
destinationIds[i],
|
|
false
|
|
);
|
|
results.push(result);
|
|
}
|
|
|
|
// Generate task files once at the end if requested
|
|
if (generateFiles) {
|
|
log("info", "Regenerating task files...");
|
|
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
|
}
|
|
|
|
return {
|
|
message: `Successfully moved ${sourceIds.length} tasks/subtasks`,
|
|
moves: results,
|
|
};
|
|
} catch (error) {
|
|
log("error", `Error moving multiple tasks/subtasks: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Move a single task or subtask to a new position
|
|
* @param {string} tasksPath - Path to tasks.json file
|
|
* @param {string} sourceId - ID of the task/subtask to move (e.g., '5' or '5.2')
|
|
* @param {string} destinationId - ID of the destination (e.g., '7' or '7.3')
|
|
* @param {boolean} generateFiles - Whether to regenerate task files after moving
|
|
* @returns {Object} Result object with moved task details
|
|
*/
|
|
async function moveSingleTask(
|
|
tasksPath,
|
|
sourceId,
|
|
destinationId,
|
|
generateFiles = true
|
|
) {
|
|
try {
|
|
log("info", `Moving task/subtask ${sourceId} to ${destinationId}...`);
|
|
|
|
// Read the existing tasks
|
|
const data = readJSON(tasksPath);
|
|
if (!data || !data.tasks) {
|
|
throw new Error(`Invalid or missing tasks file at ${tasksPath}`);
|
|
}
|
|
|
|
// Parse source ID to determine if it's a task or subtask
|
|
const isSourceSubtask = sourceId.includes(".");
|
|
let sourceTask,
|
|
sourceParentTask,
|
|
sourceSubtask,
|
|
sourceTaskIndex,
|
|
sourceSubtaskIndex;
|
|
|
|
// Parse destination ID to determine the target
|
|
const isDestinationSubtask = destinationId.includes(".");
|
|
let destTask, destParentTask, destSubtask, destTaskIndex, destSubtaskIndex;
|
|
|
|
// Validate source exists
|
|
if (isSourceSubtask) {
|
|
// Source is a subtask
|
|
const [parentIdStr, subtaskIdStr] = sourceId.split(".");
|
|
const parentIdNum = parseInt(parentIdStr, 10);
|
|
const subtaskIdNum = parseInt(subtaskIdStr, 10);
|
|
|
|
sourceParentTask = data.tasks.find((t) => t.id === parentIdNum);
|
|
if (!sourceParentTask) {
|
|
throw new Error(`Source parent task with ID ${parentIdNum} not found`);
|
|
}
|
|
|
|
if (
|
|
!sourceParentTask.subtasks ||
|
|
sourceParentTask.subtasks.length === 0
|
|
) {
|
|
throw new Error(`Source parent task ${parentIdNum} has no subtasks`);
|
|
}
|
|
|
|
sourceSubtaskIndex = sourceParentTask.subtasks.findIndex(
|
|
(st) => st.id === subtaskIdNum
|
|
);
|
|
if (sourceSubtaskIndex === -1) {
|
|
throw new Error(`Source subtask ${sourceId} not found`);
|
|
}
|
|
|
|
sourceSubtask = { ...sourceParentTask.subtasks[sourceSubtaskIndex] };
|
|
} else {
|
|
// Source is a task
|
|
const sourceIdNum = parseInt(sourceId, 10);
|
|
sourceTaskIndex = data.tasks.findIndex((t) => t.id === sourceIdNum);
|
|
if (sourceTaskIndex === -1) {
|
|
throw new Error(`Source task with ID ${sourceIdNum} not found`);
|
|
}
|
|
|
|
sourceTask = { ...data.tasks[sourceTaskIndex] };
|
|
}
|
|
|
|
// Validate destination exists
|
|
if (isDestinationSubtask) {
|
|
// Destination is a subtask (target will be the parent of this subtask)
|
|
const [parentIdStr, subtaskIdStr] = destinationId.split(".");
|
|
const parentIdNum = parseInt(parentIdStr, 10);
|
|
const subtaskIdNum = parseInt(subtaskIdStr, 10);
|
|
|
|
destParentTask = data.tasks.find((t) => t.id === parentIdNum);
|
|
if (!destParentTask) {
|
|
throw new Error(
|
|
`Destination parent task with ID ${parentIdNum} not found`
|
|
);
|
|
}
|
|
|
|
// Initialize subtasks array if it doesn't exist
|
|
if (!destParentTask.subtasks) {
|
|
destParentTask.subtasks = [];
|
|
}
|
|
|
|
// If there are existing subtasks, try to find the specific destination subtask
|
|
if (destParentTask.subtasks.length > 0) {
|
|
destSubtaskIndex = destParentTask.subtasks.findIndex(
|
|
(st) => st.id === subtaskIdNum
|
|
);
|
|
if (destSubtaskIndex !== -1) {
|
|
destSubtask = destParentTask.subtasks[destSubtaskIndex];
|
|
} else {
|
|
// Subtask doesn't exist, we'll insert at the end
|
|
destSubtaskIndex = destParentTask.subtasks.length - 1;
|
|
}
|
|
} else {
|
|
// No existing subtasks, this will be the first one
|
|
destSubtaskIndex = -1; // Will insert at position 0
|
|
}
|
|
} else {
|
|
// Destination is a task
|
|
const destIdNum = parseInt(destinationId, 10);
|
|
destTaskIndex = data.tasks.findIndex((t) => t.id === destIdNum);
|
|
|
|
if (destTaskIndex === -1) {
|
|
// Create placeholder for destination if it doesn't exist
|
|
log("info", `Creating placeholder for destination task ${destIdNum}`);
|
|
const newTask = {
|
|
id: destIdNum,
|
|
title: `Task ${destIdNum}`,
|
|
description: "",
|
|
status: "pending",
|
|
priority: "medium",
|
|
details: "",
|
|
testStrategy: "",
|
|
};
|
|
|
|
// Find correct position to insert the new task
|
|
let insertIndex = 0;
|
|
while (
|
|
insertIndex < data.tasks.length &&
|
|
data.tasks[insertIndex].id < destIdNum
|
|
) {
|
|
insertIndex++;
|
|
}
|
|
|
|
// Insert the new task at the appropriate position
|
|
data.tasks.splice(insertIndex, 0, newTask);
|
|
destTaskIndex = insertIndex;
|
|
destTask = data.tasks[destTaskIndex];
|
|
} else {
|
|
destTask = data.tasks[destTaskIndex];
|
|
}
|
|
}
|
|
|
|
// Validate that we aren't trying to move a task to itself
|
|
if (sourceId === destinationId) {
|
|
throw new Error("Cannot move a task/subtask to itself");
|
|
}
|
|
|
|
// Prevent moving a parent to its own subtask
|
|
if (!isSourceSubtask && isDestinationSubtask) {
|
|
const destParentId = parseInt(destinationId.split(".")[0], 10);
|
|
if (parseInt(sourceId, 10) === destParentId) {
|
|
throw new Error("Cannot move a parent task to one of its own subtasks");
|
|
}
|
|
}
|
|
|
|
// Check for circular dependency when moving tasks
|
|
if (!isSourceSubtask && !isDestinationSubtask) {
|
|
const sourceIdNum = parseInt(sourceId, 10);
|
|
const destIdNum = parseInt(destinationId, 10);
|
|
|
|
// Check if destination is dependent on source
|
|
if (isTaskDependentOn(data.tasks, destTask, sourceIdNum)) {
|
|
throw new Error(
|
|
`Cannot move task ${sourceId} to task ${destinationId} as it would create a circular dependency`
|
|
);
|
|
}
|
|
}
|
|
|
|
let movedTask;
|
|
|
|
// Handle different move scenarios
|
|
if (!isSourceSubtask && !isDestinationSubtask) {
|
|
// Case: Moving task to task position
|
|
// Always treat this as a task replacement/move to new ID
|
|
// The destination task will be replaced by the source task
|
|
movedTask = moveTaskToNewId(
|
|
data,
|
|
sourceTask,
|
|
sourceTaskIndex,
|
|
destTask,
|
|
destTaskIndex
|
|
);
|
|
} else if (!isSourceSubtask && isDestinationSubtask) {
|
|
// Case 2: Move standalone task to become a subtask at a specific position
|
|
movedTask = moveTaskToSubtaskPosition(
|
|
data,
|
|
sourceTask,
|
|
sourceTaskIndex,
|
|
destParentTask,
|
|
destSubtaskIndex
|
|
);
|
|
} else if (isSourceSubtask && !isDestinationSubtask) {
|
|
// Case 3: Move subtask to become a standalone task
|
|
movedTask = moveSubtaskToTask(
|
|
data,
|
|
sourceSubtask,
|
|
sourceParentTask,
|
|
sourceSubtaskIndex,
|
|
destTask
|
|
);
|
|
} else if (isSourceSubtask && isDestinationSubtask) {
|
|
// Case 4: Move subtask to another parent or position
|
|
// First check if it's the same parent
|
|
const sourceParentId = parseInt(sourceId.split(".")[0], 10);
|
|
const destParentId = parseInt(destinationId.split(".")[0], 10);
|
|
|
|
if (sourceParentId === destParentId) {
|
|
// Case 4a: Move subtask within the same parent (reordering)
|
|
movedTask = reorderSubtask(
|
|
sourceParentTask,
|
|
sourceSubtaskIndex,
|
|
destSubtaskIndex
|
|
);
|
|
} else {
|
|
// Case 4b: Move subtask to a different parent
|
|
movedTask = moveSubtaskToAnotherParent(
|
|
sourceSubtask,
|
|
sourceParentTask,
|
|
sourceSubtaskIndex,
|
|
destParentTask,
|
|
destSubtaskIndex
|
|
);
|
|
}
|
|
}
|
|
|
|
// Write the updated tasks back to the file
|
|
writeJSON(tasksPath, data);
|
|
|
|
// Generate task files if requested
|
|
if (generateFiles) {
|
|
log("info", "Regenerating task files...");
|
|
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
|
}
|
|
|
|
return movedTask;
|
|
} catch (error) {
|
|
log("error", `Error moving task/subtask: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Move a standalone task to become a subtask of another task
|
|
* @param {Object} data - Tasks data object
|
|
* @param {Object} sourceTask - Source task to move
|
|
* @param {number} sourceTaskIndex - Index of source task in data.tasks
|
|
* @param {Object} destTask - Destination task
|
|
* @returns {Object} Moved task object
|
|
*/
|
|
function moveTaskToTask(data, sourceTask, sourceTaskIndex, destTask) {
|
|
// Initialize subtasks array if it doesn't exist
|
|
if (!destTask.subtasks) {
|
|
destTask.subtasks = [];
|
|
}
|
|
|
|
// Find the highest subtask ID to determine the next ID
|
|
const highestSubtaskId =
|
|
destTask.subtasks.length > 0
|
|
? Math.max(...destTask.subtasks.map((st) => st.id))
|
|
: 0;
|
|
const newSubtaskId = highestSubtaskId + 1;
|
|
|
|
// Create the new subtask from the source task
|
|
const newSubtask = {
|
|
...sourceTask,
|
|
id: newSubtaskId,
|
|
parentTaskId: destTask.id,
|
|
};
|
|
|
|
// Add to destination's subtasks
|
|
destTask.subtasks.push(newSubtask);
|
|
|
|
// Remove the original task from the tasks array
|
|
data.tasks.splice(sourceTaskIndex, 1);
|
|
|
|
log(
|
|
"info",
|
|
`Moved task ${sourceTask.id} to become subtask ${destTask.id}.${newSubtaskId}`
|
|
);
|
|
|
|
return newSubtask;
|
|
}
|
|
|
|
/**
|
|
* Move a standalone task to become a subtask at a specific position
|
|
* @param {Object} data - Tasks data object
|
|
* @param {Object} sourceTask - Source task to move
|
|
* @param {number} sourceTaskIndex - Index of source task in data.tasks
|
|
* @param {Object} destParentTask - Destination parent task
|
|
* @param {number} destSubtaskIndex - Index of the subtask before which to insert
|
|
* @returns {Object} Moved task object
|
|
*/
|
|
function moveTaskToSubtaskPosition(
|
|
data,
|
|
sourceTask,
|
|
sourceTaskIndex,
|
|
destParentTask,
|
|
destSubtaskIndex
|
|
) {
|
|
// Initialize subtasks array if it doesn't exist
|
|
if (!destParentTask.subtasks) {
|
|
destParentTask.subtasks = [];
|
|
}
|
|
|
|
// Find the highest subtask ID to determine the next ID
|
|
const highestSubtaskId =
|
|
destParentTask.subtasks.length > 0
|
|
? Math.max(...destParentTask.subtasks.map((st) => st.id))
|
|
: 0;
|
|
const newSubtaskId = highestSubtaskId + 1;
|
|
|
|
// Create the new subtask from the source task
|
|
const newSubtask = {
|
|
...sourceTask,
|
|
id: newSubtaskId,
|
|
parentTaskId: destParentTask.id,
|
|
};
|
|
|
|
// Insert at specific position
|
|
// If destSubtaskIndex is -1, insert at the beginning (position 0)
|
|
// Otherwise, insert after the specified subtask
|
|
const insertPosition = destSubtaskIndex === -1 ? 0 : destSubtaskIndex + 1;
|
|
destParentTask.subtasks.splice(insertPosition, 0, newSubtask);
|
|
|
|
// Remove the original task from the tasks array
|
|
data.tasks.splice(sourceTaskIndex, 1);
|
|
|
|
log(
|
|
"info",
|
|
`Moved task ${sourceTask.id} to become subtask ${destParentTask.id}.${newSubtaskId}`
|
|
);
|
|
|
|
return newSubtask;
|
|
}
|
|
|
|
/**
|
|
* Move a subtask to become a standalone task
|
|
* @param {Object} data - Tasks data object
|
|
* @param {Object} sourceSubtask - Source subtask to move
|
|
* @param {Object} sourceParentTask - Parent task of the source subtask
|
|
* @param {number} sourceSubtaskIndex - Index of source subtask in parent's subtasks
|
|
* @param {Object} destTask - Destination task (will be replaced)
|
|
* @returns {Object} Moved task object
|
|
*/
|
|
function moveSubtaskToTask(
|
|
data,
|
|
sourceSubtask,
|
|
sourceParentTask,
|
|
sourceSubtaskIndex,
|
|
destTask
|
|
) {
|
|
// Use the destination task's ID instead of generating a new one
|
|
const newTaskId = destTask.id;
|
|
|
|
// Create the new task from the subtask, using the destination task's ID
|
|
const newTask = {
|
|
...sourceSubtask,
|
|
id: newTaskId,
|
|
priority: sourceParentTask.priority || "medium", // Inherit priority from parent
|
|
};
|
|
delete newTask.parentTaskId;
|
|
|
|
// Add the parent task as a dependency if not already present
|
|
if (!newTask.dependencies) {
|
|
newTask.dependencies = [];
|
|
}
|
|
if (!newTask.dependencies.includes(sourceParentTask.id)) {
|
|
newTask.dependencies.push(sourceParentTask.id);
|
|
}
|
|
|
|
// Find the destination index to replace the destination task
|
|
const destTaskIndex = data.tasks.findIndex((t) => t.id === destTask.id);
|
|
|
|
// Replace the destination task with the new task
|
|
data.tasks[destTaskIndex] = newTask;
|
|
|
|
// Remove the subtask from the parent
|
|
sourceParentTask.subtasks.splice(sourceSubtaskIndex, 1);
|
|
|
|
// If parent has no more subtasks, remove the subtasks array
|
|
if (sourceParentTask.subtasks.length === 0) {
|
|
delete sourceParentTask.subtasks;
|
|
}
|
|
|
|
log(
|
|
"info",
|
|
`Moved subtask ${sourceParentTask.id}.${sourceSubtask.id} to become task ${newTaskId}`
|
|
);
|
|
|
|
return newTask;
|
|
}
|
|
|
|
/**
|
|
* Reorder a subtask within the same parent
|
|
* @param {Object} parentTask - Parent task containing the subtask
|
|
* @param {number} sourceIndex - Current index of the subtask
|
|
* @param {number} destIndex - Destination index for the subtask
|
|
* @returns {Object} Moved subtask object
|
|
*/
|
|
function reorderSubtask(parentTask, sourceIndex, destIndex) {
|
|
// Get the subtask to move
|
|
const subtask = parentTask.subtasks[sourceIndex];
|
|
|
|
// Remove the subtask from its current position
|
|
parentTask.subtasks.splice(sourceIndex, 1);
|
|
|
|
// Insert the subtask at the new position
|
|
// If destIndex was after sourceIndex, it's now one less because we removed an item
|
|
const adjustedDestIndex = sourceIndex < destIndex ? destIndex - 1 : destIndex;
|
|
parentTask.subtasks.splice(adjustedDestIndex, 0, subtask);
|
|
|
|
log(
|
|
"info",
|
|
`Reordered subtask ${parentTask.id}.${subtask.id} within parent task ${parentTask.id}`
|
|
);
|
|
|
|
return subtask;
|
|
}
|
|
|
|
/**
|
|
* Move a subtask to a different parent
|
|
* @param {Object} sourceSubtask - Source subtask to move
|
|
* @param {Object} sourceParentTask - Parent task of the source subtask
|
|
* @param {number} sourceSubtaskIndex - Index of source subtask in parent's subtasks
|
|
* @param {Object} destParentTask - Destination parent task
|
|
* @param {number} destSubtaskIndex - Index of the subtask before which to insert
|
|
* @returns {Object} Moved subtask object
|
|
*/
|
|
function moveSubtaskToAnotherParent(
|
|
sourceSubtask,
|
|
sourceParentTask,
|
|
sourceSubtaskIndex,
|
|
destParentTask,
|
|
destSubtaskIndex
|
|
) {
|
|
// Find the highest subtask ID in the destination parent
|
|
const highestSubtaskId =
|
|
destParentTask.subtasks.length > 0
|
|
? Math.max(...destParentTask.subtasks.map((st) => st.id))
|
|
: 0;
|
|
const newSubtaskId = highestSubtaskId + 1;
|
|
|
|
// Create the new subtask with updated parent reference
|
|
const newSubtask = {
|
|
...sourceSubtask,
|
|
id: newSubtaskId,
|
|
parentTaskId: destParentTask.id,
|
|
};
|
|
|
|
// If the subtask depends on its original parent, keep that dependency
|
|
if (!newSubtask.dependencies) {
|
|
newSubtask.dependencies = [];
|
|
}
|
|
if (!newSubtask.dependencies.includes(sourceParentTask.id)) {
|
|
newSubtask.dependencies.push(sourceParentTask.id);
|
|
}
|
|
|
|
// Insert at the destination position
|
|
// If destSubtaskIndex is -1, insert at the beginning (position 0)
|
|
// Otherwise, insert after the specified subtask
|
|
const insertPosition = destSubtaskIndex === -1 ? 0 : destSubtaskIndex + 1;
|
|
destParentTask.subtasks.splice(insertPosition, 0, newSubtask);
|
|
|
|
// Remove the subtask from the original parent
|
|
sourceParentTask.subtasks.splice(sourceSubtaskIndex, 1);
|
|
|
|
// If original parent has no more subtasks, remove the subtasks array
|
|
if (sourceParentTask.subtasks.length === 0) {
|
|
delete sourceParentTask.subtasks;
|
|
}
|
|
|
|
log(
|
|
"info",
|
|
`Moved subtask ${sourceParentTask.id}.${sourceSubtask.id} to become subtask ${destParentTask.id}.${newSubtaskId}`
|
|
);
|
|
|
|
return newSubtask;
|
|
}
|
|
|
|
/**
|
|
* Move a standalone task to a new ID position
|
|
* @param {Object} data - Tasks data object
|
|
* @param {Object} sourceTask - Source task to move
|
|
* @param {number} sourceTaskIndex - Index of source task in data.tasks
|
|
* @param {Object} destTask - Destination task (will be replaced)
|
|
* @param {number} destTaskIndex - Index of destination task in data.tasks
|
|
* @returns {Object} Moved task object
|
|
*/
|
|
function moveTaskToNewId(
|
|
data,
|
|
sourceTask,
|
|
sourceTaskIndex,
|
|
destTask,
|
|
destTaskIndex
|
|
) {
|
|
// Create a copy of the source task with the new ID
|
|
const movedTask = {
|
|
...sourceTask,
|
|
id: destTask.id,
|
|
};
|
|
|
|
// Get numeric IDs for comparison
|
|
const sourceIdNum = parseInt(sourceTask.id, 10);
|
|
const destIdNum = parseInt(destTask.id, 10);
|
|
|
|
// Handle subtasks if present
|
|
if (sourceTask.subtasks && sourceTask.subtasks.length > 0) {
|
|
// Update subtasks to reference the new parent ID if needed
|
|
movedTask.subtasks = sourceTask.subtasks.map((subtask) => ({
|
|
...subtask,
|
|
parentTaskId: destIdNum,
|
|
}));
|
|
}
|
|
|
|
// Update any dependencies in other tasks that referenced the old source ID
|
|
data.tasks.forEach((task) => {
|
|
if (task.dependencies && task.dependencies.includes(sourceIdNum)) {
|
|
// Replace the old source ID with the new destination ID
|
|
const depIndex = task.dependencies.indexOf(sourceIdNum);
|
|
task.dependencies[depIndex] = destIdNum;
|
|
}
|
|
|
|
// Also check for subtask dependencies that might reference the source task
|
|
if (task.subtasks && task.subtasks.length > 0) {
|
|
task.subtasks.forEach((subtask) => {
|
|
if (
|
|
subtask.dependencies &&
|
|
subtask.dependencies.includes(sourceIdNum)
|
|
) {
|
|
const depIndex = subtask.dependencies.indexOf(sourceIdNum);
|
|
subtask.dependencies[depIndex] = destIdNum;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Remove tasks in the correct order to avoid index shifting issues
|
|
// Always remove the higher index first to avoid shifting the lower index
|
|
if (sourceTaskIndex > destTaskIndex) {
|
|
// Remove source first (higher index), then destination
|
|
data.tasks.splice(sourceTaskIndex, 1);
|
|
data.tasks.splice(destTaskIndex, 1);
|
|
// Insert the moved task at the destination position
|
|
data.tasks.splice(destTaskIndex, 0, movedTask);
|
|
} else {
|
|
// Remove destination first (higher index), then source
|
|
data.tasks.splice(destTaskIndex, 1);
|
|
data.tasks.splice(sourceTaskIndex, 1);
|
|
// Insert the moved task at the original destination position (now shifted down by 1)
|
|
data.tasks.splice(sourceTaskIndex, 0, movedTask);
|
|
}
|
|
|
|
log("info", `Moved task ${sourceIdNum} to replace task ${destIdNum}`);
|
|
|
|
return movedTask;
|
|
}
|
|
|
|
export default moveTask;
|