diff --git a/app/src/plugins/tm@_utils/useEmployeeTimeline.ts b/app/src/plugins/tm@_utils/useEmployeeTimeline.ts index 646a994..459585d 100644 --- a/app/src/plugins/tm@_utils/useEmployeeTimeline.ts +++ b/app/src/plugins/tm@_utils/useEmployeeTimeline.ts @@ -19,6 +19,53 @@ interface Employee { work_end: number; } +/** + * Orezá multi-dňový blocker na segment v rámci jedného dňa a pracovného času. + * Blocker začína najskôr na začiatku pracovného času a končí najneskôr na konci pracovného času. + * + * Príklad: blocker pondelok 12:00 – streda 12:00, pracovný čas 8:00–16:00 + * - Pondelok: 12:00–16:00 + * - Utorok: 8:00–16:00 + * - Streda: 8:00–12:00 + * + * @returns { start: Date, end: Date } ak má segment na daný deň prienik s pracovným časom, inak null + */ +function clipBlockerToWorkHoursOnDay( + blockerStart: moment.Moment, + blockerEnd: moment.Moment, + selectedDateStr: string, + workStartHours: number, + workEndHours: number, +): { start: Date; end: Date } | null { + const dayWorkStart = moment(selectedDateStr) + .set({ + hour: Math.floor(workStartHours), + minute: Math.round((workStartHours % 1) * 60), + second: 0, + millisecond: 0, + }) + .startOf('minute'); + const dayWorkEnd = moment(selectedDateStr) + .set({ + hour: Math.floor(workEndHours), + minute: Math.round((workEndHours % 1) * 60), + second: 0, + millisecond: 0, + }) + .startOf('minute'); + + const clipStart = blockerStart.isBefore(dayWorkStart) + ? dayWorkStart + : blockerStart; + const clipEnd = blockerEnd.isAfter(dayWorkEnd) ? dayWorkEnd : blockerEnd; + + if (!clipEnd.isAfter(clipStart)) { + return null; + } + + return { start: clipStart.toDate(), end: clipEnd.toDate() }; +} + export function useEmployeeTimeline( employee: Ref, date: Ref, @@ -129,7 +176,7 @@ export function useEmployeeTimeline( const tasks: any[] = []; subtasks.forEach((subtask) => { - // Check if this is a blocker/adhoc/etc with direct start/end fields (no timeblocks array) + // Blocker/adhoc/vacation/… – majú priamo start/end (nie pole timeblocks) if ( !subtask.timeblocks && subtask.start && @@ -143,22 +190,42 @@ export function useEmployeeTimeline( 'office365_blocker', ].includes(subtask.type) ) { - // Skip the blocker being edited + // Pri editácii blokera ho na timeline nezobrazujeme if (excludeBlockerId && subtask.id === excludeBlockerId) { return; } - const blockerDateStr = moment(subtask.start).format('YYYY-MM-DD'); + const blockerStart = moment(subtask.start); + const blockerEnd = moment(subtask.end); + const selectedDay = moment(selectedDateStr); + // Blocker zobrazíme len ak je vybraný deň v jeho rozsahu (FE kontrola aj pri multi-day z BE) + const selectedDayInRange = + selectedDay.isSameOrAfter(blockerStart, 'day') && + selectedDay.isSameOrBefore(blockerEnd, 'day'); - if (blockerDateStr === selectedDateStr) { + if (selectedDayInRange) { + const workStart = emp.work_start ?? 8; + const workEnd = emp.work_end ?? 16; + const segment = clipBlockerToWorkHoursOnDay( + blockerStart, + blockerEnd, + selectedDateStr, + workStart, + workEnd, + ); + if (!segment) { + return; // na tento deň nemá blocker prienik s pracovným časom + } + + const { start: realStart, end: realEnd } = segment; const type = subtask.type; const task = { id: subtask.id, - start: new Date(subtask.start), - end: new Date(subtask.end), + start: realStart, + end: realEnd, master_task: null, - date: new Date(subtask.start), + date: realStart, project_code: null, subtask: subtask, is_draft: !!subtask.is_draft, @@ -166,17 +233,17 @@ export function useEmployeeTimeline( type, _timeblock_id: null, _timeblock_is_finished: subtask.is_finished || false, - _real_start: new Date(subtask.start), - _real_end: new Date(subtask.end), + _real_start: realStart, + _real_end: realEnd, }; tasks.push(task); } } else { - // Determine the correct timeblocks array based on task type + // Bežné úlohy – časové bloky z timeblocks (alebo z employees pri draft) let timeblocks: any[] | undefined; - // For draft tasks with employees array, get timeblocks from the matching employee + // Draft úlohy: timeblocks hľadáme u zodpovedajúceho zamestnanca if (subtask.is_draft && Array.isArray(subtask.employees)) { const employeeData = subtask.employees.find( (empData: any) => Number(empData.id) === Number(emp.id), @@ -187,13 +254,11 @@ export function useEmployeeTimeline( timeblocks = subtask.timeblocks; } - // Process timeblocks (same logic for both regular and concept tasks) timeblocks?.forEach((timeblock: any) => { const timeblockDateStr = moment(timeblock.start).format( 'YYYY-MM-DD', ); - - // Only include timeblocks for the selected date + // Len bloky na vybraný deň if (timeblockDateStr === selectedDateStr) { let type = 'subtask'; if (subtask.type === 'office365') type = 'office365'; @@ -221,16 +286,13 @@ export function useEmployeeTimeline( } }); - // Process draft timeblocks from other employees in the same draft subtask + // Draft timeblocky od iných zamestnancov v rámci toho istého draft subtasku draftTbs.forEach((timeblock: any) => { - // Skip if timeblock doesn't have required fields if (!timeblock.start || !timeblock.end) { return; } - const timeblockDateStr = moment(timeblock.start).format('YYYY-MM-DD'); - - // Only include timeblocks for the selected date + // Len bloky na vybraný deň if (timeblockDateStr === selectedDateStr) { const task = { id: null, // Draft timeblock doesn't have a subtask ID @@ -301,7 +363,7 @@ export function useEmployeeTimeline( // Calculate total planned time including local edits (for display purposes) const totalPlannedTime = computed(() => { const scheduled = parseFloat(String(unref(scheduledTime))); - + // Calculate duration of other local unsaved timeblocks for the same date let localTimeblocksHours = 0; if (otherLocalTimeblocks) { @@ -332,7 +394,6 @@ export function useEmployeeTimeline( if (!emp || !d) return '0'; const dailyCapacityHours = emp.work_end - emp.work_start; - return (dailyCapacityHours - totalPlanned).toFixed(1); });