feat(task-management): enhance task assignment handling and UI feedback in AssigneeSelector

- Introduced optimistic updates for assignee selection to improve UI responsiveness.
- Updated AssigneeSelector to initialize optimistic assignees from task data on mount.
- Refactored task assignment logic to ensure unique assignee IDs and improved state management.
- Enhanced TaskGroupHeader and TaskListV2 to support bulk actions and selection state.
- Integrated a new bulk action bar for managing selected tasks efficiently.
This commit is contained in:
chamikaJ
2025-07-03 19:28:14 +05:30
parent 6b7f412341
commit 6bf98b787e
6 changed files with 468 additions and 153 deletions

View File

@@ -1,7 +1,12 @@
import React from 'react';
import React, { useMemo, useCallback } from 'react';
import { useDroppable } from '@dnd-kit/core';
import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
import { Checkbox } from 'antd';
import { getContrastColor } from '@/utils/colorUtils';
import { useAppSelector } from '@/hooks/useAppSelector';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { selectSelectedTaskIds, selectTask, deselectTask } from '@/features/task-management/selection.slice';
import { selectGroups } from '@/features/task-management/task-management.slice';
interface TaskGroupHeaderProps {
group: {
@@ -15,9 +20,52 @@ interface TaskGroupHeaderProps {
}
const TaskGroupHeader: React.FC<TaskGroupHeaderProps> = ({ group, isCollapsed, onToggle }) => {
const dispatch = useAppDispatch();
const selectedTaskIds = useAppSelector(selectSelectedTaskIds);
const groups = useAppSelector(selectGroups);
const headerBackgroundColor = group.color || '#F0F0F0'; // Default light gray if no color
const headerTextColor = getContrastColor(headerBackgroundColor);
// Get tasks in this group
const currentGroup = useMemo(() => {
return groups.find(g => g.id === group.id);
}, [groups, group.id]);
const tasksInGroup = useMemo(() => {
return currentGroup?.taskIds || [];
}, [currentGroup]);
// Calculate selection state for this group
const { isAllSelected, isPartiallySelected } = useMemo(() => {
if (tasksInGroup.length === 0) {
return { isAllSelected: false, isPartiallySelected: false };
}
const selectedTasksInGroup = tasksInGroup.filter(taskId => selectedTaskIds.includes(taskId));
const allSelected = selectedTasksInGroup.length === tasksInGroup.length;
const partiallySelected = selectedTasksInGroup.length > 0 && selectedTasksInGroup.length < tasksInGroup.length;
return { isAllSelected: allSelected, isPartiallySelected: partiallySelected };
}, [tasksInGroup, selectedTaskIds]);
// Handle select all checkbox change
const handleSelectAllChange = useCallback((e: any) => {
e.stopPropagation();
if (isAllSelected) {
// Deselect all tasks in this group
tasksInGroup.forEach(taskId => {
dispatch(deselectTask(taskId));
});
} else {
// Select all tasks in this group
tasksInGroup.forEach(taskId => {
dispatch(selectTask(taskId));
});
}
}, [dispatch, isAllSelected, tasksInGroup]);
// Make the group header droppable
const { isOver, setNodeRef } = useDroppable({
id: group.id,
@@ -42,21 +90,37 @@ const TaskGroupHeader: React.FC<TaskGroupHeaderProps> = ({ group, isCollapsed, o
}}
onClick={onToggle}
>
{/* Chevron button */}
<button
className="p-1 rounded-md hover:bg-opacity-20 transition-colors"
style={{ backgroundColor: headerBackgroundColor, color: headerTextColor, borderColor: headerTextColor, border: '1px solid' }}
onClick={(e) => {
e.stopPropagation();
onToggle();
}}
>
{isCollapsed ? (
<ChevronRightIcon className="h-4 w-4" style={{ color: headerTextColor }} />
) : (
<ChevronDownIcon className="h-4 w-4" style={{ color: headerTextColor }} />
)}
</button>
{/* Drag Handle Space */}
<div style={{ width: '32px' }} className="flex items-center justify-center">
{/* Chevron button */}
<button
className="p-1 rounded-md hover:bg-opacity-20 transition-colors"
style={{ backgroundColor: headerBackgroundColor, color: headerTextColor, borderColor: headerTextColor, border: '1px solid' }}
onClick={(e) => {
e.stopPropagation();
onToggle();
}}
>
{isCollapsed ? (
<ChevronRightIcon className="h-4 w-4" style={{ color: headerTextColor }} />
) : (
<ChevronDownIcon className="h-4 w-4" style={{ color: headerTextColor }} />
)}
</button>
</div>
{/* Select All Checkbox Space */}
<div style={{ width: '40px' }} className="flex items-center justify-center">
<Checkbox
checked={isAllSelected}
indeterminate={isPartiallySelected}
onChange={handleSelectAllChange}
onClick={(e) => e.stopPropagation()}
style={{
color: headerTextColor,
}}
/>
</div>
{/* Group indicator and name */}
<div className="ml-2 flex items-center gap-3 flex-1">