178 lines
5.7 KiB
TypeScript
178 lines
5.7 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
|
import { HttpStatus } from 'http-status-ts'
|
|
import { toValue, type MaybeRefOrGetter } from 'vue'
|
|
|
|
import type {
|
|
CourseTasks,
|
|
CreateTaskBody,
|
|
FocusStart,
|
|
FocusStop,
|
|
Task,
|
|
TaskBody,
|
|
TimetaggerRecord,
|
|
} from '@/utils/types'
|
|
import { getClient } from './client'
|
|
|
|
// Every mutation answers with all the tasks of the course (nested, sorted) and the
|
|
// deadlines, which replace the cache: no refetch needed.
|
|
|
|
function tasksKey(courseId: MaybeRefOrGetter<number>) {
|
|
return ['course', courseId, 'tasks']
|
|
}
|
|
|
|
const TODO_KEY = ['todo-tasks']
|
|
|
|
function unexpected(status: number): never {
|
|
throw new Error(`Unexpected status code received: ${status}`)
|
|
}
|
|
|
|
/** Body replacing `task` by itself, with `changes` applied */
|
|
export function toTaskBody(task: Task, changes: Partial<TaskBody> = {}): TaskBody {
|
|
return {
|
|
name: task.name,
|
|
description: task.description,
|
|
links: task.links,
|
|
deadline: task.deadline,
|
|
done: task.done,
|
|
tag_ids: task.tag_ids,
|
|
...changes,
|
|
}
|
|
}
|
|
|
|
export function useCourseTasks(courseId: MaybeRefOrGetter<number>) {
|
|
return useQuery({
|
|
queryKey: tasksKey(courseId),
|
|
queryFn: async () => {
|
|
const { data, response } = await getClient().GET('/api/courses/{id}/tasks', {
|
|
params: { path: { id: toValue(courseId) } },
|
|
})
|
|
return response.status === HttpStatus.OK && data ? data : unexpected(response.status)
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Tasks and subtasks of every course with a deadline in `[from, to[` (ISO dates), for
|
|
* the calendar
|
|
*/
|
|
export function useCalendarTasks(period: MaybeRefOrGetter<{ from: string; to: string }>) {
|
|
return useQuery({
|
|
queryKey: ['calendar-tasks', period],
|
|
queryFn: async () => {
|
|
const { data, response } = await getClient().GET('/api/tasks', {
|
|
params: { query: toValue(period) },
|
|
})
|
|
return response.status === HttpStatus.OK && data ? data : unexpected(response.status)
|
|
},
|
|
})
|
|
}
|
|
|
|
/** Tasks not done of every course and association, the closest deadline first */
|
|
export function useTodoTasks() {
|
|
return useQuery({
|
|
queryKey: TODO_KEY,
|
|
queryFn: async () => {
|
|
const { data, response } = await getClient().GET('/api/tasks/todo')
|
|
return response.status === HttpStatus.OK && data ? data : unexpected(response.status)
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Mutations of the tasks of any course: the course is given with each call. See
|
|
* `useTaskMutations` for the tasks of a single course.
|
|
*/
|
|
export function useAnyTaskMutations() {
|
|
const queryClient = useQueryClient()
|
|
const onSuccess = (tasks: CourseTasks, { courseId }: { courseId: number }) => {
|
|
queryClient.setQueryData(tasksKey(courseId), tasks)
|
|
// Refetched when they are shown again
|
|
queryClient.invalidateQueries({ queryKey: ['calendar-tasks'] })
|
|
queryClient.invalidateQueries({ queryKey: TODO_KEY })
|
|
}
|
|
|
|
const create = useMutation({
|
|
mutationFn: async ({ courseId, body }: { courseId: number; body: CreateTaskBody }) => {
|
|
const { data, response } = await getClient().POST('/api/courses/{id}/tasks', {
|
|
params: { path: { id: courseId } },
|
|
body,
|
|
})
|
|
return response.status === HttpStatus.OK && data ? data : unexpected(response.status)
|
|
},
|
|
onSuccess,
|
|
})
|
|
|
|
const update = useMutation({
|
|
mutationFn: async ({ id, body }: { courseId: number; id: number; body: TaskBody }) => {
|
|
const { data, response } = await getClient().PUT('/api/tasks/{id}', {
|
|
params: { path: { id } },
|
|
body,
|
|
})
|
|
return response.status === HttpStatus.OK && data ? data : unexpected(response.status)
|
|
},
|
|
onSuccess,
|
|
})
|
|
|
|
const remove = useMutation({
|
|
mutationFn: async ({ id }: { courseId: number; id: number }) => {
|
|
const { data, response } = await getClient().DELETE('/api/tasks/{id}', {
|
|
params: { path: { id } },
|
|
})
|
|
return response.status === HttpStatus.OK && data ? data : unexpected(response.status)
|
|
},
|
|
onSuccess,
|
|
})
|
|
|
|
return { create, update, remove }
|
|
}
|
|
|
|
/** Mutations of the tasks of a single course */
|
|
export function useTaskMutations(courseId: MaybeRefOrGetter<number>) {
|
|
const { create, update, remove } = useAnyTaskMutations()
|
|
return {
|
|
create: {
|
|
...create,
|
|
mutateAsync: (body: CreateTaskBody) =>
|
|
create.mutateAsync({ courseId: toValue(courseId), body }),
|
|
},
|
|
update: {
|
|
...update,
|
|
mutateAsync: ({ id, body }: { id: number; body: TaskBody }) =>
|
|
update.mutateAsync({ courseId: toValue(courseId), id, body }),
|
|
},
|
|
remove: {
|
|
...remove,
|
|
mutateAsync: (id: number) => remove.mutateAsync({ courseId: toValue(courseId), id }),
|
|
},
|
|
}
|
|
}
|
|
|
|
/** Starts a focus session on a task, and its TimeTagger record */
|
|
export async function startFocus(taskId: number): Promise<FocusStart> {
|
|
const { data, response } = await getClient().POST('/api/tasks/{id}/focus', {
|
|
params: { path: { id: taskId } },
|
|
})
|
|
return response.status === HttpStatus.OK && data ? data : unexpected(response.status)
|
|
}
|
|
|
|
/** Ends (or pauses) a focus session: its time is added to the task */
|
|
export async function stopFocus(
|
|
taskId: number,
|
|
seconds: number,
|
|
timetagger: TimetaggerRecord | null,
|
|
): Promise<FocusStop> {
|
|
const { data, response } = await getClient().POST('/api/tasks/{id}/focus/stop', {
|
|
params: { path: { id: taskId } },
|
|
body: { seconds, timetagger },
|
|
})
|
|
return response.status === HttpStatus.OK && data ? data : unexpected(response.status)
|
|
}
|
|
|
|
/** The time spent on the tasks changed: shown again from the server */
|
|
export function useRefreshTasks() {
|
|
const queryClient = useQueryClient()
|
|
return (courseId: number) => {
|
|
queryClient.invalidateQueries({ queryKey: tasksKey(courseId) })
|
|
queryClient.invalidateQueries({ queryKey: TODO_KEY })
|
|
}
|
|
}
|