mirror of
https://bitbucket.org/studiotem/jandl-taskmanager-app.git
synced 2026-10-21 14:29:34 +00:00
238 lines
5.8 KiB
TypeScript
238 lines
5.8 KiB
TypeScript
import { useQuery } from '@tanstack/vue-query';
|
|
import { computed, Ref, unref, watch } 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';
|
|
|
|
interface Employee {
|
|
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>,
|
|
) {
|
|
// Fetch employee's tasks for the selected date
|
|
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);
|
|
|
|
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(),
|
|
}).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 emp = unref(employee);
|
|
const d = unref(date);
|
|
|
|
if (!data || !emp || !d) {
|
|
return [];
|
|
}
|
|
|
|
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[] = [];
|
|
|
|
subtasks.forEach((subtask) => {
|
|
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',
|
|
);
|
|
|
|
// Only include timeblocks for the selected date
|
|
if (timeblockDateStr === selectedDateStr) {
|
|
let type = 'subtask';
|
|
if (subtask.type === 'office365') type = 'office365';
|
|
|
|
tasks.push({
|
|
id: subtask.id,
|
|
start: new Date(timeblock.start),
|
|
end: new Date(timeblock.end),
|
|
master_task: subtask.master_task,
|
|
date: new Date(timeblock.start),
|
|
project_code: subtask.project_code,
|
|
subtask: subtask,
|
|
type,
|
|
_timeblock_id: timeblock.id,
|
|
_timeblock_is_finished: timeblock.is_finished,
|
|
_real_start: new Date(timeblock.start),
|
|
_real_end: new Date(timeblock.end),
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
return tasks;
|
|
} catch (error) {
|
|
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);
|
|
|
|
if (!tasks || !d) return 0;
|
|
|
|
let totalMinutes = 0;
|
|
|
|
tasks.forEach((task) => {
|
|
if (
|
|
task.type === 'non-working-hours' ||
|
|
task.subtask?.is_finished ||
|
|
task.blocker?.is_finished
|
|
) {
|
|
return;
|
|
}
|
|
|
|
totalMinutes += timelineUtils._getDifferenceInMinutes(
|
|
task._real_end,
|
|
task._real_start,
|
|
);
|
|
});
|
|
|
|
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)));
|
|
|
|
if (!emp || !d) return '0';
|
|
|
|
const dailyCapacityHours = emp.work_end - emp.work_start;
|
|
|
|
return (dailyCapacityHours - scheduled).toFixed(1);
|
|
});
|
|
|
|
// Detect collisions with current timeblock
|
|
const collision = computed<CollisionInfo>(() => {
|
|
const timeblock = unref(currentTimeblock);
|
|
const tasks = unref(timelineTasks);
|
|
|
|
if (!timeblock || !tasks) {
|
|
return {
|
|
hasCollision: false,
|
|
startCollides: false,
|
|
endCollides: false,
|
|
collidingTasks: [],
|
|
};
|
|
}
|
|
|
|
return detectCollision(timeblock, tasks);
|
|
});
|
|
|
|
// Get collision segments for timeline visualization
|
|
const collisionSegments = computed(() => {
|
|
const timeblock = unref(currentTimeblock);
|
|
const tasks = unref(timelineTasks);
|
|
|
|
if (!timeblock || !tasks) {
|
|
return [];
|
|
}
|
|
|
|
return getCollisionSegments(timeblock, tasks);
|
|
});
|
|
|
|
// Calculate current timeblock duration
|
|
const timeblockDuration = computed(() => {
|
|
const timeblock = unref(currentTimeblock);
|
|
if (!timeblock) return 0;
|
|
return calculateTimeblockDuration(timeblock);
|
|
});
|
|
|
|
watch(
|
|
scheduledTime,
|
|
(newVal) => {
|
|
console.log('scheduledTime', newVal);
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
|
|
watch(
|
|
freeTime,
|
|
(newVal) => {
|
|
console.log('freeTime', newVal);
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
|
|
return {
|
|
// Query states
|
|
isLoading,
|
|
error,
|
|
refetch,
|
|
|
|
// Timeline data
|
|
timelineTasks,
|
|
scheduledTime,
|
|
freeTime,
|
|
|
|
// Collision detection
|
|
collision,
|
|
collisionSegments,
|
|
timeblockDuration,
|
|
};
|
|
}
|