From c9772400bd5417e2a52c00bbb7125e3fbe053440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Glasn=C3=A1k?= Date: Tue, 3 Feb 2026 04:18:40 +0100 Subject: [PATCH] fix(task-edit): fixed free time calculation --- .../plugins/tm@_utils/useEmployeeTimeline.ts | 35 ++++++++++++++----- .../employees/a-employee-timeblock.vue | 11 +++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/app/src/plugins/tm@_utils/useEmployeeTimeline.ts b/app/src/plugins/tm@_utils/useEmployeeTimeline.ts index 8a6333e..646a994 100644 --- a/app/src/plugins/tm@_utils/useEmployeeTimeline.ts +++ b/app/src/plugins/tm@_utils/useEmployeeTimeline.ts @@ -26,6 +26,7 @@ export function useEmployeeTimeline( otherLocalTimeblocks?: Ref, editingBlockerId?: Ref, draftTimeblocks?: Ref, + allEditingTimeblockIds?: Ref, ) { // Fetch employee's tasks for the selected date const { @@ -267,6 +268,11 @@ export function useEmployeeTimeline( if (!tasks || !d) return 0; + // Get IDs of timeblocks being edited locally to exclude them from scheduled time + const editingTimeblockIds = allEditingTimeblockIds + ? new Set(unref(allEditingTimeblockIds).filter((id) => id != null)) + : new Set(); + let totalMinutes = 0; tasks.forEach((task) => { @@ -278,6 +284,11 @@ export function useEmployeeTimeline( return; } + // Skip timeblocks that are being edited locally to avoid double-counting + if (task._timeblock_id && editingTimeblockIds.has(task._timeblock_id)) { + return; + } + totalMinutes += timelineUtils._getDifferenceInMinutes( task._real_end, task._real_start, @@ -287,14 +298,10 @@ export function useEmployeeTimeline( return (totalMinutes / 60).toFixed(1); }); - // Calculate free/unscheduled time for the day (in hours) - const freeTime = computed(() => { - const emp = unref(employee); - const d = unref(date); + // Calculate total planned time including local edits (for display purposes) + const totalPlannedTime = computed(() => { const scheduled = parseFloat(String(unref(scheduledTime))); - - if (!emp || !d) return '0'; - + // Calculate duration of other local unsaved timeblocks for the same date let localTimeblocksHours = 0; if (otherLocalTimeblocks) { @@ -313,9 +320,20 @@ export function useEmployeeTimeline( localTimeblocksHours += calculateTimeblockDuration(currentTb); } + return (scheduled + localTimeblocksHours).toFixed(1); + }); + + // Calculate free/unscheduled time for the day (in hours) + const freeTime = computed(() => { + const emp = unref(employee); + const d = unref(date); + const totalPlanned = parseFloat(String(unref(totalPlannedTime))); + + if (!emp || !d) return '0'; + const dailyCapacityHours = emp.work_end - emp.work_start; - return (dailyCapacityHours - scheduled - localTimeblocksHours).toFixed(1); + return (dailyCapacityHours - totalPlanned).toFixed(1); }); // Detect collisions with current timeblock @@ -367,6 +385,7 @@ export function useEmployeeTimeline( // Timeline data timelineTasks, scheduledTime, + totalPlannedTime, freeTime, // Collision detection diff --git a/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock.vue b/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock.vue index 6c31a79..3ab62ec 100644 --- a/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock.vue +++ b/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock.vue @@ -14,7 +14,7 @@ :has-start-time-error="hasStartTimeError" :has-end-time-error="hasEndTimeError" :end-time-error-message="endTimeErrorMessage" - :planned-time="_scheduledTime" + :planned-time="_totalPlannedTime" :free-time="_freeTime" :employee="employee" :index="index" @@ -238,6 +238,13 @@ const otherTimeblocksForSameDate = computed(() => { }); }); +// Get all timeblock IDs being edited locally for this employee (to exclude from server scheduled time) +const allEditingTimeblockIds = computed(() => { + return props.allEmployeeTimeblocks + .filter((tb) => tb.id != null) + .map((tb) => tb.id) as number[]; +}); + // Use timeline composable const editingBlockerIdRef = computed(() => props.editingBlockerId); const draftTimeblocksRef = computed(() => props.draftTimeblocks); @@ -246,6 +253,7 @@ const { isLoading, timelineTasks, scheduledTime: _scheduledTime, + totalPlannedTime: _totalPlannedTime, freeTime: _freeTime, timeblockDuration, refetch, @@ -256,6 +264,7 @@ const { otherTimeblocksForSameDate, editingBlockerIdRef, draftTimeblocksRef, + allEditingTimeblockIds, ); // Force refetch when date changes