feat(drag-and-drop): enhance task grouping updates and socket event handling
- Updated the `useDragAndDrop` hook to emit specific grouping field change events (phase, priority, status) when tasks are moved between groups. - Refactored the task management slice to prevent direct updates to task grouping fields during drag-and-drop operations, ensuring these updates are handled via socket events after backend confirmation. - Introduced a new socket handler for task sort order changes to update task properties based on backend responses, improving synchronization between frontend and backend task states.
This commit is contained in:
@@ -37,7 +37,7 @@ export const useDragAndDrop = (allTasks: Task[], groups: TaskGroup[]) => {
|
|||||||
const teamId = currentSession?.team_id || '';
|
const teamId = currentSession?.team_id || '';
|
||||||
|
|
||||||
// Use new bulk update approach - recalculate ALL task orders to prevent duplicates
|
// 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
|
// Create a copy of all groups and perform the move operation
|
||||||
const updatedGroups = groups.map(group => ({
|
const updatedGroups = groups.map(group => ({
|
||||||
@@ -108,6 +108,32 @@ export const useDragAndDrop = (allTasks: Task[], groups: TaskGroup[]) => {
|
|||||||
|
|
||||||
console.log('Emitting TASK_SORT_ORDER_CHANGE:', socketData);
|
console.log('Emitting TASK_SORT_ORDER_CHANGE:', socketData);
|
||||||
socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), 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]
|
[socket, connected, projectId, allTasks, groups, currentGrouping, currentSession]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -660,7 +660,7 @@ const taskManagementSlice = createSlice({
|
|||||||
const [removed] = newTasks.splice(newTasks.indexOf(sourceTaskId), 1);
|
const [removed] = newTasks.splice(newTasks.indexOf(sourceTaskId), 1);
|
||||||
newTasks.splice(newTasks.indexOf(destinationTaskId), 0, removed);
|
newTasks.splice(newTasks.indexOf(destinationTaskId), 0, removed);
|
||||||
group.taskIds = newTasks;
|
group.taskIds = newTasks;
|
||||||
|
|
||||||
// Update order for affected tasks using the appropriate sort field
|
// Update order for affected tasks using the appropriate sort field
|
||||||
const sortField = getSortOrderField(state.grouping?.id);
|
const sortField = getSortOrderField(state.grouping?.id);
|
||||||
newTasks.forEach((id, index) => {
|
newTasks.forEach((id, index) => {
|
||||||
@@ -673,11 +673,11 @@ const taskManagementSlice = createSlice({
|
|||||||
// Moving between different groups
|
// Moving between different groups
|
||||||
const sourceGroup = state.groups.find(g => g.id === sourceGroupId);
|
const sourceGroup = state.groups.find(g => g.id === sourceGroupId);
|
||||||
const destinationGroup = state.groups.find(g => g.id === destinationGroupId);
|
const destinationGroup = state.groups.find(g => g.id === destinationGroupId);
|
||||||
|
|
||||||
if (sourceGroup && destinationGroup) {
|
if (sourceGroup && destinationGroup) {
|
||||||
// Remove from source group
|
// Remove from source group
|
||||||
sourceGroup.taskIds = sourceGroup.taskIds.filter(id => id !== sourceTaskId);
|
sourceGroup.taskIds = sourceGroup.taskIds.filter(id => id !== sourceTaskId);
|
||||||
|
|
||||||
// Add to destination group at the correct position relative to destinationTask
|
// Add to destination group at the correct position relative to destinationTask
|
||||||
const destinationIndex = destinationGroup.taskIds.indexOf(destinationTaskId);
|
const destinationIndex = destinationGroup.taskIds.indexOf(destinationTaskId);
|
||||||
if (destinationIndex !== -1) {
|
if (destinationIndex !== -1) {
|
||||||
@@ -685,44 +685,10 @@ const taskManagementSlice = createSlice({
|
|||||||
} else {
|
} else {
|
||||||
destinationGroup.taskIds.push(sourceTaskId); // Add to end if destination task not found
|
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)
|
// Do NOT update the task's grouping field (priority, phase, status) here.
|
||||||
// This assumes the group ID directly corresponds to the task's field value
|
// This will be handled by the socket event handler after backend confirmation.
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update order for affected tasks in both groups using the appropriate sort field
|
// Update order for affected tasks in both groups using the appropriate sort field
|
||||||
const sortField = getSortOrderField(state.grouping?.id);
|
const sortField = getSortOrderField(state.grouping?.id);
|
||||||
sourceGroup.taskIds.forEach((id, index) => {
|
sourceGroup.taskIds.forEach((id, index) => {
|
||||||
|
|||||||
@@ -989,6 +989,71 @@ export const useTaskSocketHandlers = () => {
|
|||||||
}
|
}
|
||||||
}, [dispatch]);
|
}, [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
|
// Register socket event listeners
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
@@ -1022,6 +1087,7 @@ export const useTaskSocketHandlers = () => {
|
|||||||
{ event: SocketEvents.TASK_CUSTOM_COLUMN_UPDATE.toString(), handler: handleCustomColumnUpdate },
|
{ event: SocketEvents.TASK_CUSTOM_COLUMN_UPDATE.toString(), handler: handleCustomColumnUpdate },
|
||||||
{ event: SocketEvents.TASK_TIMER_START.toString(), handler: handleTimerStart },
|
{ event: SocketEvents.TASK_TIMER_START.toString(), handler: handleTimerStart },
|
||||||
{ event: SocketEvents.TASK_TIMER_STOP.toString(), handler: handleTimerStop },
|
{ 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,
|
handleCustomColumnUpdate,
|
||||||
handleTimerStart,
|
handleTimerStart,
|
||||||
handleTimerStop,
|
handleTimerStop,
|
||||||
|
handleTaskSortOrderChange,
|
||||||
|
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user