Merge branch 'dev' into feat/assignment-page
This commit is contained in:
		
						commit
						c29b4f8c29
					
				
					 21 changed files with 1004 additions and 490 deletions
				
			
		|  | @ -1,16 +1,37 @@ | |||
| import type { QuestionId } from "@dwengo-1/common/dist/interfaces/question.ts"; | ||||
| import { type MaybeRefOrGetter, toValue } from "vue"; | ||||
| import { useMutation, type UseMutationReturnType, useQuery, type UseQueryReturnType } from "@tanstack/vue-query"; | ||||
| import { computed, type MaybeRefOrGetter, toValue } from "vue"; | ||||
| import { | ||||
|     useMutation, | ||||
|     type UseMutationReturnType, | ||||
|     useQuery, | ||||
|     type UseQueryReturnType, | ||||
|     useQueryClient, | ||||
| } from "@tanstack/vue-query"; | ||||
| import { AnswerController, type AnswerResponse, type AnswersResponse } from "@/controllers/answers.ts"; | ||||
| import type { AnswerData } from "@dwengo-1/common/dist/interfaces/answer.ts"; | ||||
| import type { AnswerData } from "@dwengo-1/common/interfaces/answer"; | ||||
| import type { QuestionId } from "@dwengo-1/common/interfaces/question"; | ||||
| 
 | ||||
| // TODO caching
 | ||||
| /** 🔑 Query keys */ | ||||
| export function answersQueryKey( | ||||
|     questionId: QuestionId, | ||||
|     full: boolean, | ||||
| ): [string, string, number, string, number, boolean] { | ||||
|     const loId = questionId.learningObjectIdentifier; | ||||
|     return ["answers", loId.hruid, loId.version!, loId.language, questionId.sequenceNumber, full]; | ||||
| } | ||||
| export function answerQueryKey( | ||||
|     questionId: QuestionId, | ||||
|     sequenceNumber: number, | ||||
| ): [string, string, number, string, number, number] { | ||||
|     const loId = questionId.learningObjectIdentifier; | ||||
|     return ["answer", loId.hruid, loId.version!, loId.language, questionId.sequenceNumber, sequenceNumber]; | ||||
| } | ||||
| 
 | ||||
