feat(timeline): add function to clip multi-day blockers to work hours and update timeline rendering

This commit is contained in:
Matej Glasnák committed 2026-02-25 12:03:26 +01:00
1 parent 4f8332e08e
commit b6c4badd67
1 file changed
+82 -21
@@ -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<Employee | null>,
date: Ref<Date | string | null>,
@@ -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);
});