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
+81 -20
@@ -19,6 +19,53 @@ interface Employee {
work_end: number; 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( export function useEmployeeTimeline(
employee: Ref<Employee | null>, employee: Ref<Employee | null>,
date: Ref<Date | string | null>, date: Ref<Date | string | null>,
@@ -129,7 +176,7 @@ export function useEmployeeTimeline(
const tasks: any[] = []; const tasks: any[] = [];
subtasks.forEach((subtask) => { 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 ( if (
!subtask.timeblocks && !subtask.timeblocks &&
subtask.start && subtask.start &&
@@ -143,22 +190,42 @@ export function useEmployeeTimeline(
'office365_blocker', 'office365_blocker',
].includes(subtask.type) ].includes(subtask.type)
) { ) {
// Skip the blocker being edited // Pri editácii blokera ho na timeline nezobrazujeme
if (excludeBlockerId && subtask.id === excludeBlockerId) { if (excludeBlockerId && subtask.id === excludeBlockerId) {
return; 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 type = subtask.type;
const task = { const task = {
id: subtask.id, id: subtask.id,
start: new Date(subtask.start), start: realStart,
end: new Date(subtask.end), end: realEnd,
master_task: null, master_task: null,
date: new Date(subtask.start), date: realStart,
project_code: null, project_code: null,
subtask: subtask, subtask: subtask,
is_draft: !!subtask.is_draft, is_draft: !!subtask.is_draft,
@@ -166,17 +233,17 @@ export function useEmployeeTimeline(
type, type,
_timeblock_id: null, _timeblock_id: null,
_timeblock_is_finished: subtask.is_finished || false, _timeblock_is_finished: subtask.is_finished || false,
_real_start: new Date(subtask.start), _real_start: realStart,
_real_end: new Date(subtask.end), _real_end: realEnd,
}; };
tasks.push(task); tasks.push(task);
} }
} else { } 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; 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)) { if (subtask.is_draft && Array.isArray(subtask.employees)) {
const employeeData = subtask.employees.find( const employeeData = subtask.employees.find(
(empData: any) => Number(empData.id) === Number(emp.id), (empData: any) => Number(empData.id) === Number(emp.id),
@@ -187,13 +254,11 @@ export function useEmployeeTimeline(
timeblocks = subtask.timeblocks; timeblocks = subtask.timeblocks;
} }
// Process timeblocks (same logic for both regular and concept tasks)
timeblocks?.forEach((timeblock: any) => { timeblocks?.forEach((timeblock: any) => {
const timeblockDateStr = moment(timeblock.start).format( const timeblockDateStr = moment(timeblock.start).format(
'YYYY-MM-DD', 'YYYY-MM-DD',
); );
// Len bloky na vybraný deň
// Only include timeblocks for the selected date
if (timeblockDateStr === selectedDateStr) { if (timeblockDateStr === selectedDateStr) {
let type = 'subtask'; let type = 'subtask';
if (subtask.type === 'office365') type = 'office365'; 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) => { draftTbs.forEach((timeblock: any) => {
// Skip if timeblock doesn't have required fields
if (!timeblock.start || !timeblock.end) { if (!timeblock.start || !timeblock.end) {
return; return;
} }
const timeblockDateStr = moment(timeblock.start).format('YYYY-MM-DD'); const timeblockDateStr = moment(timeblock.start).format('YYYY-MM-DD');
// Len bloky na vybraný deň
// Only include timeblocks for the selected date
if (timeblockDateStr === selectedDateStr) { if (timeblockDateStr === selectedDateStr) {
const task = { const task = {
id: null, // Draft timeblock doesn't have a subtask ID id: null, // Draft timeblock doesn't have a subtask ID
@@ -332,7 +394,6 @@ export function useEmployeeTimeline(
if (!emp || !d) return '0'; if (!emp || !d) return '0';
const dailyCapacityHours = emp.work_end - emp.work_start; const dailyCapacityHours = emp.work_end - emp.work_start;
return (dailyCapacityHours - totalPlanned).toFixed(1); return (dailyCapacityHours - totalPlanned).toFixed(1);
}); });