From a6f9046b42ce8871751d31461dafc173e2c027e1 Mon Sep 17 00:00:00 2001 From: chamikaJ Date: Mon, 7 Jul 2025 09:40:56 +0530 Subject: [PATCH] feat(task-management): add all_labels support and improve label handling - Introduced all_labels property in task management to provide a complete list of labels for selection logic. - Updated TasksControllerV2, TaskRow, and LabelsSelector components to utilize all_labels for enhanced label management. - Improved checkbox handling in LabelsSelector to prevent event propagation and ensure better user interaction. - Enhanced useTaskSocketHandlers to manage temporary subtasks effectively, preventing duplication during optimistic updates. --- .../src/controllers/tasks-controller-v2.ts | 5 ++++ .../src/components/LabelsSelector.tsx | 19 ++++++++----- .../src/components/task-list-v2/TaskRow.tsx | 8 +++--- .../task-management/task-management.slice.ts | 5 ++++ .../src/hooks/useTaskSocketHandlers.ts | 27 ++++++++++++++++++- .../src/types/task-management.types.ts | 1 + 6 files changed, 53 insertions(+), 12 deletions(-) diff --git a/worklenz-backend/src/controllers/tasks-controller-v2.ts b/worklenz-backend/src/controllers/tasks-controller-v2.ts index 162d5dd2..3a575bba 100644 --- a/worklenz-backend/src/controllers/tasks-controller-v2.ts +++ b/worklenz-backend/src/controllers/tasks-controller-v2.ts @@ -1078,6 +1078,11 @@ export default class TasksControllerV2 extends TasksControllerBase { end: l.end, names: l.names })) || [], + all_labels: task.all_labels?.map((l: any) => ({ + id: l.id || l.label_id, + name: l.name, + color_code: l.color_code || "#1890ff" + })) || [], dueDate: task.end_date || task.END_DATE, startDate: task.start_date, timeTracking: { diff --git a/worklenz-frontend/src/components/LabelsSelector.tsx b/worklenz-frontend/src/components/LabelsSelector.tsx index 960970ae..37f39c78 100644 --- a/worklenz-frontend/src/components/LabelsSelector.tsx +++ b/worklenz-frontend/src/components/LabelsSelector.tsx @@ -223,16 +223,21 @@ const LabelsSelector: React.FC = ({ task, isDarkMode = fals
handleLabelToggle(label)} + onClick={(e) => { + e.stopPropagation(); + handleLabelToggle(label); + }} > - handleLabelToggle(label)} - isDarkMode={isDarkMode} - /> +
+ {}} // Empty handler since we handle click on the div + isDarkMode={isDarkMode} + /> +
= memo(({ taskId, projectId, visibleColumn name: task.title || task.name, parent_task_id: task.parent_task_id, manual_progress: false, - all_labels: task.labels?.map(label => ({ + all_labels: task.all_labels?.map(label => ({ id: label.id, name: label.name, - color_code: label.color, + color_code: label.color_code, })) || [], labels: task.labels?.map(label => ({ id: label.id, name: label.name, color_code: label.color, })) || [], - }), [task.id, task.title, task.name, task.parent_task_id, task.labels, task.labels?.length]); + }), [task.id, task.title, task.name, task.parent_task_id, task.all_labels, task.labels, task.all_labels?.length, task.labels?.length]); // Handle checkbox change const handleCheckboxChange = useCallback((e: any) => { @@ -556,7 +556,7 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn case 'labels': return ( -
+
diff --git a/worklenz-frontend/src/features/task-management/task-management.slice.ts b/worklenz-frontend/src/features/task-management/task-management.slice.ts index 7a7a0177..08febf40 100644 --- a/worklenz-frontend/src/features/task-management/task-management.slice.ts +++ b/worklenz-frontend/src/features/task-management/task-management.slice.ts @@ -271,6 +271,11 @@ export const fetchTasksV3 = createAsyncThunk( end: l.end, names: l.names, })) || [], + all_labels: task.all_labels?.map((l: { id: string; label_id: string; name: string; color_code: string }) => ({ + id: l.id || l.label_id, + name: l.name, + color_code: l.color_code || '#1890ff', + })) || [], dueDate: task.dueDate, startDate: task.startDate, timeTracking: { diff --git a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts index c1696ae4..cbcb46c1 100644 --- a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts +++ b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts @@ -43,6 +43,7 @@ import { selectCurrentGroupingV3, fetchTasksV3, addSubtaskToParent, + removeTemporarySubtask, } from '@/features/task-management/task-management.slice'; import { updateEnhancedKanbanSubtask, @@ -153,13 +154,19 @@ export const useTaskSocketHandlers = () => { const updatedTask: Task = { ...currentTask, labels: - labels.all_labels?.map(l => ({ + labels.labels?.map(l => ({ id: l.id || '', name: l.name || '', color: l.color_code || '#1890ff', end: l.end, names: l.names, })) || [], + all_labels: + labels.all_labels?.map(l => ({ + id: l.id || '', + name: l.name || '', + color_code: l.color_code || '#1890ff', + })) || [], updatedAt: new Date().toISOString(), updated_at: new Date().toISOString(), }; @@ -675,6 +682,24 @@ export const useTaskSocketHandlers = () => { parent_task_id: data.parent_task_id, is_sub_task: true, }; + + // Before adding the real subtask, remove any temporary subtasks with the same name + // This prevents duplication from optimistic updates + const parentTask = store.getState().taskManagement.entities[data.parent_task_id]; + if (parentTask && parentTask.sub_tasks) { + const temporarySubtasks = parentTask.sub_tasks.filter( + (st: Task) => st.isTemporary && st.name === subtask.title + ); + + // Remove each temporary subtask + temporarySubtasks.forEach((tempSubtask: Task) => { + dispatch(removeTemporarySubtask({ + parentTaskId: data.parent_task_id, + tempId: tempSubtask.id + })); + }); + } + dispatch(addSubtaskToParent({ parentId: data.parent_task_id, subtask })); // Also update enhanced kanban slice for subtask creation diff --git a/worklenz-frontend/src/types/task-management.types.ts b/worklenz-frontend/src/types/task-management.types.ts index 519b10d2..32518142 100644 --- a/worklenz-frontend/src/types/task-management.types.ts +++ b/worklenz-frontend/src/types/task-management.types.ts @@ -33,6 +33,7 @@ export interface Task { statusColor?: string; priorityColor?: string; labels?: { id: string; name: string; color: string; end?: boolean; names?: string[] }[]; + all_labels?: { id: string; name: string; color_code: string }[]; // Complete list of labels for selection logic comments_count?: number; attachments_count?: number; has_dependencies?: boolean;