diff --git a/worklenz-frontend/src/components/task-list-v2/hooks/useDragAndDrop.ts b/worklenz-frontend/src/components/task-list-v2/hooks/useDragAndDrop.ts index b70f4c69..5838ba8e 100644 --- a/worklenz-frontend/src/components/task-list-v2/hooks/useDragAndDrop.ts +++ b/worklenz-frontend/src/components/task-list-v2/hooks/useDragAndDrop.ts @@ -37,7 +37,7 @@ export const useDragAndDrop = (allTasks: Task[], groups: TaskGroup[]) => { const teamId = currentSession?.team_id || ''; // Use new bulk update approach - recalculate ALL task orders to prevent duplicates - const taskUpdates = []; + const taskUpdates: any[] = []; // Create a copy of all groups and perform the move operation const updatedGroups = groups.map(group => ({ @@ -108,6 +108,32 @@ export const useDragAndDrop = (allTasks: Task[], groups: TaskGroup[]) => { console.log('Emitting TASK_SORT_ORDER_CHANGE:', socketData); socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), socketData); + + // Also emit the specific grouping field change event for the moved task + if (sourceGroup.id !== targetGroup.id) { + if (currentGrouping === 'phase') { + // Emit phase change event + socket.emit(SocketEvents.TASK_PHASE_CHANGE.toString(), { + task_id: taskId, + phase_id: targetGroup.id, + parent_task: task.parent_task_id || null, + }); + } else if (currentGrouping === 'priority') { + // Emit priority change event + socket.emit(SocketEvents.TASK_PRIORITY_CHANGE.toString(), JSON.stringify({ + task_id: taskId, + priority_id: targetGroup.id, + team_id: teamId, + })); + } else if (currentGrouping === 'status') { + // Emit status change event + socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), JSON.stringify({ + task_id: taskId, + status_id: targetGroup.id, + team_id: teamId, + })); + } + } }, [socket, connected, projectId, allTasks, groups, currentGrouping, currentSession] ); diff --git a/worklenz-frontend/src/features/task-management/task-management.slice.ts b/worklenz-frontend/src/features/task-management/task-management.slice.ts index 15020447..f0e9c494 100644 --- a/worklenz-frontend/src/features/task-management/task-management.slice.ts +++ b/worklenz-frontend/src/features/task-management/task-management.slice.ts @@ -660,7 +660,7 @@ const taskManagementSlice = createSlice({ const [removed] = newTasks.splice(newTasks.indexOf(sourceTaskId), 1); newTasks.splice(newTasks.indexOf(destinationTaskId), 0, removed); group.taskIds = newTasks; - + // Update order for affected tasks using the appropriate sort field const sortField = getSortOrderField(state.grouping?.id); newTasks.forEach((id, index) => { @@ -673,11 +673,11 @@ const taskManagementSlice = createSlice({ // Moving between different groups const sourceGroup = state.groups.find(g => g.id === sourceGroupId); const destinationGroup = state.groups.find(g => g.id === destinationGroupId); - + if (sourceGroup && destinationGroup) { // Remove from source group sourceGroup.taskIds = sourceGroup.taskIds.filter(id => id !== sourceTaskId); - + // Add to destination group at the correct position relative to destinationTask const destinationIndex = destinationGroup.taskIds.indexOf(destinationTaskId); if (destinationIndex !== -1) { @@ -685,44 +685,10 @@ const taskManagementSlice = createSlice({ } else { destinationGroup.taskIds.push(sourceTaskId); // Add to end if destination task not found } - - // Update task's grouping field to reflect new group (e.g., status, priority, phase) - // This assumes the group ID directly corresponds to the task's field value - if (sourceTask) { - let updatedTask = { ...sourceTask }; - switch (state.grouping?.id) { - case IGroupBy.STATUS: - updatedTask.status = destinationGroup.id; - break; - case IGroupBy.PRIORITY: - updatedTask.priority = destinationGroup.id; - break; - case IGroupBy.PHASE: - // Handle unmapped group specially - if (destinationGroup.id === 'Unmapped' || destinationGroup.title === 'Unmapped') { - updatedTask.phase = ''; // Clear phase for unmapped group - } else { - updatedTask.phase = destinationGroup.id; - } - break; - case IGroupBy.MEMBERS: - // If moving to a member group, ensure task is assigned to that member - // This assumes the group ID is the member ID - if (!updatedTask.assignees) { - updatedTask.assignees = []; - } - if (!updatedTask.assignees.includes(destinationGroup.id)) { - updatedTask.assignees.push(destinationGroup.id); - } - // If moving from a member group, and the task is no longer in any member group, - // consider removing the assignment (more complex logic might be needed here) - break; - default: - break; - } - newEntities[sourceTaskId] = updatedTask; - } - + + // Do NOT update the task's grouping field (priority, phase, status) here. + // This will be handled by the socket event handler after backend confirmation. + // Update order for affected tasks in both groups using the appropriate sort field const sortField = getSortOrderField(state.grouping?.id); sourceGroup.taskIds.forEach((id, index) => { diff --git a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts index 77e18263..2cb419a9 100644 --- a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts +++ b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts @@ -989,6 +989,71 @@ export const useTaskSocketHandlers = () => { } }, [dispatch]); + // Handler for task sort order change events + const handleTaskSortOrderChange = useCallback((data: any[]) => { + try { + if (!Array.isArray(data) || data.length === 0) return; + + // DEBUG: Log the data received from the backend + console.log('[TASK_SORT_ORDER_CHANGE] Received data:', data); + + // Get canonical lists from Redux + const state = store.getState(); + const priorityList = state.priorityReducer?.priorities || []; + const phaseList = state.phaseReducer?.phaseList || []; + const statusList = state.taskStatusReducer?.status || []; + + // The backend sends an array of tasks with updated sort orders and possibly grouping fields + data.forEach((taskData: any) => { + const currentTask = state.taskManagement.entities[taskData.id]; + if (currentTask) { + let updatedTask: Task = { + ...currentTask, + order: taskData.sort_order || taskData.current_sort_order || currentTask.order, + updatedAt: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + + // Update grouping fields if present + if (typeof taskData.priority_id !== 'undefined') { + const found = priorityList.find(p => p.id === taskData.priority_id); + if (found) { + updatedTask.priority = found.name; + // updatedTask.priority_id = found.id; // Only if Task type has priority_id + } else { + updatedTask.priority = taskData.priority_id || ''; + // updatedTask.priority_id = taskData.priority_id; + } + } + if (typeof taskData.phase_id !== 'undefined') { + const found = phaseList.find(p => p.id === taskData.phase_id); + if (found) { + updatedTask.phase = found.name; + // updatedTask.phase_id = found.id; // Only if Task type has phase_id + } else { + updatedTask.phase = taskData.phase_id || ''; + // updatedTask.phase_id = taskData.phase_id; + } + } + if (typeof taskData.status_id !== 'undefined') { + const found = statusList.find(s => s.id === taskData.status_id); + if (found) { + updatedTask.status = found.name; + // updatedTask.status_id = found.id; // Only if Task type has status_id + } else { + updatedTask.status = taskData.status_id || ''; + // updatedTask.status_id = taskData.status_id; + } + } + + dispatch(updateTask(updatedTask)); + } + }); + } catch (error) { + logger.error('Error handling task sort order change event:', error); + } + }, [dispatch]); + // Register socket event listeners useEffect(() => { if (!socket) return; @@ -1022,6 +1087,7 @@ export const useTaskSocketHandlers = () => { { event: SocketEvents.TASK_CUSTOM_COLUMN_UPDATE.toString(), handler: handleCustomColumnUpdate }, { event: SocketEvents.TASK_TIMER_START.toString(), handler: handleTimerStart }, { event: SocketEvents.TASK_TIMER_STOP.toString(), handler: handleTimerStop }, + { event: SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), handler: handleTaskSortOrderChange }, ]; @@ -1056,6 +1122,7 @@ export const useTaskSocketHandlers = () => { handleCustomColumnUpdate, handleTimerStart, handleTimerStop, + handleTaskSortOrderChange, ]); };