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 {
|
import {
|
||||||
fetchEnhancedKanbanGroups,
|
fetchEnhancedKanbanGroups,
|
||||||
reorderEnhancedKanbanTasks,
|
reorderEnhancedKanbanTasks,
|
||||||
|
reorderEnhancedKanbanGroups,
|
||||||
setDragState
|
setDragState
|
||||||
} from '@/features/enhanced-kanban/enhanced-kanban.slice';
|
} from '@/features/enhanced-kanban/enhanced-kanban.slice';
|
||||||
import EnhancedKanbanGroup from './EnhancedKanbanGroup';
|
import EnhancedKanbanGroup from './EnhancedKanbanGroup';
|
||||||
@@ -119,8 +120,22 @@ const EnhancedKanbanBoard: React.FC<EnhancedKanbanBoardProps> = ({ projectId, cl
|
|||||||
const handleDragStart = (event: DragStartEvent) => {
|
const handleDragStart = (event: DragStartEvent) => {
|
||||||
const { active } = event;
|
const { active } = event;
|
||||||
const activeId = active.id as string;
|
const activeId = active.id as string;
|
||||||
|
const activeData = active.data.current;
|
||||||
|
|
||||||
// Find the active task and group
|
// 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);
|
||||||
|
|
||||||
|
dispatch(setDragState({
|
||||||
|
activeTaskId: null,
|
||||||
|
activeGroupId: activeId,
|
||||||
|
isDragging: true,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
// Dragging a task
|
||||||
let foundTask = null;
|
let foundTask = null;
|
||||||
let foundGroup = null;
|
let foundGroup = null;
|
||||||
|
|
||||||
@@ -134,14 +149,14 @@ const EnhancedKanbanBoard: React.FC<EnhancedKanbanBoardProps> = ({ projectId, cl
|
|||||||
}
|
}
|
||||||
|
|
||||||
setActiveTask(foundTask);
|
setActiveTask(foundTask);
|
||||||
setActiveGroup(foundGroup);
|
setActiveGroup(null);
|
||||||
|
|
||||||
// Update Redux drag state
|
|
||||||
dispatch(setDragState({
|
dispatch(setDragState({
|
||||||
activeTaskId: activeId,
|
activeTaskId: activeId,
|
||||||
activeGroupId: foundGroup?.id || null,
|
activeGroupId: foundGroup?.id || null,
|
||||||
isDragging: true,
|
isDragging: true,
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragOver = (event: DragOverEvent) => {
|
const handleDragOver = (event: DragOverEvent) => {
|
||||||
@@ -164,6 +179,7 @@ const EnhancedKanbanBoard: React.FC<EnhancedKanbanBoardProps> = ({ projectId, cl
|
|||||||
|
|
||||||
const handleDragEnd = (event: DragEndEvent) => {
|
const handleDragEnd = (event: DragEndEvent) => {
|
||||||
const { active, over } = event;
|
const { active, over } = event;
|
||||||
|
const activeData = active.data.current;
|
||||||
|
|
||||||
// Reset local state
|
// Reset local state
|
||||||
setActiveTask(null);
|
setActiveTask(null);
|
||||||
@@ -183,7 +199,28 @@ const EnhancedKanbanBoard: React.FC<EnhancedKanbanBoardProps> = ({ projectId, cl
|
|||||||
const activeId = active.id as string;
|
const activeId = active.id as string;
|
||||||
const overId = over.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 sourceGroup = null;
|
||||||
let targetGroup = null;
|
let targetGroup = null;
|
||||||
let sourceIndex = -1;
|
let sourceIndex = -1;
|
||||||
@@ -309,6 +346,14 @@ const EnhancedKanbanBoard: React.FC<EnhancedKanbanBoardProps> = ({ projectId, cl
|
|||||||
isDragOverlay={true}
|
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>
|
</DragOverlay>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
.enhanced-kanban-group {
|
.enhanced-kanban-group {
|
||||||
min-width: 280px;
|
width: 300px;
|
||||||
max-width: 320px;
|
min-width: 300px;
|
||||||
|
max-width: 300px;
|
||||||
background: var(--ant-color-bg-elevated);
|
background: var(--ant-color-bg-elevated);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border: 1px solid var(--ant-color-border);
|
border: 1px solid var(--ant-color-border);
|
||||||
box-shadow: 0 1px 2px var(--ant-color-shadow);
|
box-shadow: 0 1px 2px var(--ant-color-shadow);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enhanced-kanban-group.drag-over {
|
.enhanced-kanban-group.drag-over {
|
||||||
@@ -15,6 +18,16 @@
|
|||||||
box-shadow: 0 0 0 2px var(--ant-color-primary-border);
|
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 {
|
.enhanced-kanban-group-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -22,21 +35,44 @@
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
border-bottom: 1px solid var(--ant-color-border);
|
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 {
|
.enhanced-kanban-group-header h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
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 {
|
.task-count {
|
||||||
background: var(--ant-color-fill-secondary);
|
background: var(--ant-color-bg-container);
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
font-size: 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 {
|
.virtualization-indicator {
|
||||||
@@ -62,7 +98,9 @@
|
|||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
overflow: hidden;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Performance optimizations for large lists */
|
/* Performance optimizations for large lists */
|
||||||
@@ -127,6 +165,42 @@
|
|||||||
color: var(--ant-color-primary);
|
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 */
|
/* Responsive design for different screen sizes */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.enhanced-kanban-group {
|
.enhanced-kanban-group {
|
||||||
@@ -149,3 +223,22 @@
|
|||||||
max-height: 300px;
|
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 React, { useMemo, useRef, useEffect, useState } from 'react';
|
||||||
import { useDroppable } from '@dnd-kit/core';
|
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 { ITaskListGroup } from '@/types/tasks/taskList.types';
|
||||||
import EnhancedKanbanTaskCard from './EnhancedKanbanTaskCard';
|
import EnhancedKanbanTaskCard from './EnhancedKanbanTaskCard';
|
||||||
import VirtualizedTaskList from './VirtualizedTaskList';
|
import VirtualizedTaskList from './VirtualizedTaskList';
|
||||||
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
import './EnhancedKanbanGroup.css';
|
import './EnhancedKanbanGroup.css';
|
||||||
|
|
||||||
interface EnhancedKanbanGroupProps {
|
interface EnhancedKanbanGroupProps {
|
||||||
@@ -20,7 +22,25 @@ const EnhancedKanbanGroup: React.FC<EnhancedKanbanGroupProps> = React.memo(({
|
|||||||
activeTaskId,
|
activeTaskId,
|
||||||
overId
|
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,
|
id: group.id,
|
||||||
data: {
|
data: {
|
||||||
type: 'group',
|
type: 'group',
|
||||||
@@ -67,13 +87,41 @@ const EnhancedKanbanGroup: React.FC<EnhancedKanbanGroupProps> = React.memo(({
|
|||||||
// Performance optimization: Only render drop indicators when needed
|
// Performance optimization: Only render drop indicators when needed
|
||||||
const shouldShowDropIndicators = isDraggingOver && !shouldVirtualize;
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setRefs}
|
||||||
className={`enhanced-kanban-group ${isDraggingOver ? 'drag-over' : ''}`}
|
style={style}
|
||||||
|
className={`enhanced-kanban-group ${isDraggingOver ? 'drag-over' : ''} ${isGroupDragging ? 'group-dragging' : ''}`}
|
||||||
>
|
>
|
||||||
<div className="enhanced-kanban-group-header">
|
<div
|
||||||
<h3>{group.name}</h3>
|
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>
|
<span className="task-count">({group.tasks.length})</span>
|
||||||
{shouldVirtualize && (
|
{shouldVirtualize && (
|
||||||
<span className="virtualization-indicator" title="Virtualized for performance">
|
<span className="virtualization-indicator" title="Virtualized for performance">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
|
import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
|
||||||
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
import './EnhancedKanbanTaskCard.css';
|
import './EnhancedKanbanTaskCard.css';
|
||||||
|
|
||||||
interface EnhancedKanbanTaskCardProps {
|
interface EnhancedKanbanTaskCardProps {
|
||||||
@@ -17,6 +18,8 @@ const EnhancedKanbanTaskCard: React.FC<EnhancedKanbanTaskCardProps> = React.memo
|
|||||||
isDragOverlay = false,
|
isDragOverlay = false,
|
||||||
isDropTarget = false
|
isDropTarget = false
|
||||||
}) => {
|
}) => {
|
||||||
|
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
attributes,
|
attributes,
|
||||||
listeners,
|
listeners,
|
||||||
@@ -37,6 +40,7 @@ const EnhancedKanbanTaskCard: React.FC<EnhancedKanbanTaskCardProps> = React.memo
|
|||||||
transform: CSS.Transform.toString(transform),
|
transform: CSS.Transform.toString(transform),
|
||||||
transition,
|
transition,
|
||||||
opacity: isDragging ? 0.5 : 1,
|
opacity: isDragging ? 0.5 : 1,
|
||||||
|
backgroundColor: themeMode === 'dark' ? '#292929' : '#fafafa',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -48,7 +52,7 @@ const EnhancedKanbanTaskCard: React.FC<EnhancedKanbanTaskCardProps> = React.memo
|
|||||||
{...listeners}
|
{...listeners}
|
||||||
>
|
>
|
||||||
<div className="task-content">
|
<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.task_key && <div className="task-key">{task.task_key}</div>} */}
|
||||||
{task.assignees && task.assignees.length > 0 && (
|
{task.assignees && task.assignees.length > 0 && (
|
||||||
<div className="task-assignees">
|
<div className="task-assignees">
|
||||||
|
|||||||
@@ -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({
|
const enhancedKanbanSlice = createSlice({
|
||||||
name: 'enhancedKanbanReducer',
|
name: 'enhancedKanbanReducer',
|
||||||
initialState,
|
initialState,
|
||||||
@@ -420,6 +449,19 @@ const enhancedKanbanSlice = createSlice({
|
|||||||
state.taskGroups[targetGroupIndex].tasks = updatedTargetTasks;
|
state.taskGroups[targetGroupIndex].tasks = updatedTargetTasks;
|
||||||
state.groupCache[overGroupId] = state.taskGroups[targetGroupIndex];
|
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