diff --git a/worklenz-frontend/src/components/CustomColordLabel.tsx b/worklenz-frontend/src/components/CustomColordLabel.tsx index ebe9087b..83c25281 100644 --- a/worklenz-frontend/src/components/CustomColordLabel.tsx +++ b/worklenz-frontend/src/components/CustomColordLabel.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { Tooltip } from 'antd'; import { Label } from '@/types/task-management.types'; import { ITaskLabel } from '@/types/tasks/taskLabel.types'; -import { ALPHA_CHANNEL } from '@/shared/constants'; interface CustomColordLabelProps { label: Label | ITaskLabel; @@ -15,11 +14,26 @@ const CustomColordLabel = React.forwardRef 10 ? `${label.name.substring(0, 10)}...` : label.name; // Handle different color property names for different types - const baseColor = (label as Label).color || (label as ITaskLabel).color_code || '#6b7280'; // Default to gray-500 if no color + const backgroundColor = (label as Label).color || (label as ITaskLabel).color_code || '#6b7280'; // Default to gray-500 if no color - // Add alpha channel to the base color - const backgroundColor = baseColor + ALPHA_CHANNEL; - const textColor = baseColor; + // Function to determine if we should use white or black text based on background color + const getTextColor = (bgColor: string): string => { + // Remove # if present + const color = bgColor.replace('#', ''); + + // Convert to RGB + const r = parseInt(color.substr(0, 2), 16); + const g = parseInt(color.substr(2, 2), 16); + const b = parseInt(color.substr(4, 2), 16); + + // Calculate luminance + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + + // Return white for dark backgrounds, black for light backgrounds + return luminance > 0.5 ? '#000000' : '#ffffff'; + }; + + const textColor = getTextColor(backgroundColor); return ( @@ -29,7 +43,7 @@ const CustomColordLabel = React.forwardRef {truncatedName} diff --git a/worklenz-frontend/src/components/CustomNumberLabel.tsx b/worklenz-frontend/src/components/CustomNumberLabel.tsx index c603c1d4..e13289aa 100644 --- a/worklenz-frontend/src/components/CustomNumberLabel.tsx +++ b/worklenz-frontend/src/components/CustomNumberLabel.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Tooltip } from 'antd'; -import { NumbersColorMap, ALPHA_CHANNEL } from '@/shared/constants'; +import { NumbersColorMap } from '@/shared/constants'; interface CustomNumberLabelProps { labelList: string[]; @@ -12,24 +12,17 @@ interface CustomNumberLabelProps { const CustomNumberLabel = React.forwardRef( ({ labelList, namesString, isDarkMode = false, color }, ref) => { // Use provided color, or fall back to NumbersColorMap based on first digit - const baseColor = color || (() => { + const backgroundColor = color || (() => { const firstDigit = namesString.match(/\d/)?.[0] || '0'; return NumbersColorMap[firstDigit] || NumbersColorMap['0']; })(); - - // Add alpha channel to the base color - const backgroundColor = baseColor + ALPHA_CHANNEL; return ( {namesString} 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 8f953d1a..d647a815 100644 --- a/worklenz-frontend/src/features/task-management/task-management.slice.ts +++ b/worklenz-frontend/src/features/task-management/task-management.slice.ts @@ -959,8 +959,26 @@ const taskManagementSlice = createSlice({ .addCase(fetchTasksV3.fulfilled, (state, action) => { state.loading = false; const { allTasks, groups, grouping } = action.payload; - tasksAdapter.setAll(state as EntityState, allTasks || []); // Ensure allTasks is an array - state.ids = (allTasks || []).map(task => task.id); // Also update ids + + // Preserve existing timer state from old tasks before replacing + const oldTasks = state.entities; + const tasksWithTimers = (allTasks || []).map(task => { + const oldTask = oldTasks[task.id]; + if (oldTask?.timeTracking?.activeTimer) { + // Preserve the timer state from the old task + return { + ...task, + timeTracking: { + ...task.timeTracking, + activeTimer: oldTask.timeTracking.activeTimer + } + }; + } + return task; + }); + + tasksAdapter.setAll(state as EntityState, tasksWithTimers); // Ensure allTasks is an array + state.ids = tasksWithTimers.map(task => task.id); // Also update ids state.groups = groups; state.grouping = grouping; }) diff --git a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts index 7cf88cfc..77e18263 100644 --- a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts +++ b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts @@ -33,6 +33,7 @@ import { updateTaskDescription, updateSubTasks, updateTaskProgress, + updateTaskTimeTracking, } from '@/features/tasks/tasks.slice'; import { addTask, @@ -936,6 +937,8 @@ export const useTaskSocketHandlers = () => { const { task_id, start_time } = typeof data === 'string' ? JSON.parse(data) : data; if (!task_id) return; + const timerTimestamp = start_time ? (typeof start_time === 'number' ? start_time : parseInt(start_time)) : Date.now(); + // Update the task-management slice to include timer state const currentTask = store.getState().taskManagement.entities[task_id]; if (currentTask) { @@ -943,13 +946,16 @@ export const useTaskSocketHandlers = () => { ...currentTask, timeTracking: { ...currentTask.timeTracking, - activeTimer: start_time ? (typeof start_time === 'number' ? start_time : parseInt(start_time)) : Date.now(), + activeTimer: timerTimestamp, }, updatedAt: new Date().toISOString(), updated_at: new Date().toISOString(), }; dispatch(updateTask(updatedTask)); } + + // Also update the tasks slice activeTimers to keep both slices in sync + dispatch(updateTaskTimeTracking({ taskId: task_id, timeTracking: timerTimestamp })); } catch (error) { logger.error('Error handling timer start event:', error); } @@ -975,6 +981,9 @@ export const useTaskSocketHandlers = () => { }; dispatch(updateTask(updatedTask)); } + + // Also update the tasks slice activeTimers to keep both slices in sync + dispatch(updateTaskTimeTracking({ taskId: task_id, timeTracking: null })); } catch (error) { logger.error('Error handling timer stop event:', error); } diff --git a/worklenz-frontend/src/hooks/useTaskTimer.ts b/worklenz-frontend/src/hooks/useTaskTimer.ts index 0e7f7885..806cde90 100644 --- a/worklenz-frontend/src/hooks/useTaskTimer.ts +++ b/worklenz-frontend/src/hooks/useTaskTimer.ts @@ -50,7 +50,11 @@ export const useTaskTimer = (taskId: string, initialStartTime: number | null) => // Timer management effect useEffect(() => { - if (started && localStarted && reduxStartTime) { + if (started && reduxStartTime) { + // Sync local state with Redux state + if (!localStarted) { + setLocalStarted(true); + } clearTimerInterval(); timerTick(); intervalRef.current = setInterval(timerTick, 1000);