diff --git a/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx b/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx index 6a0d5943..b7a0d404 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useCallback } from 'react'; import { useDroppable } from '@dnd-kit/core'; +// @ts-ignore: Heroicons module types import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/24/outline'; import { Checkbox } from 'antd'; import { getContrastColor } from '@/utils/colorUtils'; @@ -78,7 +79,7 @@ const TaskGroupHeader: React.FC = ({ group, isCollapsed, o return (
= ({ group, isCollapsed, o {/* Color indicator (removed as full header is colored) */} {/* Group name and count */} -
+
- {group.name} - - - {group.count} + {group.name} ({group.count})
diff --git a/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx b/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx index 61bfb246..2d36857d 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx @@ -55,7 +55,7 @@ import { TaskListField } from '@/types/task-list-field.types'; import { useParams } from 'react-router-dom'; import ImprovedTaskFilters from '@/components/task-management/improved-task-filters'; import OptimizedBulkActionBar from '@/components/task-management/optimized-bulk-action-bar'; -import { Bars3Icon } from '@heroicons/react/24/outline'; +import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers'; import { HolderOutlined } from '@ant-design/icons'; import { COLUMN_KEYS } from '@/features/tasks/tasks.slice'; @@ -137,6 +137,9 @@ const TaskListV2: React.FC = ({ projectId }) => { const fields = useAppSelector(state => state.taskManagementFields) || []; + // Enable real-time updates via socket handlers + useTaskSocketHandlers(); + // Filter visible columns based on fields const visibleColumns = useMemo(() => { return BASE_COLUMNS.filter(column => { @@ -298,22 +301,13 @@ const TaskListV2: React.FC = ({ projectId }) => { targetGroupId: targetGroup.id, })); - // If we need to insert at a specific position (not at the end) - if (insertIndex < targetGroup.taskIds.length) { - const newTaskIds = [...targetGroup.taskIds]; - // Remove the task if it was already added at the end - const taskIndex = newTaskIds.indexOf(activeId as string); - if (taskIndex > -1) { - newTaskIds.splice(taskIndex, 1); - } - // Insert at the correct position - newTaskIds.splice(insertIndex, 0, activeId as string); - - dispatch(reorderTasksInGroup({ - taskIds: newTaskIds, - groupId: targetGroup.id, - })); - } + // Reorder task within target group at drop position + dispatch(reorderTasksInGroup({ + sourceTaskId: activeId as string, + destinationTaskId: over.id as string, + sourceGroupId: activeGroup.id, + destinationGroupId: targetGroup.id, + })); } else { // Reordering within the same group console.log('Reordering task within same group:', { @@ -324,15 +318,12 @@ const TaskListV2: React.FC = ({ projectId }) => { }); if (activeIndex !== insertIndex) { - const newTaskIds = [...activeGroup.taskIds]; - // Remove task from old position - newTaskIds.splice(activeIndex, 1); - // Insert at new position - newTaskIds.splice(insertIndex, 0, activeId as string); - + // Reorder task within same group at drop position dispatch(reorderTasksInGroup({ - taskIds: newTaskIds, - groupId: activeGroup.id, + sourceTaskId: activeId as string, + destinationTaskId: over.id as string, + sourceGroupId: activeGroup.id, + destinationGroupId: activeGroup.id, })); } } @@ -470,7 +461,7 @@ const TaskListV2: React.FC = ({ projectId }) => { const isGroupEmpty = group.count === 0; return ( -
+
0 ? 'mt-2' : ''}> = ({ projectId }) => { return ( ); diff --git a/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx b/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx index 31f4951d..ef5e65e7 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx @@ -13,6 +13,8 @@ import { ClockIcon } from '@heroicons/react/24/outline'; import AvatarGroup from '../AvatarGroup'; import { DEFAULT_TASK_NAME } from '@/shared/constants'; import TaskProgress from '@/pages/projects/project-view-1/taskList/taskListTable/taskListTableCells/TaskProgress'; +import TaskStatusDropdown from '@/components/task-management/task-status-dropdown'; +import TaskPriorityDropdown from '@/components/task-management/task-priority-dropdown'; import { useAppSelector } from '@/hooks/useAppSelector'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { selectTaskById } from '@/features/task-management/task-management.slice'; @@ -20,6 +22,7 @@ import { selectIsTaskSelected, toggleTaskSelection } from '@/features/task-manag interface TaskRowProps { taskId: string; + projectId: string; visibleColumns: Array<{ id: string; width: string; @@ -47,7 +50,7 @@ const formatDate = (dateString: string): string => { // Memoized date formatter to avoid repeated date parsing -const TaskRow: React.FC = memo(({ taskId, visibleColumns }) => { +const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumns }) => { const dispatch = useAppDispatch(); const task = useAppSelector(state => selectTaskById(state, taskId)); const isSelected = useAppSelector(state => selectIsTaskSelected(state, taskId)); @@ -207,12 +210,11 @@ const TaskRow: React.FC = memo(({ taskId, visibleColumns }) => { case 'status': return (
- - {task.status} - +
); @@ -236,12 +238,11 @@ const TaskRow: React.FC = memo(({ taskId, visibleColumns }) => { case 'priority': return (
- - {task.priority} - +
); diff --git a/worklenz-frontend/src/components/task-management/task-priority-dropdown.tsx b/worklenz-frontend/src/components/task-management/task-priority-dropdown.tsx index e1a225dc..75544ddf 100644 --- a/worklenz-frontend/src/components/task-management/task-priority-dropdown.tsx +++ b/worklenz-frontend/src/components/task-management/task-priority-dropdown.tsx @@ -66,11 +66,18 @@ const TaskPriorityDropdown: React.FC = ({ }; if (isOpen && buttonRef.current) { - // Calculate position + // Calculate position with better handling of scrollable containers const rect = buttonRef.current.getBoundingClientRect(); + const viewportHeight = window.innerHeight; + const dropdownHeight = 200; // Estimated dropdown height + + // Check if dropdown would go below viewport + const spaceBelow = viewportHeight - rect.bottom; + const shouldShowAbove = spaceBelow < dropdownHeight && rect.top > dropdownHeight; + setDropdownPosition({ - top: rect.bottom + window.scrollY + 4, - left: rect.left + window.scrollX, + top: shouldShowAbove ? rect.top - dropdownHeight - 4 : rect.bottom + 4, + left: rect.left, }); document.addEventListener('mousedown', handleClickOutside); diff --git a/worklenz-frontend/src/components/task-management/task-status-dropdown.tsx b/worklenz-frontend/src/components/task-management/task-status-dropdown.tsx index 4da0cd76..302367c0 100644 --- a/worklenz-frontend/src/components/task-management/task-status-dropdown.tsx +++ b/worklenz-frontend/src/components/task-management/task-status-dropdown.tsx @@ -73,11 +73,18 @@ const TaskStatusDropdown: React.FC = ({ }; if (isOpen && buttonRef.current) { - // Calculate position + // Calculate position with better handling of scrollable containers const rect = buttonRef.current.getBoundingClientRect(); + const viewportHeight = window.innerHeight; + const dropdownHeight = 200; // Estimated dropdown height + + // Check if dropdown would go below viewport + const spaceBelow = viewportHeight - rect.bottom; + const shouldShowAbove = spaceBelow < dropdownHeight && rect.top > dropdownHeight; + setDropdownPosition({ - top: rect.bottom + window.scrollY + 4, - left: rect.left + window.scrollX, + top: shouldShowAbove ? rect.top - dropdownHeight - 4 : rect.bottom + 4, + left: rect.left, }); document.addEventListener('mousedown', handleClickOutside); diff --git a/worklenz-frontend/src/types/heroicons-react.d.ts b/worklenz-frontend/src/types/heroicons-react.d.ts new file mode 100644 index 00000000..2d33b39d --- /dev/null +++ b/worklenz-frontend/src/types/heroicons-react.d.ts @@ -0,0 +1,8 @@ +import { ComponentType, SVGProps } from 'react'; + +declare module '@heroicons/react/24/outline' { + export const ChevronDownIcon: ComponentType>; + export const ChevronRightIcon: ComponentType>; + export const Bars3Icon: ComponentType>; + export const ClockIcon: ComponentType>; +} \ No newline at end of file