feat(enhanced-kanban): implement group reordering and improve drag-and-drop functionality
- Added support for reordering kanban groups via drag-and-drop, enhancing user experience. - Updated EnhancedKanbanBoard and EnhancedKanbanGroup components to handle group dragging and state management. - Introduced visual feedback for dragging groups and tasks, improving usability. - Refined CSS styles for better layout and responsiveness during drag operations.
This commit is contained in:
@@ -26,6 +26,7 @@ import { RootState } from '@/app/store';
|
||||
import {
|
||||
fetchEnhancedKanbanGroups,
|
||||
reorderEnhancedKanbanTasks,
|
||||
reorderEnhancedKanbanGroups,
|
||||
setDragState
|
||||
} from '@/features/enhanced-kanban/enhanced-kanban.slice';
|
||||
import EnhancedKanbanGroup from './EnhancedKanbanGroup';
|
||||
@@ -119,29 +120,43 @@ const EnhancedKanbanBoard: React.FC<EnhancedKanbanBoardProps> = ({ projectId, cl
|
||||
const handleDragStart = (event: DragStartEvent) => {
|
||||
const { active } = event;
|
||||
const activeId = active.id as string;
|
||||
const activeData = active.data.current;
|
||||
|
||||
// Find the active task and group
|
||||
let foundTask = null;
|
||||
let foundGroup = null;
|
||||
// Check if dragging a group or a task
|
||||
if (activeData?.type === 'group') {
|
||||
// Dragging a group
|
||||
const foundGroup = taskGroups.find(g => g.id === activeId);
|
||||
setActiveGroup(foundGroup);
|
||||
setActiveTask(null);
|
||||
|
||||
for (const group of taskGroups) {
|
||||
const task = group.tasks.find(t => t.id === activeId);
|
||||
if (task) {
|
||||
foundTask = task;
|
||||
foundGroup = group;
|
||||
break;
|
||||
dispatch(setDragState({
|
||||
activeTaskId: null,
|
||||
activeGroupId: activeId,
|
||||
isDragging: true,
|
||||
}));
|
||||
} else {
|
||||
// Dragging a task
|
||||
let foundTask = null;
|
||||
let foundGroup = null;
|
||||
|
||||
for (const group of taskGroups) {
|
||||
const task = group.tasks.find(t => t.id === activeId);
|
||||
if (task) {
|
||||
foundTask = task;
|
||||
foundGroup = group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setActiveTask(foundTask);
|
||||
setActiveGroup(null);
|
||||
|
||||
dispatch(setDragState({
|
||||
activeTaskId: activeId,
|
||||
activeGroupId: foundGroup?.id || null,
|
||||
isDragging: true,
|
||||
}));
|
||||
}
|
||||
|
||||
setActiveTask(foundTask);
|
||||
setActiveGroup(foundGroup);
|
||||
|
||||
// Update Redux drag state
|
||||
dispatch(setDragState({
|
||||
activeTaskId: activeId,
|
||||
activeGroupId: foundGroup?.id || null,
|
||||
isDragging: true,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleDragOver = (event: DragOverEvent) => {
|
||||
@@ -164,6 +179,7 @@ const EnhancedKanbanBoard: React.FC<EnhancedKanbanBoardProps> = ({ projectId, cl
|
||||
|
||||
const handleDragEnd = (event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
const activeData = active.data.current;
|
||||
|
||||
// Reset local state
|
||||
setActiveTask(null);
|
||||
@@ -183,7 +199,28 @@ const EnhancedKanbanBoard: React.FC<EnhancedKanbanBoardProps> = ({ projectId, cl
|
||||
const activeId = active.id as string;
|
||||
const overId = over.id as string;
|
||||
|
||||
// Find source and target groups
|
||||
// Handle group reordering
|
||||
if (activeData?.type === 'group') {
|
||||
const fromIndex = taskGroups.findIndex(g => g.id === activeId);
|
||||
const toIndex = taskGroups.findIndex(g => g.id === overId);
|
||||
|
||||
if (fromIndex !== -1 && toIndex !== -1 && fromIndex !== toIndex) {
|
||||
// Create new array with reordered groups
|
||||
const reorderedGroups = [...taskGroups];
|
||||
const [movedGroup] = reorderedGroups.splice(fromIndex, 1);
|
||||
reorderedGroups.splice(toIndex, 0, movedGroup);
|
||||
|
||||
// Dispatch group reorder action
|
||||
dispatch(reorderEnhancedKanbanGroups({
|
||||
fromIndex,
|
||||
toIndex,
|
||||
reorderedGroups,
|
||||
}) as any);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle task reordering (existing logic)
|
||||
let sourceGroup = null;
|
||||
let targetGroup = null;
|
||||
let sourceIndex = -1;
|
||||
@@ -309,6 +346,14 @@ const EnhancedKanbanBoard: React.FC<EnhancedKanbanBoardProps> = ({ projectId, cl
|
||||
isDragOverlay={true}
|
||||
/>
|
||||
)}
|
||||
{activeGroup && (
|
||||
<div className="group-drag-overlay">
|
||||
<div className="group-header-content">
|
||||
<h3>{activeGroup.name}</h3>
|
||||
<span className="task-count">({activeGroup.tasks.length})</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
)}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
.enhanced-kanban-group {
|
||||
min-width: 280px;
|
||||
max-width: 320px;
|
||||
width: 300px;
|
||||
min-width: 300px;
|
||||
max-width: 300px;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
box-shadow: 0 1px 2px var(--ant-color-shadow);
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.enhanced-kanban-group.drag-over {
|
||||
@@ -15,6 +18,16 @@
|
||||
box-shadow: 0 0 0 2px var(--ant-color-primary-border);
|
||||
}
|
||||
|
||||
.enhanced-kanban-group.group-dragging {
|
||||
opacity: 0.5;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 8px 24px var(--ant-color-shadow);
|
||||
}
|
||||
|
||||
.enhanced-kanban-group.group-dragging .enhanced-kanban-group-tasks {
|
||||
background: var(--ant-color-bg-elevated);
|
||||
}
|
||||
|
||||
.enhanced-kanban-group-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -22,21 +35,44 @@
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--ant-color-border);
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
margin: -8px -8px 4px -8px;
|
||||
}
|
||||
|
||||
.enhanced-kanban-group-header:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.enhanced-kanban-group-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
color: inherit;
|
||||
text-shadow: 0 1px 2px var(--ant-color-shadow);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 180px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.task-count {
|
||||
background: var(--ant-color-fill-secondary);
|
||||
background: var(--ant-color-bg-container);
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
color: var(--ant-color-text);
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.virtualization-indicator {
|
||||
@@ -62,7 +98,9 @@
|
||||
min-height: 200px;
|
||||
max-height: 600px;
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Performance optimizations for large lists */
|
||||
@@ -127,6 +165,42 @@
|
||||
color: var(--ant-color-primary);
|
||||
}
|
||||
|
||||
/* Group drag overlay */
|
||||
.group-drag-overlay {
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 8px 24px var(--ant-color-shadow);
|
||||
min-width: 280px;
|
||||
max-width: 320px;
|
||||
opacity: 0.9;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.group-drag-overlay .group-header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.group-drag-overlay h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.group-drag-overlay .task-count {
|
||||
background: var(--ant-color-bg-container);
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text);
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
|
||||
/* Responsive design for different screen sizes */
|
||||
@media (max-width: 768px) {
|
||||
.enhanced-kanban-group {
|
||||
@@ -148,4 +222,23 @@
|
||||
.enhanced-kanban-group-tasks {
|
||||
max-height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.enhanced-kanban-task-card {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-weight: 500;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 220px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useMemo, useRef, useEffect, useState } from 'react';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||
import { SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { ITaskListGroup } from '@/types/tasks/taskList.types';
|
||||
import EnhancedKanbanTaskCard from './EnhancedKanbanTaskCard';
|
||||
import VirtualizedTaskList from './VirtualizedTaskList';
|
||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||
import './EnhancedKanbanGroup.css';
|
||||
|
||||
interface EnhancedKanbanGroupProps {
|
||||
@@ -20,7 +22,25 @@ const EnhancedKanbanGroup: React.FC<EnhancedKanbanGroupProps> = React.memo(({
|
||||
activeTaskId,
|
||||
overId
|
||||
}) => {
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||
|
||||
const { setNodeRef: setDroppableRef, isOver } = useDroppable({
|
||||
id: group.id,
|
||||
data: {
|
||||
type: 'group',
|
||||
group,
|
||||
},
|
||||
});
|
||||
|
||||
// Add sortable functionality for group header
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef: setSortableRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging: isGroupDragging,
|
||||
} = useSortable({
|
||||
id: group.id,
|
||||
data: {
|
||||
type: 'group',
|
||||
@@ -67,13 +87,41 @@ const EnhancedKanbanGroup: React.FC<EnhancedKanbanGroupProps> = React.memo(({
|
||||
// Performance optimization: Only render drop indicators when needed
|
||||
const shouldShowDropIndicators = isDraggingOver && !shouldVirtualize;
|
||||
|
||||
// Combine refs for the main container
|
||||
const setRefs = (el: HTMLElement | null) => {
|
||||
setDroppableRef(el);
|
||||
setSortableRef(el);
|
||||
};
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isGroupDragging ? 0.5 : 1,
|
||||
};
|
||||
|
||||
// Get the appropriate background color based on theme
|
||||
const headerBackgroundColor = useMemo(() => {
|
||||
if (themeMode === 'dark') {
|
||||
return group.color_code_dark || group.color_code || '#1e1e1e';
|
||||
}
|
||||
return group.color_code || '#f5f5f5';
|
||||
}, [themeMode, group.color_code, group.color_code_dark]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
className={`enhanced-kanban-group ${isDraggingOver ? 'drag-over' : ''}`}
|
||||
ref={setRefs}
|
||||
style={style}
|
||||
className={`enhanced-kanban-group ${isDraggingOver ? 'drag-over' : ''} ${isGroupDragging ? 'group-dragging' : ''}`}
|
||||
>
|
||||
<div className="enhanced-kanban-group-header">
|
||||
<h3>{group.name}</h3>
|
||||
<div
|
||||
className="enhanced-kanban-group-header"
|
||||
style={{
|
||||
backgroundColor: headerBackgroundColor,
|
||||
}}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
>
|
||||
<h3 title={group.name}>{group.name}</h3>
|
||||
<span className="task-count">({group.tasks.length})</span>
|
||||
{shouldVirtualize && (
|
||||
<span className="virtualization-indicator" title="Virtualized for performance">
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
|
||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||
import './EnhancedKanbanTaskCard.css';
|
||||
|
||||
interface EnhancedKanbanTaskCardProps {
|
||||
@@ -11,12 +12,14 @@ interface EnhancedKanbanTaskCardProps {
|
||||
isDropTarget?: boolean;
|
||||
}
|
||||
|
||||
const EnhancedKanbanTaskCard: React.FC<EnhancedKanbanTaskCardProps> = React.memo(({
|
||||
task,
|
||||
const EnhancedKanbanTaskCard: React.FC<EnhancedKanbanTaskCardProps> = React.memo(({
|
||||
task,
|
||||
isActive = false,
|
||||
isDragOverlay = false,
|
||||
isDropTarget = false
|
||||
}) => {
|
||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
@@ -37,6 +40,7 @@ const EnhancedKanbanTaskCard: React.FC<EnhancedKanbanTaskCardProps> = React.memo
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
backgroundColor: themeMode === 'dark' ? '#292929' : '#fafafa',
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -48,7 +52,7 @@ const EnhancedKanbanTaskCard: React.FC<EnhancedKanbanTaskCardProps> = React.memo
|
||||
{...listeners}
|
||||
>
|
||||
<div className="task-content">
|
||||
<div className="task-title">{task.name}</div>
|
||||
<div className="task-title" title={task.name}>{task.name}</div>
|
||||
{/* {task.task_key && <div className="task-key">{task.task_key}</div>} */}
|
||||
{task.assignees && task.assignees.length > 0 && (
|
||||
<div className="task-assignees">
|
||||
|
||||
@@ -49,12 +49,12 @@ interface EnhancedKanbanState {
|
||||
groupBy: IGroupBy;
|
||||
isSubtasksInclude: boolean;
|
||||
fields: ITaskListSortableColumn[];
|
||||
|
||||
|
||||
// Task data
|
||||
taskGroups: ITaskListGroup[];
|
||||
loadingGroups: boolean;
|
||||
error: string | null;
|
||||
|
||||
|
||||
// Filters
|
||||
taskAssignees: ITaskListMemberFilter[];
|
||||
loadingAssignees: boolean;
|
||||
@@ -63,12 +63,12 @@ interface EnhancedKanbanState {
|
||||
loadingLabels: boolean;
|
||||
priorities: string[];
|
||||
members: string[];
|
||||
|
||||
|
||||
// Performance optimizations
|
||||
virtualizedRendering: boolean;
|
||||
taskCache: Record<string, IProjectTask>;
|
||||
groupCache: Record<string, ITaskListGroup>;
|
||||
|
||||
|
||||
// Performance monitoring
|
||||
performanceMetrics: {
|
||||
totalTasks: number;
|
||||
@@ -78,7 +78,7 @@ interface EnhancedKanbanState {
|
||||
lastUpdateTime: number;
|
||||
virtualizationEnabled: boolean;
|
||||
};
|
||||
|
||||
|
||||
// Drag and drop state
|
||||
dragState: {
|
||||
activeTaskId: string | null;
|
||||
@@ -86,7 +86,7 @@ interface EnhancedKanbanState {
|
||||
overId: string | null;
|
||||
isDragging: boolean;
|
||||
};
|
||||
|
||||
|
||||
// UI state
|
||||
selectedTaskIds: string[];
|
||||
expandedSubtasks: Record<string, boolean>;
|
||||
@@ -137,7 +137,7 @@ const calculatePerformanceMetrics = (taskGroups: ITaskListGroup[]) => {
|
||||
const groupSizes = taskGroups.map(group => group.tasks.length);
|
||||
const largestGroupSize = Math.max(...groupSizes, 0);
|
||||
const averageGroupSize = groupSizes.length > 0 ? totalTasks / groupSizes.length : 0;
|
||||
|
||||
|
||||
return {
|
||||
totalTasks,
|
||||
largestGroupSize,
|
||||
@@ -234,6 +234,35 @@ export const reorderEnhancedKanbanTasks = createAsyncThunk(
|
||||
}
|
||||
);
|
||||
|
||||
// Group reordering
|
||||
export const reorderEnhancedKanbanGroups = createAsyncThunk(
|
||||
'enhancedKanban/reorderGroups',
|
||||
async (
|
||||
{
|
||||
fromIndex,
|
||||
toIndex,
|
||||
reorderedGroups,
|
||||
}: {
|
||||
fromIndex: number;
|
||||
toIndex: number;
|
||||
reorderedGroups: ITaskListGroup[];
|
||||
},
|
||||
{ rejectWithValue }
|
||||
) => {
|
||||
try {
|
||||
// Optimistic update - return immediately for UI responsiveness
|
||||
return {
|
||||
fromIndex,
|
||||
toIndex,
|
||||
reorderedGroups,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Reorder Enhanced Kanban Groups', error);
|
||||
return rejectWithValue('Failed to reorder groups');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const enhancedKanbanSlice = createSlice({
|
||||
name: 'enhancedKanbanReducer',
|
||||
initialState,
|
||||
@@ -327,7 +356,7 @@ const enhancedKanbanSlice = createSlice({
|
||||
// Status updates
|
||||
updateTaskStatus: (state, action: PayloadAction<ITaskListStatusChangeResponse>) => {
|
||||
const { id: task_id, status_id } = action.payload;
|
||||
|
||||
|
||||
// Update in all groups
|
||||
state.taskGroups.forEach(group => {
|
||||
group.tasks.forEach(task => {
|
||||
@@ -342,7 +371,7 @@ const enhancedKanbanSlice = createSlice({
|
||||
|
||||
updateTaskPriority: (state, action: PayloadAction<ITaskListPriorityChangeResponse>) => {
|
||||
const { id: task_id, priority_id } = action.payload;
|
||||
|
||||
|
||||
// Update in all groups
|
||||
state.taskGroups.forEach(group => {
|
||||
group.tasks.forEach(task => {
|
||||
@@ -358,12 +387,12 @@ const enhancedKanbanSlice = createSlice({
|
||||
// Task deletion
|
||||
deleteTask: (state, action: PayloadAction<string>) => {
|
||||
const taskId = action.payload;
|
||||
|
||||
|
||||
// Remove from all groups
|
||||
state.taskGroups.forEach(group => {
|
||||
group.tasks = group.tasks.filter(task => task.id !== taskId);
|
||||
});
|
||||
|
||||
|
||||
// Remove from caches
|
||||
delete state.taskCache[taskId];
|
||||
state.selectedTaskIds = state.selectedTaskIds.filter(id => id !== taskId);
|
||||
@@ -383,10 +412,10 @@ const enhancedKanbanSlice = createSlice({
|
||||
.addCase(fetchEnhancedKanbanGroups.fulfilled, (state, action) => {
|
||||
state.loadingGroups = false;
|
||||
state.taskGroups = action.payload;
|
||||
|
||||
|
||||
// Update performance metrics
|
||||
state.performanceMetrics = calculatePerformanceMetrics(action.payload);
|
||||
|
||||
|
||||
// Update caches
|
||||
action.payload.forEach(group => {
|
||||
state.groupCache[group.id] = group;
|
||||
@@ -394,7 +423,7 @@ const enhancedKanbanSlice = createSlice({
|
||||
state.taskCache[task.id!] = task;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Initialize column order if not set
|
||||
if (state.columnOrder.length === 0) {
|
||||
state.columnOrder = action.payload.map(group => group.id);
|
||||
@@ -406,20 +435,33 @@ const enhancedKanbanSlice = createSlice({
|
||||
})
|
||||
.addCase(reorderEnhancedKanbanTasks.fulfilled, (state, action) => {
|
||||
const { activeGroupId, overGroupId, updatedSourceTasks, updatedTargetTasks } = action.payload;
|
||||
|
||||
|
||||
// Update groups
|
||||
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];
|
||||
}
|
||||
})
|
||||
.addCase(reorderEnhancedKanbanGroups.fulfilled, (state, action) => {
|
||||
const { fromIndex, toIndex, reorderedGroups } = action.payload;
|
||||
|
||||
// Update groups
|
||||
state.taskGroups = reorderedGroups;
|
||||
state.groupCache = reorderedGroups.reduce((cache, group) => {
|
||||
cache[group.id] = group;
|
||||
return cache;
|
||||
}, {} as Record<string, ITaskListGroup>);
|
||||
|
||||
// Update column order
|
||||
state.columnOrder = reorderedGroups.map(group => group.id);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user