refactor(employee-timeline): improve code readability and consistency by adding semicolons and restructuring computed properties

This commit is contained in:
Matej Glasnák committed 2025-12-08 03:29:49 +01:00
1 parent c55c2ce3e9
commit d3addd1a39
3 files changed
+110 -98

No files matched your search

+107 -88
View File
@@ -1,89 +1,104 @@
import { useQuery } from '@tanstack/vue-query'
import { computed, Ref, unref } from 'vue'
import axios from '@/plugins/w@custom/axios/models/axios'
import moment from 'moment'
import timelineUtils from '@/plugins/tm@dashboard/tasks/_utils/timeline.utils'
import { useQuery } from '@tanstack/vue-query';
import { computed, Ref, unref } from 'vue';
import axios from '@/plugins/w@custom/axios/models/axios';
import moment from 'moment';
import timelineUtils from '@/plugins/tm@dashboard/tasks/_utils/timeline.utils';
import {
detectCollision,
calculateTimeblockDuration,
getCollisionSegments,
type Timeblock,
type CollisionInfo,
} from './timeblock.utils'
} from './timeblock.utils';
interface Employee {
id: number
name: string
surname: string
work_start: number
work_end: number
id: number;
name: string;
surname: string;
work_start: number;
work_end: number;
}
export function useEmployeeTimeline(
employee: Ref<Employee | null>,
date: Ref<Date | string | null>,
currentTimeblock: Ref<Timeblock | null>
currentTimeblock: Ref<Timeblock | null>,
) {
// Fetch employee's tasks for the selected date
const { data: tasksData, isLoading, error, refetch } = useQuery({
const {
data: tasksData,
isLoading,
error,
refetch,
} = useQuery({
queryKey: computed(() => [
'employee-timeline',
unref(employee)?.id,
unref(date) ? moment(unref(date)).format('YYYY-MM-DD') : null,
]),
queryFn: async () => {
const emp = unref(employee)
const d = unref(date)
const emp = unref(employee);
const d = unref(date);
if (!emp?.id || !d) {
return null
return null;
}
const dateStr = moment(d).format('YYYY-MM-DD')
const dateStr = moment(d).format('YYYY-MM-DD');
const queryParams = new URLSearchParams({
start: dateStr,
end: dateStr,
employee_id: emp.id.toString(),
}).toString()
}).toString();
const response = await axios.get_auth(`v1/subtasks?${queryParams}`)
return response
const response = await axios.get_auth(`v1/subtasks?${queryParams}`);
return response;
},
enabled: computed(() => {
const emp = unref(employee)
const d = unref(date)
return !!(emp?.id && d)
const emp = unref(employee);
const d = unref(date);
return !!(emp?.id && d);
}),
})
});
// Generate timeline tasks from fetched data
const timelineTasks = computed(() => {
const data = unref(tasksData)
const emp = unref(employee)
const d = unref(date)
const data = unref(tasksData);
const emp = unref(employee);
const d = unref(date);
if (!data || !emp || !d) {
return []
return [];
}
const subtasks = [...(data.subtasks || []), ...(data.blockers || [])]
const dateObj = new Date(d)
const selectedDateStr = moment(d).format('YYYY-MM-DD')
const subtasks = [...(data.subtasks || []), ...(data.blockers || [])];
const selectedDateStr = moment(d).format('YYYY-MM-DD');
try {
// Process timeblocks directly instead of using timeline utils
// which is designed for multi-day views
const tasks: any[] = []
const tasks: any[] = [];
subtasks.forEach((subtask) => {
if (!['blocker', 'adhoc', 'home_office', 'vacation', 'free_day', 'office365_blocker'].includes(subtask.type)) {
if (
![
'blocker',
'adhoc',
'home_office',
'vacation',
'free_day',
'office365_blocker',
].includes(subtask.type)
) {
subtask.timeblocks?.forEach((timeblock: any) => {
const timeblockDateStr = moment(timeblock.start).format('YYYY-MM-DD')
const timeblockDateStr = moment(timeblock.start).format(
'YYYY-MM-DD',
);
// Only include timeblocks for the selected date
if (timeblockDateStr === selectedDateStr) {
let type = 'subtask'
if (subtask.type === 'office365') type = 'office365'
let type = 'subtask';
if (subtask.type === 'office365') type = 'office365';
tasks.push({
id: subtask.id,
@@ -98,28 +113,28 @@ export function useEmployeeTimeline(
_timeblock_is_finished: timeblock.is_finished,
_real_start: new Date(timeblock.start),
_real_end: new Date(timeblock.end),
})
});
}
})
});
}
})
});
return tasks
return tasks;
} catch (error) {
console.error('Error generating timeline tasks:', error)
return []
console.error('Error generating timeline tasks:', error);
return [];
}
})
});
// Calculate scheduled time for the day (in hours)
const scheduledTime = computed(() => {
const tasks = unref(timelineTasks)
const d = unref(date)
const now = moment()
const tasks = unref(timelineTasks);
const d = unref(date);
const now = moment();
if (!tasks || !d) return 0
if (!tasks || !d) return 0;
let totalMinutes = 0
let totalMinutes = 0;
tasks.forEach((task) => {
if (
@@ -127,64 +142,69 @@ export function useEmployeeTimeline(
task.subtask?.is_finished ||
task.blocker?.is_finished
) {
return
return;
}
if (now.isAfter(moment(task._real_end))) {
// Ignore elapsed tasks
return
return;
} else if (
now.isAfter(moment(task._real_start)) &&
now.isBefore(moment(task._real_end))
) {
// Task in progress
totalMinutes += timelineUtils._getDifferenceInMinutes(task._real_end, new Date())
totalMinutes += timelineUtils._getDifferenceInMinutes(
task._real_end,
new Date(),
);
} else {
// Future task
totalMinutes += timelineUtils._getDifferenceInMinutes(
task._real_end,
task._real_start
)
task._real_start,
);
}
})
});
return (totalMinutes / 60).toFixed(1)
})
return (totalMinutes / 60).toFixed(1);
});
// Calculate free/unscheduled time for the day (in hours)
const freeTime = computed(() => {
const emp = unref(employee)
const d = unref(date)
const scheduled = parseFloat(String(unref(scheduledTime)))
const emp = unref(employee);
const d = unref(date);
const scheduled = parseFloat(String(unref(scheduledTime)));
if (!emp || !d) return '0'
if (!emp || !d) return '0';
const now = moment()
const dateObj = moment(d)
const dailyCapacityHours = emp.work_end - emp.work_start
const now = moment();
const dateObj = moment(d);
const dailyCapacityHours = emp.work_end - emp.work_start;
if (dateObj.isBefore(now, 'days')) {
// Past date
return '0'
return '0';
} else if (dateObj.isSame(now, 'days')) {
// Today - calculate remaining capacity
const elapsedMinutes = now.diff(now.clone().startOf('day'), 'minutes')
const elapsedMinutes = now.diff(now.clone().startOf('day'), 'minutes');
const workStart =
emp.work_start * 60 > elapsedMinutes ? emp.work_start : elapsedMinutes / 60
const todayCapacityHours = emp.work_end - workStart
const free = todayCapacityHours - scheduled
return free < 0 ? '0' : free.toFixed(1)
emp.work_start * 60 > elapsedMinutes
? emp.work_start
: elapsedMinutes / 60;
const todayCapacityHours = emp.work_end - workStart;
const free = todayCapacityHours - scheduled;
return free < 0 ? '0' : free.toFixed(1);
} else {
// Future date
const free = dailyCapacityHours - scheduled
return free < 0 ? '0' : free.toFixed(1)
const free = dailyCapacityHours - scheduled;
return free < 0 ? '0' : free.toFixed(1);
}
})
});
// Detect collisions with current timeblock
const collision = computed<CollisionInfo>(() => {
const timeblock = unref(currentTimeblock)
const tasks = unref(timelineTasks)
const timeblock = unref(currentTimeblock);
const tasks = unref(timelineTasks);
if (!timeblock || !tasks) {
return {
@@ -192,30 +212,30 @@ export function useEmployeeTimeline(
startCollides: false,
endCollides: false,
collidingTasks: [],
}
};
}
return detectCollision(timeblock, tasks)
})
return detectCollision(timeblock, tasks);
});
// Get collision segments for timeline visualization
const collisionSegments = computed(() => {
const timeblock = unref(currentTimeblock)
const tasks = unref(timelineTasks)
const timeblock = unref(currentTimeblock);
const tasks = unref(timelineTasks);
if (!timeblock || !tasks) {
return []
return [];
}
return getCollisionSegments(timeblock, tasks)
})
return getCollisionSegments(timeblock, tasks);
});
// Calculate current timeblock duration
const timeblockDuration = computed(() => {
const timeblock = unref(currentTimeblock)
if (!timeblock) return 0
return calculateTimeblockDuration(timeblock)
})
const timeblock = unref(currentTimeblock);
if (!timeblock) return 0;
return calculateTimeblockDuration(timeblock);
});
return {
// Query states
@@ -232,6 +252,5 @@ export function useEmployeeTimeline(
collision,
collisionSegments,
timeblockDuration,
};
}
}
@@ -3,8 +3,8 @@
<div v-if="isLoading" class="timeline-loading">
<span>Načítavam...</span>
</div>
<div v-else class="timeline__content" ref="timelineContainer">
<div class="timeline-scroll" ref="timelineScroll">
<div v-else ref="timelineContainer" class="timeline__content">
<div ref="timelineScroll" class="timeline-scroll">
<a-employee-timeblock-timeline-header
:start-hour="startHour"
:end-hour="endHour"
@@ -29,14 +29,7 @@
</template>
<script setup lang="ts">
import {
computed,
ref,
watch,
nextTick,
onMounted,
getCurrentInstance,
} from 'vue';
import { computed, ref, watch, nextTick, onMounted } from 'vue';
import { useStore } from 'vuex';
import AEmployeeSelect from './a-employee-select.vue';
import AEmployeeTimeblock from './a-employee-timeblock.vue';