From d70fb133b71c5e339383e872039ee5bb44d83a1b Mon Sep 17 00:00:00 2001 From: shancds Date: Wed, 2 Jul 2025 14:33:51 +0530 Subject: [PATCH 01/15] feat(enhanced-kanban): integrate native drag-and-drop functionality - Replaced the existing EnhancedKanbanBoard component with EnhancedKanbanBoardNativeDnD to support native drag-and-drop interactions. - Commented out the previous EnhancedKanbanBoard usage for potential future reference. --- .../EnhancedKanbanBoardNativeDnD.tsx | 208 ++++++++++++++++++ .../project-view-enhanced-board.tsx | 4 +- 2 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx new file mode 100644 index 00000000..568c0cab --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx @@ -0,0 +1,208 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { useSelector } 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'; + +// Minimal task card for prototype (reuse your styles) +const TaskCard: React.FC<{ + task: IProjectTask; + onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; + onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + groupId: string; + isDropIndicator: boolean; +}> = ({ task, onTaskDragStart, onTaskDragOver, onTaskDrop, groupId, isDropIndicator }) => { + const themeMode = useSelector((state: RootState) => state.themeReducer.mode); + const background = themeMode === 'dark' ? '#23272f' : '#fff'; + const color = themeMode === 'dark' ? '#fff' : '#23272f'; + return ( + <> + {isDropIndicator && ( +
+ )} +
onTaskDragStart(e, task.id!, groupId)} + onDragOver={e => onTaskDragOver(e, groupId, -1)} + onDrop={e => onTaskDrop(e, groupId, -1)} + style={{ background, color }} + > +
+
{task.name}
+
{task.task_key}
+
+ {task.assignees?.map(a => a.name).join(', ')} +
+
+
+ + ); +}; + +// Minimal group column for prototype +const KanbanGroup: React.FC<{ + group: ITaskListGroup; + onGroupDragStart: (e: React.DragEvent, groupId: string) => void; + onGroupDragOver: (e: React.DragEvent) => void; + onGroupDrop: (e: React.DragEvent, groupId: string) => void; + onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; + onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + hoveredTaskIdx: number | null; + hoveredGroupId: string | null; +}> = ({ group, onGroupDragStart, onGroupDragOver, onGroupDrop, onTaskDragStart, onTaskDragOver, onTaskDrop, hoveredTaskIdx, hoveredGroupId }) => ( +
+
onGroupDragStart(e, group.id)} + onDragOver={onGroupDragOver} + onDrop={e => onGroupDrop(e, group.id)} + > +

{group.name}

