From 1b7cea85bfe3ae30b38bdcae3674549a0de8e345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Glasn=C3=A1k?= Date: Thu, 8 Jan 2026 00:19:35 +0100 Subject: [PATCH] feat(timeline): add support for calculating local timeblock durations and update employee timeblock handling in modal --- .../plugins/tm@_utils/useEmployeeTimeline.ts | 21 +++++++- .../employees/a-employee-timeblock.vue | 53 ++++++++++--------- .../plugins/tm@modal/task/z-task-modal.vue | 27 +++++++++- 3 files changed, 75 insertions(+), 26 deletions(-) diff --git a/app/src/plugins/tm@_utils/useEmployeeTimeline.ts b/app/src/plugins/tm@_utils/useEmployeeTimeline.ts index a237918..e4754ca 100644 --- a/app/src/plugins/tm@_utils/useEmployeeTimeline.ts +++ b/app/src/plugins/tm@_utils/useEmployeeTimeline.ts @@ -23,6 +23,7 @@ export function useEmployeeTimeline( employee: Ref, date: Ref, currentTimeblock: Ref, + otherLocalTimeblocks?: Ref, ) { // Fetch employee's tasks for the selected date const { @@ -193,9 +194,27 @@ export function useEmployeeTimeline( if (!emp || !d) return '0'; + // Calculate duration of other local unsaved timeblocks for the same date + let localTimeblocksHours = 0; + if (otherLocalTimeblocks) { + const localTbs = unref(otherLocalTimeblocks) || []; + localTbs.forEach((tb) => { + // Only count timeblocks with complete time data + if (tb.starts_at_time && tb.ends_at_time) { + localTimeblocksHours += calculateTimeblockDuration(tb); + } + }); + } + + // Also include the current timeblock if it has both start and end times + const currentTb = unref(currentTimeblock); + if (currentTb?.starts_at_time && currentTb?.ends_at_time) { + localTimeblocksHours += calculateTimeblockDuration(currentTb); + } + const dailyCapacityHours = emp.work_end - emp.work_start; - return (dailyCapacityHours - scheduled).toFixed(1); + return (dailyCapacityHours - scheduled - localTimeblocksHours).toFixed(1); }); // Detect collisions with current timeblock 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 0eec7df..191f4a4 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 @@ -215,6 +215,29 @@ const employeeRef = computed(() => props.employee); const dateRef = computed(() => props.modelValue.date); const timeblockRef = computed(() => props.modelValue); +// Get other timeblocks for the same date (excluding current one) +// This includes timeblocks from both the current employee and other employees +const otherTimeblocksForSameDate = computed(() => { + if (!props.modelValue.date) return []; + + const currentDate = moment(props.modelValue.date).format('YYYY-MM-DD'); + + // Use allEmployeeTimeblocks which contains timeblocks for this specific employee + // Filter out the current timeblock by comparing index directly + return props.allEmployeeTimeblocks.filter((tb, index) => { + // Skip current timeblock by index + if (index === props.currentIndex) { + return false; + } + + // Must have required fields + if (!tb.date || !tb.starts_at_time || !tb.ends_at_time) return false; + + // Must be the same date + return moment(tb.date).format('YYYY-MM-DD') === currentDate; + }); +}); + // Use timeline composable const { isLoading, @@ -223,7 +246,12 @@ const { freeTime: _freeTime, timeblockDuration, refetch, -} = useEmployeeTimeline(employeeRef, dateRef, timeblockRef); +} = useEmployeeTimeline( + employeeRef, + dateRef, + timeblockRef, + otherTimeblocksForSameDate, +); // Force refetch when date changes watch( @@ -264,29 +292,6 @@ const collisionSegments = computed(() => { return getCollisionSegmentsUtil(props.modelValue, scheduledBlocks.value); }); -// Get other timeblocks for the same date (excluding current one) -// This includes timeblocks from both the current employee and other employees -const otherTimeblocksForSameDate = computed(() => { - if (!props.modelValue.date) return []; - - const currentDate = moment(props.modelValue.date).format('YYYY-MM-DD'); - - // Use allEmployeeTimeblocks which contains timeblocks for this specific employee - // Filter out the current timeblock by comparing index directly - return props.allEmployeeTimeblocks.filter((tb, index) => { - // Skip current timeblock by index - if (index === props.currentIndex) { - return false; - } - - // Must have required fields - if (!tb.date || !tb.starts_at_time || !tb.ends_at_time) return false; - - // Must be the same date - return moment(tb.date).format('YYYY-MM-DD') === currentDate; - }); -}); - // Get current timeblock range const currentTimeblockRange = computed(() => { const { start, end } = timeblockToDateRange(props.modelValue); diff --git a/app/src/plugins/tm@modal/task/z-task-modal.vue b/app/src/plugins/tm@modal/task/z-task-modal.vue index 504e2ad..f22498e 100644 --- a/app/src/plugins/tm@modal/task/z-task-modal.vue +++ b/app/src/plugins/tm@modal/task/z-task-modal.vue @@ -922,12 +922,37 @@ export default { const employee = this.enrichedEmployees.find( (employee) => employee.id == subtask.employee_id, ); - this.subtask.employees = [employee]; + + // Get current logged-in user + const currentUserId = this.$store.getters['auth/user']?.id; + const isCurrentUser = currentUserId === subtask.employee_id; + + // If the clicked employee is different from current user, add current user first as empty slot + if (!isCurrentUser) { + this.subtask.employees = [ + { id: currentUserId, duration: null }, // Empty current user slot + employee, // The clicked employee with data + ]; + } else { + // Same user or no current user, just add the clicked employee + this.subtask.employees = [employee]; + } + this.subtask.deadline = subtask.deadline; this.subtask._deadline_time = subtask._deadline_time; setTimeout(() => { this.timeblocks = [ + ...(!isCurrentUser + ? [ + { + date: new Date(subtask.deadline), + starts_at_time: subtask._deadline_time, + ends_at_time: null, + employee_id: currentUserId, + }, + ] + : []), { date: new Date(subtask.deadline), starts_at_time: subtask._deadline_time,