add set scheduling ui

This commit is contained in:
Yisroel Baum 2026-08-10 20:16:44 +03:00
parent b7fb594c26
commit 7e9e93f8d0
Signed by: yisroelbaum
GPG key ID: 0FA60884F75520A9
8 changed files with 1182 additions and 8 deletions

View 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,
}
})