+ {group.tasks.length} +
+
onTaskDragOver(e, group.id, 0)} + onDrop={e => onTaskDrop(e, group.id, 0)} + > + {/* Drop indicator at the top of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === 0 && ( +
+ )} + {group.tasks.map((task, idx) => ( + + + + ))} + {/* Drop indicator at the end of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && ( +
+ )} +
+
+); + +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 [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 + useEffect(() => { + setGroups(reduxGroups.map(g => ({ ...g, tasks: [...g.tasks] }))); + }, [reduxGroups, projectId]); + + // Group drag handlers + const handleGroupDragStart = (e: React.DragEvent, groupId: string) => { + setDraggedGroupId(groupId); + setDragType('group'); + e.dataTransfer.effectAllowed = 'move'; + }; + const handleGroupDragOver = (e: React.DragEvent) => { + if (dragType !== 'group') return; + e.preventDefault(); + }; + const handleGroupDrop = (e: React.DragEvent, targetGroupId: string) => { + 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); + setDraggedGroupId(null); + setDragType(null); + }; + + // Task drag handlers + const handleTaskDragStart = (e: React.DragEvent, taskId: string, groupId: string) => { + setDraggedTaskId(taskId); + setDraggedTaskGroupId(groupId); + setDragType('task'); + e.dataTransfer.effectAllowed = 'move'; + }; + const handleTaskDragOver = (e: React.DragEvent, groupId: string, taskIdx: number) => { + if (dragType !== 'task') return; + e.preventDefault(); + if (draggedTaskId) { + setHoveredGroupId(groupId); + setHoveredTaskIdx(taskIdx); + } + }; + const handleTaskDrop = (e: React.DragEvent, targetGroupId: string, targetTaskIdx: number) => { + 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); + 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 + 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); + setDraggedTaskId(null); + setDraggedTaskGroupId(null); + setHoveredGroupId(null); + setHoveredTaskIdx(null); + setDragType(null); + }; + + return ( +
+
+ {groups.map(group => ( + + ))} +
+
+ ); +}; + +export default EnhancedKanbanBoardNativeDnD; \ No newline at end of file diff --git a/worklenz-frontend/src/pages/projects/projectView/enhancedBoard/project-view-enhanced-board.tsx b/worklenz-frontend/src/pages/projects/projectView/enhancedBoard/project-view-enhanced-board.tsx index 704802bd..27db13e4 100644 --- a/worklenz-frontend/src/pages/projects/projectView/enhancedBoard/project-view-enhanced-board.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/enhancedBoard/project-view-enhanced-board.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { useAppSelector } from '@/hooks/useAppSelector'; import EnhancedKanbanBoard from '@/components/enhanced-kanban/EnhancedKanbanBoard'; +import EnhancedKanbanBoardNativeDnD from '@/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD'; const ProjectViewEnhancedBoard: React.FC = () => { const { project } = useAppSelector(state => state.projectReducer); @@ -15,7 +16,8 @@ const ProjectViewEnhancedBoard: React.FC = () => { return (
- + {/* */} +
); }; From 11e5a6d379d99e7a3d7d730867e8fa6c29435e7c Mon Sep 17 00:00:00 2001 From: shancds Date: Wed, 2 Jul 2025 15:42:53 +0530 Subject: [PATCH 02/15] feat(enhanced-kanban): enhance Kanban board with improved task filtering and loading states - Integrated ImprovedTaskFilters component for better task management. - Added loading and error handling states to the Kanban board for improved user experience. - Updated drag-and-drop functionality to dispatch actions for reordering tasks and groups directly from Redux state. --- .../EnhancedKanbanBoardNativeDnD.tsx | 180 +++++++++++++----- 1 file changed, 132 insertions(+), 48 deletions(-) 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 => ( + + ))} +
+ )} +
+ ); }; From 775a91889f36311f250cd1468cf68fc071da0efd Mon Sep 17 00:00:00 2001 From: shancds Date: Wed, 2 Jul 2025 16:36:11 +0530 Subject: [PATCH 03/15] refactor(enhanced-kanban): relocate EnhancedKanbanBoardNativeDnD component - Moved EnhancedKanbanBoardNativeDnD to a new directory for better organization. - Updated import paths in ProjectViewEnhancedBoard to reflect the new location. --- .../EnhancedKanbanBoardNativeDnD.tsx | 103 +++++++++--------- .../project-view-enhanced-board.tsx | 2 +- 2 files changed, 53 insertions(+), 52 deletions(-) rename worklenz-frontend/src/components/enhanced-kanban/{ => EnhancedKanbanBoardNativeDnD}/EnhancedKanbanBoardNativeDnD.tsx (82%) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx similarity index 82% rename from worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx rename to worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 8f6ba995..53cbdae4 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -3,10 +3,10 @@ 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 '../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'; @@ -63,56 +63,56 @@ const KanbanGroup: React.FC<{ hoveredTaskIdx: number | null; hoveredGroupId: string | null; }> = ({ 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 ( -
-
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} - onDrop={e => onGroupDrop(e, group.id)} - > -

{group.name}

- {group.tasks.length} + draggable + onDragStart={e => onGroupDragStart(e, group.id)} + onDragOver={onGroupDragOver} + onDrop={e => onGroupDrop(e, group.id)} + > +

{group.name}

+ {group.tasks.length} +
+
onTaskDragOver(e, group.id, 0)} + onDrop={e => onTaskDrop(e, group.id, 0)} + > + {/* Drop indicator at the top of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === 0 && ( +
+ )} + {group.tasks.map((task, idx) => ( + + + + ))} + {/* Drop indicator at the end of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && ( +
+ )} +
-
onTaskDragOver(e, group.id, 0)} - onDrop={e => onTaskDrop(e, group.id, 0)} - > - {/* Drop indicator at the top of the group */} - {hoveredGroupId === group.id && hoveredTaskIdx === 0 && ( -
- )} - {group.tasks.map((task, idx) => ( - - - - ))} - {/* Drop indicator at the end of the group */} - {hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && ( -
- )} -
-
-)}; + ) +}; const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ projectId }) => { const dispatch = useDispatch(); @@ -135,6 +135,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project dispatch(fetchEnhancedKanbanTaskAssignees(projectId) as any); dispatch(fetchEnhancedKanbanLabels(projectId) as any); } + if (!statusCategories.length) { dispatch(fetchStatusesCategories() as any); } diff --git a/worklenz-frontend/src/pages/projects/projectView/enhancedBoard/project-view-enhanced-board.tsx b/worklenz-frontend/src/pages/projects/projectView/enhancedBoard/project-view-enhanced-board.tsx index 27db13e4..9681545c 100644 --- a/worklenz-frontend/src/pages/projects/projectView/enhancedBoard/project-view-enhanced-board.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/enhancedBoard/project-view-enhanced-board.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useAppSelector } from '@/hooks/useAppSelector'; import EnhancedKanbanBoard from '@/components/enhanced-kanban/EnhancedKanbanBoard'; -import EnhancedKanbanBoardNativeDnD from '@/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD'; +import EnhancedKanbanBoardNativeDnD from '@/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD'; const ProjectViewEnhancedBoard: React.FC = () => { const { project } = useAppSelector(state => state.projectReducer); From 7dfaacd28e9b9ccb3b39057ef5f629d422f2802e Mon Sep 17 00:00:00 2001 From: shancds Date: Wed, 2 Jul 2025 18:11:31 +0530 Subject: [PATCH 04/15] refactor(enhanced-kanban): update drag-and-drop functionality in EnhancedKanbanBoardNativeDnD - Added `idx` prop to TaskCard for better task index management during drag-and-drop. - Adjusted drop indicator styling for improved visibility. - Commented out unused drag-and-drop handlers in KanbanGroup for clarity. --- .../EnhancedKanbanBoardNativeDnD.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 53cbdae4..9b65f73d 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -22,21 +22,22 @@ const TaskCard: React.FC<{ onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; groupId: string; isDropIndicator: boolean; -}> = ({ task, onTaskDragStart, onTaskDragOver, onTaskDrop, groupId, isDropIndicator }) => { + idx: number; +}> = ({ task, onTaskDragStart, onTaskDragOver, onTaskDrop, groupId, isDropIndicator, idx }) => { const themeMode = useSelector((state: RootState) => state.themeReducer.mode); const background = themeMode === 'dark' ? '#23272f' : '#fff'; const color = themeMode === 'dark' ? '#fff' : '#23272f'; return ( <> {isDropIndicator && ( -
+
)}
onTaskDragStart(e, task.id!, groupId)} - onDragOver={e => onTaskDragOver(e, groupId, -1)} - onDrop={e => onTaskDrop(e, groupId, -1)} + onDragOver={e => onTaskDragOver(e, groupId, idx)} + onDrop={e => onTaskDrop(e, groupId, idx)} style={{ background, color }} >
@@ -86,8 +87,8 @@ const KanbanGroup: React.FC<{ {group.tasks.length}
onTaskDragOver(e, group.id, 0)} - onDrop={e => onTaskDrop(e, group.id, 0)} + // onDragOver={e => onTaskDragOver(e, group.id, 0)} + // onDrop={e => onTaskDrop(e, group.id, 0)} > {/* Drop indicator at the top of the group */} {hoveredGroupId === group.id && hoveredTaskIdx === 0 && ( @@ -101,7 +102,8 @@ const KanbanGroup: React.FC<{ onTaskDragOver={onTaskDragOver} onTaskDrop={onTaskDrop} groupId={group.id} - isDropIndicator={hoveredGroupId === group.id && hoveredTaskIdx === idx + 1} + isDropIndicator={hoveredGroupId === group.id && hoveredTaskIdx === idx} + idx={idx} /> ))} From df62f15734131dcc99c6bbf87b8de6efd2f7a1fc Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 11:22:52 +0530 Subject: [PATCH 05/15] refactor(enhanced-kanban): improve task reordering logic in EnhancedKanbanBoardNativeDnD - Enhanced drag-and-drop functionality to handle both same-group and cross-group task reordering more efficiently. - Simplified the task update process by consolidating logic for updating task arrays. - Ensured proper index adjustments during reordering to maintain task integrity and prevent errors. --- .../EnhancedKanbanBoardNativeDnD.tsx | 98 +++++++++++++------ 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 9b65f73d..2c87afe7 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -198,44 +198,82 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project if (dragType !== 'task') return; e.preventDefault(); if (!draggedTaskId || !draggedTaskGroupId || hoveredGroupId === null || hoveredTaskIdx === null) return; + // 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; + const taskIdx = sourceGroup.tasks.findIndex(t => t.id === draggedTaskId); if (taskIdx === -1) return; + 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--; + let insertIdx = hoveredTaskIdx; + + // Handle same group reordering + if (sourceGroup.id === targetGroup.id) { + // Create a single updated array for the same group + const updatedTasks = [...sourceGroup.tasks]; + updatedTasks.splice(taskIdx, 1); // Remove from original position + + // Adjust insert index if moving forward in the same array + if (taskIdx < insertIdx) { + insertIdx--; + } + + if (insertIdx < 0) insertIdx = 0; + if (insertIdx > updatedTasks.length) insertIdx = updatedTasks.length; + + updatedTasks.splice(insertIdx, 0, movedTask); // Insert at new position + + dispatch(reorderTasks({ + activeGroupId: sourceGroup.id, + overGroupId: targetGroup.id, + fromIndex: taskIdx, + toIndex: insertIdx, + task: movedTask, + updatedSourceTasks: updatedTasks, + updatedTargetTasks: updatedTasks, + })); + dispatch(reorderEnhancedKanbanTasks({ + activeGroupId: sourceGroup.id, + overGroupId: targetGroup.id, + fromIndex: taskIdx, + toIndex: insertIdx, + task: movedTask, + updatedSourceTasks: updatedTasks, + updatedTargetTasks: updatedTasks, + }) as any); + } else { + // Handle cross-group reordering + const updatedSourceTasks = [...sourceGroup.tasks]; + updatedSourceTasks.splice(taskIdx, 1); + + const updatedTargetTasks = [...targetGroup.tasks]; + if (insertIdx < 0) insertIdx = 0; + if (insertIdx > updatedTargetTasks.length) insertIdx = updatedTargetTasks.length; + 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); } - if (insertIdx < 0) insertIdx = 0; - if (insertIdx > targetGroup.tasks.length) insertIdx = targetGroup.tasks.length; - 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); From e05169b7b4a0142dcfc7ac68e0a97758cbb8b00a Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 11:33:31 +0530 Subject: [PATCH 06/15] refactor(enhanced-kanban): simplify EnhancedKanbanBoardNativeDnD component - Removed unused imports and commented-out code to clean up the component. - Consolidated task and group rendering logic by importing KanbanGroup directly. - Streamlined the code structure for better readability and maintainability. --- .../EnhancedKanbanBoardNativeDnD.tsx | 105 +----------------- .../KanbanGroup.tsx | 88 +++++++++++++++ .../EnhancedKanbanBoardNativeDnD/TaskCard.tsx | 64 +++++++++++ .../EnhancedKanbanBoardNativeDnD/index.ts | 3 + 4 files changed, 157 insertions(+), 103 deletions(-) create mode 100644 worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx create mode 100644 worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx create mode 100644 worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/index.ts diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 2c87afe7..01600dfb 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -1,8 +1,6 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect } 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'; @@ -13,108 +11,9 @@ 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'; +import KanbanGroup from './KanbanGroup'; -// Minimal task card for prototype (reuse your styles) -const TaskCard: React.FC<{ - task: IProjectTask; - onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; - onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; - onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; - groupId: string; - isDropIndicator: boolean; - idx: number; -}> = ({ task, onTaskDragStart, onTaskDragOver, onTaskDrop, groupId, isDropIndicator, idx }) => { - const themeMode = useSelector((state: RootState) => state.themeReducer.mode); - const background = themeMode === 'dark' ? '#23272f' : '#fff'; - const color = themeMode === 'dark' ? '#fff' : '#23272f'; - return ( - <> - {isDropIndicator && ( -
- )} -
onTaskDragStart(e, task.id!, groupId)} - onDragOver={e => onTaskDragOver(e, groupId, idx)} - onDrop={e => onTaskDrop(e, groupId, idx)} - style={{ background, color }} - > -
-
{task.name}
-
{task.task_key}
-
- {task.assignees?.map(a => a.name).join(', ')} -
-
-
- - ); -}; -// Minimal group column for prototype -const KanbanGroup: React.FC<{ - group: ITaskListGroup; - onGroupDragStart: (e: React.DragEvent, groupId: string) => void; - onGroupDragOver: (e: React.DragEvent) => void; - onGroupDrop: (e: React.DragEvent, groupId: string) => void; - onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; - onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; - onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; - hoveredTaskIdx: number | null; - hoveredGroupId: string | null; -}> = ({ 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} - onDrop={e => onGroupDrop(e, group.id)} - > -

