From b436db183fe965a0d5941feb5edc5337f1f023e6 Mon Sep 17 00:00:00 2001 From: shancds Date: Mon, 23 Jun 2025 14:08:32 +0530 Subject: [PATCH] feat(enhanced-kanban): implement synchronous reordering for tasks and groups - Added synchronous state updates for task and group reordering in the EnhancedKanbanBoard component, improving UI responsiveness during drag-and-drop operations. - Introduced new actions `reorderTasks` and `reorderGroups` in the enhanced-kanban slice for better state management. - Updated EnhancedKanbanGroup and EnhancedKanbanTaskCard components to utilize the new layout change animations, enhancing the user experience during reordering. --- .../enhanced-kanban/EnhancedKanbanBoard.tsx | 26 ++++++++---- .../enhanced-kanban/EnhancedKanbanGroup.tsx | 3 +- .../EnhancedKanbanTaskCard.tsx | 3 +- .../enhanced-kanban/enhanced-kanban.slice.ts | 40 +++++++++++++++++++ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx index 35c72de7..eee90b66 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoard.tsx @@ -27,7 +27,9 @@ import { fetchEnhancedKanbanGroups, reorderEnhancedKanbanTasks, reorderEnhancedKanbanGroups, - setDragState + setDragState, + reorderTasks, + reorderGroups, } from '@/features/enhanced-kanban/enhanced-kanban.slice'; import EnhancedKanbanGroup from './EnhancedKanbanGroup'; import EnhancedKanbanTaskCard from './EnhancedKanbanTaskCard'; @@ -210,12 +212,10 @@ const EnhancedKanbanBoard: React.FC = ({ projectId, cl const [movedGroup] = reorderedGroups.splice(fromIndex, 1); reorderedGroups.splice(toIndex, 0, movedGroup); - // Dispatch group reorder action - dispatch(reorderEnhancedKanbanGroups({ - fromIndex, - toIndex, - reorderedGroups, - }) as any); + // Synchronous UI update + dispatch(reorderGroups({ fromIndex, toIndex, reorderedGroups })); + // Async backend sync (optional) + dispatch(reorderEnhancedKanbanGroups({ fromIndex, toIndex, reorderedGroups }) as any); } return; } @@ -274,7 +274,17 @@ const EnhancedKanbanBoard: React.FC = ({ projectId, cl updatedTargetTasks.splice(targetIndex, 0, movedTask); } - // Dispatch the reorder action + // Synchronous UI update + dispatch(reorderTasks({ + activeGroupId: sourceGroup.id, + overGroupId: targetGroup.id, + fromIndex: sourceIndex, + toIndex: targetIndex, + task: movedTask, + updatedSourceTasks, + updatedTargetTasks, + })); + // Async backend sync (optional) dispatch(reorderEnhancedKanbanTasks({ activeGroupId: sourceGroup.id, overGroupId: targetGroup.id, diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanGroup.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanGroup.tsx index 9caed509..ce934d34 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanGroup.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanGroup.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useRef, useEffect, useState } from 'react'; import { useDroppable } from '@dnd-kit/core'; -import { SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable'; +import { SortableContext, verticalListSortingStrategy, useSortable, defaultAnimateLayoutChanges } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { ITaskListGroup } from '@/types/tasks/taskList.types'; import EnhancedKanbanTaskCard from './EnhancedKanbanTaskCard'; @@ -46,6 +46,7 @@ const EnhancedKanbanGroup: React.FC = React.memo(({ type: 'group', group, }, + animateLayoutChanges: defaultAnimateLayoutChanges, }); const groupRef = useRef(null); diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx index a04ddb21..855fb82a 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useSortable } from '@dnd-kit/sortable'; +import { useSortable, defaultAnimateLayoutChanges } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; import { useAppSelector } from '@/hooks/useAppSelector'; @@ -34,6 +34,7 @@ const EnhancedKanbanTaskCard: React.FC = React.memo task, }, disabled: isDragOverlay, + animateLayoutChanges: defaultAnimateLayoutChanges, }); const style = { diff --git a/worklenz-frontend/src/features/enhanced-kanban/enhanced-kanban.slice.ts b/worklenz-frontend/src/features/enhanced-kanban/enhanced-kanban.slice.ts index 36326269..706e2d6d 100644 --- a/worklenz-frontend/src/features/enhanced-kanban/enhanced-kanban.slice.ts +++ b/worklenz-frontend/src/features/enhanced-kanban/enhanced-kanban.slice.ts @@ -402,6 +402,44 @@ const enhancedKanbanSlice = createSlice({ resetState: (state) => { return { ...initialState, groupBy: state.groupBy }; }, + + // Synchronous reorder for tasks + reorderTasks: (state, action: PayloadAction<{ + activeGroupId: string; + overGroupId: string; + fromIndex: number; + toIndex: number; + task: IProjectTask; + updatedSourceTasks: IProjectTask[]; + updatedTargetTasks: IProjectTask[]; + }>) => { + const { activeGroupId, overGroupId, updatedSourceTasks, updatedTargetTasks } = action.payload; + const sourceGroupIndex = state.taskGroups.findIndex(group => group.id === activeGroupId); + const targetGroupIndex = state.taskGroups.findIndex(group => group.id === overGroupId); + if (sourceGroupIndex !== -1) { + state.taskGroups[sourceGroupIndex].tasks = updatedSourceTasks; + state.groupCache[activeGroupId] = state.taskGroups[sourceGroupIndex]; + } + if (targetGroupIndex !== -1 && activeGroupId !== overGroupId) { + state.taskGroups[targetGroupIndex].tasks = updatedTargetTasks; + state.groupCache[overGroupId] = state.taskGroups[targetGroupIndex]; + } + }, + + // Synchronous reorder for groups + reorderGroups: (state, action: PayloadAction<{ + fromIndex: number; + toIndex: number; + reorderedGroups: ITaskListGroup[]; + }>) => { + const { reorderedGroups } = action.payload; + state.taskGroups = reorderedGroups; + state.groupCache = reorderedGroups.reduce((cache, group) => { + cache[group.id] = group; + return cache; + }, {} as Record); + state.columnOrder = reorderedGroups.map(group => group.id); + }, }, extraReducers: (builder) => { builder @@ -488,6 +526,8 @@ export const { updateTaskPriority, deleteTask, resetState, + reorderTasks, + reorderGroups, } = enhancedKanbanSlice.actions; export default enhancedKanbanSlice.reducer; \ No newline at end of file