diff --git a/worklenz-frontend/src/components/task-list-v2/TaskListV2Table.tsx b/worklenz-frontend/src/components/task-list-v2/TaskListV2Table.tsx index 997a8256..99fb5bb1 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskListV2Table.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskListV2Table.tsx @@ -62,10 +62,7 @@ import ImprovedTaskFilters from '@/components/task-management/improved-task-filt import OptimizedBulkActionBar from '@/components/task-management/optimized-bulk-action-bar'; import CustomColumnModal from '@/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/custom-column-modal'; import AddTaskRow from './components/AddTaskRow'; -import { - AddCustomColumnButton, - CustomColumnHeader, -} from './components/CustomColumnComponents'; +import { AddCustomColumnButton, CustomColumnHeader } from './components/CustomColumnComponents'; // Hooks and utilities import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers'; @@ -126,7 +123,10 @@ const TaskListV2Section: React.FC = () => { ); // Custom hooks - const { activeId, handleDragStart, handleDragOver, handleDragEnd } = useDragAndDrop(allTasks, groups); + const { activeId, handleDragStart, handleDragOver, handleDragEnd } = useDragAndDrop( + allTasks, + groups + ); const bulkActions = useBulkActions(); // Enable real-time updates via socket handlers @@ -156,49 +156,51 @@ const TaskListV2Section: React.FC = () => { }); // Add visible custom columns - const visibleCustomColumns = customColumns - ?.filter(column => column.pinned) - ?.map(column => { - // Give selection columns more width for dropdown content - const fieldType = column.custom_column_obj?.fieldType; - let defaultWidth = 160; - if (fieldType === 'selection') { - defaultWidth = 150; // Reduced width for selection dropdowns - } else if (fieldType === 'people') { - defaultWidth = 170; // Extra width for people with avatars - } + const visibleCustomColumns = + customColumns + ?.filter(column => column.pinned) + ?.map(column => { + // Give selection columns more width for dropdown content + const fieldType = column.custom_column_obj?.fieldType; + let defaultWidth = 160; + if (fieldType === 'selection') { + defaultWidth = 150; // Reduced width for selection dropdowns + } else if (fieldType === 'people') { + defaultWidth = 170; // Extra width for people with avatars + } - // Map the configuration data structure to the expected format - const customColumnObj = column.custom_column_obj || (column as any).configuration; + // Map the configuration data structure to the expected format + const customColumnObj = column.custom_column_obj || (column as any).configuration; - // Transform configuration format to custom_column_obj format if needed - let transformedColumnObj = customColumnObj; - if (customColumnObj && !customColumnObj.fieldType && customColumnObj.field_type) { - transformedColumnObj = { - ...customColumnObj, - fieldType: customColumnObj.field_type, - numberType: customColumnObj.number_type, - labelPosition: customColumnObj.label_position, - previewValue: customColumnObj.preview_value, - firstNumericColumn: customColumnObj.first_numeric_column_key, - secondNumericColumn: customColumnObj.second_numeric_column_key, - selectionsList: customColumnObj.selections_list || customColumnObj.selectionsList || [], - labelsList: customColumnObj.labels_list || customColumnObj.labelsList || [], + // Transform configuration format to custom_column_obj format if needed + let transformedColumnObj = customColumnObj; + if (customColumnObj && !customColumnObj.fieldType && customColumnObj.field_type) { + transformedColumnObj = { + ...customColumnObj, + fieldType: customColumnObj.field_type, + numberType: customColumnObj.number_type, + labelPosition: customColumnObj.label_position, + previewValue: customColumnObj.preview_value, + firstNumericColumn: customColumnObj.first_numeric_column_key, + secondNumericColumn: customColumnObj.second_numeric_column_key, + selectionsList: + customColumnObj.selections_list || customColumnObj.selectionsList || [], + labelsList: customColumnObj.labels_list || customColumnObj.labelsList || [], + }; + } + + return { + id: column.key || column.id || 'unknown', + label: column.name || t('customColumns.customColumnHeader'), + width: `${(column as any).width || defaultWidth}px`, + key: column.key || column.id || 'unknown', + custom_column: true, + custom_column_obj: transformedColumnObj, + isCustom: true, + name: column.name, + uuid: column.id, }; - } - - return { - id: column.key || column.id || 'unknown', - label: column.name || t('customColumns.customColumnHeader'), - width: `${(column as any).width || defaultWidth}px`, - key: column.key || column.id || 'unknown', - custom_column: true, - custom_column_obj: transformedColumnObj, - isCustom: true, - name: column.name, - uuid: column.id, - }; - }) || []; + }) || []; return [...baseVisibleColumns, ...visibleCustomColumns]; }, [fields, columns, customColumns, t]); @@ -222,15 +224,15 @@ const TaskListV2Section: React.FC = () => { if (backendColumn) { return { ...field, - visible: backendColumn.pinned ?? field.visible + visible: backendColumn.pinned ?? field.visible, }; } return field; }); // Only update if there are actual changes - const hasChanges = updatedFields.some((field, index) => - field.visible !== fields[index].visible + const hasChanges = updatedFields.some( + (field, index) => field.visible !== fields[index].visible ); if (hasChanges) { @@ -269,65 +271,73 @@ const TaskListV2Section: React.FC = () => { ); // Function to update custom column values - const updateTaskCustomColumnValue = useCallback((taskId: string, columnKey: string, value: string) => { - try { - if (!urlProjectId) { - console.error('Project ID is missing'); - return; - } + const updateTaskCustomColumnValue = useCallback( + (taskId: string, columnKey: string, value: string) => { + try { + if (!urlProjectId) { + console.error('Project ID is missing'); + return; + } - const body = { - task_id: taskId, - column_key: columnKey, - value: value, - project_id: urlProjectId, - }; - - // Update the Redux store immediately for optimistic updates - const currentTask = allTasks.find(task => task.id === taskId); - if (currentTask) { - const updatedTask = { - ...currentTask, - custom_column_values: { - ...currentTask.custom_column_values, - [columnKey]: value, - }, - updated_at: new Date().toISOString(), + const body = { + task_id: taskId, + column_key: columnKey, + value: value, + project_id: urlProjectId, }; - // Import and dispatch the updateTask action - import('@/features/task-management/task-management.slice').then(({ updateTask }) => { - dispatch(updateTask(updatedTask)); - }); - } + // Update the Redux store immediately for optimistic updates + const currentTask = allTasks.find(task => task.id === taskId); + if (currentTask) { + const updatedTask = { + ...currentTask, + custom_column_values: { + ...currentTask.custom_column_values, + [columnKey]: value, + }, + updated_at: new Date().toISOString(), + }; - if (socket && connected) { - socket.emit(SocketEvents.TASK_CUSTOM_COLUMN_UPDATE.toString(), JSON.stringify(body)); - } else { - console.warn('Socket not connected, unable to emit TASK_CUSTOM_COLUMN_UPDATE event'); + // Import and dispatch the updateTask action + import('@/features/task-management/task-management.slice').then(({ updateTask }) => { + dispatch(updateTask(updatedTask)); + }); + } + + if (socket && connected) { + socket.emit(SocketEvents.TASK_CUSTOM_COLUMN_UPDATE.toString(), JSON.stringify(body)); + } else { + console.warn('Socket not connected, unable to emit TASK_CUSTOM_COLUMN_UPDATE event'); + } + } catch (error) { + console.error('Error updating custom column value:', error); } - } catch (error) { - console.error('Error updating custom column value:', error); - } - }, [urlProjectId, socket, connected, allTasks, dispatch]); + }, + [urlProjectId, socket, connected, allTasks, dispatch] + ); // Custom column settings handler - const handleCustomColumnSettings = useCallback((columnKey: string) => { - if (!columnKey) return; + const handleCustomColumnSettings = useCallback( + (columnKey: string) => { + if (!columnKey) return; - const columnData = visibleColumns.find(col => col.key === columnKey || col.id === columnKey); + const columnData = visibleColumns.find(col => col.key === columnKey || col.id === columnKey); - // Use the UUID for API calls, not the key (nanoid) - // For custom columns, prioritize the uuid field over id field - const columnId = (columnData as any)?.uuid || columnData?.id || columnKey; + // Use the UUID for API calls, not the key (nanoid) + // For custom columns, prioritize the uuid field over id field + const columnId = (columnData as any)?.uuid || columnData?.id || columnKey; - dispatch(setCustomColumnModalAttributes({ - modalType: 'edit', - columnId: columnId, - columnData: columnData - })); - dispatch(toggleCustomColumnModalOpen(true)); - }, [dispatch, visibleColumns]); + dispatch( + setCustomColumnModalAttributes({ + modalType: 'edit', + columnId: columnId, + columnData: columnData, + }) + ); + dispatch(toggleCustomColumnModalOpen(true)); + }, + [dispatch, visibleColumns] + ); // Add callback for task added const handleTaskAdded = useCallback(() => { @@ -350,25 +360,27 @@ const TaskListV2Section: React.FC = () => { const visibleTasksInGroup = isCurrentGroupCollapsed ? [] : group.taskIds - .map(taskId => allTasks.find(task => task.id === taskId)) - .filter((task): task is Task => task !== undefined); + .map(taskId => allTasks.find(task => task.id === taskId)) + .filter((task): task is Task => task !== undefined); const tasksForVirtuoso = visibleTasksInGroup.map(task => ({ ...task, originalIndex: allTasks.indexOf(task), })); - const itemsWithAddTask = !isCurrentGroupCollapsed ? [ - ...tasksForVirtuoso, - { - id: `add-task-${group.id}`, - isAddTaskRow: true, - groupId: group.id, - groupType: currentGrouping || 'status', - groupValue: group.id, // Use the actual database ID from backend - projectId: urlProjectId, - } - ] : tasksForVirtuoso; + const itemsWithAddTask = !isCurrentGroupCollapsed + ? [ + ...tasksForVirtuoso, + { + id: `add-task-${group.id}`, + isAddTaskRow: true, + groupId: group.id, + groupType: currentGrouping || 'status', + groupValue: group.id, // Use the actual database ID from backend + projectId: urlProjectId, + }, + ] + : tasksForVirtuoso; const groupData = { ...group, @@ -398,8 +410,6 @@ const TaskListV2Section: React.FC = () => { const isGroupCollapsed = collapsedGroups.has(group.id); const isGroupEmpty = group.actualCount === 0; - - return (
0 ? 'mt-2' : ''}> { {isGroupEmpty && !isGroupCollapsed && (
- {visibleColumns.map((column, index) => ( -
- ))} + {visibleColumns.map((column, index) => { + const emptyColumnStyle = { + width: column.width, + flexShrink: 0, + ...(column.id === 'labels' && column.width === 'auto' + ? { minWidth: '200px', flexGrow: 1 } + : {}), + }; + return ( +
+ ); + })}
@@ -440,7 +460,6 @@ const TaskListV2Section: React.FC = () => { (taskIndex: number) => { const item = virtuosoItems[taskIndex]; - if (!item || !urlProjectId) return null; if ('isAddTaskRow' in item && item.isAddTaskRow) { @@ -469,70 +488,86 @@ const TaskListV2Section: React.FC = () => { ); // Render column headers - const renderColumnHeaders = useCallback(() => ( -
-
- {visibleColumns.map((column, index) => { - const columnStyle: ColumnStyle = { - width: column.width, - flexShrink: 0, - ...(column.id === 'labels' && column.width === 'auto' - ? { - minWidth: '200px', - flexGrow: 1, - } - : {}), - ...((column as any).minWidth && { minWidth: (column as any).minWidth }), - ...((column as any).maxWidth && { maxWidth: (column as any).maxWidth }), - }; + const renderColumnHeaders = useCallback( + () => ( +
+
+ {visibleColumns.map((column, index) => { + const columnStyle: ColumnStyle = { + width: column.width, + flexShrink: 0, + ...(column.id === 'labels' && column.width === 'auto' + ? { + minWidth: '200px', + flexGrow: 1, + } + : {}), + ...((column as any).minWidth && { minWidth: (column as any).minWidth }), + ...((column as any).maxWidth && { maxWidth: (column as any).maxWidth }), + }; - return ( -
- {column.id === 'dragHandle' || column.id === 'checkbox' ? ( - - ) : (column as any).isCustom ? ( - - ) : ( - t(column.label || '') - )} -
- ); - })} - {/* Add Custom Column Button - positioned at the end and scrolls with content */} -
- + style={columnStyle} + > + {column.id === 'dragHandle' || column.id === 'checkbox' ? ( + + ) : (column as any).isCustom ? ( + + ) : ( + t(column.label || '') + )} +
+ ); + })} + {/* Add Custom Column Button - positioned at the end and scrolls with content */} +
+ +
-
- ), [visibleColumns, t, handleCustomColumnSettings]); - - + ), + [visibleColumns, t, handleCustomColumnSettings] + ); // Loading and error states if (loading || loadingColumns) return ; - if (error) return
{t('emptyStates.errorPrefix')} {error}
; + if (error) + return ( +
+ {t('emptyStates.errorPrefix')} {error} +
+ ); // Show message when no data if (groups.length === 0 && !loading) { @@ -556,58 +591,61 @@ const TaskListV2Section: React.FC = () => { } return ( - -
- - {/* Table Container */} + +
+ {/* Table Container */} +
+ {/* Task List Content with Sticky Header */}
- {/* Task List Content with Sticky Header */} + {/* Sticky Column Headers */}
- {/* Sticky Column Headers */} -
- {renderColumnHeaders()} -
- !('isAddTaskRow' in item) && !item.parent_task_id) - .map(item => item.id) - .filter((id): id is string => id !== undefined)} - strategy={verticalListSortingStrategy} - > -
- {/* Render groups manually for debugging */} - {virtuosoGroups.map((group, groupIndex) => ( -
- {/* Group Header */} - {renderGroup(groupIndex)} + {renderColumnHeaders()} +
+ !('isAddTaskRow' in item) && !item.parent_task_id) + .map(item => item.id) + .filter((id): id is string => id !== undefined)} + strategy={verticalListSortingStrategy} + > +
+ {/* Render groups manually for debugging */} + {virtuosoGroups.map((group, groupIndex) => ( +
+ {/* Group Header */} + {renderGroup(groupIndex)} - {/* Group Tasks */} - {!collapsedGroups.has(group.id) && group.tasks.map((task, taskIndex) => { - const globalTaskIndex = virtuosoGroups - .slice(0, groupIndex) - .reduce((sum, g) => sum + g.count, 0) + taskIndex; + {/* Group Tasks */} + {!collapsedGroups.has(group.id) && + group.tasks.map((task, taskIndex) => { + const globalTaskIndex = + virtuosoGroups.slice(0, groupIndex).reduce((sum, g) => sum + g.count, 0) + + taskIndex; return (
@@ -615,63 +653,73 @@ const TaskListV2Section: React.FC = () => {
); })} -
- ))} -
-
-
+
+ ))} +
+
+
- {/* Drag Overlay */} - - {activeId ? ( -
-
-
- -
-
- {allTasks.find(task => task.id === activeId)?.name || - allTasks.find(task => task.id === activeId)?.title || - t('emptyStates.dragTaskFallback')} -
-
- {allTasks.find(task => task.id === activeId)?.task_key} -
+ {/* Drag Overlay */} + + {activeId ? ( +
+
+
+ +
+
+ {allTasks.find(task => task.id === activeId)?.name || + allTasks.find(task => task.id === activeId)?.title || + t('emptyStates.dragTaskFallback')} +
+
+ {allTasks.find(task => task.id === activeId)?.task_key}
- ) : null} - - - {/* Bulk Action Bar */} - {selectedTaskIds.length > 0 && urlProjectId && ( -
- bulkActions.handleBulkStatusChange(statusId, selectedTaskIds)} - onBulkPriorityChange={(priorityId) => bulkActions.handleBulkPriorityChange(priorityId, selectedTaskIds)} - onBulkPhaseChange={(phaseId) => bulkActions.handleBulkPhaseChange(phaseId, selectedTaskIds)} - onBulkAssignToMe={() => bulkActions.handleBulkAssignToMe(selectedTaskIds)} - onBulkAssignMembers={(memberIds) => bulkActions.handleBulkAssignMembers(memberIds, selectedTaskIds)} - onBulkAddLabels={(labelIds) => bulkActions.handleBulkAddLabels(labelIds, selectedTaskIds)} - onBulkArchive={() => bulkActions.handleBulkArchive(selectedTaskIds)} - onBulkDelete={() => bulkActions.handleBulkDelete(selectedTaskIds)} - onBulkDuplicate={() => bulkActions.handleBulkDuplicate(selectedTaskIds)} - onBulkExport={() => bulkActions.handleBulkExport(selectedTaskIds)} - onBulkSetDueDate={(date) => bulkActions.handleBulkSetDueDate(date, selectedTaskIds)} - />
- )} + ) : null} + - {/* Custom Column Modal */} - {createPortal(, document.body, 'custom-column-modal')} -
- + {/* Bulk Action Bar */} + {selectedTaskIds.length > 0 && urlProjectId && ( +
+ + bulkActions.handleBulkStatusChange(statusId, selectedTaskIds) + } + onBulkPriorityChange={priorityId => + bulkActions.handleBulkPriorityChange(priorityId, selectedTaskIds) + } + onBulkPhaseChange={phaseId => + bulkActions.handleBulkPhaseChange(phaseId, selectedTaskIds) + } + onBulkAssignToMe={() => bulkActions.handleBulkAssignToMe(selectedTaskIds)} + onBulkAssignMembers={memberIds => + bulkActions.handleBulkAssignMembers(memberIds, selectedTaskIds) + } + onBulkAddLabels={labelIds => + bulkActions.handleBulkAddLabels(labelIds, selectedTaskIds) + } + onBulkArchive={() => bulkActions.handleBulkArchive(selectedTaskIds)} + onBulkDelete={() => bulkActions.handleBulkDelete(selectedTaskIds)} + onBulkDuplicate={() => bulkActions.handleBulkDuplicate(selectedTaskIds)} + onBulkExport={() => bulkActions.handleBulkExport(selectedTaskIds)} + onBulkSetDueDate={date => bulkActions.handleBulkSetDueDate(date, selectedTaskIds)} + /> +
+ )} + + {/* Custom Column Modal */} + {createPortal(, document.body, 'custom-column-modal')} +
+ ); }; diff --git a/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx b/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx index bf2a663c..93510cd0 100644 --- a/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx +++ b/worklenz-frontend/src/components/task-list-v2/TaskRow.tsx @@ -271,7 +271,7 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn case 'checkbox': return ( -
+
= memo(({ taskId, projectId, visibleColumn case 'taskKey': return ( -
+
{task.task_key || 'N/A'} @@ -291,7 +291,7 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn case 'title': return ( -
+
{/* Indentation for subtasks - tighter spacing */} {isSubtask &&
} @@ -417,7 +417,7 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn case 'description': return ( -
+
= memo(({ taskId, projectId, visibleColumn case 'status': return ( -
+
= memo(({ taskId, projectId, visibleColumn case 'assignees': return ( -
+
= memo(({ taskId, projectId, visibleColumn case 'priority': return ( -
+
= memo(({ taskId, projectId, visibleColumn case 'dueDate': return ( -
+
{activeDatePicker === 'dueDate' ? (
= memo(({ taskId, projectId, visibleColumn case 'progress': return ( -
+
{task.progress !== undefined && task.progress >= 0 && (task.progress === 100 ? ( @@ -555,8 +555,13 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn ); case 'labels': + const labelsColumn = visibleColumns.find(col => col.id === 'labels'); + const labelsStyle = { + ...baseStyle, + ...(labelsColumn?.width === 'auto' ? { minWidth: '200px', flexGrow: 1 } : {}) + }; return ( -
+
@@ -564,7 +569,7 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn case 'phase': return ( -
+
= memo(({ taskId, projectId, visibleColumn case 'timeTracking': return ( -
+
); case 'estimation': + // Use timeTracking.estimated which is the converted value from backend's total_minutes + const estimationDisplay = (() => { + const estimatedHours = task.timeTracking?.estimated; + + if (estimatedHours && estimatedHours > 0) { + // Convert decimal hours to hours and minutes for display + const hours = Math.floor(estimatedHours); + const minutes = Math.round((estimatedHours - hours) * 60); + + if (hours > 0 && minutes > 0) { + return `${hours}h ${minutes}m`; + } else if (hours > 0) { + return `${hours}h`; + } else if (minutes > 0) { + return `${minutes}m`; + } + } + + return null; + })(); + return ( -
- {task.timeTracking?.estimated ? ( +
+ {estimationDisplay ? ( - {task.timeTracking.estimated}h + {estimationDisplay} ) : ( - 0 + - )}
@@ -597,7 +623,7 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn case 'startDate': return ( -
+
{activeDatePicker === 'startDate' ? (
= memo(({ taskId, projectId, visibleColumn case 'completedDate': return ( -
+
{formattedDates.completed ? ( {formattedDates.completed} @@ -668,7 +694,7 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn case 'createdDate': return ( -
+
{formattedDates.created ? ( {formattedDates.created} @@ -680,9 +706,8 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn ); case 'lastUpdated': - console.log('formattedDates.updated', formattedDates.updated); return ( -
+
{formattedDates.updated ? ( {formattedDates.updated} @@ -695,7 +720,7 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn case 'reporter': return ( -
+
{task.reporter ? ( {task.reporter} ) : ( @@ -709,7 +734,7 @@ const TaskRow: React.FC = memo(({ taskId, projectId, visibleColumn const column = visibleColumns.find(col => col.id === columnId); if (column && (column.custom_column || column.isCustom) && updateTaskCustomColumnValue) { return ( -
+
= memo(({ case 'checkbox': case 'taskKey': case 'description': - return
; + return
; + case 'labels': + const labelsStyle = { + ...baseStyle, + ...(width === 'auto' ? { minWidth: '200px', flexGrow: 1 } : {}) + }; + return
; case 'title': return ( -
+
@@ -129,7 +135,7 @@ const AddTaskRow: React.FC = memo(({
); default: - return
; + return
; } }, [isAdding, taskName, handleAddTask, handleCancel, t]); 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 c555f6e3..c10805e1 100644 --- a/worklenz-frontend/src/features/task-management/task-management.slice.ts +++ b/worklenz-frontend/src/features/task-management/task-management.slice.ts @@ -281,8 +281,8 @@ export const fetchTasksV3 = createAsyncThunk( dueDate: task.dueDate, startDate: task.startDate, timeTracking: { - estimated: convertTimeValue(task.total_time), - logged: convertTimeValue(task.time_spent), + estimated: task.timeTracking?.estimated || 0, + logged: task.timeTracking?.logged || 0, }, customFields: {}, custom_column_values: task.custom_column_values || {}, diff --git a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts index cf857653..cc55829b 100644 --- a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts +++ b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts @@ -670,15 +670,32 @@ export const useTaskSocketHandlers = () => { const handleEstimationChange = useCallback( - (task: { id: string; parent_task: string | null; estimation: number }) => { - if (!task) return; + (data: { id: string; parent_task: string | null; total_hours: number; total_minutes: number }) => { + if (!data) return; + // Update the old task slice (for backward compatibility) const taskWithProgress = { - ...task, + ...data, manual_progress: false, } as IProjectTask; dispatch(updateTaskEstimation({ task: taskWithProgress })); + + // Update task-management slice for task-list-v2 components + const currentTask = store.getState().taskManagement.entities[data.id]; + if (currentTask) { + const estimatedHours = (data.total_hours || 0) + (data.total_minutes || 0) / 60; + const updatedTask: Task = { + ...currentTask, + timeTracking: { + ...currentTask.timeTracking, + estimated: estimatedHours, + }, + updatedAt: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + dispatch(updateTask(updatedTask)); + } }, [dispatch] );