add assignment completion ui

This commit is contained in:
Yisroel Baum 2026-08-15 22:52:13 +03:00
parent 0d9e2fa71b
commit 9f53245987
Signed by: yisroelbaum
GPG key ID: 0FA60884F75520A9
3 changed files with 335 additions and 22 deletions

View file

@ -17,7 +17,7 @@ export const scheduleSummarySchema = z.object({
assignmentCount: z.number().int().nonnegative(),
})
const scheduleAssignmentSchema = z.object({
const assignmentIdentitySchema = z.object({
id: z.number().int().positive(),
element: z.object({
name: z.string().min(1),
@ -26,7 +26,11 @@ const scheduleAssignmentSchema = z.object({
}),
})
export const assignmentForDateSchema = scheduleAssignmentSchema.extend({
const scheduleAssignmentSchema = assignmentIdentitySchema.extend({
completedAt: z.string().datetime({ offset: true }).nullable(),
})
export const assignmentForDateSchema = assignmentIdentitySchema.extend({
schedule: z.object({
id: z.number().int().positive(),
set: z.object({
@ -57,6 +61,13 @@ const assignmentsForDateResponseSchema = z.object({
assignments: z.array(assignmentForDateSchema),
})
const assignmentCompletionResponseSchema = z.object({
assignment: z.object({
id: z.number().int().positive(),
completedAt: z.string().datetime({ offset: true }).nullable(),
}),
})
const errorResponseSchema = z.object({
error: z.string().min(1),
})
@ -75,6 +86,7 @@ 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."
const ASSIGNMENTS_ERROR = "We couldn't load today's assignments."
const COMPLETION_ERROR = "We couldn't update this assignment."
export const useSchedulesStore = defineStore('schedules', () => {
const schedules = ref<ScheduleSummary[]>([])
@ -89,6 +101,8 @@ export const useSchedulesStore = defineStore('schedules', () => {
const assignmentsForDate = ref<AssignmentForDate[]>([])
const assignmentsLoading = ref(false)
const assignmentsError = ref<string | null>(null)
const assignmentCompletionPendingIds = ref<number[]>([])
const assignmentCompletionErrors = ref<Record<number, string>>({})
let activeDetailRequestId = 0
let assignmentsRequestId = 0
@ -274,6 +288,84 @@ export const useSchedulesStore = defineStore('schedules', () => {
}
}
async function setAssignmentCompleted(
assignmentId: number,
completed: boolean,
): Promise<boolean> {
if (assignmentCompletionPendingIds.value.includes(assignmentId)) {
return false
}
assignmentCompletionPendingIds.value = [...assignmentCompletionPendingIds.value, assignmentId]
const remainingErrors = { ...assignmentCompletionErrors.value }
delete remainingErrors[assignmentId]
assignmentCompletionErrors.value = remainingErrors
try {
const response = await fetch(`${API_BASE}/api/assignments/${assignmentId}`, {
method: 'PATCH',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ completed }),
})
if (response.status !== 200) {
throw new Error('assignment completion request failed')
}
const responseBody: unknown = await response.json()
const assignment = assignmentCompletionResponseSchema.parse(responseBody).assignment
const completionDoesNotMatchRequest = completed
? assignment.completedAt === null
: assignment.completedAt !== null
if (assignment.id !== assignmentId || completionDoesNotMatchRequest) {
throw new Error('assignment completion response did not match request')
}
if (activeSchedule.value !== null) {
for (const day of activeSchedule.value.days) {
const activeAssignment = day.assignments.find(
(candidate) => candidate.id === assignmentId,
)
if (activeAssignment !== undefined) {
activeAssignment.completedAt = assignment.completedAt
break
}
}
}
if (assignment.completedAt !== null) {
assignmentsForDate.value = assignmentsForDate.value.filter(
(candidate) => candidate.id !== assignmentId,
)
}
return true
} catch {
assignmentCompletionErrors.value = {
...assignmentCompletionErrors.value,
[assignmentId]: COMPLETION_ERROR,
}
return false
} finally {
assignmentCompletionPendingIds.value = assignmentCompletionPendingIds.value.filter(
(pendingId) => pendingId !== assignmentId,
)
}
}
function isAssignmentCompletionPending(assignmentId: number): boolean {
return assignmentCompletionPendingIds.value.includes(assignmentId)
}
function assignmentCompletionError(assignmentId: number): string | null {
return assignmentCompletionErrors.value[assignmentId] ?? null
}
return {
schedules,
listLoading,
@ -287,9 +379,14 @@ export const useSchedulesStore = defineStore('schedules', () => {
assignmentsForDate,
assignmentsLoading,
assignmentsError,
assignmentCompletionPendingIds,
assignmentCompletionErrors,
fetchSchedules,
fetchSchedule,
createSchedule,
fetchAssignmentsForDate,
setAssignmentCompleted,
isAssignmentCompletionPending,
assignmentCompletionError,
}
})