add set scheduling ui
This commit is contained in:
parent
b7fb594c26
commit
7e9e93f8d0
8 changed files with 1182 additions and 8 deletions
220
frontend/website/src/stores/schedules.ts
Normal file
220
frontend/website/src/stores/schedules.ts
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
import { ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { API_BASE } from '@/utils/apiBase'
|
||||
|
||||
const isoDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/)
|
||||
|
||||
export const scheduleSummarySchema = z.object({
|
||||
id: z.number().int().positive(),
|
||||
set: z.object({
|
||||
id: z.number().int().positive(),
|
||||
name: z.string().min(1),
|
||||
}),
|
||||
elementKind: z.string().min(1),
|
||||
startDate: isoDateSchema,
|
||||
targetDate: isoDateSchema,
|
||||
assignmentCount: z.number().int().nonnegative(),
|
||||
})
|
||||
|
||||
const scheduleAssignmentSchema = z.object({
|
||||
element: z.object({
|
||||
id: z.number().int().positive(),
|
||||
name: z.string().min(1),
|
||||
kind: z.string().min(1),
|
||||
path: z.array(z.string().min(1)).min(1),
|
||||
}),
|
||||
})
|
||||
|
||||
export const scheduleDetailSchema = scheduleSummarySchema.extend({
|
||||
days: z.array(
|
||||
z.object({
|
||||
date: isoDateSchema,
|
||||
assignments: z.array(scheduleAssignmentSchema),
|
||||
}),
|
||||
),
|
||||
})
|
||||
|
||||
const schedulesResponseSchema = z.object({
|
||||
schedules: z.array(scheduleSummarySchema),
|
||||
})
|
||||
|
||||
const scheduleResponseSchema = z.object({
|
||||
schedule: scheduleDetailSchema,
|
||||
})
|
||||
|
||||
const errorResponseSchema = z.object({
|
||||
error: z.string().min(1),
|
||||
})
|
||||
|
||||
export type ScheduleSummary = z.infer<typeof scheduleSummarySchema>
|
||||
export type ScheduleDetail = z.infer<typeof scheduleDetailSchema>
|
||||
export type CreateScheduleInput = {
|
||||
setId: number
|
||||
elementKind: string
|
||||
startDate: string
|
||||
targetDate: string
|
||||
}
|
||||
|
||||
const LIST_ERROR = "We couldn't load your schedules."
|
||||
const DETAIL_ERROR = "We couldn't load this schedule."
|
||||
const CREATE_ERROR = "We couldn't create this schedule."
|
||||
|
||||
export const useSchedulesStore = defineStore('schedules', () => {
|
||||
const schedules = ref<ScheduleSummary[]>([])
|
||||
const listLoading = ref(false)
|
||||
const listError = ref<string | null>(null)
|
||||
const activeSchedule = ref<ScheduleDetail | null>(null)
|
||||
const detailLoading = ref(false)
|
||||
const detailError = ref<string | null>(null)
|
||||
const detailNotFound = ref(false)
|
||||
const creating = ref(false)
|
||||
const createError = ref<string | null>(null)
|
||||
let activeDetailRequestId = 0
|
||||
|
||||
async function fetchSchedules(): Promise<boolean> {
|
||||
listLoading.value = true
|
||||
listError.value = null
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/schedules`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (response.status !== 200) {
|
||||
schedules.value = []
|
||||
listError.value = LIST_ERROR
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const responseBody: unknown = await response.json()
|
||||
schedules.value = schedulesResponseSchema.parse(responseBody).schedules
|
||||
|
||||
return true
|
||||
} catch {
|
||||
schedules.value = []
|
||||
listError.value = LIST_ERROR
|
||||
|
||||
return false
|
||||
} finally {
|
||||
listLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSchedule(scheduleId: number): Promise<boolean> {
|
||||
const requestId = ++activeDetailRequestId
|
||||
activeSchedule.value = null
|
||||
detailLoading.value = true
|
||||
detailError.value = null
|
||||
detailNotFound.value = false
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/schedules/${scheduleId}`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (requestId !== activeDetailRequestId) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (response.status === 404) {
|
||||
detailNotFound.value = true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
detailError.value = DETAIL_ERROR
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const responseBody: unknown = await response.json()
|
||||
if (requestId !== activeDetailRequestId) {
|
||||
return false
|
||||
}
|
||||
|
||||
const parsedSchedule = scheduleResponseSchema.parse(responseBody).schedule
|
||||
if (parsedSchedule.id !== scheduleId) {
|
||||
throw new Error('schedule response did not match requested schedule')
|
||||
}
|
||||
activeSchedule.value = parsedSchedule
|
||||
|
||||
return true
|
||||
} catch {
|
||||
if (requestId === activeDetailRequestId) {
|
||||
activeSchedule.value = null
|
||||
detailError.value = DETAIL_ERROR
|
||||
}
|
||||
|
||||
return false
|
||||
} finally {
|
||||
if (requestId === activeDetailRequestId) {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createSchedule(input: CreateScheduleInput): Promise<ScheduleDetail | null> {
|
||||
creating.value = true
|
||||
createError.value = null
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/schedules`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
})
|
||||
const responseBody: unknown = await response.json()
|
||||
|
||||
if (response.status !== 201) {
|
||||
const parsedError = errorResponseSchema.safeParse(responseBody)
|
||||
createError.value = parsedError.success ? parsedError.data.error : CREATE_ERROR
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const createdSchedule = scheduleResponseSchema.parse(responseBody).schedule
|
||||
activeSchedule.value = createdSchedule
|
||||
detailError.value = null
|
||||
detailNotFound.value = false
|
||||
|
||||
return createdSchedule
|
||||
} catch {
|
||||
createError.value = CREATE_ERROR
|
||||
|
||||
return null
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
schedules,
|
||||
listLoading,
|
||||
listError,
|
||||
activeSchedule,
|
||||
detailLoading,
|
||||
detailError,
|
||||
detailNotFound,
|
||||
creating,
|
||||
createError,
|
||||
fetchSchedules,
|
||||
fetchSchedule,
|
||||
createSchedule,
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue