mirror of
https://bitbucket.org/studiotem/jandl-taskmanager-app.git
synced 2026-10-21 14:29:34 +00:00
wip(task-timeline): stage changes
This commit is contained in:
1 parent
4768986a25
commit
b1771ead05
11 files changed
+2220
-682
No files matched your search
@@ -0,0 +1,237 @@
|
||||
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'
|
||||
|
||||
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 dateObj = new Date(d)
|
||||
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)
|
||||
const now = moment()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if (now.isAfter(moment(task._real_end))) {
|
||||
// Ignore elapsed tasks
|
||||
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())
|
||||
} else {
|
||||
// Future task
|
||||
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 now = moment()
|
||||
const dateObj = moment(d)
|
||||
const dailyCapacityHours = emp.work_end - emp.work_start
|
||||
|
||||
if (dateObj.isBefore(now, 'days')) {
|
||||
// Past date
|
||||
return '0'
|
||||
} else if (dateObj.isSame(now, 'days')) {
|
||||
// Today - calculate remaining capacity
|
||||
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)
|
||||
} else {
|
||||
// Future date
|
||||
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)
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
return {
|
||||
// Query states
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
|
||||
// Timeline data
|
||||
timelineTasks,
|
||||
scheduledTime,
|
||||
freeTime,
|
||||
|
||||
// Collision detection
|
||||
collision,
|
||||
collisionSegments,
|
||||
timeblockDuration,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user