diff --git a/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx b/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx index 04b1d134..64ac806b 100644 --- a/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx +++ b/worklenz-frontend/src/components/project-task-filters/create-status-button/create-status-button.tsx @@ -21,7 +21,7 @@ const CreateStatusButton = () => { onClick={() => dispatch(toggleDrawer())} icon={ } /> diff --git a/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx b/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx index dcddc6d8..32456b86 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskGroupHeader.tsx @@ -235,7 +235,7 @@ const TaskGroupHeader: React.FC = ({ group, isCollapsed, o return ( = ({ group, isCollapsed, o {/* Group name and count */} {group.name} diff --git a/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx b/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx index 5a95db6e..6c89515c 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskListV2.tsx @@ -491,7 +491,7 @@ const TaskListV2: React.FC = () => { ); })} - + diff --git a/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx b/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx index 3a25e61c..152340d1 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx @@ -49,6 +49,7 @@ interface TaskLabelsCellProps { } const TaskLabelsCell: React.FC = memo(({ labels, isDarkMode }) => { + console.log('labels', labels); if (!labels) { return null; } @@ -190,7 +191,7 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn name: label.name, color_code: label.color, })) || [], - }), [task.id, task.title, task.name, task.parent_task_id, task.labels]); + }), [task.id, task.title, task.name, task.parent_task_id, task.labels, task.labels?.length]); // Handle checkbox change const handleCheckboxChange = useCallback((e: any) => { @@ -654,8 +655,10 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn isDarkMode, projectId, - // Task data + // Task data - include specific fields that might update via socket task, + task.labels, // Explicit dependency for labels updates + task.phase, // Explicit dependency for phase updates taskDisplayName, convertedTask, diff --git a/worklenz-frontend/src/components/task-list-v2/components/CustomColumnComponents.tsx b/worklenz-frontend/src/components/task-list-v2/components/CustomColumnComponents.tsx index 12ff6f73..8df48ff2 100644 --- a/worklenz-frontend/src/components/task-list-v2/components/CustomColumnComponents.tsx +++ b/worklenz-frontend/src/components/task-list-v2/components/CustomColumnComponents.tsx @@ -29,7 +29,7 @@ export const AddCustomColumnButton: React.FC = memo(() => { { } `} > - + {/* Subtle glow effect on hover */} = ({ position, cla }`} /> {t('showArchivedText')} - )} diff --git a/worklenz-frontend/src/features/projects/singleProject/phase/PhaseDrawer.tsx b/worklenz-frontend/src/features/projects/singleProject/phase/PhaseDrawer.tsx index 053076c7..3b9d8f52 100644 --- a/worklenz-frontend/src/features/projects/singleProject/phase/PhaseDrawer.tsx +++ b/worklenz-frontend/src/features/projects/singleProject/phase/PhaseDrawer.tsx @@ -37,6 +37,7 @@ import { fetchTaskGroups } from '@/features/tasks/tasks.slice'; import { updatePhaseLabel } from '@/features/project/project.slice'; import useTabSearchParam from '@/hooks/useTabSearchParam'; import { fetchBoardTaskGroups } from '@/features/board/board-slice'; +import { fetchTasksV3 } from '@/features/task-management/task-management.slice'; interface UpdateSortOrderBody { from_index: number; @@ -67,6 +68,7 @@ const PhaseDrawer = () => { const refreshTasks = async () => { if (tab === 'tasks-list') { + await dispatch(fetchTasksV3(projectId || '')); await dispatch(fetchTaskGroups(projectId || '')); } else if (tab === 'board') { await dispatch(fetchBoardTaskGroups(projectId || '')); @@ -131,6 +133,8 @@ const PhaseDrawer = () => { if (res.done) { dispatch(updatePhaseLabel(phaseName)); setInitialPhaseName(phaseName); + // Refresh tasks to update phase label in task list + await refreshTasks(); } } catch (error) { logger.error('Error updating phase name', error); 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 3a719d35..4ec7d68b 100644 --- a/worklenz-frontend/src/features/task-management/task-management.slice.ts +++ b/worklenz-frontend/src/features/task-management/task-management.slice.ts @@ -264,10 +264,10 @@ export const fetchTasksV3 = createAsyncThunk( progress: typeof task.complete_ratio === 'number' ? task.complete_ratio : 0, assignees: task.assignees?.map((a: { team_member_id: string }) => a.team_member_id) || [], assignee_names: task.assignee_names || task.names || [], - labels: task.labels?.map((l: { id: string; label_id: string; name: string; color_code: string; end: boolean; names: string[] }) => ({ + labels: task.labels?.map((l: { id: string; label_id: string; name: string; color: string; end: boolean; names: string[] }) => ({ id: l.id || l.label_id, name: l.name, - color: l.color_code || '#1890ff', + color: l.color || '#1890ff', end: l.end, names: l.names, })) || [], diff --git a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts index 63ad3ae4..19b78dd4 100644 --- a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts +++ b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts @@ -91,16 +91,17 @@ export const useTaskSocketHandlers = () => { // REAL-TIME UPDATES: Update the task-management slice for immediate UI updates if (data.id) { - dispatch( - updateTask({ - id: data.id, - changes: { - assignees: data.assignees?.map(a => a.team_member_id) || [], - assignee_names: data.names || [], - updatedAt: new Date().toISOString(), - }, - }) - ); + const currentTask = store.getState().taskManagement.entities[data.id]; + if (currentTask) { + const updatedTask: Task = { + ...currentTask, + assignees: data.assignees?.map(a => a.team_member_id) || [], + assignee_names: data.names || [], + updatedAt: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + dispatch(updateTask(updatedTask)); + } } // Update the old task slice (for backward compatibility) @@ -147,22 +148,23 @@ export const useTaskSocketHandlers = () => { // REAL-TIME UPDATES: Update the task-management slice for immediate UI updates if (labels.id) { - dispatch( - updateTask({ - id: labels.id, - changes: { - labels: - labels.labels?.map(l => ({ - id: l.id || '', - name: l.name || '', - color: l.color_code || '#1890ff', - end: l.end, - names: l.names, - })) || [], - updatedAt: new Date().toISOString(), - }, - }) - ); + const currentTask = store.getState().taskManagement.entities[labels.id]; + if (currentTask) { + const updatedTask: Task = { + ...currentTask, + labels: + labels.all_labels?.map(l => ({ + id: l.id || '', + name: l.name || '', + color: l.color_code || '#1890ff', + end: l.end, + names: l.names, + })) || [], + updatedAt: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + dispatch(updateTask(updatedTask)); + } } // Update the old task slice and other related slices (for backward compatibility) @@ -281,15 +283,16 @@ export const useTaskSocketHandlers = () => { // For the task management slice, update task progress const taskId = data.parent_task || data.id; if (taskId) { - dispatch( - updateTask({ - id: taskId, - changes: { - progress: data.complete_ratio, - updatedAt: new Date().toISOString(), - }, - }) - ); + const currentTask = store.getState().taskManagement.entities[taskId]; + if (currentTask) { + const updatedTask: Task = { + ...currentTask, + progress: data.complete_ratio, + updatedAt: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + dispatch(updateTask(updatedTask)); + } } // Update enhanced kanban slice @@ -326,14 +329,19 @@ export const useTaskSocketHandlers = () => { if (currentTask) { // Get priority list to map priority_id to priority name const priorityList = state.priorityReducer?.priorities || []; - const priority = priorityList.find(p => p.id === response.priority_id); - let newPriorityValue: 'critical' | 'high' | 'medium' | 'low' = 'medium'; - if (priority?.name) { - const priorityName = priority.name.toLowerCase(); - if (['critical', 'high', 'medium', 'low'].includes(priorityName)) { - newPriorityValue = priorityName as 'critical' | 'high' | 'medium' | 'low'; + + if (response.priority_id) { + const priority = priorityList.find(p => p.id === response.priority_id); + if (priority?.name) { + const priorityName = priority.name.toLowerCase(); + if (['critical', 'high', 'medium', 'low'].includes(priorityName)) { + newPriorityValue = priorityName as 'critical' | 'high' | 'medium' | 'low'; + } } + } else { + // No priority selected (cleared) - default to medium or find unmapped + newPriorityValue = 'medium'; } // Update the task entity first @@ -353,11 +361,33 @@ export const useTaskSocketHandlers = () => { const currentGroup = groups.find(group => group.taskIds.includes(response.id)); // Find target group based on new priority value - const targetGroup = groups.find( - group => group.groupValue?.toLowerCase() === newPriorityValue.toLowerCase() - ); + let targetGroup: any = null; + + if (response.priority_id) { + // Find group by priority name (groupValue should match the priority name) + targetGroup = groups.find( + group => group.groupValue?.toLowerCase() === newPriorityValue.toLowerCase() || + group.title?.toLowerCase() === newPriorityValue.toLowerCase() + ); + } else { + // Find "Unmapped" group for tasks without a priority + targetGroup = groups.find( + group => + group.groupValue === 'Unmapped' || + group.title === 'Unmapped' || + group.groupValue === '' || + group.title?.toLowerCase().includes('unmapped') || + group.groupValue?.toLowerCase().includes('unmapped') + ); + } if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) { + console.log('🔄 Moving task between priority groups:', { + taskId: response.id, + from: currentGroup.title, + to: targetGroup.title, + newPriorityValue + }); dispatch( moveTaskBetweenGroups({ taskId: response.id, @@ -365,6 +395,8 @@ export const useTaskSocketHandlers = () => { targetGroupId: targetGroup.id, }) ); + } else if (!targetGroup && response.priority_id) { + console.log('🔧 Target priority group not found for priority:', newPriorityValue); } else { console.log('🔧 No group movement needed for priority change'); } @@ -413,19 +445,24 @@ export const useTaskSocketHandlers = () => { // For the task management slice, update task name if (data.id) { - dispatch( - updateTask({ - id: data.id, - changes: { - title: data.name, - updatedAt: new Date().toISOString(), - }, - }) - ); + const currentTask = store.getState().taskManagement.entities[data.id]; + if (currentTask) { + const updatedTask: Task = { + ...currentTask, + title: data.name, + updatedAt: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + dispatch(updateTask(updatedTask)); + } } - // Update enhanced kanban slice - dispatch(updateEnhancedKanbanTaskName({ task: data })); + // Update enhanced kanban slice (add manual_progress property for compatibility) + const taskWithProgress = { + ...data, + manual_progress: false, + } as IProjectTask; + dispatch(updateEnhancedKanbanTaskName({ task: taskWithProgress })); }, [dispatch] ); @@ -460,15 +497,13 @@ export const useTaskSocketHandlers = () => { } // Update the task entity - dispatch( - updateTask({ - id: taskId, - changes: { - phase: newPhaseValue, - updatedAt: new Date().toISOString(), - }, - }) - ); + const updatedTask: Task = { + ...currentTask, + phase: newPhaseValue, + updatedAt: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + dispatch(updateTask(updatedTask)); // Handle group movement ONLY if grouping by phase const groups = state.taskManagement.groups; @@ -482,31 +517,41 @@ export const useTaskSocketHandlers = () => { let targetGroup: any = null; if (newPhaseValue && newPhaseValue.trim() !== '') { - // Find group by phase name + // Find group by phase name (groupValue should match the phase name) targetGroup = groups.find( - group => group.groupValue === newPhaseValue || group.title === newPhaseValue + group => group.groupValue === newPhaseValue || + group.title === newPhaseValue || + group.groupValue?.toLowerCase() === newPhaseValue.toLowerCase() || + group.title?.toLowerCase() === newPhaseValue.toLowerCase() ); } else { - // Find "Unmapped" group for tasks without a phase or with default phase + // Find "Unmapped" group for tasks without a phase targetGroup = groups.find( group => group.groupValue === 'Unmapped' || group.title === 'Unmapped' || - group.title.toLowerCase().includes('unmapped') + group.groupValue === '' || + group.title?.toLowerCase().includes('unmapped') || + group.groupValue?.toLowerCase().includes('unmapped') ); } if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) { + console.log('🔄 Moving task between phase groups:', { + taskId, + from: currentGroup.title, + to: targetGroup.title, + newPhaseValue + }); dispatch( moveTaskBetweenGroups({ taskId: taskId, - fromGroupId: currentGroup.id, - toGroupId: targetGroup.id, - taskUpdate: { - phase: newPhaseValue, - }, + sourceGroupId: currentGroup.id, + targetGroupId: targetGroup.id, }) ); + } else if (!targetGroup && newPhaseValue) { + console.log('🔧 Target phase group not found for phase:', newPhaseValue); } else { console.log('🔧 No group movement needed for phase change'); } @@ -534,11 +579,13 @@ export const useTaskSocketHandlers = () => { // Update task-management slice for task-list-v2 components const currentTask = store.getState().taskManagement.entities[task.id]; if (currentTask) { - dispatch(updateTask({ + const updatedTask: Task = { ...currentTask, startDate: task.start_date, updatedAt: new Date().toISOString(), - })); + updated_at: new Date().toISOString(), + }; + dispatch(updateTask(updatedTask)); } }, [dispatch] diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/custom-column-modal.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/custom-column-modal.tsx index d70f74f6..52b8e553 100644 --- a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/custom-column-modal.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/custom-column-modal.tsx @@ -91,15 +91,7 @@ const CustomColumnModal = () => { // Use the column data passed from TaskListV2 const openedColumn = currentColumnData; - // Debug logging - console.log('Modal Debug Info:', { - customColumnId, - customColumnModalType, - currentColumnData, - openedColumn, - openedColumnFound: !!openedColumn, - openedColumnId: openedColumn?.id - }); + // Function to reset all form and Redux state const resetModalData = () => { @@ -110,14 +102,6 @@ const CustomColumnModal = () => { // Function to handle deleting a custom column const handleDeleteColumn = async () => { - console.log('Delete function called with:', { - customColumnId, - openedColumn, - openedColumnId: openedColumn?.id, - openedColumnKey: openedColumn?.key, - fullColumnData: openedColumn - }); - // The customColumnId should now be the UUID passed from TaskListV2 // But also check the column data as a fallback, prioritizing uuid over id const columnUUID = customColumnId || @@ -126,26 +110,12 @@ const CustomColumnModal = () => { openedColumn?.custom_column_obj?.uuid || openedColumn?.custom_column_obj?.id; - console.log('Extracted UUID candidates:', { - 'openedColumn?.id': openedColumn?.id, - 'openedColumn?.uuid': openedColumn?.uuid, - 'openedColumn?.custom_column_obj?.id': openedColumn?.custom_column_obj?.id, - 'openedColumn?.custom_column_obj?.uuid': openedColumn?.custom_column_obj?.uuid, - 'finalColumnUUID': columnUUID - }); - if (!customColumnId || !columnUUID) { - console.error('Missing required data for deletion:', { - customColumnId, - columnUUID, - openedColumn - }); message.error('Cannot delete column: Missing UUID'); return; } try { - console.log('Attempting to delete column with UUID:', columnUUID); // Make API request to delete the custom column using the service await tasksCustomColumnsService.deleteCustomColumn(columnUUID); @@ -442,16 +412,11 @@ const CustomColumnModal = () => { } else if (openedColumn.custom_column_obj?.fieldType === 'selection') { // Directly set the selections list in the Redux store if (Array.isArray(openedColumn.custom_column_obj?.selectionsList)) { - console.log( - 'Setting selections list:', - openedColumn.custom_column_obj.selectionsList - ); dispatch(setSelectionsList(openedColumn.custom_column_obj.selectionsList)); } } else if (openedColumn.custom_column_obj?.fieldType === 'labels') { // Directly set the labels list in the Redux store if (Array.isArray(openedColumn.custom_column_obj?.labelsList)) { - console.log('Setting labels list:', openedColumn.custom_column_obj.labelsList); dispatch(setLabelsList(openedColumn.custom_column_obj.labelsList)); } } diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/selection-type-column/selection-type-column.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/selection-type-column/selection-type-column.tsx index 9dbe601d..415d58ca 100644 --- a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/selection-type-column/selection-type-column.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/selection-type-column/selection-type-column.tsx @@ -35,20 +35,12 @@ const SelectionTypeColumn = () => { // Use the current column data passed from TaskListV2 const openedColumn = currentColumnData; - console.log('SelectionTypeColumn render:', { - customColumnModalType, - customColumnId, - openedColumn, - storeSelectionsList, - 'openedColumn?.custom_column_obj?.selectionsList': - openedColumn?.custom_column_obj?.selectionsList, - }); + // Load existing selections when in edit mode useEffect(() => { if (customColumnModalType === 'edit' && openedColumn?.custom_column_obj?.selectionsList) { const existingSelections = openedColumn.custom_column_obj.selectionsList; - console.log('Loading existing selections:', existingSelections); if (Array.isArray(existingSelections) && existingSelections.length > 0) { setSelections(existingSelections);