diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx
index 568c0cab..8f6ba995 100644
--- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx
+++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx
@@ -1,11 +1,18 @@
-import React, { useState, useRef, useEffect } from 'react';
-import { useSelector } from 'react-redux';
+import React, { useState, useEffect, useMemo } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '@/app/store';
import { ITaskListGroup } from '@/types/tasks/taskList.types';
import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
import './EnhancedKanbanBoard.css';
import './EnhancedKanbanGroup.css';
import './EnhancedKanbanTaskCard.css';
+import ImprovedTaskFilters from '../task-management/improved-task-filters';
+import Card from 'antd/es/card';
+import Spin from 'antd/es/spin';
+import Empty from 'antd/es/empty';
+import { reorderGroups, reorderEnhancedKanbanGroups, reorderTasks, reorderEnhancedKanbanTasks, fetchEnhancedKanbanLabels, fetchEnhancedKanbanGroups, fetchEnhancedKanbanTaskAssignees } from '@/features/enhanced-kanban/enhanced-kanban.slice';
+import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice';
+import { useAppSelector } from '@/hooks/useAppSelector';
// Minimal task card for prototype (reuse your styles)
const TaskCard: React.FC<{
@@ -55,13 +62,22 @@ const KanbanGroup: React.FC<{
onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void;
hoveredTaskIdx: number | null;
hoveredGroupId: string | null;
-}> = ({ group, onGroupDragStart, onGroupDragOver, onGroupDrop, onTaskDragStart, onTaskDragOver, onTaskDrop, hoveredTaskIdx, hoveredGroupId }) => (
-
+}> = ({ group, onGroupDragStart, onGroupDragOver, onGroupDrop, onTaskDragStart, onTaskDragOver, onTaskDrop, hoveredTaskIdx, hoveredGroupId }) => {
+ const themeMode = useAppSelector(state => state.themeReducer.mode);
+
+ 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 (
+
onGroupDragStart(e, group.id)}
onDragOver={onGroupDragOver}
@@ -96,25 +112,42 @@ const KanbanGroup: React.FC<{
)}
-);
+)};
const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ projectId }) => {
- // Get initial groups from Redux
- const reduxGroups = useSelector((state: RootState) => state.enhancedKanbanReducer.taskGroups);
- // Local state for groups/tasks
- const [groups, setGroups] = useState
([]);
- // Drag state
+ const dispatch = useDispatch();
+ const {
+ taskGroups,
+ loadingGroups,
+ error,
+ } = useSelector((state: RootState) => state.enhancedKanbanReducer);
const [draggedGroupId, setDraggedGroupId] = useState(null);
const [draggedTaskId, setDraggedTaskId] = useState(null);
const [draggedTaskGroupId, setDraggedTaskGroupId] = useState(null);
const [hoveredGroupId, setHoveredGroupId] = useState(null);
const [hoveredTaskIdx, setHoveredTaskIdx] = useState(null);
const [dragType, setDragType] = useState<'group' | 'task' | null>(null);
-
- // Sync local state with Redux on mount or when reduxGroups or projectId change
+ const { statusCategories, status: existingStatuses } = useAppSelector((state) => state.taskStatusReducer);
useEffect(() => {
- setGroups(reduxGroups.map(g => ({ ...g, tasks: [...g.tasks] })));
- }, [reduxGroups, projectId]);
+ if (projectId) {
+ dispatch(fetchEnhancedKanbanGroups(projectId) as any);
+ // Load filter data for enhanced kanban
+ dispatch(fetchEnhancedKanbanTaskAssignees(projectId) as any);
+ dispatch(fetchEnhancedKanbanLabels(projectId) as any);
+ }
+ if (!statusCategories.length) {
+ dispatch(fetchStatusesCategories() as any);
+ }
+ }, [dispatch, projectId]);
+ // Reset drag state if taskGroups changes (e.g., real-time update)
+ useEffect(() => {
+ setDraggedGroupId(null);
+ setDraggedTaskId(null);
+ setDraggedTaskGroupId(null);
+ setHoveredGroupId(null);
+ setHoveredTaskIdx(null);
+ setDragType(null);
+ }, [taskGroups]);
// Group drag handlers
const handleGroupDragStart = (e: React.DragEvent, groupId: string) => {
@@ -130,12 +163,15 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project
if (dragType !== 'group') return;
e.preventDefault();
if (!draggedGroupId || draggedGroupId === targetGroupId) return;
- const updated = [...groups];
- const fromIdx = updated.findIndex(g => g.id === draggedGroupId);
- const [moved] = updated.splice(fromIdx, 1);
- const toIdx = updated.findIndex(g => g.id === targetGroupId);
- updated.splice(toIdx, 0, moved);
- setGroups(updated);
+ // Calculate new order and dispatch
+ const fromIdx = taskGroups.findIndex(g => g.id === draggedGroupId);
+ const toIdx = taskGroups.findIndex(g => g.id === targetGroupId);
+ if (fromIdx === -1 || toIdx === -1) return;
+ const reorderedGroups = [...taskGroups];
+ const [moved] = reorderedGroups.splice(fromIdx, 1);
+ reorderedGroups.splice(toIdx, 0, moved);
+ dispatch(reorderGroups({ fromIndex: fromIdx, toIndex: toIdx, reorderedGroups }));
+ dispatch(reorderEnhancedKanbanGroups({ fromIndex: fromIdx, toIndex: toIdx, reorderedGroups }) as any);
setDraggedGroupId(null);
setDragType(null);
};
@@ -159,23 +195,44 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project
if (dragType !== 'task') return;
e.preventDefault();
if (!draggedTaskId || !draggedTaskGroupId || hoveredGroupId === null || hoveredTaskIdx === null) return;
- const updated = [...groups];
- const sourceGroup = updated.find(g => g.id === draggedTaskGroupId);
- const targetGroup = updated.find(g => g.id === targetGroupId);
+ // Calculate new order and dispatch
+ const sourceGroup = taskGroups.find(g => g.id === draggedTaskGroupId);
+ const targetGroup = taskGroups.find(g => g.id === targetGroupId);
if (!sourceGroup || !targetGroup) return;
- // Remove from source
const taskIdx = sourceGroup.tasks.findIndex(t => t.id === draggedTaskId);
if (taskIdx === -1) return;
- const [movedTask] = sourceGroup.tasks.splice(taskIdx, 1);
- // Insert into target at the correct index
+ const movedTask = sourceGroup.tasks[taskIdx];
+ // Prepare updated task arrays
+ const updatedSourceTasks = [...sourceGroup.tasks];
+ updatedSourceTasks.splice(taskIdx, 1);
let insertIdx = targetTaskIdx;
if (sourceGroup.id === targetGroup.id && taskIdx < insertIdx) {
insertIdx--;
}
if (insertIdx < 0) insertIdx = 0;
if (insertIdx > targetGroup.tasks.length) insertIdx = targetGroup.tasks.length;
- targetGroup.tasks.splice(insertIdx, 0, movedTask);
- setGroups(updated);
+ const updatedTargetTasks = sourceGroup.id === targetGroup.id
+ ? [...updatedSourceTasks]
+ : [...targetGroup.tasks];
+ updatedTargetTasks.splice(insertIdx, 0, movedTask);
+ dispatch(reorderTasks({
+ activeGroupId: sourceGroup.id,
+ overGroupId: targetGroup.id,
+ fromIndex: taskIdx,
+ toIndex: insertIdx,
+ task: movedTask,
+ updatedSourceTasks,
+ updatedTargetTasks,
+ }));
+ dispatch(reorderEnhancedKanbanTasks({
+ activeGroupId: sourceGroup.id,
+ overGroupId: targetGroup.id,
+ fromIndex: taskIdx,
+ toIndex: insertIdx,
+ task: movedTask,
+ updatedSourceTasks,
+ updatedTargetTasks,
+ }) as any);
setDraggedTaskId(null);
setDraggedTaskGroupId(null);
setHoveredGroupId(null);
@@ -183,25 +240,52 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project
setDragType(null);
};
+ if (error) {
+ return (
+
+
+
+ );
+ }
+
return (
-
-
- {groups.map(group => (
-
- ))}
+ <>
+
+ Loading filters...
}>
+
+
-
+
+ {loadingGroups ? (
+
+
+
+
+
+ ) : taskGroups.length === 0 ? (
+
+
+
+ ) : (
+
+ {taskGroups.map(group => (
+
+ ))}
+
+ )}
+
+ >
);
};