feat(timeline): enhance employee timeline with concept tasks integration and visual differentiation for draft tasks

This commit is contained in:
Matej Glasnák committed 2026-01-15 02:30:38 +01:00
1 parent 78d4d3c970
commit 0d49225217
3 files changed
+63 -2

No files matched your search

@@ -62,17 +62,60 @@ export function useEmployeeTimeline(
}),
});
const {
data: conceptTasksData,
isLoading: conceptTasksLoading,
error: conceptTasksError,
refetch: conceptTasksRefetch,
} = useQuery({
queryKey: computed(() => [
'concept-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);
if (!emp?.id || !d) {
return null;
}
const dateStr = moment(d).format('YYYY-MM-DD');
const queryParams = new URLSearchParams({
start: dateStr,
end: dateStr,
employee_id: emp.id.toString(),
type: 'draft',
}).toString();
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);
}),
});
// Generate timeline tasks from fetched data
const timelineTasks = computed(() => {
const data = unref(tasksData);
const conceptData = unref(conceptTasksData);
const emp = unref(employee);
const d = unref(date);
if (!data || !emp || !d) {
if (!data || !conceptData || !emp || !d) {
return [];
}
const subtasks = [...(data.subtasks || []), ...(data.blockers || [])];
const subtasks = [
...(data.subtasks || []),
...(data.blockers || []),
...(conceptData.subtasks || []),
];
const selectedDateStr = moment(d).format('YYYY-MM-DD');
try {
@@ -108,6 +151,7 @@ export function useEmployeeTimeline(
date: new Date(subtask.start),
project_code: null,
subtask: subtask,
is_draft: !!subtask.is_draft,
blocker: subtask,
type,
_timeblock_id: null,
@@ -138,6 +182,7 @@ export function useEmployeeTimeline(
date: new Date(timeblock.start),
project_code: subtask.project_code,
subtask: subtask,
is_draft: !!subtask.is_draft,
blocker: undefined,
type,
_timeblock_id: timeblock.id,
@@ -259,6 +304,10 @@ export function useEmployeeTimeline(
error,
refetch,
conceptTasksLoading,
conceptTasksError,
conceptTasksRefetch,
// Timeline data
timelineTasks,
scheduledTime,
@@ -64,6 +64,7 @@
'free_day',
'office365_blocker',
].includes(task.type),
'scheduled-block--concept': task.is_draft === true,
}"
:style="{
left: `${getTimePosition(task._real_start)}px`,
@@ -213,6 +214,7 @@ interface TimelineTask {
subtask?: { title: string };
blocker?: { type: string };
type?: string;
is_draft?: boolean;
}
interface CollisionSegment {
@@ -883,6 +885,9 @@ defineExpose({
border: 1px solid rgb(156, 0, 28)
opacity: 0.7
&--concept
background: repeating-linear-gradient(135deg, transparent, transparent 10px, rgba(127, 57, 251, 0.3) 10px, rgba(127, 57, 251, 0.3) 20px)
.other-timeblock
position: absolute
top: 5px
@@ -242,6 +242,7 @@ const {
freeTime: _freeTime,
timeblockDuration,
refetch,
conceptTasksRefetch,
} = useEmployeeTimeline(
employeeRef,
dateRef,
@@ -255,10 +256,16 @@ watch(
() => {
if (props.modelValue.date && props.employee?.id) {
refetch();
conceptTasksRefetch();
}
},
);
onMounted(() => {
refetch();
conceptTasksRefetch();
});
// All tasks from timelineTasks are already filtered for the selected date
// Filter out timeblocks that are being edited or deleted locally
const scheduledBlocks = computed(() => {