{group.name}

- {group.tasks.length} -
-
onTaskDragOver(e, group.id, 0)} - // onDrop={e => onTaskDrop(e, group.id, 0)} - > - {/* Drop indicator at the top of the group */} - {hoveredGroupId === group.id && hoveredTaskIdx === 0 && ( -
- )} - {group.tasks.map((task, idx) => ( - - - - ))} - {/* Drop indicator at the end of the group */} - {hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && ( -
- )} -
-
- ) -}; const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ projectId }) => { const dispatch = useDispatch(); diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx new file mode 100644 index 00000000..f69ad544 --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx @@ -0,0 +1,88 @@ +import React, { memo, useMemo } from 'react'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import TaskCard from './TaskCard'; + +interface KanbanGroupProps { + group: ITaskListGroup; + onGroupDragStart: (e: React.DragEvent, groupId: string) => void; + onGroupDragOver: (e: React.DragEvent) => void; + onGroupDrop: (e: React.DragEvent, groupId: string) => void; + onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; + onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + hoveredTaskIdx: number | null; + hoveredGroupId: string | null; +} + +const KanbanGroup: React.FC = memo(({ + 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} + onDrop={e => onGroupDrop(e, group.id)} + > +

{group.name}

+ {group.tasks.length} +
+
+ {/* Drop indicator at the top of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === 0 && ( +
+
+
+ )} + + {group.tasks.map((task, idx) => ( + + + + ))} + + {/* Drop indicator at the end of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && ( +
+
+
+ )} +
+
+ ); +}); + +KanbanGroup.displayName = 'KanbanGroup'; + +export default KanbanGroup; \ No newline at end of file diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx new file mode 100644 index 00000000..f70761c6 --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx @@ -0,0 +1,64 @@ +import React, { memo } from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from '@/app/store'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; + +interface TaskCardProps { + task: IProjectTask; + onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; + onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + groupId: string; + isDropIndicator: boolean; + idx: number; +} + +const TaskCard: React.FC = memo(({ + task, + onTaskDragStart, + onTaskDragOver, + onTaskDrop, + groupId, + isDropIndicator, + idx +}) => { + const themeMode = useSelector((state: RootState) => state.themeReducer.mode); + const background = themeMode === 'dark' ? '#23272f' : '#fff'; + const color = themeMode === 'dark' ? '#fff' : '#23272f'; + + return ( + <> + {isDropIndicator && ( +
+ )} +
onTaskDragStart(e, task.id!, groupId)} + onDragOver={e => onTaskDragOver(e, groupId, idx)} + onDrop={e => onTaskDrop(e, groupId, idx)} + style={{ background, color }} + > +
+
{task.name}
+
{task.task_key}
+
+ {task.assignees?.map(a => a.name).join(', ')} +
+
+
+ + ); +}); + +TaskCard.displayName = 'TaskCard'; + +export default TaskCard; \ No newline at end of file diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/index.ts b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/index.ts new file mode 100644 index 00000000..1ddc40f1 --- /dev/null +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/index.ts @@ -0,0 +1,3 @@ +export { default } from './EnhancedKanbanBoardNativeDnD'; +export { default as TaskCard } from './TaskCard'; +export { default as KanbanGroup } from './KanbanGroup'; \ No newline at end of file From 8134c6af357c97e3b8ebd0fc9666698c729a3575 Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 12:31:51 +0530 Subject: [PATCH 07/15] feat(enhanced-kanban): add task creation functionality to Kanban groups - Introduced EnhancedKanbanCreateTaskCard component for adding tasks directly within Kanban groups. - Implemented conditional rendering of task creation buttons based on user roles (owner/admin or project manager). - Enhanced the KanbanGroup component to support task creation at both the top and bottom of the task list, improving user experience. --- .../EnhancedKanbanBoardNativeDnD.tsx | 2 + .../KanbanGroup.tsx | 231 ++++++++++++------ 2 files changed, 159 insertions(+), 74 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 01600dfb..ab7254b1 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -12,6 +12,7 @@ import { reorderGroups, reorderEnhancedKanbanGroups, reorderTasks, reorderEnhanc import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice'; import { useAppSelector } from '@/hooks/useAppSelector'; import KanbanGroup from './KanbanGroup'; +import EnhancedKanbanCreateSection from '../EnhancedKanbanCreateSection'; @@ -222,6 +223,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project hoveredGroupId={hoveredGroupId} /> ))} +
)}
diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx index f69ad544..5d73437e 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx @@ -1,86 +1,169 @@ -import React, { memo, useMemo } from 'react'; +import React, { memo, useMemo, useState } from 'react'; import { useAppSelector } from '@/hooks/useAppSelector'; import { ITaskListGroup } from '@/types/tasks/taskList.types'; import TaskCard from './TaskCard'; +import { themeWiseColor } from '@/utils/themeWiseColor'; +import EnhancedKanbanCreateTaskCard from '../EnhancedKanbanCreateTaskCard'; +import { PlusOutlined } from '@ant-design/icons'; +import Button from 'antd/es/button'; +import { useTranslation } from 'react-i18next'; +import { useAuthService } from '@/hooks/useAuth'; +import useIsProjectManager from '@/hooks/useIsProjectManager'; interface KanbanGroupProps { - group: ITaskListGroup; - onGroupDragStart: (e: React.DragEvent, groupId: string) => void; - onGroupDragOver: (e: React.DragEvent) => void; - onGroupDrop: (e: React.DragEvent, groupId: string) => void; - onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; - onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; - onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; - hoveredTaskIdx: number | null; - hoveredGroupId: string | null; + group: ITaskListGroup; + onGroupDragStart: (e: React.DragEvent, groupId: string) => void; + onGroupDragOver: (e: React.DragEvent) => void; + onGroupDrop: (e: React.DragEvent, groupId: string) => void; + onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; + onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + hoveredTaskIdx: number | null; + hoveredGroupId: string | null; } -const KanbanGroup: React.FC = memo(({ - group, - onGroupDragStart, - onGroupDragOver, - onGroupDrop, - onTaskDragStart, - onTaskDragOver, - onTaskDrop, - hoveredTaskIdx, - hoveredGroupId +const KanbanGroup: React.FC = memo(({ + 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]); + const themeMode = useAppSelector(state => state.themeReducer.mode); + const { t } = useTranslation('kanban-board'); + const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin(); + const isProjectManager = useIsProjectManager(); + const [showNewCardTop, setShowNewCardTop] = useState(false); + const [showNewCardBottom, setShowNewCardBottom] = useState(false); + 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} - onDrop={e => onGroupDrop(e, group.id)} - > -

{group.name}

- {group.tasks.length} -
-
- {/* Drop indicator at the top of the group */} - {hoveredGroupId === group.id && hoveredTaskIdx === 0 && ( -
-
-
- )} - - {group.tasks.map((task, idx) => ( - - - - ))} - - {/* Drop indicator at the end of the group */} - {hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && ( -
-
-
- )} -
-
- ); + return ( +
+
onGroupDragStart(e, group.id)} + onDragOver={onGroupDragOver} + onDrop={e => onGroupDrop(e, group.id)} + > +

{group.name}

+ {group.tasks.length} +
+
+ {/* If group is empty, render a drop zone */} + {group.tasks.length === 0 && ( +
{ e.preventDefault(); onTaskDragOver(e, group.id, 0); }} + onDrop={e => { e.preventDefault(); onTaskDrop(e, group.id, 0); }} + > + {/* Drop indicator at the end of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && ( +
+
+
+ )} + {(isOwnerOrAdmin || isProjectManager) && !showNewCardTop && !showNewCardBottom && ( + + )} + {showNewCardTop && } +
+ )} + + {/* Drop indicator at the top of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === 0 && ( +
+
+
+ )} + + {group.tasks.map((task, idx) => ( + + + + ))} + {(isOwnerOrAdmin || isProjectManager) && !showNewCardTop && !showNewCardBottom && group.tasks.length > 0 && ( + + )} + {showNewCardBottom && } + + {/* Drop indicator at the end of the group */} + {hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && ( +
+
+
+ )} +
+
+ ); }); KanbanGroup.displayName = 'KanbanGroup'; From e84ab43b366f797a9ab68b5809e3f4c03b1e078d Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 14:08:09 +0530 Subject: [PATCH 08/15] refactor(enhanced-kanban): clean up TaskCard component structure - Reformatted the TaskCard component for improved readability by adjusting indentation and spacing. - Ensured consistent styling and structure for the drop indicator and task card elements. - Maintained existing functionality while enhancing code clarity and maintainability. --- .../EnhancedKanbanBoardNativeDnD/TaskCard.tsx | 100 +++++++++--------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx index f70761c6..45c47d7d 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx @@ -4,59 +4,61 @@ import { RootState } from '@/app/store'; import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; interface TaskCardProps { - task: IProjectTask; - onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; - onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; - onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; - groupId: string; - isDropIndicator: boolean; - idx: number; + task: IProjectTask; + onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void; + onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void; + groupId: string; + isDropIndicator: boolean; + idx: number; } -const TaskCard: React.FC = memo(({ - task, - onTaskDragStart, - onTaskDragOver, - onTaskDrop, - groupId, - isDropIndicator, - idx +const TaskCard: React.FC = memo(({ + task, + onTaskDragStart, + onTaskDragOver, + onTaskDrop, + groupId, + isDropIndicator, + idx }) => { - const themeMode = useSelector((state: RootState) => state.themeReducer.mode); - const background = themeMode === 'dark' ? '#23272f' : '#fff'; - const color = themeMode === 'dark' ? '#fff' : '#23272f'; + const themeMode = useSelector((state: RootState) => state.themeReducer.mode); + const background = themeMode === 'dark' ? '#23272f' : '#fff'; + const color = themeMode === 'dark' ? '#fff' : '#23272f'; - return ( - <> - {isDropIndicator && ( -
- )} -
onTaskDragStart(e, task.id!, groupId)} - onDragOver={e => onTaskDragOver(e, groupId, idx)} - onDrop={e => onTaskDrop(e, groupId, idx)} - style={{ background, color }} - > -
-
{task.name}
-
{task.task_key}
-
- {task.assignees?.map(a => a.name).join(', ')} -
-
-
- - ); + return ( + <> + {isDropIndicator && ( +
onTaskDragStart(e, task.id!, groupId)} + onDragOver={e => onTaskDragOver(e, groupId, idx)} + onDrop={e => onTaskDrop(e, groupId, idx)} + /> + )} +
onTaskDragStart(e, task.id!, groupId)} + onDragOver={e => onTaskDragOver(e, groupId, idx)} + onDrop={e => onTaskDrop(e, groupId, idx)} + style={{ background, color }} + > +
+
{task.name}
+
{task.task_key}
+
+ {task.assignees?.map(a => a.name).join(', ')} +
+
+
+ + ); }); TaskCard.displayName = 'TaskCard'; From 2ff0555493f64dd8356d9dae6853efe49b6b3b06 Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 14:52:42 +0530 Subject: [PATCH 09/15] feat(enhanced-kanban): implement real-time task and group reordering with socket integration - Added socket functionality to emit task sort order changes during drag-and-drop operations. - Enhanced group drop handling to include API calls for updating group order, with error handling and alerts for failed updates. - Integrated authentication and socket context to manage project and team information effectively. --- .../EnhancedKanbanBoardNativeDnD.tsx | 70 +++++++++++++++++-- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index ab7254b1..8c078cdc 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -13,11 +13,19 @@ import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSli import { useAppSelector } from '@/hooks/useAppSelector'; import KanbanGroup from './KanbanGroup'; import EnhancedKanbanCreateSection from '../EnhancedKanbanCreateSection'; - - +import { useSocket } from '@/socket/socketContext'; +import { SocketEvents } from '@/shared/socket-events'; +import { useAuthService } from '@/hooks/useAuth'; +import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; +import alertService from '@/services/alerts/alertService'; +import logger from '@/utils/errorLogger'; const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ projectId }) => { const dispatch = useDispatch(); + const authService = useAuthService(); + const { socket } = useSocket(); + const project = useAppSelector((state: RootState) => state.projectReducer.project); + const teamId = authService.getCurrentSession()?.team_id; const { taskGroups, loadingGroups, @@ -62,7 +70,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project if (dragType !== 'group') return; e.preventDefault(); }; - const handleGroupDrop = (e: React.DragEvent, targetGroupId: string) => { + const handleGroupDrop = async (e: React.DragEvent, targetGroupId: string) => { if (dragType !== 'group') return; e.preventDefault(); if (!draggedGroupId || draggedGroupId === targetGroupId) return; @@ -75,6 +83,30 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project reorderedGroups.splice(toIdx, 0, moved); dispatch(reorderGroups({ fromIndex: fromIdx, toIndex: toIdx, reorderedGroups })); dispatch(reorderEnhancedKanbanGroups({ fromIndex: fromIdx, toIndex: toIdx, reorderedGroups }) as any); + + // API call for group order + try { + const columnOrder = reorderedGroups.map(group => group.id); + const requestBody = { status_order: columnOrder }; + const response = await statusApiService.updateStatusOrder(requestBody, projectId); + if (!response.done) { + // Revert the change if API call fails + const revertedGroups = [...reorderedGroups]; + const [movedBackGroup] = revertedGroups.splice(toIdx, 1); + revertedGroups.splice(fromIdx, 0, movedBackGroup); + dispatch(reorderGroups({ fromIndex: toIdx, toIndex: fromIdx, reorderedGroups: revertedGroups })); + alertService.error('Failed to update column order', 'Please try again'); + } + } catch (error) { + // Revert the change if API call fails + const revertedGroups = [...reorderedGroups]; + const [movedBackGroup] = revertedGroups.splice(toIdx, 1); + revertedGroups.splice(fromIdx, 0, movedBackGroup); + dispatch(reorderGroups({ fromIndex: toIdx, toIndex: fromIdx, reorderedGroups: revertedGroups })); + alertService.error('Failed to update column order', 'Please try again'); + logger.error('Failed to update column order', error); + } + setDraggedGroupId(null); setDragType(null); }; @@ -173,7 +205,37 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project updatedTargetTasks, }) as any); } - + + // Socket emit for task order + if (socket && projectId && teamId && movedTask) { + let toSortOrder = -1; + let toLastIndex = false; + if (insertIdx === targetGroup.tasks.length) { + toSortOrder = -1; + toLastIndex = true; + } else if (targetGroup.tasks[insertIdx]) { + toSortOrder = typeof targetGroup.tasks[insertIdx].sort_order === 'number' + ? targetGroup.tasks[insertIdx].sort_order + : -1; + toLastIndex = false; + } else if (targetGroup.tasks.length > 0) { + const lastSortOrder = targetGroup.tasks[targetGroup.tasks.length - 1].sort_order; + toSortOrder = typeof lastSortOrder === 'number' ? lastSortOrder : -1; + toLastIndex = false; + } + socket.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), { + project_id: projectId, + from_index: movedTask.sort_order, + to_index: toSortOrder, + to_last_index: toLastIndex, + from_group: sourceGroup.id, + to_group: targetGroup.id, + group_by: 'status', + task: movedTask, + team_id: teamId, + }); + } + setDraggedTaskId(null); setDraggedTaskGroupId(null); setHoveredGroupId(null); From 6c4bcbe3000a69c23ce9c178a28e6601a00ffa57 Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 15:17:19 +0530 Subject: [PATCH 10/15] feat(enhanced-kanban): allow dynamic grouping in task reordering - Integrated dynamic grouping by utilizing the `groupBy` state from the enhancedKanbanReducer. - Updated the task reordering logic to respect the selected grouping, defaulting to 'status' when no group is specified. - Enhanced the overall flexibility of the EnhancedKanbanBoardNativeDnD component for improved user experience. --- .../EnhancedKanbanBoardNativeDnD.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 8c078cdc..498413c4 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -25,6 +25,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project const authService = useAuthService(); const { socket } = useSocket(); const project = useAppSelector((state: RootState) => state.projectReducer.project); + const groupBy = useSelector((state: RootState) => state.enhancedKanbanReducer.groupBy); const teamId = authService.getCurrentSession()?.team_id; const { taskGroups, @@ -230,7 +231,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project to_last_index: toLastIndex, from_group: sourceGroup.id, to_group: targetGroup.id, - group_by: 'status', + group_by: groupBy || 'status', task: movedTask, team_id: teamId, }); From aee09aeb0d779b50f25127e1dc2f7c08c9f237c4 Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 15:39:11 +0530 Subject: [PATCH 11/15] feat(enhanced-kanban): implement real-time task updates via socket integration - Added a useEffect hook to handle incoming tasks through a socket connection. - Implemented logic to differentiate between regular tasks and subtasks, updating the state accordingly. - Enhanced the EnhancedKanbanBoardNativeDnD component to support real-time task management, improving user interactivity. --- .../EnhancedKanbanBoardNativeDnD.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 498413c4..755ce2d0 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -244,6 +244,37 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project setDragType(null); }; + useEffect(() => { + if (!socket) return; + + // Handler for new task received via socket + const handleNewTaskReceived = (data: any) => { + if (!data) return; + if (data.parent_task_id) { + // Subtask: update subtasks in the correct group + dispatch({ + type: 'enhancedKanbanReducer/updateEnhancedKanbanSubtask', + payload: { sectionId: '', subtask: data, mode: 'add' } + }); + } else { + // Regular task: add to the correct group + let sectionId = ''; + if (groupBy === 'status') sectionId = data.status; + else if (groupBy === 'priority') sectionId = data.priority; + else if (groupBy === 'phase') sectionId = data.phase_id; + dispatch({ + type: 'enhancedKanbanReducer/addTaskToGroup', + payload: { sectionId, task: data } + }); + } + }; + + socket.on(SocketEvents.QUICK_TASK.toString(), handleNewTaskReceived); + return () => { + socket.off(SocketEvents.QUICK_TASK.toString(), handleNewTaskReceived); + }; + }, [socket, groupBy, dispatch]); + if (error) { return ( From 73c78dd28f35b1aed2c0c0b4682bc96d0fec0dbf Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 18:51:48 +0530 Subject: [PATCH 12/15] feat(enhanced-kanban): enhance TaskCard with task selection and improved UI - Added task selection functionality by dispatching actions on card click to show the task drawer. - Introduced labels display for tasks, enhancing visual organization. - Improved task content layout with priority indicators and due date formatting. - Integrated AvatarGroup and LazyAssigneeSelector for better assignee management and visibility. --- .../EnhancedKanbanBoardNativeDnD/TaskCard.tsx | 66 +++++++++++++++++-- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx index 45c47d7d..1929351d 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx @@ -1,7 +1,13 @@ -import React, { memo } from 'react'; +import React, { memo, useCallback } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from '@/app/store'; import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { setSelectedTaskId, setShowTaskDrawer } from '@/features/task-drawer/task-drawer.slice'; +import { useTranslation } from 'react-i18next'; +import AvatarGroup from '@/components/AvatarGroup'; +import LazyAssigneeSelectorWrapper from '@/components/task-management/lazy-assignee-selector'; +import { format } from 'date-fns'; interface TaskCardProps { task: IProjectTask; @@ -25,6 +31,15 @@ const TaskCard: React.FC = memo(({ const themeMode = useSelector((state: RootState) => state.themeReducer.mode); const background = themeMode === 'dark' ? '#23272f' : '#fff'; const color = themeMode === 'dark' ? '#fff' : '#23272f'; + const dispatch = useAppDispatch(); + const { t } = useTranslation('kanban-board'); + + const handleCardClick = useCallback((e: React.MouseEvent, id: string) => { + // Prevent the event from propagating to parent elements + e.stopPropagation(); + dispatch(setSelectedTaskId(id)); + dispatch(setShowTaskDrawer(true)); + }, [dispatch]); return ( <> @@ -32,7 +47,7 @@ const TaskCard: React.FC = memo(({
= memo(({ onDragOver={e => onTaskDragOver(e, groupId, idx)} onDrop={e => onTaskDrop(e, groupId, idx)} style={{ background, color }} + onClick={e => handleCardClick(e, task.id!)} >
-
{task.name}
-
{task.task_key}
-
- {task.assignees?.map(a => a.name).join(', ')} +
+ {task.labels?.map(label => ( +
+ {label.name} +
+ ))} +
+
+
+
{task.name}
+
+ +
+
+ + +
+
+ {task.end_date ? format(new Date(task.end_date), 'MMM d, yyyy') : ''} +
From fb9e430ba0d6afddff763a49ffd78b7ae7b2c9ad Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 19:05:40 +0530 Subject: [PATCH 13/15] feat(enhanced-kanban): update TaskCard to display task due date - Added a new due date display within the TaskCard component, enhancing task visibility. - Removed the previous due date element to streamline the layout and improve UI consistency. --- .../EnhancedKanbanBoardNativeDnD/TaskCard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx index 1929351d..237cd80b 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/TaskCard.tsx @@ -96,6 +96,9 @@ const TaskCard: React.FC = memo(({
+
+ {task.end_date ? format(new Date(task.end_date), 'MMM d, yyyy') : ''} +
= memo(({ />
-
- {task.end_date ? format(new Date(task.end_date), 'MMM d, yyyy') : ''} -
From 6d4d851f1d6bce96cd39d32321fa736e80401118 Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 19:13:03 +0530 Subject: [PATCH 14/15] feat(enhanced-kanban): replace loading spinner with skeleton loader - Updated the loading state in the EnhancedKanbanBoardNativeDnD component to use a Skeleton loader instead of a Spin component for improved user experience during data fetching. --- .../EnhancedKanbanBoardNativeDnD.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx index 755ce2d0..f9600aa8 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/EnhancedKanbanBoardNativeDnD.tsx @@ -19,6 +19,7 @@ import { useAuthService } from '@/hooks/useAuth'; import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; import alertService from '@/services/alerts/alertService'; import logger from '@/utils/errorLogger'; +import Skeleton from 'antd/es/skeleton/Skeleton'; const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ projectId }) => { const dispatch = useDispatch(); @@ -294,7 +295,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project {loadingGroups ? (
- +
) : taskGroups.length === 0 ? ( From 3532b0bbfb3b959bd8cb4c87a8c823e2cdc0c71b Mon Sep 17 00:00:00 2001 From: shancds Date: Thu, 3 Jul 2025 19:24:17 +0530 Subject: [PATCH 15/15] feat(enhanced-kanban): enhance task card styling for improved UI - Added a light border, box shadow, and background color to the EnhancedKanbanTaskCard for a more visually appealing design. - Refactored the rendering of TaskCard components in KanbanGroup to eliminate unnecessary React.Fragment wrappers, improving code clarity. --- .../KanbanGroup.tsx | 22 +++++++++---------- .../EnhancedKanbanTaskCard.css | 6 +++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx index 5d73437e..e7dd5591 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanBoardNativeDnD/KanbanGroup.tsx @@ -122,17 +122,15 @@ const KanbanGroup: React.FC = memo(({ )} {group.tasks.map((task, idx) => ( - - - + ))} {(isOwnerOrAdmin || isProjectManager) && !showNewCardTop && !showNewCardBottom && group.tasks.length > 0 && (
); diff --git a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.css b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.css index 0425f61d..e7f7ae02 100644 --- a/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.css +++ b/worklenz-frontend/src/components/enhanced-kanban/EnhancedKanbanTaskCard.css @@ -13,6 +13,12 @@ position: relative; } +html.light .enhanced-kanban-task-card { + border: 1.5px solid #e1e4e8 !important; /* Asana-like light border */ + box-shadow: 0 1px 4px 0 rgba(60, 64, 67, 0.08), 0 0.5px 1.5px 0 rgba(60, 64, 67, 0.03); + background: #fff !important; +} + .enhanced-kanban-task-card:hover { box-shadow: 0 2px 6px var(--ant-color-shadow); transform: translateY(-1px);