feat(timeline): add support for calculating local timeblock durations and update employee timeblock handling in modal

This commit is contained in:
Matej Glasnák committed 2026-01-08 00:19:35 +01:00
1 parent 03974b1b99
commit 1b7cea85bf
3 files changed
+74 -25

No files matched your search

@@ -23,6 +23,7 @@ export function useEmployeeTimeline(
employee: Ref<Employee | null>, employee: Ref<Employee | null>,
date: Ref<Date | string | null>, date: Ref<Date | string | null>,
currentTimeblock: Ref<Timeblock | null>, currentTimeblock: Ref<Timeblock | null>,
otherLocalTimeblocks?: Ref<Timeblock[]>,
) { ) {
// Fetch employee's tasks for the selected date // Fetch employee's tasks for the selected date
const { const {
@@ -193,9 +194,27 @@ export function useEmployeeTimeline(
if (!emp || !d) return '0'; 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; const dailyCapacityHours = emp.work_end - emp.work_start;
return (dailyCapacityHours - scheduled).toFixed(1); return (dailyCapacityHours - scheduled - localTimeblocksHours).toFixed(1);
}); });
// Detect collisions with current timeblock // Detect collisions with current timeblock
@@ -215,6 +215,29 @@ const employeeRef = computed(() => props.employee);
const dateRef = computed(() => props.modelValue.date); const dateRef = computed(() => props.modelValue.date);
const timeblockRef = computed(() => props.modelValue); 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 // Use timeline composable
const { const {
isLoading, isLoading,
@@ -223,7 +246,12 @@ const {
freeTime: _freeTime, freeTime: _freeTime,
timeblockDuration, timeblockDuration,
refetch, refetch,
} = useEmployeeTimeline(employeeRef, dateRef, timeblockRef); } = useEmployeeTimeline(
employeeRef,
dateRef,
timeblockRef,
otherTimeblocksForSameDate,
);
// Force refetch when date changes // Force refetch when date changes
watch( watch(
@@ -264,29 +292,6 @@ const collisionSegments = computed(() => {
return getCollisionSegmentsUtil(props.modelValue, scheduledBlocks.value); 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 // Get current timeblock range
const currentTimeblockRange = computed(() => { const currentTimeblockRange = computed(() => {
const { start, end } = timeblockToDateRange(props.modelValue); const { start, end } = timeblockToDateRange(props.modelValue);
@@ -922,12 +922,37 @@ export default {
const employee = this.enrichedEmployees.find( const employee = this.enrichedEmployees.find(
(employee) => employee.id == subtask.employee_id, (employee) => employee.id == subtask.employee_id,
); );
// 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.employees = [employee];
}
this.subtask.deadline = subtask.deadline; this.subtask.deadline = subtask.deadline;
this.subtask._deadline_time = subtask._deadline_time; this.subtask._deadline_time = subtask._deadline_time;
setTimeout(() => { setTimeout(() => {
this.timeblocks = [ 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), date: new Date(subtask.deadline),
starts_at_time: subtask._deadline_time, starts_at_time: subtask._deadline_time,