diff --git a/worklenz-frontend/src/components/task-management/TaskGroup.tsx b/worklenz-frontend/src/components/task-management/TaskGroup.tsx index 24f1a384..1231464e 100644 --- a/worklenz-frontend/src/components/task-management/TaskGroup.tsx +++ b/worklenz-frontend/src/components/task-management/TaskGroup.tsx @@ -1,14 +1,15 @@ import React, { useState } from 'react'; import { useDroppable } from '@dnd-kit/core'; import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; -import { Card, Button, Typography, Badge, Collapse, Space, Tooltip } from 'antd'; +import { Button, Typography, Badge, Space, Tooltip } from 'antd'; import { CaretRightOutlined, CaretDownOutlined, PlusOutlined, MoreOutlined, } from '@ant-design/icons'; -import { ITaskListGroup, IProjectTask } from '@/types/tasks/taskList.types'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; import { IGroupBy } from '@/features/tasks/tasks.slice'; import TaskRow from './TaskRow'; @@ -21,6 +22,8 @@ interface TaskGroupProps { selectedTaskIds: string[]; onAddTask?: (groupId: string) => void; onToggleCollapse?: (groupId: string) => void; + onSelectTask?: (taskId: string, selected: boolean) => void; + onToggleSubtasks?: (taskId: string) => void; } const TaskGroup: React.FC = ({ @@ -30,6 +33,8 @@ const TaskGroup: React.FC = ({ selectedTaskIds, onAddTask, onToggleCollapse, + onSelectTask, + onToggleSubtasks, }) => { const [isCollapsed, setIsCollapsed] = useState(false); @@ -46,7 +51,7 @@ const TaskGroup: React.FC = ({ // Calculate group statistics const completedTasks = group.tasks.filter(task => - task.status_category === 'DONE' || task.complete_ratio === 100 + task.status_category?.is_done || task.complete_ratio === 100 ).length; const totalTasks = group.tasks.length; const completionRate = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0; @@ -81,101 +86,158 @@ const TaskGroup: React.FC = ({ }; return ( - - {/* Group Header */} -
-
-
-
+
- - {group.name} - -
- - {completionRate > 0 && ( - - {completionRate}% complete - - )} +
+
+
+ + {group.name} + + + {completionRate > 0 && ( + + {completionRate}% complete + + )} +
+
+
+
+
+
+
+
+
+
+ + +
- - - -
{/* Progress Bar */} - {totalTasks > 0 && ( -
-
-
+ {totalTasks > 0 && !isCollapsed && ( +
+
+
+
+
+
+
)}
+ {/* Column Headers */} + {!isCollapsed && totalTasks > 0 && ( +
+
+
+
+
+
+ Key +
+
+ Task +
+
+
+
+ Progress +
+
+ Members +
+
+ Labels +
+
+ Status +
+
+ Priority +
+
+ Time Tracking +
+
+
+
+ )} + {/* Tasks List */} {!isCollapsed && ( -
+
{group.tasks.length === 0 ? ( -
- No tasks in this group -
- +
+
+
+
+ No tasks in this group +
+ +
+
+
) : ( -
+
{group.tasks.map((task, index) => ( = ({ currentGrouping={currentGrouping} isSelected={selectedTaskIds.includes(task.id!)} index={index} + onSelect={onSelectTask} + onToggleSubtasks={onToggleSubtasks} /> ))}
@@ -192,7 +256,155 @@ const TaskGroup: React.FC = ({ )}
)} - + + +
); }; diff --git a/worklenz-frontend/src/components/task-management/TaskListBoard.tsx b/worklenz-frontend/src/components/task-management/TaskListBoard.tsx index 3f2361be..2621ea91 100644 --- a/worklenz-frontend/src/components/task-management/TaskListBoard.tsx +++ b/worklenz-frontend/src/components/task-management/TaskListBoard.tsx @@ -26,10 +26,9 @@ import { setGroup, fetchTaskGroups, reorderTasks, - collapseAllGroups, - expandAllGroups, } from '@/features/tasks/tasks.slice'; -import { IProjectTask, ITaskListGroup } from '@/types/tasks/taskList.types'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; import TaskGroup from './TaskGroup'; import TaskRow from './TaskRow'; import BulkActionBar from './BulkActionBar'; @@ -66,9 +65,8 @@ const TaskListBoard: React.FC = ({ projectId, className = '' archived, } = useSelector((state: RootState) => state.taskReducer); - // Selection state (assuming you have a selection slice) - // const selectedTaskIds = useSelector((state: RootState) => state.selection?.selectedTaskIds || []); - const selectedTaskIds: string[] = []; // Temporary placeholder + // Selection state + const [selectedTaskIds, setSelectedTaskIds] = useState([]); // Drag and Drop sensors const sensors = useSensors( @@ -204,12 +202,12 @@ const TaskListBoard: React.FC = ({ projectId, className = '' const handleCollapseAll = () => { // This would need to be implemented in the tasks slice - // dispatch(collapseAllGroups()); + console.log('Collapse all groups'); }; const handleExpandAll = () => { // This would need to be implemented in the tasks slice - // dispatch(expandAllGroups()); + console.log('Expand all groups'); }; const handleRefresh = () => { @@ -218,6 +216,21 @@ const TaskListBoard: React.FC = ({ projectId, className = '' } }; + const handleSelectTask = (taskId: string, selected: boolean) => { + setSelectedTaskIds(prev => { + if (selected) { + return [...prev, taskId]; + } else { + return prev.filter(id => id !== taskId); + } + }); + }; + + const handleToggleSubtasks = (taskId: string) => { + // Implementation for toggling subtasks + console.log('Toggle subtasks for task:', taskId); + }; + if (error) { return ( @@ -285,7 +298,7 @@ const TaskListBoard: React.FC = ({ projectId, className = '' /> )} - {/* Task Groups */} + {/* Task Groups Container */}
{loadingGroups ? ( @@ -308,7 +321,7 @@ const TaskListBoard: React.FC = ({ projectId, className = '' onDragOver={handleDragOver} onDragEnd={handleDragEnd} > -
+
{taskGroups.map((group) => ( = ({ projectId, className = '' projectId={projectId} currentGrouping={groupBy} selectedTaskIds={selectedTaskIds} + onSelectTask={handleSelectTask} + onToggleSubtasks={handleToggleSubtasks} /> ))}
@@ -335,6 +350,60 @@ const TaskListBoard: React.FC = ({ projectId, className = '' )}
+ +
); }; diff --git a/worklenz-frontend/src/components/task-management/TaskRow.tsx b/worklenz-frontend/src/components/task-management/TaskRow.tsx index fdb30413..f20188b9 100644 --- a/worklenz-frontend/src/components/task-management/TaskRow.tsx +++ b/worklenz-frontend/src/components/task-management/TaskRow.tsx @@ -3,13 +3,13 @@ import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { Checkbox, Avatar, Tag, Progress, Typography, Space, Button, Tooltip } from 'antd'; import { - DragOutlined, + HolderOutlined, EyeOutlined, MessageOutlined, PaperClipOutlined, ClockCircleOutlined, } from '@ant-design/icons'; -import { IProjectTask } from '@/types/tasks/taskList.types'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; import { IGroupBy } from '@/features/tasks/tasks.slice'; const { Text } = Typography; @@ -90,113 +90,94 @@ const TaskRow: React.FC = ({ const dueDate = formatDueDate(task.end_date); return ( -
-
- {/* Drag Handle */} -
- {/* Selection Checkbox */} - handleSelectChange(e.target.checked)} - /> + {/* Selection Checkbox */} +
+ handleSelectChange(e.target.checked)} + /> +
- {/* Task Content */} -
-
-
- {/* Task Name and Key */} -
- {task.project_id && ( - - {task.key} - - )} - - {task.name} + {/* Task Key */} +
+ {task.project_id && task.task_key && ( + + {task.task_key} - {task.sub_tasks_count && task.sub_tasks_count > 0 && ( - - )} -
- - {/* Description (if exists) */} - {task.description && ( - - {task.description} - - )} - - {/* Labels */} - {task.labels && task.labels.length > 0 && ( -
- {task.labels.slice(0, 3).map((label) => ( - - {label.name} - - ))} - {task.labels.length > 3 && ( - - +{task.labels.length - 3} more - - )} -
)}
- {/* Task Metadata */} -
- {/* Progress */} - {task.complete_ratio !== undefined && task.complete_ratio > 0 && ( -
+ {/* Task Name */} +
+
+
+ + {task.name} + + {task.sub_tasks_count && task.sub_tasks_count > 0 && ( + + )} +
+
+
+
+ + {/* Scrollable Columns */} +
+ {/* Progress */} +
+ {task.complete_ratio !== undefined && task.complete_ratio >= 0 && ( +
+ {task.complete_ratio}%
)} +
- {/* Assignees */} + {/* Members */} +
{task.assignees && task.assignees.length > 0 && ( {task.assignees.map((assignee) => ( {assignee.name?.charAt(0)?.toUpperCase()} @@ -204,59 +185,87 @@ const TaskRow: React.FC = ({ ))} )} +
- {/* Priority Indicator */} - {task.priority_color && ( -
- )} - - {/* Due Date */} - {dueDate && ( -
- - - {dueDate.text} - + {/* Labels */} +
+ {task.labels && task.labels.length > 0 && ( +
+ {task.labels.slice(0, 3).map((label) => ( + + {label.name} + + ))} + {task.labels.length > 3 && ( + + +{task.labels.length - 3} + + )}
)} +
- {/* Task Indicators */} - - {task.comments_count && task.comments_count > 0 && ( -
- - {task.comments_count} + {/* Status */} +
+ {task.status_name && ( +
+ {task.status_name} +
+ )} +
+ + {/* Priority */} +
+ {task.priority_name && ( +
+
+ {task.priority_name} +
+ )} +
+ + {/* Time Tracking */} +
+
+ {task.time_spent_string && ( +
+ + {task.time_spent_string}
)} - - {task.attachments_count && task.attachments_count > 0 && ( -
- - {task.attachments_count} -
- )} - - - {/* View/Edit Button */} -
@@ -264,7 +273,7 @@ const TaskRow: React.FC = ({ {/* Subtasks */} {task.show_sub_tasks && task.sub_tasks && task.sub_tasks.length > 0 && ( -
+
{task.sub_tasks.map((subtask) => ( = ({ ))}
)} -
+ + + ); };