refactor(task-management): optimize task selection handling in TaskListBoard
- Improved task selection logic by utilizing a Set for efficient addition and removal of selected task IDs. - Streamlined the dispatch of selected tasks and IDs to Redux state, enhancing performance and readability. - Removed unused context menu logic from TaskRow component, simplifying the codebase and focusing on subtask expansion management.
This commit is contained in:
@@ -414,14 +414,26 @@ const TaskListBoard: React.FC<TaskListBoardProps> = ({ projectId, className = ''
|
|||||||
|
|
||||||
const handleSelectTask = useCallback(
|
const handleSelectTask = useCallback(
|
||||||
(taskId: string, selected: boolean) => {
|
(taskId: string, selected: boolean) => {
|
||||||
|
// Create a new Set from existing selections for efficient lookup and modification
|
||||||
|
const currentSelectedIds = new Set(selectedTaskIds);
|
||||||
|
|
||||||
|
// Update the selection state based on the checkbox action
|
||||||
if (selected) {
|
if (selected) {
|
||||||
// Add task to bulk selection
|
currentSelectedIds.add(taskId);
|
||||||
const task = tasks.find(t => t.id === taskId);
|
} else {
|
||||||
if (task) {
|
currentSelectedIds.delete(taskId);
|
||||||
// Convert Task to IProjectTask format for bulk actions
|
}
|
||||||
const projectTask: IProjectTask = {
|
|
||||||
|
// Convert Set back to array for Redux state
|
||||||
|
const newSelectedIds = Array.from(currentSelectedIds);
|
||||||
|
|
||||||
|
// Map selected tasks to the required format
|
||||||
|
const newSelectedTasks = tasks
|
||||||
|
.filter((t) => newSelectedIds.includes(t.id))
|
||||||
|
.map(
|
||||||
|
(task): IProjectTask => ({
|
||||||
id: task.id,
|
id: task.id,
|
||||||
name: task.title, // Always use title as the name
|
name: task.title,
|
||||||
task_key: task.task_key,
|
task_key: task.task_key,
|
||||||
status: task.status,
|
status: task.status,
|
||||||
status_id: task.status,
|
status_id: task.status,
|
||||||
@@ -435,7 +447,7 @@ const TaskListBoard: React.FC<TaskListBoardProps> = ({ projectId, className = ''
|
|||||||
total_minutes: task.timeTracking.logged || 0,
|
total_minutes: task.timeTracking.logged || 0,
|
||||||
progress: task.progress,
|
progress: task.progress,
|
||||||
sub_tasks_count: task.sub_tasks_count || 0,
|
sub_tasks_count: task.sub_tasks_count || 0,
|
||||||
assignees: task.assignees.map(assigneeId => ({
|
assignees: task.assignees.map((assigneeId) => ({
|
||||||
id: assigneeId,
|
id: assigneeId,
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
email: '',
|
||||||
@@ -444,23 +456,18 @@ const TaskListBoard: React.FC<TaskListBoardProps> = ({ projectId, className = ''
|
|||||||
project_member_id: assigneeId,
|
project_member_id: assigneeId,
|
||||||
})),
|
})),
|
||||||
labels: task.labels,
|
labels: task.labels,
|
||||||
manual_progress: false, // Default value for Task type
|
manual_progress: false,
|
||||||
created_at: task.createdAt,
|
created_at: task.createdAt,
|
||||||
updated_at: task.updatedAt,
|
updated_at: task.updatedAt,
|
||||||
sort_order: task.order,
|
sort_order: task.order,
|
||||||
};
|
})
|
||||||
dispatch(selectTasks([...selectedTasks, projectTask]));
|
);
|
||||||
dispatch(selectTaskIds([...selectedTaskIds, taskId]));
|
|
||||||
}
|
// Dispatch both actions to update the Redux state
|
||||||
} else {
|
dispatch(selectTasks(newSelectedTasks));
|
||||||
// Remove task from bulk selection
|
dispatch(selectTaskIds(newSelectedIds));
|
||||||
const updatedTasks = selectedTasks.filter(t => t.id !== taskId);
|
|
||||||
const updatedTaskIds = selectedTaskIds.filter(id => id !== taskId);
|
|
||||||
dispatch(selectTasks(updatedTasks));
|
|
||||||
dispatch(selectTaskIds(updatedTaskIds));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[dispatch, selectedTasks, selectedTaskIds, tasks]
|
[dispatch, selectedTaskIds, tasks]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleToggleSubtasks = useCallback(
|
const handleToggleSubtasks = useCallback(
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ import {
|
|||||||
fetchTask,
|
fetchTask,
|
||||||
} from '@/features/task-drawer/task-drawer.slice';
|
} from '@/features/task-drawer/task-drawer.slice';
|
||||||
import useDragCursor from '@/hooks/useDragCursor';
|
import useDragCursor from '@/hooks/useDragCursor';
|
||||||
import TaskContextMenu from './task-context-menu/task-context-menu';
|
|
||||||
|
|
||||||
interface TaskRowProps {
|
interface TaskRowProps {
|
||||||
task: Task;
|
task: Task;
|
||||||
@@ -430,9 +429,7 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(
|
|||||||
const addSubtaskInputRef = useRef<InputRef>(null);
|
const addSubtaskInputRef = useRef<InputRef>(null);
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Context menu state
|
// Subtask expansion state (managed by Redux)
|
||||||
const [showContextMenu, setShowContextMenu] = useState(false);
|
|
||||||
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
|
|
||||||
|
|
||||||
// PERFORMANCE OPTIMIZATION: Intersection Observer for lazy loading
|
// PERFORMANCE OPTIMIZATION: Intersection Observer for lazy loading
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -575,11 +572,6 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(
|
|||||||
onToggleSubtasks?.(task.id);
|
onToggleSubtasks?.(task.id);
|
||||||
}, [task.id, onToggleSubtasks]);
|
}, [task.id, onToggleSubtasks]);
|
||||||
|
|
||||||
const handleContextMenu = useCallback((e: React.MouseEvent) => {
|
|
||||||
setShowContextMenu(true);
|
|
||||||
setContextMenuPosition({ x: e.clientX, y: e.clientY });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle successful subtask creation
|
// Handle successful subtask creation
|
||||||
const handleSubtaskCreated = useCallback(
|
const handleSubtaskCreated = useCallback(
|
||||||
(newTask: any) => {
|
(newTask: any) => {
|
||||||
@@ -1015,54 +1007,6 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Indicators section */}
|
|
||||||
{!editTaskName && (
|
|
||||||
<div className="task-indicators flex items-center gap-2">
|
|
||||||
{/* Comments indicator */}
|
|
||||||
{(task as any).comments_count > 0 && (
|
|
||||||
<Tooltip title={t('taskManagement.comments', 'Comments')}>
|
|
||||||
<MessageOutlined
|
|
||||||
style={{ fontSize: 14, color: isDarkMode ? '#b0b3b8' : '#888' }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{/* Attachments indicator */}
|
|
||||||
{(task as any).attachments_count > 0 && (
|
|
||||||
<Tooltip title={t('taskManagement.attachments', 'Attachments')}>
|
|
||||||
<PaperClipOutlined
|
|
||||||
style={{ fontSize: 14, color: isDarkMode ? '#b0b3b8' : '#888' }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{/* Dependencies indicator */}
|
|
||||||
{(task as any).has_dependencies && (
|
|
||||||
<Tooltip title={t('taskManagement.dependencies', 'Dependencies')}>
|
|
||||||
<MinusCircleOutlined
|
|
||||||
style={{ fontSize: 14, color: isDarkMode ? '#b0b3b8' : '#888' }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{/* Subscribers indicator */}
|
|
||||||
{(task as any).has_subscribers && (
|
|
||||||
<Tooltip title={t('taskManagement.subscribers', 'Subscribers')}>
|
|
||||||
<EyeOutlined
|
|
||||||
style={{ fontSize: 14, color: isDarkMode ? '#b0b3b8' : '#888' }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{/* Recurring indicator */}
|
|
||||||
{(task as any).schedule_id && (
|
|
||||||
<Tooltip title={t('taskManagement.recurringTask', 'Recurring Task')}>
|
|
||||||
<RetweetOutlined
|
|
||||||
style={{ fontSize: 14, color: isDarkMode ? '#b0b3b8' : '#888' }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1112,8 +1056,6 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right section with open button - CSS hover only */}
|
{/* Right section with open button - CSS hover only */}
|
||||||
@@ -1536,7 +1478,6 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(
|
|||||||
data-dnd-dragging={isDragging ? 'true' : 'false'}
|
data-dnd-dragging={isDragging ? 'true' : 'false'}
|
||||||
data-task-id={task.id}
|
data-task-id={task.id}
|
||||||
data-group-id={groupId}
|
data-group-id={groupId}
|
||||||
onContextMenu={handleContextMenu}
|
|
||||||
>
|
>
|
||||||
<div className="task-row-container flex h-10 max-h-10 relative">
|
<div className="task-row-container flex h-10 max-h-10 relative">
|
||||||
{/* All Columns - No Fixed Positioning */}
|
{/* All Columns - No Fixed Positioning */}
|
||||||
@@ -1565,14 +1506,6 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{showContextMenu && (
|
|
||||||
<TaskContextMenu
|
|
||||||
task={task}
|
|
||||||
projectId={projectId}
|
|
||||||
position={contextMenuPosition}
|
|
||||||
onClose={() => setShowContextMenu(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -1660,4 +1593,4 @@ const TaskRow: React.FC<TaskRowProps> = React.memo(
|
|||||||
|
|
||||||
TaskRow.displayName = 'TaskRow';
|
TaskRow.displayName = 'TaskRow';
|
||||||
|
|
||||||
export default TaskRow;
|
export default TaskRow;
|
||||||
Reference in New Issue
Block a user