| export function useAnswersQuery( | ||||
|     questionId: MaybeRefOrGetter<QuestionId>, | ||||
|     full: MaybeRefOrGetter<boolean> = true, | ||||
| ): UseQueryReturnType<AnswersResponse, Error> { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => answersQueryKey(toValue(questionId), toValue(full))), | ||||
|         queryFn: async () => new AnswerController(toValue(questionId)).getAll(toValue(full)), | ||||
|         enabled: () => Boolean(toValue(questionId)), | ||||
|     }); | ||||
|  | @ -21,31 +42,68 @@ export function useAnswerQuery( | |||
|     sequenceNumber: MaybeRefOrGetter<number>, | ||||
| ): UseQueryReturnType<AnswerResponse, Error> { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => answerQueryKey(toValue(questionId), toValue(sequenceNumber))), | ||||
|         queryFn: async () => new AnswerController(toValue(questionId)).getBy(toValue(sequenceNumber)), | ||||
|         enabled: () => Boolean(toValue(questionId)), | ||||
|         enabled: () => Boolean(toValue(questionId)) && Boolean(toValue(sequenceNumber)), | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useCreateAnswerMutation( | ||||
|     questionId: MaybeRefOrGetter<QuestionId>, | ||||
| ): UseMutationReturnType<AnswerResponse, Error, AnswerData, unknown> { | ||||
|     const queryClient = useQueryClient(); | ||||
| 
 | ||||
|     return useMutation({ | ||||
|         mutationFn: async (data) => new AnswerController(toValue(questionId)).create(data), | ||||
|         onSuccess: async () => { | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: answersQueryKey(toValue(questionId), true), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: answersQueryKey(toValue(questionId), false), | ||||
|             }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useDeleteAnswerMutation( | ||||
|     questionId: MaybeRefOrGetter<QuestionId>, | ||||
| ): UseMutationReturnType<AnswerResponse, Error, number, unknown> { | ||||
|     const queryClient = useQueryClient(); | ||||
| 
 | ||||
|     return useMutation({ | ||||
|         mutationFn: async (seq) => new AnswerController(toValue(questionId)).remove(seq), | ||||
|         onSuccess: async () => { | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: answersQueryKey(toValue(questionId), true), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: answersQueryKey(toValue(questionId), false), | ||||
|             }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useUpdateAnswerMutation( | ||||
|     questionId: MaybeRefOrGetter<QuestionId>, | ||||
| ): UseMutationReturnType<AnswerResponse, Error, { answerData: AnswerData; seq: number }, unknown> { | ||||
|     const queryClient = useQueryClient(); | ||||
| 
 | ||||
|     return useMutation({ | ||||
|         mutationFn: async (data, seq) => new AnswerController(toValue(questionId)).update(seq, data), | ||||
|         mutationFn: async ({ answerData, seq }) => new AnswerController(toValue(questionId)).update(seq, answerData), | ||||
|         onSuccess: async (_, { seq }) => { | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: answerQueryKey(toValue(questionId), seq), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: answersQueryKey(toValue(questionId), true), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: answersQueryKey(toValue(questionId), true), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: answersQueryKey(toValue(questionId), false), | ||||
|             }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
|  |  | |||
|  | @ -13,6 +13,8 @@ import { computed, toValue, type MaybeRefOrGetter } from "vue"; | |||
| import { invalidateAllAssignmentKeys } from "./assignments"; | ||||
| import { invalidateAllGroupKeys } from "./groups"; | ||||
| import { invalidateAllSubmissionKeys } from "./submissions"; | ||||
| import type { TeachersResponse } from "@/controllers/teachers"; | ||||
| import type { TeacherInvitationsResponse } from "@/controllers/teacher-invitations"; | ||||
| 
 | ||||
| const classController = new ClassController(); | ||||
| 
 | ||||
|  | @ -176,7 +178,7 @@ export function useClassDeleteStudentMutation(): UseMutationReturnType< | |||
| export function useClassTeachersQuery( | ||||
|     id: MaybeRefOrGetter<string | undefined>, | ||||
|     full: MaybeRefOrGetter<boolean> = true, | ||||
| ): UseQueryReturnType<StudentsResponse, Error> { | ||||
| ): UseQueryReturnType<TeachersResponse, Error> { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => classTeachersKey(toValue(id)!, toValue(full))), | ||||
|         queryFn: async () => classController.getTeachers(toValue(id)!, toValue(full)), | ||||
|  | @ -223,7 +225,7 @@ export function useClassDeleteTeacherMutation(): UseMutationReturnType< | |||
| export function useClassTeacherInvitationsQuery( | ||||
|     id: MaybeRefOrGetter<string | undefined>, | ||||
|     full: MaybeRefOrGetter<boolean> = true, | ||||
| ): UseQueryReturnType<StudentsResponse, Error> { | ||||
| ): UseQueryReturnType<TeacherInvitationsResponse, Error> { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => classTeacherInvitationsKey(toValue(id)!, toValue(full))), | ||||
|         queryFn: async () => classController.getTeacherInvitations(toValue(id)!, toValue(full)), | ||||
|  |  | |||
|  | @ -14,12 +14,12 @@ export function questionsQueryKey( | |||
|     loId: LearningObjectIdentifierDTO, | ||||
|     full: boolean, | ||||
| ): [string, string, number, string, boolean] { | ||||
|     return ["questions", loId.hruid, loId.version, loId.language, full]; | ||||
|     return ["questions", loId.hruid, loId.version!, loId.language, full]; | ||||
| } | ||||
| 
 | ||||
| export function questionQueryKey(questionId: QuestionId): [string, string, number, string, number] { | ||||
|     const loId = questionId.learningObjectIdentifier; | ||||
|     return ["question", loId.hruid, loId.version, loId.language, questionId.sequenceNumber]; | ||||
|     return ["question", loId.hruid, loId.version!, loId.language, questionId.sequenceNumber]; | ||||
| } | ||||
| 
 | ||||
| export function useQuestionsQuery( | ||||
|  | @ -39,7 +39,7 @@ export function useQuestionQuery( | |||
|     const loId = toValue(questionId).learningObjectIdentifier; | ||||
|     const sequenceNumber = toValue(questionId).sequenceNumber; | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => questionQueryKey(loId, sequenceNumber)), | ||||
|         queryKey: computed(() => questionQueryKey(toValue(questionId))), | ||||
|         queryFn: async () => new QuestionController(loId).getBy(sequenceNumber), | ||||
|         enabled: () => Boolean(toValue(questionId)), | ||||
|     }); | ||||
|  | @ -55,6 +55,7 @@ export function useCreateQuestionMutation( | |||
|         onSuccess: async () => { | ||||
|             await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), true) }); | ||||
|             await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), false) }); | ||||
|             await queryClient.invalidateQueries({ queryKey: ["answers"] }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
|  | @ -88,6 +89,8 @@ export function useDeleteQuestionMutation( | |||
|             await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), true) }); | ||||
|             await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), false) }); | ||||
|             await queryClient.invalidateQueries({ queryKey: questionQueryKey(toValue(questionId)) }); | ||||
|             await queryClient.invalidateQueries({ queryKey: ["answers"] }); | ||||
|             await queryClient.invalidateQueries({ queryKey: ["answer"] }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import type { GroupsResponse } from "@/controllers/groups.ts"; | |||
| import type { SubmissionsResponse } from "@/controllers/submissions.ts"; | ||||
| import type { QuestionsResponse } from "@/controllers/questions.ts"; | ||||
| import type { StudentDTO } from "@dwengo-1/common/interfaces/student"; | ||||
| import { teacherClassJoinRequests } from "@/queries/teachers.ts"; | ||||
| 
 | ||||
| const studentController = new StudentController(); | ||||
| 
 | ||||
|  | @ -189,13 +190,13 @@ export function useCreateJoinRequestMutation(): UseMutationReturnType< | |||
|     unknown | ||||
| > { | ||||
|     const queryClient = useQueryClient(); | ||||
| 
 | ||||
|     return useMutation({ | ||||
|         mutationFn: async ({ username, classId }) => studentController.createJoinRequest(username, classId), | ||||
|         onSuccess: async (newJoinRequest) => { | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: studentJoinRequestsQueryKey(newJoinRequest.request.requester.username), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ queryKey: teacherClassJoinRequests(newJoinRequest.request.class) }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
|  | @ -215,6 +216,7 @@ export function useDeleteJoinRequestMutation(): UseMutationReturnType< | |||
|             const classId = deletedJoinRequest.request.class; | ||||
|             await queryClient.invalidateQueries({ queryKey: studentJoinRequestsQueryKey(username) }); | ||||
|             await queryClient.invalidateQueries({ queryKey: studentJoinRequestQueryKey(username, classId) }); | ||||
|             await queryClient.invalidateQueries({ queryKey: teacherClassJoinRequests(classId) }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
|  |  | |||
|  | @ -1,36 +1,56 @@ | |||
| import { useMutation, useQuery, type UseMutationReturnType, type UseQueryReturnType } from "@tanstack/vue-query"; | ||||
| import { computed, toValue } from "vue"; | ||||
| import { | ||||
|     useMutation, | ||||
|     useQuery, | ||||
|     useQueryClient, | ||||
|     type UseMutationReturnType, | ||||
|     type UseQueryReturnType, | ||||
| } from "@tanstack/vue-query"; | ||||
| import { toValue } from "vue"; | ||||
| import type { MaybeRefOrGetter } from "vue"; | ||||
| import { | ||||
|     TeacherInvitationController, | ||||
|     type TeacherInvitationResponse, | ||||
|     type TeacherInvitationsResponse, | ||||
| } from "@/controllers/teacher-invitations.ts"; | ||||
| } from "@/controllers/teacher-invitations"; | ||||
| import type { TeacherInvitationData } from "@dwengo-1/common/interfaces/teacher-invitation"; | ||||
| import type { TeacherDTO } from "@dwengo-1/common/interfaces/teacher"; | ||||
| 
 | ||||
| const controller = new TeacherInvitationController(); | ||||
| 
 | ||||
| /** 🔑 Query keys */ | ||||
| export function teacherInvitationsSentQueryKey(username: string): [string, string, string] { | ||||
|     return ["teacher-invitations", "sent", username]; | ||||
| } | ||||
| 
 | ||||
| export function teacherInvitationsReceivedQueryKey(username: string): [string, string, string] { | ||||
|     return ["teacher-invitations", "received", username]; | ||||
| } | ||||
| 
 | ||||
| export function teacherInvitationQueryKey(data: TeacherInvitationData): [string, string, string, string] { | ||||
|     return ["teacher-invitation", data.sender, data.receiver, data.class]; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|     All the invitations the teacher sent | ||||
| **/ | ||||
|  * All the invitations the teacher sent | ||||
|  */ | ||||
| export function useTeacherInvitationsSentQuery( | ||||
|     username: MaybeRefOrGetter<string | undefined>, | ||||
| ): UseQueryReturnType<TeacherInvitationsResponse, Error> { | ||||
|     return useQuery({ | ||||
|         queryFn: computed(async () => controller.getAll(toValue(username), true)), | ||||
|         queryKey: teacherInvitationsSentQueryKey(toValue(username)!), | ||||
|         queryFn: async () => controller.getAll(toValue(username)!, true), | ||||
|         enabled: () => Boolean(toValue(username)), | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|     All the pending invitations sent to this teacher | ||||
|  * All the pending invitations sent to this teacher | ||||
|  */ | ||||
| export function useTeacherInvitationsReceivedQuery( | ||||
|     username: MaybeRefOrGetter<string | undefined>, | ||||
| ): UseQueryReturnType<TeacherInvitationsResponse, Error> { | ||||
|     return useQuery({ | ||||
|         queryFn: computed(async () => controller.getAll(toValue(username), false)), | ||||
|         queryKey: teacherInvitationsReceivedQueryKey(toValue(username)!), | ||||
|         queryFn: async () => controller.getAll(toValue(username)!, false), | ||||
|         enabled: () => Boolean(toValue(username)), | ||||
|     }); | ||||
| } | ||||
|  | @ -39,7 +59,8 @@ export function useTeacherInvitationQuery( | |||
|     data: MaybeRefOrGetter<TeacherInvitationData | undefined>, | ||||
| ): UseQueryReturnType<TeacherInvitationResponse, Error> { | ||||
|     return useQuery({ | ||||
|         queryFn: computed(async () => controller.getBy(toValue(data))), | ||||
|         queryKey: teacherInvitationQueryKey(toValue(data)), | ||||
|         queryFn: async () => controller.getBy(toValue(data)), | ||||
|         enabled: () => Boolean(toValue(data)), | ||||
|     }); | ||||
| } | ||||
|  | @ -47,32 +68,68 @@ export function useTeacherInvitationQuery( | |||
| export function useCreateTeacherInvitationMutation(): UseMutationReturnType< | ||||
|     TeacherInvitationResponse, | ||||
|     Error, | ||||
|     TeacherDTO, | ||||
|     TeacherInvitationData, | ||||
|     unknown | ||||
| > { | ||||
|     const queryClient = useQueryClient(); | ||||
| 
 | ||||
|     return useMutation({ | ||||
|         mutationFn: async (data: TeacherInvitationData) => controller.create(data), | ||||
|         mutationFn: async (data) => controller.create(data), | ||||
|         onSuccess: async (_, data) => { | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: teacherInvitationsSentQueryKey(data.sender), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: teacherInvitationsReceivedQueryKey(data.receiver), | ||||
|             }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useRespondTeacherInvitationMutation(): UseMutationReturnType< | ||||
|     TeacherInvitationResponse, | ||||
|     Error, | ||||
|     TeacherDTO, | ||||
|     TeacherInvitationData, | ||||
|     unknown | ||||
| > { | ||||
|     const queryClient = useQueryClient(); | ||||
| 
 | ||||
|     return useMutation({ | ||||
|         mutationFn: async (data: TeacherInvitationData) => controller.respond(data), | ||||
|         mutationFn: async (data) => controller.respond(data), | ||||
|         onSuccess: async (_, data) => { | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: teacherInvitationsSentQueryKey(data.sender), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: teacherInvitationsReceivedQueryKey(data.receiver), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: teacherInvitationQueryKey(data), | ||||
|             }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useDeleteTeacherInvitationMutation(): UseMutationReturnType< | ||||
|     TeacherInvitationResponse, | ||||
|     Error, | ||||
|     TeacherDTO, | ||||
|     TeacherInvitationData, | ||||
|     unknown | ||||
| > { | ||||
|     const queryClient = useQueryClient(); | ||||
| 
 | ||||
|     return useMutation({ | ||||
|         mutationFn: async (data: TeacherInvitationData) => controller.remove(data), | ||||
|         mutationFn: async (data) => controller.remove(data), | ||||
|         onSuccess: async (_, data) => { | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: teacherInvitationsSentQueryKey(data.sender), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: teacherInvitationsReceivedQueryKey(data.receiver), | ||||
|             }); | ||||
|             await queryClient.invalidateQueries({ | ||||
|                 queryKey: teacherInvitationQueryKey(data), | ||||
|             }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
|  |  | |||
|  | @ -37,6 +37,10 @@ function teacherQuestionsQueryKey(username: string, full: boolean): [string, str | |||
|     return ["teacher-questions", username, full]; | ||||
| } | ||||
| 
 | ||||
| export function teacherClassJoinRequests(classId: string): [string, string] { | ||||
|     return ["teacher-class-join-requests", classId]; | ||||
| } | ||||
| 
 | ||||
| export function useTeachersQuery(full: MaybeRefOrGetter<boolean> = false): UseQueryReturnType<TeachersResponse, Error> { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => teachersQueryKey(toValue(full))), | ||||
|  | @ -92,7 +96,7 @@ export function useTeacherJoinRequestsQuery( | |||
|     classId: MaybeRefOrGetter<string | undefined>, | ||||
| ): UseQueryReturnType<JoinRequestsResponse, Error> { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => JOIN_REQUESTS_QUERY_KEY(toValue(username)!, toValue(classId)!)), | ||||
|         queryKey: computed(() => teacherClassJoinRequests(toValue(classId)!)), | ||||
|         queryFn: async () => teacherController.getStudentJoinRequests(toValue(username)!, toValue(classId)!), | ||||
|         enabled: () => Boolean(toValue(username)) && Boolean(toValue(classId)), | ||||
|     }); | ||||
|  | @ -133,10 +137,11 @@ export function useUpdateJoinRequestMutation(): UseMutationReturnType< | |||
|         mutationFn: async ({ teacherUsername, classId, studentUsername, accepted }) => | ||||
|             teacherController.updateStudentJoinRequest(teacherUsername, classId, studentUsername, accepted), | ||||
|         onSuccess: async (deletedJoinRequest) => { | ||||
|             const username = deletedJoinRequest.request.requester; | ||||
|             const username = deletedJoinRequest.request.requester.username; | ||||
|             const classId = deletedJoinRequest.request.class; | ||||
|             await queryClient.invalidateQueries({ queryKey: studentJoinRequestsQueryKey(username) }); | ||||
|             await queryClient.invalidateQueries({ queryKey: studentJoinRequestQueryKey(username, classId) }); | ||||
|             await queryClient.invalidateQueries({ queryKey: teacherClassJoinRequests(classId) }); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import { useQuery, type UseQueryReturnType } from "@tanstack/vue-query"; | ||||
| import { type MaybeRefOrGetter, toValue } from "vue"; | ||||
| import type { Theme } from "@dwengo-1/interfaces/theme"; | ||||
| import { getThemeController } from "@/controllers/controllers.ts"; | ||||
| import type { Theme } from "@dwengo-1/common/interfaces/theme"; | ||||
| 
 | ||||
| const themeController = getThemeController(); | ||||
| 
 | ||||
|  |  | |||
		Reference in a new issue
	
	 Laure Jablonski
						Laure Jablonski