Merge remote-tracking branch 'origin/feat/class-functionality' into feat/class-functionality-fix-bugs
This commit is contained in:
		
						commit
						6c408d516c
					
				
					 9 changed files with 293 additions and 213 deletions
				
			
		|  | @ -82,5 +82,7 @@ | ||||||
|     "reject": "zurückweisen", |     "reject": "zurückweisen", | ||||||
|     "areusure": "Sind Sie sicher?", |     "areusure": "Sind Sie sicher?", | ||||||
|     "yes": "ja", |     "yes": "ja", | ||||||
|     "teachers": "Lehrer" |     "teachers": "Lehrer", | ||||||
|  |     "rejected": "abgelehnt", | ||||||
|  |     "accepted": "akzeptiert" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -82,5 +82,7 @@ | ||||||
|     "reject": "reject", |     "reject": "reject", | ||||||
|     "areusure": "Are you sure?", |     "areusure": "Are you sure?", | ||||||
|     "yes": "yes", |     "yes": "yes", | ||||||
|     "teachers": "teachers" |     "teachers": "teachers", | ||||||
|  |     "accepted": "accepted", | ||||||
|  |     "rejected": "rejected" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -82,5 +82,7 @@ | ||||||
|     "reject": "rejeter", |     "reject": "rejeter", | ||||||
|     "areusure": "Êtes-vous sûr?", |     "areusure": "Êtes-vous sûr?", | ||||||
|     "yes": "oui", |     "yes": "oui", | ||||||
|     "teachers": "enseignants" |     "teachers": "enseignants", | ||||||
|  |     "accepted": "acceptée", | ||||||
|  |     "rejected": "rejetée" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -82,5 +82,7 @@ | ||||||
|     "reject": "weiger", |     "reject": "weiger", | ||||||
|     "areusure": "Bent u zeker?", |     "areusure": "Bent u zeker?", | ||||||
|     "yes": "ja", |     "yes": "ja", | ||||||
|     "teachers": "leerkrachten" |     "teachers": "leerkrachten", | ||||||
|  |     "accepted": "geaccepteerd", | ||||||
|  |     "rejected": "geweigerd" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ import SingleAssignment from "@/views/assignments/SingleAssignment.vue"; | ||||||
| import SingleClass from "@/views/classes/SingleClass.vue"; | import SingleClass from "@/views/classes/SingleClass.vue"; | ||||||
| import SingleDiscussion from "@/views/discussions/SingleDiscussion.vue"; | import SingleDiscussion from "@/views/discussions/SingleDiscussion.vue"; | ||||||
| import NotFound from "@/components/errors/NotFound.vue"; | import NotFound from "@/components/errors/NotFound.vue"; | ||||||
| import CreateClass from "@/views/classes/CreateClass.vue"; |  | ||||||
| import CreateAssignment from "@/views/assignments/CreateAssignment.vue"; | import CreateAssignment from "@/views/assignments/CreateAssignment.vue"; | ||||||
| import CreateDiscussion from "@/views/discussions/CreateDiscussion.vue"; | import CreateDiscussion from "@/views/discussions/CreateDiscussion.vue"; | ||||||
| import CallbackPage from "@/views/CallbackPage.vue"; | import CallbackPage from "@/views/CallbackPage.vue"; | ||||||
|  | @ -84,12 +83,6 @@ const router = createRouter({ | ||||||
|             component: SingleAssignment, |             component: SingleAssignment, | ||||||
|             meta: { requiresAuth: true }, |             meta: { requiresAuth: true }, | ||||||
|         }, |         }, | ||||||
|         { |  | ||||||
|             path: "/class/create", |  | ||||||
|             name: "CreateClass", |  | ||||||
|             component: CreateClass, |  | ||||||
|             meta: { requiresAuth: true }, |  | ||||||
|         }, |  | ||||||
|         { |         { | ||||||
|             path: "/class/:id", |             path: "/class/:id", | ||||||
|             name: "SingleClass", |             name: "SingleClass", | ||||||
|  |  | ||||||
|  | @ -1,7 +0,0 @@ | ||||||
| <script setup lang="ts"></script> |  | ||||||
| 
 |  | ||||||
| <template> |  | ||||||
|     <main></main> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <style scoped></style> |  | ||||||
|  | @ -2,72 +2,82 @@ | ||||||
|     import { useI18n } from "vue-i18n"; |     import { useI18n } from "vue-i18n"; | ||||||
|     import authState from "@/services/auth/auth-service.ts"; |     import authState from "@/services/auth/auth-service.ts"; | ||||||
|     import { onMounted, ref } from "vue"; |     import { onMounted, ref } from "vue"; | ||||||
|     import type { ClassDTO } from "@dwengo-1/common/interfaces/class"; |  | ||||||
|     import { useRoute } from "vue-router"; |     import { useRoute } from "vue-router"; | ||||||
|     import { ClassController, type ClassResponse } from "@/controllers/classes"; |     import type { ClassResponse } from "@/controllers/classes"; | ||||||
|     import type { JoinRequestsResponse, StudentsResponse } from "@/controllers/students"; |     import type { JoinRequestsResponse, StudentsResponse } from "@/controllers/students"; | ||||||
|     import type { StudentDTO } from "@dwengo-1/common/interfaces/student"; |     import type { StudentDTO } from "@dwengo-1/common/interfaces/student"; | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|     import { useTeacherJoinRequestsQuery, useUpdateJoinRequestMutation } from "@/queries/teachers"; |     import { useTeacherJoinRequestsQuery, useUpdateJoinRequestMutation } from "@/queries/teachers"; | ||||||
|     import type { ClassJoinRequestDTO } from "@dwengo-1/common/interfaces/class-join-request"; |     import type { ClassJoinRequestDTO } from "@dwengo-1/common/interfaces/class-join-request"; | ||||||
|  |     import { useClassDeleteStudentMutation, useClassQuery, useClassStudentsQuery } from "@/queries/classes"; | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
| 
 | 
 | ||||||
|     // Username of logged in teacher |  | ||||||
|     const username = ref<string | undefined>(undefined); |  | ||||||
|     const classController: ClassController = new ClassController(); |  | ||||||
| 
 |  | ||||||
|     // Find class id from route |  | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
|     const classId: string = route.params.id as string; |     const classId: string = route.params.id as string; | ||||||
|  |     const username = ref<string | undefined>(undefined); | ||||||
|  |     const isLoading = ref(false); | ||||||
|  |     const isError = ref(false); | ||||||
|  |     const errorMessage = ref<string>(""); | ||||||
| 
 | 
 | ||||||
|     const isLoading = ref(true); |     // Queries used to access the backend and catch loading or errors | ||||||
|     const currentClass = ref<ClassDTO | undefined>(undefined); |  | ||||||
|     const students = ref<StudentDTO[]>([]); |  | ||||||
| 
 | 
 | ||||||
|  |     // Gets the class a teacher wants to manage | ||||||
|  |     const getClass = useClassQuery(classId); | ||||||
|  |     // Get all students part of the class | ||||||
|  |     const getStudents = useClassStudentsQuery(classId); | ||||||
|  |     // Get all join requests for this class | ||||||
|     const joinRequestsQuery = useTeacherJoinRequestsQuery(username, classId); |     const joinRequestsQuery = useTeacherJoinRequestsQuery(username, classId); | ||||||
|  |     // Handle accepting or rejecting join requests | ||||||
|     const { mutate } = useUpdateJoinRequestMutation(); |     const { mutate } = useUpdateJoinRequestMutation(); | ||||||
|  |     // Handle deletion of a student from the class | ||||||
|  |     const { mutate: deleteStudentMutation } = useClassDeleteStudentMutation(); | ||||||
| 
 | 
 | ||||||
|     // Find the username of the logged in user so it can be used to fetch other information |     // Load current user before rendering the page | ||||||
|     // When loading the page |  | ||||||
|     onMounted(async () => { |     onMounted(async () => { | ||||||
|         const userObject = await authState.loadUser(); |         isLoading.value = true; | ||||||
|         username.value = userObject?.profile?.preferred_username ?? undefined; |         try { | ||||||
| 
 |             const userObject = await authState.loadUser(); | ||||||
|         // Get class of which information should be shown |             username.value = userObject!.profile!.preferred_username; | ||||||
|         const classResponse: ClassResponse = await classController.getById(classId); |         } catch (error) { | ||||||
|         if (classResponse && classResponse.class) { |             isError.value = true; | ||||||
|             currentClass.value = classResponse.class; |             errorMessage.value = error instanceof Error ? error.message : String(error); | ||||||
|  |         } finally { | ||||||
|             isLoading.value = false; |             isLoading.value = false; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         // Fetch all students of the class |  | ||||||
|         const studentsResponse: StudentsResponse = await classController.getStudents(classId); |  | ||||||
|         if (studentsResponse && studentsResponse.students) students.value = studentsResponse.students as StudentDTO[]; |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // TODO: Boolean that handles visibility for dialogs |     // Used to set the visibility of the dialog | ||||||
|     // Popup to verify removing student |  | ||||||
|     const dialog = ref(false); |     const dialog = ref(false); | ||||||
|  |     // Student selected for deletion | ||||||
|     const selectedStudent = ref<StudentDTO | null>(null); |     const selectedStudent = ref<StudentDTO | null>(null); | ||||||
| 
 | 
 | ||||||
|  |     // Let the teacher verify deletion of a student | ||||||
|     function showPopup(s: StudentDTO): void { |     function showPopup(s: StudentDTO): void { | ||||||
|         selectedStudent.value = s; |         selectedStudent.value = s; | ||||||
|         dialog.value = true; |         dialog.value = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Remove student from class |  | ||||||
|     async function removeStudentFromclass(): Promise<void> { |     async function removeStudentFromclass(): Promise<void> { | ||||||
|         // TODO: replace by query |         // Delete student from class | ||||||
|         if (selectedStudent.value) await classController.deleteStudent(classId, selectedStudent.value.username); |         deleteStudentMutation( | ||||||
|         dialog.value = false; |             { id: classId, username: selectedStudent.value!.username }, | ||||||
| 
 |             { | ||||||
|         selectedStudent.value = null; |                 onSuccess: async () => { | ||||||
|         //TODO when query; reload table so student not longer in table |                     dialog.value = false; | ||||||
|  |                     await getStudents.refetch(); | ||||||
|  |                     showSnackbar(t("success"), "success"); | ||||||
|  |                 }, | ||||||
|  |                 onError: (e) => { | ||||||
|  |                     dialog.value = false; | ||||||
|  |                     showSnackbar(t("failed") + ": " + e.message, "error"); | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // TODO: query + relaoding |     function handleJoinRequest(c: ClassJoinRequestDTO, accepted: boolean): void { | ||||||
|     function handleJoinRequest(c: ClassJoinRequestDTO, accepted: boolean) : void { |         // Handle acception or rejection of a join request | ||||||
|         mutate( |         mutate( | ||||||
|             { |             { | ||||||
|                 teacherUsername: username.value!, |                 teacherUsername: username.value!, | ||||||
|  | @ -76,22 +86,32 @@ | ||||||
|                 accepted: accepted, |                 accepted: accepted, | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|                 onSuccess: () => { |                 onSuccess: async () => { | ||||||
|                     showSnackbar(t("sent"), "success"); |                     if (accepted) { | ||||||
|  |                         await joinRequestsQuery.refetch(); | ||||||
|  |                         await getStudents.refetch(); | ||||||
|  | 
 | ||||||
|  |                         showSnackbar(t("accepted"), "success"); | ||||||
|  |                     } else { | ||||||
|  |                         await joinRequestsQuery.refetch(); | ||||||
|  |                         showSnackbar(t("rejected"), "success"); | ||||||
|  |                     } | ||||||
|                 }, |                 }, | ||||||
|                 onError: (e) => { |                 onError: (e) => { | ||||||
|                     // ShowSnackbar(t("failed") + ": " + e.message, "error"); |                     showSnackbar(t("failed") + ": " + e.message, "error"); | ||||||
|                     throw e; |  | ||||||
|                 }, |                 }, | ||||||
|             }, |             }, | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     // Default of snackbar values | ||||||
|     const snackbar = ref({ |     const snackbar = ref({ | ||||||
|         visible: false, |         visible: false, | ||||||
|         message: "", |         message: "", | ||||||
|         color: "success", |         color: "success", | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     // Function to show snackbar on success or failure | ||||||
|     function showSnackbar(message: string, color: string): void { |     function showSnackbar(message: string, color: string): void { | ||||||
|         snackbar.value.message = message; |         snackbar.value.message = message; | ||||||
|         snackbar.value.color = color; |         snackbar.value.color = color; | ||||||
|  | @ -101,131 +121,144 @@ | ||||||
| <template> | <template> | ||||||
|     <main> |     <main> | ||||||
|         <div |         <div | ||||||
|  |             class="loading-div" | ||||||
|             v-if="isLoading" |             v-if="isLoading" | ||||||
|             class="text-center py-10" |  | ||||||
|         > |         > | ||||||
|             <v-progress-circular |             <v-progress-circular indeterminate></v-progress-circular> | ||||||
|                 indeterminate |  | ||||||
|                 color="primary" |  | ||||||
|             /> |  | ||||||
|             <p>Loading...</p> |  | ||||||
|         </div> |         </div> | ||||||
|         <div v-else> |         <div v-if="isError"> | ||||||
|             <h1 class="title">{{ currentClass!.displayName }}</h1> |             <v-empty-state | ||||||
|             <v-container |                 icon="mdi-alert-circle-outline" | ||||||
|                 fluid |                 :text="errorMessage" | ||||||
|                 class="ma-4" |                 :title="t('error_title')" | ||||||
|             > |             ></v-empty-state> | ||||||
|                 <v-row |         </div> | ||||||
|                     no-gutters |         <using-query-result | ||||||
|                     fluid |             :query-result="getClass" | ||||||
|  |             v-slot="classResponse: { data: ClassResponse }" | ||||||
|  |         > | ||||||
|  |             <div> | ||||||
|  |                 <h1 class="title">{{ classResponse.data.class.displayName }}</h1> | ||||||
|  |                 <using-query-result | ||||||
|  |                     :query-result="getStudents" | ||||||
|  |                     v-slot="studentsResponse: { data: StudentsResponse }" | ||||||
|                 > |                 > | ||||||
|                     <v-col |                     <v-container | ||||||
|                         cols="12" |                         fluid | ||||||
|                         sm="6" |                         class="ma-4" | ||||||
|                         md="6" |  | ||||||
|                     > |                     > | ||||||
|                         <v-table class="table"> |                         <v-row | ||||||
|                             <thead> |                             no-gutters | ||||||
|                                 <tr> |                             fluid | ||||||
|                                     <th class="header">{{ t("students") }}</th> |  | ||||||
|                                     <th class="header"></th> |  | ||||||
|                                 </tr> |  | ||||||
|                             </thead> |  | ||||||
|                             <tbody> |  | ||||||
|                                 <tr |  | ||||||
|                                     v-for="s in students" |  | ||||||
|                                     :key="s.id" |  | ||||||
|                                 > |  | ||||||
|                                     <td> |  | ||||||
|                                         {{ s.firstName + " " + s.lastName }} |  | ||||||
|                                     </td> |  | ||||||
|                                     <td> |  | ||||||
|                                         <v-btn @click="showPopup(s)"> {{ t("remove") }} </v-btn> |  | ||||||
|                                     </td> |  | ||||||
|                                 </tr> |  | ||||||
|                             </tbody> |  | ||||||
|                         </v-table> |  | ||||||
|                     </v-col> |  | ||||||
|                     <using-query-result |  | ||||||
|                         :query-result="joinRequestsQuery" |  | ||||||
|                         v-slot="joinRequests: { data: JoinRequestsResponse }" |  | ||||||
|                     > |  | ||||||
|                         <v-col |  | ||||||
|                             cols="12" |  | ||||||
|                             sm="6" |  | ||||||
|                             md="6" |  | ||||||
|                         > |                         > | ||||||
|                             <v-table class="table"> |                             <v-col | ||||||
|                                 <thead> |                                 cols="12" | ||||||
|                                     <tr> |                                 sm="6" | ||||||
|                                         <th class="header">{{ t("classJoinRequests") }}</th> |                                 md="6" | ||||||
|                                         <th class="header">{{ t("accept") + "/" + t("reject") }}</th> |                             > | ||||||
|                                     </tr> |                                 <v-table class="table"> | ||||||
|                                 </thead> |                                     <thead> | ||||||
|                                 <tbody> |                                         <tr> | ||||||
|                                     <tr |                                             <th class="header">{{ t("students") }}</th> | ||||||
|                                         v-for="jr in joinRequests.data.joinRequests as ClassJoinRequestDTO[]" |                                             <th class="header"></th> | ||||||
|                                         :key="(jr.class, jr.requester, jr.status)" |                                         </tr> | ||||||
|                                     > |                                     </thead> | ||||||
|                                         <td> |                                     <tbody> | ||||||
|                                             {{ jr.requester.firstName + " " + jr.requester.lastName }} |                                         <tr | ||||||
|                                         </td> |                                             v-for="s in studentsResponse.data.students as StudentDTO[]" | ||||||
|                                         <td> |                                             :key="s.id" | ||||||
|                                             <v-btn |                                         > | ||||||
|                                                 @click="handleJoinRequest(jr, true)" |                                             <td> | ||||||
|                                                 class="mr-2" |                                                 {{ s.firstName + " " + s.lastName }} | ||||||
|                                                 color="green" |                                             </td> | ||||||
|                                             > |                                             <td> | ||||||
|                                                 {{ t("accept") }}</v-btn |                                                 <v-btn @click="showPopup(s)"> {{ t("remove") }} </v-btn> | ||||||
|  |                                             </td> | ||||||
|  |                                         </tr> | ||||||
|  |                                     </tbody> | ||||||
|  |                                 </v-table> | ||||||
|  |                             </v-col> | ||||||
|  |                             <using-query-result | ||||||
|  |                                 :query-result="joinRequestsQuery" | ||||||
|  |                                 v-slot="joinRequests: { data: JoinRequestsResponse }" | ||||||
|  |                             > | ||||||
|  |                                 <v-col | ||||||
|  |                                     cols="12" | ||||||
|  |                                     sm="6" | ||||||
|  |                                     md="6" | ||||||
|  |                                 > | ||||||
|  |                                     <v-table class="table"> | ||||||
|  |                                         <thead> | ||||||
|  |                                             <tr> | ||||||
|  |                                                 <th class="header">{{ t("classJoinRequests") }}</th> | ||||||
|  |                                                 <th class="header">{{ t("accept") + "/" + t("reject") }}</th> | ||||||
|  |                                             </tr> | ||||||
|  |                                         </thead> | ||||||
|  |                                         <tbody> | ||||||
|  |                                             <tr | ||||||
|  |                                                 v-for="jr in joinRequests.data.joinRequests as ClassJoinRequestDTO[]" | ||||||
|  |                                                 :key="(jr.class, jr.requester, jr.status)" | ||||||
|                                             > |                                             > | ||||||
|  |                                                 <td> | ||||||
|  |                                                     {{ jr.requester.firstName + " " + jr.requester.lastName }} | ||||||
|  |                                                 </td> | ||||||
|  |                                                 <td> | ||||||
|  |                                                     <v-btn | ||||||
|  |                                                         @click="handleJoinRequest(jr, true)" | ||||||
|  |                                                         class="mr-2" | ||||||
|  |                                                         color="green" | ||||||
|  |                                                     > | ||||||
|  |                                                         {{ t("accept") }}</v-btn | ||||||
|  |                                                     > | ||||||
| 
 | 
 | ||||||
|                                             <v-btn |                                                     <v-btn | ||||||
|                                                 @click="handleJoinRequest(jr, false)" |                                                         @click="handleJoinRequest(jr, false)" | ||||||
|                                                 class="mr-2" |                                                         class="mr-2" | ||||||
|                                                 color="red" |                                                         color="red" | ||||||
|                                             > |                                                     > | ||||||
|                                                 {{ t("reject") }} |                                                         {{ t("reject") }} | ||||||
|                                             </v-btn> |                                                     </v-btn> | ||||||
|                                         </td> |                                                 </td> | ||||||
|                                     </tr> |                                             </tr> | ||||||
|                                 </tbody> |                                         </tbody> | ||||||
|                             </v-table> |                                     </v-table> | ||||||
|                         </v-col> |                                 </v-col> | ||||||
|                     </using-query-result> |                             </using-query-result> | ||||||
|                 </v-row> |                         </v-row> | ||||||
|             </v-container> |                     </v-container> | ||||||
|         </div> |                 </using-query-result> | ||||||
|         <v-dialog |             </div> | ||||||
|             v-model="dialog" |             <v-dialog | ||||||
|             max-width="400px" |                 v-model="dialog" | ||||||
|         > |                 max-width="400px" | ||||||
|             <v-card> |             > | ||||||
|                 <v-card-title class="headline">{{ t("areusure") }}</v-card-title> |                 <v-card> | ||||||
|  |                     <v-card-title class="headline">{{ t("areusure") }}</v-card-title> | ||||||
| 
 | 
 | ||||||
|                 <v-card-actions> |                     <v-card-actions> | ||||||
|                     <v-spacer></v-spacer> |                         <v-spacer></v-spacer> | ||||||
|                     <v-btn |                         <v-btn | ||||||
|                         text |                             text | ||||||
|                         @click="dialog = false" |                             @click="dialog = false" | ||||||
|                     > |                         > | ||||||
|                         {{ t("cancel") }} |                             {{ t("cancel") }} | ||||||
|                     </v-btn> |                         </v-btn> | ||||||
|                     <v-btn |                         <v-btn | ||||||
|                         text |                             text | ||||||
|                         @click="removeStudentFromclass" |                             @click="removeStudentFromclass" | ||||||
|                         >{{ t("yes") }}</v-btn |                             >{{ t("yes") }}</v-btn | ||||||
|                     > |                         > | ||||||
|                 </v-card-actions> |                     </v-card-actions> | ||||||
|             </v-card> |                 </v-card> | ||||||
|         </v-dialog> |             </v-dialog> | ||||||
|         <v-snackbar |             <v-snackbar | ||||||
|             v-model="snackbar.visible" |                 v-model="snackbar.visible" | ||||||
|             :color="snackbar.color" |                 :color="snackbar.color" | ||||||
|             timeout="3000" |                 timeout="3000" | ||||||
|         > |             > | ||||||
|             {{ snackbar.message }} |                 {{ snackbar.message }} | ||||||
|         </v-snackbar> |             </v-snackbar> | ||||||
|  |         </using-query-result> | ||||||
|     </main> |     </main> | ||||||
| </template> | </template> | ||||||
| <style scoped> | <style scoped> | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
|     import { useCreateJoinRequestMutation, useStudentClassesQuery } from "@/queries/students"; |     import { useCreateJoinRequestMutation, useStudentClassesQuery } from "@/queries/students"; | ||||||
|     import type { StudentDTO } from "@dwengo-1/common/interfaces/student"; |     import type { StudentDTO } from "@dwengo-1/common/interfaces/student"; | ||||||
|     import type { TeacherDTO } from "@dwengo-1/common/interfaces/teacher"; |     import type { TeacherDTO } from "@dwengo-1/common/interfaces/teacher"; | ||||||
|     import { type ClassesResponse } from "@/controllers/classes"; |     import type { ClassesResponse } from "@/controllers/classes"; | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|     import { useClassStudentsQuery, useClassTeachersQuery } from "@/queries/classes"; |     import { useClassStudentsQuery, useClassTeachersQuery } from "@/queries/classes"; | ||||||
|     import type { StudentsResponse } from "@/controllers/students"; |     import type { StudentsResponse } from "@/controllers/students"; | ||||||
|  | @ -17,16 +17,26 @@ | ||||||
| 
 | 
 | ||||||
|     // Username of logged in student |     // Username of logged in student | ||||||
|     const username = ref<string | undefined>(undefined); |     const username = ref<string | undefined>(undefined); | ||||||
|  |     const isLoading = ref(false); | ||||||
|  |     const isError = ref(false); | ||||||
|  |     const errorMessage = ref<string>(""); | ||||||
| 
 | 
 | ||||||
|     // Students of selected class are shown when logged in student presses on the member count |     // Students of selected class are shown when logged in student presses on the member count | ||||||
|     const selectedClass = ref<ClassDTO | null>(null); |     const selectedClass = ref<ClassDTO | null>(null); | ||||||
|     const getStudents = ref(false); |     const getStudents = ref(false); | ||||||
| 
 | 
 | ||||||
|     // Find the username of the logged in user so it can be used to fetch other information |     // Load current user before rendering the page | ||||||
|     // When loading the page |  | ||||||
|     onMounted(async () => { |     onMounted(async () => { | ||||||
|         const userObject = await authState.loadUser(); |         isLoading.value = true; | ||||||
|         username.value = userObject?.profile?.preferred_username ?? undefined; |         try { | ||||||
|  |             const userObject = await authState.loadUser(); | ||||||
|  |             username.value = userObject!.profile!.preferred_username; | ||||||
|  |         } catch (error) { | ||||||
|  |             isError.value = true; | ||||||
|  |             errorMessage.value = error instanceof Error ? error.message : String(error); | ||||||
|  |         } finally { | ||||||
|  |             isLoading.value = false; | ||||||
|  |         } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Fetch all classes of the logged in student |     // Fetch all classes of the logged in student | ||||||
|  | @ -44,7 +54,7 @@ | ||||||
|     async function openStudentDialog(c: ClassDTO): Promise<void> { |     async function openStudentDialog(c: ClassDTO): Promise<void> { | ||||||
|         selectedClass.value = c; |         selectedClass.value = c; | ||||||
| 
 | 
 | ||||||
|         // let the component know it should show the students in a class |         // Let the component know it should show the students in a class | ||||||
|         getStudents.value = true; |         getStudents.value = true; | ||||||
|         await getStudentsQuery.refetch(); |         await getStudentsQuery.refetch(); | ||||||
|         dialog.value = true; |         dialog.value = true; | ||||||
|  | @ -53,7 +63,7 @@ | ||||||
|     async function openTeacherDialog(c: ClassDTO): Promise<void> { |     async function openTeacherDialog(c: ClassDTO): Promise<void> { | ||||||
|         selectedClass.value = c; |         selectedClass.value = c; | ||||||
| 
 | 
 | ||||||
|         // let the component know it should show teachers of a class |         // Let the component know it should show teachers of a class | ||||||
|         getStudents.value = false; |         getStudents.value = false; | ||||||
|         await getTeachersQuery.refetch(); |         await getTeachersQuery.refetch(); | ||||||
|         dialog.value = true; |         dialog.value = true; | ||||||
|  | @ -111,7 +121,20 @@ | ||||||
| </script> | </script> | ||||||
| <template> | <template> | ||||||
|     <main> |     <main> | ||||||
|         <div> |         <div | ||||||
|  |             class="loading-div" | ||||||
|  |             v-if="isLoading" | ||||||
|  |         > | ||||||
|  |             <v-progress-circular indeterminate></v-progress-circular> | ||||||
|  |         </div> | ||||||
|  |         <div v-if="isError"> | ||||||
|  |             <v-empty-state | ||||||
|  |                 icon="mdi-alert-circle-outline" | ||||||
|  |                 :text="errorMessage" | ||||||
|  |                 :title="t('error_title')" | ||||||
|  |             ></v-empty-state> | ||||||
|  |         </div> | ||||||
|  |         <div v-else> | ||||||
|             <h1 class="title">{{ t("classes") }}</h1> |             <h1 class="title">{{ t("classes") }}</h1> | ||||||
|             <using-query-result |             <using-query-result | ||||||
|                 :query-result="classesQuery" |                 :query-result="classesQuery" | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ | ||||||
|     import type { ClassDTO } from "@dwengo-1/common/interfaces/class"; |     import type { ClassDTO } from "@dwengo-1/common/interfaces/class"; | ||||||
|     import type { TeacherInvitationDTO } from "@dwengo-1/common/interfaces/teacher-invitation"; |     import type { TeacherInvitationDTO } from "@dwengo-1/common/interfaces/teacher-invitation"; | ||||||
|     import { useTeacherClassesQuery } from "@/queries/teachers"; |     import { useTeacherClassesQuery } from "@/queries/teachers"; | ||||||
|     import { type ClassesResponse, type ClassResponse } from "@/controllers/classes"; |     import type { ClassesResponse } from "@/controllers/classes"; | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|     import { useClassesQuery, useClassTeacherInvitationsQuery, useCreateClassMutation } from "@/queries/classes"; |     import { useClassesQuery, useClassTeacherInvitationsQuery, useCreateClassMutation } from "@/queries/classes"; | ||||||
|     import type { TeacherInvitationsResponse } from "@/controllers/teacher-invitations"; |     import type { TeacherInvitationsResponse } from "@/controllers/teacher-invitations"; | ||||||
|  | @ -15,19 +15,29 @@ | ||||||
| 
 | 
 | ||||||
|     // Username of logged in teacher |     // Username of logged in teacher | ||||||
|     const username = ref<string | undefined>(undefined); |     const username = ref<string | undefined>(undefined); | ||||||
|  |     const isLoading = ref(false); | ||||||
|  |     const isError = ref(false); | ||||||
|  |     const errorMessage = ref<string>(""); | ||||||
| 
 | 
 | ||||||
|     // Find the username of the logged in user so it can be used to fetch other information |     // Load current user before rendering the page | ||||||
|     // When loading the page |  | ||||||
|     onMounted(async () => { |     onMounted(async () => { | ||||||
|         const userObject = await authState.loadUser(); |         isLoading.value = true; | ||||||
|         username.value = userObject?.profile?.preferred_username ?? undefined; |         try { | ||||||
|  |             const userObject = await authState.loadUser(); | ||||||
|  |             username.value = userObject!.profile!.preferred_username; | ||||||
|  |         } catch (error) { | ||||||
|  |             isError.value = true; | ||||||
|  |             errorMessage.value = error instanceof Error ? error.message : String(error); | ||||||
|  |         } finally { | ||||||
|  |             isLoading.value = false; | ||||||
|  |         } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Fetch all classes of the logged in teacher |     // Fetch all classes of the logged in teacher | ||||||
|     const classesQuery = useTeacherClassesQuery(username, true); |     const classesQuery = useTeacherClassesQuery(username, true); | ||||||
|     const allClassesQuery = useClassesQuery(); |     const allClassesQuery = useClassesQuery(); | ||||||
|     const { mutate } = useCreateClassMutation(); |     const { mutate } = useCreateClassMutation(); | ||||||
|     const getInvitationsQuery = useClassTeacherInvitationsQuery(username); |     const getInvitationsQuery = useClassTeacherInvitationsQuery(username); // TODO: use useTeacherInvitationsReceivedQuery | ||||||
| 
 | 
 | ||||||
|     // Boolean that handles visibility for dialogs |     // Boolean that handles visibility for dialogs | ||||||
|     // Creating a class will generate a popup with the generated code |     // Creating a class will generate a popup with the generated code | ||||||
|  | @ -111,6 +121,19 @@ | ||||||
| </script> | </script> | ||||||
| <template> | <template> | ||||||
|     <main> |     <main> | ||||||
|  |         <div | ||||||
|  |         class="loading-div" | ||||||
|  |         v-if="isLoading" | ||||||
|  |     > | ||||||
|  |         <v-progress-circular indeterminate></v-progress-circular> | ||||||
|  |     </div> | ||||||
|  |     <div v-if="isError"> | ||||||
|  |         <v-empty-state | ||||||
|  |             icon="mdi-alert-circle-outline" | ||||||
|  |             :text="errorMessage" | ||||||
|  |             :title="t('error_title')" | ||||||
|  |         ></v-empty-state> | ||||||
|  |     </div v-else> | ||||||
|         <div> |         <div> | ||||||
|             <h1 class="title">{{ t("classes") }}</h1> |             <h1 class="title">{{ t("classes") }}</h1> | ||||||
|             <using-query-result |             <using-query-result | ||||||
|  | @ -252,34 +275,41 @@ | ||||||
|                         :query-result="getInvitationsQuery" |                         :query-result="getInvitationsQuery" | ||||||
|                         v-slot="invitationsResponse: { data: TeacherInvitationsResponse }" |                         v-slot="invitationsResponse: { data: TeacherInvitationsResponse }" | ||||||
|                     > |                     > | ||||||
|                     <using-query-result :query-result="allClassesQuery" v-slot="classesResponse: {data: ClassesResponse}"> |                         <using-query-result | ||||||
|                         <tr |                             :query-result="allClassesQuery" | ||||||
|                             v-for="i in invitationsResponse.data.invitations as TeacherInvitationDTO[]" |                             v-slot="classesResponse: { data: ClassesResponse }" | ||||||
|                             :key="i.classId" |  | ||||||
|                         > |                         > | ||||||
|                             <td> |                             <tr | ||||||
|                                 {{ (classesResponse.data.classes as ClassDTO[]).filter((c) => c.id == i.classId)[0] }} |                                 v-for="i in invitationsResponse.data.invitations as TeacherInvitationDTO[]" | ||||||
|                             </td> |                                 :key="i.classId" | ||||||
|                             <td>{{ (i.sender as TeacherDTO).firstName + " " + (i.sender as TeacherDTO).lastName }}</td> |                             > | ||||||
|                             <td class="text-right"> |                                 <td> | ||||||
|                                 <div> |                                     {{ | ||||||
|                                     <v-btn |                                         (classesResponse.data.classes as ClassDTO[]).filter((c) => c.id == i.classId)[0] | ||||||
|                                         color="green" |                                     }} | ||||||
|                                         @click="acceptRequest" |                                 </td> | ||||||
|                                         class="mr-2" |                                 <td> | ||||||
|                                     > |                                     {{ (i.sender as TeacherDTO).firstName + " " + (i.sender as TeacherDTO).lastName }} | ||||||
|                                         {{ t("accept") }} |                                 </td> | ||||||
|                                     </v-btn> |                                 <td class="text-right"> | ||||||
|                                     <v-btn |                                     <div> | ||||||
|                                         color="red" |                                         <v-btn | ||||||
|                                         @click="denyRequest" |                                             color="green" | ||||||
|                                     > |                                             @click="acceptRequest" | ||||||
|                                         {{ t("deny") }} |                                             class="mr-2" | ||||||
|                                     </v-btn> |                                         > | ||||||
|                                 </div> |                                             {{ t("accept") }} | ||||||
|                             </td> |                                         </v-btn> | ||||||
|                         </tr> |                                         <v-btn | ||||||
|                     </using-query-result> |                                             color="red" | ||||||
|  |                                             @click="denyRequest" | ||||||
|  |                                         > | ||||||
|  |                                             {{ t("deny") }} | ||||||
|  |                                         </v-btn> | ||||||
|  |                                     </div> | ||||||
|  |                                 </td> | ||||||
|  |                             </tr> | ||||||
|  |                         </using-query-result> | ||||||
|                     </using-query-result> |                     </using-query-result> | ||||||
|                 </tbody> |                 </tbody> | ||||||
|             </v-table> |             </v-table> | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl