feat(task-management): implement task movement between groups

- Added `moveTaskBetweenGroups` action to facilitate moving tasks across different groups while maintaining state integrity.
- Enhanced task management slice to support task updates during group transitions, including logging for better debugging.
- Updated socket handlers to utilize the new action for moving tasks based on status, priority, and phase changes, improving task organization and user experience.
This commit is contained in:
chamiakJ
2025-06-27 07:06:14 +05:30
parent 84f77940fd
commit e73196a249
3 changed files with 225 additions and 38 deletions

View File

@@ -308,7 +308,6 @@ const TaskGroup: React.FC<TaskGroupProps> = React.memo(({
<Text type="secondary">No tasks in this group</Text>
<br />
<Button
type="link"
{...taskManagementAntdConfig.taskButtonDefaults}
icon={<PlusOutlined />}
onClick={handleAddTask}
@@ -599,10 +598,11 @@ const TaskGroup: React.FC<TaskGroupProps> = React.memo(({
</div>
);
}, (prevProps, nextProps) => {
// Simplified comparison for better performance
// More comprehensive comparison to detect task movements
return (
prevProps.group.id === nextProps.group.id &&
prevProps.group.taskIds.length === nextProps.group.taskIds.length &&
prevProps.group.taskIds.every((id, index) => id === nextProps.group.taskIds[index]) &&
prevProps.group.collapsed === nextProps.group.collapsed &&
prevProps.selectedTaskIds.length === nextProps.selectedTaskIds.length &&
prevProps.currentGrouping === nextProps.currentGrouping

View File

@@ -235,9 +235,6 @@ const taskManagementSlice = createSlice({
// Add to groups array for V3 API compatibility
if (state.groups && state.groups.length > 0) {
console.log('🔍 Looking for group with ID:', groupId);
console.log('📋 Available groups:', state.groups.map(g => ({ id: g.id, title: g.title })));
// Find the target group using the provided UUID
const targetGroup = state.groups.find(group => {
// If a specific groupId (UUID) is provided, use it directly
@@ -249,17 +246,13 @@ const taskManagementSlice = createSlice({
});
if (targetGroup) {
console.log('✅ Found target group:', targetGroup.title);
// Add task ID to the end of the group's taskIds array (newest last)
targetGroup.taskIds.push(task.id);
console.log('✅ Task added to group. New taskIds count:', targetGroup.taskIds.length);
// Also add to the tasks array if it exists (for backward compatibility)
if ((targetGroup as any).tasks) {
(targetGroup as any).tasks.push(task);
}
} else {
console.warn('❌ No matching group found for groupId:', groupId);
}
}
},
@@ -328,6 +321,60 @@ const taskManagementSlice = createSlice({
tasksAdapter.updateOne(state, { id: taskId, changes });
},
// New action to move task between groups with proper group management
moveTaskBetweenGroups: (state, action: PayloadAction<{
taskId: string;
fromGroupId: string;
toGroupId: string;
taskUpdate: Partial<Task>;
}>) => {
const { taskId, fromGroupId, toGroupId, taskUpdate } = action.payload;
console.log('🔧 moveTaskBetweenGroups action:', {
taskId,
fromGroupId,
toGroupId,
taskUpdate,
hasGroups: !!state.groups,
groupsCount: state.groups?.length || 0
});
// Update the task entity with new values
tasksAdapter.updateOne(state, {
id: taskId,
changes: {
...taskUpdate,
updatedAt: new Date().toISOString(),
},
});
// Update groups if they exist
if (state.groups && state.groups.length > 0) {
// Remove task from old group
const fromGroup = state.groups.find(group => group.id === fromGroupId);
if (fromGroup) {
const beforeCount = fromGroup.taskIds.length;
fromGroup.taskIds = fromGroup.taskIds.filter(id => id !== taskId);
console.log(`🔧 Removed task from ${fromGroup.title}: ${beforeCount} -> ${fromGroup.taskIds.length}`);
} else {
console.warn('🚨 From group not found:', fromGroupId);
}
// Add task to new group
const toGroup = state.groups.find(group => group.id === toGroupId);
if (toGroup) {
const beforeCount = toGroup.taskIds.length;
// Add to the end of the group (newest last)
toGroup.taskIds.push(taskId);
console.log(`🔧 Added task to ${toGroup.title}: ${beforeCount} -> ${toGroup.taskIds.length}`);
} else {
console.warn('🚨 To group not found:', toGroupId);
}
} else {
console.warn('🚨 No groups available for task movement');
}
},
// Optimistic update for drag operations - reduces perceived lag
optimisticTaskMove: (state, action: PayloadAction<{ taskId: string; newGroupId: string; newIndex: number }>) => {
@@ -437,6 +484,7 @@ export const {
bulkDeleteTasks,
reorderTasks,
moveTaskToGroup,
moveTaskBetweenGroups,
optimisticTaskMove,
setLoading,
setError,

View File

@@ -33,11 +33,12 @@ import {
updateSubTasks,
updateTaskProgress,
} from '@/features/tasks/tasks.slice';
import {
import {
addTask,
addTaskToGroup,
updateTask,
moveTaskToGroup,
moveTaskBetweenGroups,
selectCurrentGroupingV3,
fetchTasksV3
} from '@/features/task-management/task-management.slice';
@@ -137,14 +138,66 @@ export const useTaskSocketHandlers = () => {
dispatch(updateTaskStatus(response));
dispatch(deselectAll());
// For the task management slice, let's use a simpler approach:
// Just refetch the tasks to ensure consistency
if (response.id && projectId) {
console.log('🔄 Refetching tasks after status change to ensure consistency...');
dispatch(fetchTasksV3(projectId));
// For the task management slice, move task between groups without resetting
const state = store.getState();
const groups = state.taskManagement.groups;
const currentTask = state.taskManagement.entities[response.id];
console.log('🔍 Status change debug:', {
hasGroups: !!groups,
groupsLength: groups?.length || 0,
hasCurrentTask: !!currentTask,
statusId: response.status_id,
currentGrouping: state.taskManagement.grouping
});
if (groups && groups.length > 0 && currentTask && response.status_id) {
// Find current group containing the task
const currentGroup = groups.find(group => group.taskIds.includes(response.id));
// Find target group based on new status ID
// The status_id from response is the UUID of the new status
const targetGroup = groups.find(group => group.id === response.status_id);
if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) {
console.log('🔄 Moving task between groups:', {
taskId: response.id,
fromGroup: currentGroup.title,
toGroup: targetGroup.title
});
// Determine the new status value based on status category
let newStatusValue: 'todo' | 'doing' | 'done' = 'todo';
if (response.statusCategory) {
if (response.statusCategory.is_done) {
newStatusValue = 'done';
} else if (response.statusCategory.is_doing) {
newStatusValue = 'doing';
} else {
newStatusValue = 'todo';
}
}
// Use the new action to move task between groups
dispatch(moveTaskBetweenGroups({
taskId: response.id,
fromGroupId: currentGroup.id,
toGroupId: targetGroup.id,
taskUpdate: {
status: newStatusValue,
progress: response.complete_ratio || currentTask.progress,
}
}));
} else if (!currentGroup || !targetGroup) {
// Fallback to refetch if groups not found (shouldn't happen normally)
console.log('🔄 Groups not found, refetching tasks...');
if (projectId) {
dispatch(fetchTasksV3(projectId));
}
}
}
},
[dispatch, currentGroupingV3]
[dispatch, currentGroupingV3, projectId]
);
const handleTaskProgress = useCallback(
@@ -192,10 +245,61 @@ export const useTaskSocketHandlers = () => {
dispatch(setTaskPriority(response));
dispatch(deselectAll());
// For the task management slice, refetch tasks to ensure consistency
if (response.id && projectId) {
console.log('🔄 Refetching tasks after priority change...');
dispatch(fetchTasksV3(projectId));
// For the task management slice, move task between groups if grouping by priority
const state = store.getState();
const groups = state.taskManagement.groups;
const currentTask = state.taskManagement.entities[response.id];
const currentGrouping = state.taskManagement.grouping;
if (groups && groups.length > 0 && currentTask && response.priority_id && currentGrouping === 'priority') {
// Find current group containing the task
const currentGroup = groups.find(group => group.taskIds.includes(response.id));
// Find target group based on new priority ID
const targetGroup = groups.find(group => group.id === response.priority_id);
if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) {
console.log('🔄 Moving task between priority groups:', {
taskId: response.id,
fromGroup: currentGroup.title,
toGroup: targetGroup.title
});
// Determine priority value from target group
let newPriorityValue: 'critical' | 'high' | 'medium' | 'low' = 'medium';
const priorityValue = targetGroup.groupValue.toLowerCase();
if (['critical', 'high', 'medium', 'low'].includes(priorityValue)) {
newPriorityValue = priorityValue as 'critical' | 'high' | 'medium' | 'low';
}
dispatch(moveTaskBetweenGroups({
taskId: response.id,
fromGroupId: currentGroup.id,
toGroupId: targetGroup.id,
taskUpdate: {
priority: newPriorityValue,
}
}));
} else if (!currentGroup || !targetGroup) {
// Fallback to refetch if groups not found
console.log('🔄 Priority groups not found, refetching tasks...');
if (projectId) {
dispatch(fetchTasksV3(projectId));
}
}
} else if (currentGrouping !== 'priority') {
// If not grouping by priority, just update the task
if (currentTask) {
let newPriorityValue: 'critical' | 'high' | 'medium' | 'low' = 'medium';
// We need to map priority_id to priority value - this might require additional logic
// For now, let's just update without changing groups
dispatch(updateTask({
id: response.id,
changes: {
// priority: newPriorityValue, // We'd need to map priority_id to value
}
}));
}
}
},
[dispatch, currentGroupingV3]
@@ -249,13 +353,61 @@ export const useTaskSocketHandlers = () => {
dispatch(updateTaskPhase(data));
dispatch(deselectAll());
// For the task management slice, refetch tasks to ensure consistency
if (data.task_id && projectId) {
console.log('🔄 Refetching tasks after phase change...');
dispatch(fetchTasksV3(projectId));
// For the task management slice, move task between groups if grouping by phase
const state = store.getState();
const groups = state.taskManagement.groups;
const currentTask = state.taskManagement.entities[data.task_id || data.id];
const currentGrouping = state.taskManagement.grouping;
const taskId = data.task_id || data.id;
if (groups && groups.length > 0 && currentTask && taskId && currentGrouping === 'phase') {
// Find current group containing the task
const currentGroup = groups.find(group => group.taskIds.includes(taskId));
// For phase changes, we need to find the target group by phase name/value
// The response might not have a direct phase_id, so we'll look for the group by value
let targetGroup: any = null;
// Try to find target group - this might need adjustment based on the actual response structure
if (data.id) {
targetGroup = groups.find(group => group.id === data.id);
}
if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) {
console.log('🔄 Moving task between phase groups:', {
taskId: taskId,
fromGroup: currentGroup.title,
toGroup: targetGroup.title
});
dispatch(moveTaskBetweenGroups({
taskId: taskId,
fromGroupId: currentGroup.id,
toGroupId: targetGroup.id,
taskUpdate: {
phase: targetGroup.groupValue,
}
}));
} else if (!currentGroup || !targetGroup) {
// Fallback to refetch if groups not found
console.log('🔄 Phase groups not found, refetching tasks...');
if (projectId) {
dispatch(fetchTasksV3(projectId));
}
}
} else if (currentGrouping !== 'phase') {
// If not grouping by phase, just update the task
if (currentTask && taskId) {
dispatch(updateTask({
id: taskId,
changes: {
// phase: newPhaseValue, // We'd need to determine the phase value
}
}));
}
}
},
[dispatch, currentGroupingV3]
[dispatch, currentGroupingV3, projectId]
);
const handleStartDateChange = useCallback(
@@ -359,34 +511,21 @@ export const useTaskSocketHandlers = () => {
// Extract the group UUID from the backend response based on current grouping
let groupId: string | undefined;
console.log('🔍 Quick task received:', {
currentGrouping: currentGroupingV3,
status: data.status,
priority: data.priority,
phase_id: data.phase_id
});
// Select the correct UUID based on current grouping
// If currentGroupingV3 is null, default to 'status' since that's the most common grouping
const grouping = currentGroupingV3 || 'status';
console.log('📊 Using grouping:', grouping);
if (grouping === 'status') {
// For status grouping, use status field (which contains the status UUID)
groupId = data.status;
console.log('✅ Using status UUID:', groupId);
} else if (grouping === 'priority') {
// For priority grouping, use priority field (which contains the priority UUID)
groupId = data.priority;
console.log('✅ Using priority UUID:', groupId);
} else if (grouping === 'phase') {
// For phase grouping, use phase_id
groupId = data.phase_id;
console.log('✅ Using phase UUID:', groupId);
}
console.log('📤 Dispatching addTaskToGroup with:', { taskId: task.id, groupId });
// Use addTaskToGroup with the actual group UUID
dispatch(addTaskToGroup({ task, groupId }));
}