feat: leerkracht krijgt link naar indiening
This commit is contained in:
		
							parent
							
								
									d7688bc54c
								
							
						
					
					
						commit
						5facb54290
					
				
					 4 changed files with 165 additions and 91 deletions
				
			
		
							
								
								
									
										40
									
								
								frontend/src/components/GroupSubmissionStatus.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								frontend/src/components/GroupSubmissionStatus.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import type {Language} from "@/data-objects/language.ts"; | ||||||
|  | import {useI18n} from "vue-i18n"; | ||||||
|  | import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|  | import {useAssignmentSubmissionsQuery} from "@/queries/assignments.ts"; | ||||||
|  | import type {SubmissionsResponse} from "@/controllers/submissions.ts"; | ||||||
|  | 
 | ||||||
|  | const props = defineProps<{ | ||||||
|  |     lpHruid: string, | ||||||
|  |     group: object; | ||||||
|  |     assignmentId: number; | ||||||
|  |     classId: string; | ||||||
|  |     language: Language; | ||||||
|  |     goToGroupSubmissionLink: (groupNo: number) => void; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | const {t} = useI18n(); | ||||||
|  | // Call the submissions query | ||||||
|  | const submissionsQuery = useAssignmentSubmissionsQuery( | ||||||
|  |     () => props.classId, | ||||||
|  |     () => props.assignmentId, | ||||||
|  |     () => props.group.originalGroupNo,  // Using the classId for both class and group-related data | ||||||
|  |     () => true | ||||||
|  | ); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <using-query-result | ||||||
|  |         :query-result="submissionsQuery" | ||||||
|  |         v-slot="{ data }: { data: SubmissionsResponse }" | ||||||
|  |     > | ||||||
|  |         <v-btn | ||||||
|  |             :color="(data.submissions.length > 0) ? 'green' : 'red'" | ||||||
|  |             variant="text" | ||||||
|  |             :to="(data.submissions.length > 0) ? goToGroupSubmissionLink(props.group.groupNo) : undefined" | ||||||
|  |         > | ||||||
|  |             {{ (data.submissions.length > 0) ? t("see-submission") :  t("no-submission")}} | ||||||
|  |         </v-btn> | ||||||
|  |     </using-query-result> | ||||||
|  | </template> | ||||||
|  | @ -181,7 +181,7 @@ export function useAssignmentSubmissionsQuery( | ||||||
| 
 | 
 | ||||||
|     return useQuery({ |     return useQuery({ | ||||||
|         queryKey: computed(() => assignmentSubmissionsQueryKey(cid!, an!, f)), |         queryKey: computed(() => assignmentSubmissionsQueryKey(cid!, an!, f)), | ||||||
|         queryFn: async () => new AssignmentController(cid!).getSubmissions(gn!, f), |         queryFn: async () => new AssignmentController(cid!).getSubmissions(an!, f), | ||||||
|         enabled: () => checkEnabled(cid, an, gn), |         enabled: () => checkEnabled(cid, an, gn), | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -16,11 +16,6 @@ import {calculateProgress} from "@/utils/assignment-utils.ts"; | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|         classId: string; |         classId: string; | ||||||
|         assignmentId: number; |         assignmentId: number; | ||||||
|         useGroupsWithProgress: ( |  | ||||||
|             groups: Ref<GroupDTO[]>, |  | ||||||
|             hruid: Ref<string>, |  | ||||||
|             language: Ref<Language>, |  | ||||||
|         ) => { groupProgressMap: Map<number, number> }; |  | ||||||
|     }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
|  | @ -38,13 +33,26 @@ import {calculateProgress} from "@/utils/assignment-utils.ts"; | ||||||
|     const submitted = ref(false); //TODO: update by fetching submissions and check if group submitted |     const submitted = ref(false); //TODO: update by fetching submissions and check if group submitted | ||||||
| 
 | 
 | ||||||
|     const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); |     const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); | ||||||
|     const group = computed(() => |     const group = computed(() => { | ||||||
|         groupsQueryResult?.data.value?.groups.find((group) => |         const groups = groupsQueryResult.data.value?.groups; | ||||||
|             group.members?.some((m) => m.username === username.value), |  | ||||||
|         ), |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     watchEffect(() => { |         if (!groups) return undefined; | ||||||
|  | 
 | ||||||
|  |         // Sort by original groupNumber | ||||||
|  |         const sortedGroups = [...groups].sort((a, b) => a.groupNumber - b.groupNumber); | ||||||
|  | 
 | ||||||
|  |         return sortedGroups | ||||||
|  |             .map((group, index) => ({ | ||||||
|  |                 ...group, | ||||||
|  |                 groupNo: index + 1, // Renumbered index | ||||||
|  |             })) | ||||||
|  |             .find((group) => | ||||||
|  |                 group.members?.some((m) => m.username === username.value), | ||||||
|  |             ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | watchEffect(() => { | ||||||
|         learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; |         learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; | ||||||
|         lang.value = assignmentQueryResult.data.value?.assignment?.language as Language; |         lang.value = assignmentQueryResult.data.value?.assignment?.language as Language; | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -1,79 +1,105 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import {computed, type Ref, ref, watchEffect} from "vue"; | import {computed, ref, watchEffect} from "vue"; | ||||||
|     import { useI18n } from "vue-i18n"; | import {useI18n} from "vue-i18n"; | ||||||
|     import { useAssignmentQuery, useDeleteAssignmentMutation } from "@/queries/assignments.ts"; | import {useAssignmentQuery, useDeleteAssignmentMutation} from "@/queries/assignments.ts"; | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|     import { useGroupsQuery } from "@/queries/groups.ts"; | import {useGroupsQuery} from "@/queries/groups.ts"; | ||||||
|     import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; | import {useGetLearningPathQuery} from "@/queries/learning-paths.ts"; | ||||||
|     import type { Language } from "@/data-objects/language.ts"; | import type {Language} from "@/data-objects/language.ts"; | ||||||
|     import type { AssignmentResponse } from "@/controllers/assignments.ts"; | import type {AssignmentResponse} from "@/controllers/assignments.ts"; | ||||||
|     import type {GroupDTO, GroupDTOId} from "@dwengo-1/common/interfaces/group"; | import type {GroupDTO, GroupDTOId} from "@dwengo-1/common/interfaces/group"; | ||||||
|     import GroupProgressRow from "@/components/GroupProgressRow.vue"; | import GroupProgressRow from "@/components/GroupProgressRow.vue"; | ||||||
|  | import GroupSubmissionStatus from "@/components/GroupSubmissionStatus.vue"; | ||||||
| 
 | 
 | ||||||
|     const props = defineProps<{ | const props = defineProps<{ | ||||||
|         classId: string; |     classId: string; | ||||||
|         assignmentId: number; |     assignmentId: number; | ||||||
|     }>(); | }>(); | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); | const {t} = useI18n(); | ||||||
|     const lang = ref(); | const lang = ref(); | ||||||
|     const groups = ref<GroupDTO[] | GroupDTOId[]>([]); | const groups = ref<GroupDTO[] | GroupDTOId[]>([]); | ||||||
|     const learningPath = ref(); | const learningPath = ref(); | ||||||
| 
 | 
 | ||||||
|     const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId); | const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId); | ||||||
|     // Get learning path object | // Get learning path object | ||||||
|     const lpQueryResult = useGetLearningPathQuery( | const lpQueryResult = useGetLearningPathQuery( | ||||||
|         computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""), |     computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""), | ||||||
|         computed(() => assignmentQueryResult.data.value?.assignment?.language as Language), |     computed(() => assignmentQueryResult.data.value?.assignment?.language as Language), | ||||||
|     ); | ); | ||||||
| 
 | 
 | ||||||
|     // Get all the groups withing the assignment | // Get all the groups withing the assignment | ||||||
|     const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); | const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); | ||||||
|     groups.value = groupsQueryResult.data.value?.groups ?? []; | groups.value = groupsQueryResult.data.value?.groups ?? []; | ||||||
| 
 | 
 | ||||||
|     watchEffect(() => { | watchEffect(() => { | ||||||
|         learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; |     learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; | ||||||
|         lang.value = assignmentQueryResult.data.value?.assignment?.language as Language; |     lang.value = assignmentQueryResult.data.value?.assignment?.language as Language; | ||||||
|     }); | }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     const allGroups = computed(() => { | const allGroups = computed(() => { | ||||||
|             const groups = groupsQueryResult.data.value?.groups; |     const groups = groupsQueryResult.data.value?.groups; | ||||||
| 
 | 
 | ||||||
|             return groups?.map((group) => ({ |     if (!groups) return []; | ||||||
|                 groupNo: group.groupNumber, |  | ||||||
|                 name: `${t("group")} ${group.groupNumber}`, |  | ||||||
|                 members: group.members, |  | ||||||
|                 submitted: false, //TODO: fetch from submission |  | ||||||
|             })); |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|     const dialog = ref(false); |     // Sort by original groupNumber | ||||||
|     const selectedGroup = ref({}); |     const sortedGroups = [...groups].sort((a, b) => a.groupNumber - b.groupNumber); | ||||||
| 
 | 
 | ||||||
|     function openGroupDetails(group): void { |     // Assign new sequential numbers starting from 1 | ||||||
|         selectedGroup.value = group; |     return sortedGroups.map((group, index) => ({ | ||||||
|         dialog.value = true; |         groupNo: index + 1, // New group number that will be used | ||||||
|     } |         name: `${t("group")} ${index + 1}`, | ||||||
|  |         members: group.members, | ||||||
|  |         submitted: false, // TODO: fetch from submission | ||||||
|  |         originalGroupNo: group.groupNumber, // Keep original number if needed | ||||||
|  |     })); | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
|     const headers = computed(() => [ |  | ||||||
|         { title: t("group"), align: "start", key: "name" }, |  | ||||||
|         { title: t("progress"), align: "center", key: "progress" }, |  | ||||||
|         { title: t("submission"), align: "center", key: "submission" }, |  | ||||||
|     ]); |  | ||||||
| 
 | 
 | ||||||
|     const { mutate } = useDeleteAssignmentMutation(); | const dialog = ref(false); | ||||||
|  | const selectedGroup = ref({}); | ||||||
| 
 | 
 | ||||||
|     async function deleteAssignment(num: number, clsId: string): Promise<void> { | function openGroupDetails(group): void { | ||||||
|         mutate( |     selectedGroup.value = group; | ||||||
|             { cid: clsId, an: num }, |     dialog.value = true; | ||||||
|             { | } | ||||||
|                 onSuccess: () => { | 
 | ||||||
|                     window.location.href = "/user/assignment"; | const headers = computed(() => [ | ||||||
|                 }, |     {title: t("group"), align: "start", key: "name"}, | ||||||
|  |     {title: t("progress"), align: "center", key: "progress"}, | ||||||
|  |     {title: t("submission"), align: "center", key: "submission"}, | ||||||
|  | ]); | ||||||
|  | 
 | ||||||
|  | const {mutate} = useDeleteAssignmentMutation(); | ||||||
|  | 
 | ||||||
|  | async function deleteAssignment(num: number, clsId: string): Promise<void> { | ||||||
|  |     mutate( | ||||||
|  |         {cid: clsId, an: num}, | ||||||
|  |         { | ||||||
|  |             onSuccess: () => { | ||||||
|  |                 window.location.href = "/user/assignment"; | ||||||
|             }, |             }, | ||||||
|         ); |         }, | ||||||
|     } |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function goToLearningPathLink(): string | undefined { | ||||||
|  |     const assignment = assignmentQueryResult.data.value?.assignment; | ||||||
|  |     const lp = lpQueryResult.data.value; | ||||||
|  | 
 | ||||||
|  |     if (!assignment || !lp) return undefined; | ||||||
|  | 
 | ||||||
|  |     return `/learningPath/${lp.hruid}/${assignment.language}/${lp.startNode.learningobjectHruid}?assignmentNo=${props.assignmentId}&classId=${props.classId}`; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function goToGroupSubmissionLink(groupNo: number): string | undefined { | ||||||
|  |     const lp = lpQueryResult.data.value; | ||||||
|  |     if (!lp) return undefined; | ||||||
|  | 
 | ||||||
|  |     return `/learningPath/${lp.hruid}/${lp.language}/${lp.startNode.learningobjectHruid}?forGroup=${groupNo}&assignmentNo=${props.assignmentId}&classId=${props.classId}`; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -113,7 +139,7 @@ import {computed, type Ref, ref, watchEffect} from "vue"; | ||||||
|                     > |                     > | ||||||
|                         <v-btn |                         <v-btn | ||||||
|                             v-if="lpData" |                             v-if="lpData" | ||||||
|                             :to="`/learningPath/${lpData.hruid}/${assignmentQueryResult.data.value?.assignment?.language}/${lpData.startNode.learningobjectHruid}?assignmentNo=${assignmentId}&classId=${classId}`" |                             :to="goToLearningPathLink()" | ||||||
|                             variant="tonal" |                             variant="tonal" | ||||||
|                             color="primary" |                             color="primary" | ||||||
|                         > |                         > | ||||||
|  | @ -147,7 +173,7 @@ import {computed, type Ref, ref, watchEffect} from "vue"; | ||||||
| 
 | 
 | ||||||
|                             <template #[`item.progress`]="{ item }"> |                             <template #[`item.progress`]="{ item }"> | ||||||
|                                 <GroupProgressRow |                                 <GroupProgressRow | ||||||
|                                     :group-number="item.groupNo" |                                     :group-number="item.originalGroupNo" | ||||||
|                                     :learning-path="learningPath" |                                     :learning-path="learningPath" | ||||||
|                                     :language="lang" |                                     :language="lang" | ||||||
|                                     :assignment-id="assignmentId" |                                     :assignment-id="assignmentId" | ||||||
|  | @ -155,16 +181,15 @@ import {computed, type Ref, ref, watchEffect} from "vue"; | ||||||
|                                 /> |                                 /> | ||||||
|                             </template> |                             </template> | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|                             <template #[`item.submission`]="{ item }"> |                             <template #[`item.submission`]="{ item }"> | ||||||
|                                 <v-btn |                                 <GroupSubmissionStatus | ||||||
|                                     :to="item.submitted ? `${props.assignmentId}/submissions/` : undefined" |                                     :lp-hruid="learningPath" | ||||||
|                                     :color="item.submitted ? 'green' : 'red'" |                                     :group="item" | ||||||
|                                     variant="text" |                                     :assignmentId="assignmentId" | ||||||
|                                     class="text-capitalize" |                                     :class-id="classId" | ||||||
|                                 > |                                     :language="lang" | ||||||
|                                     {{ item.submitted ? t("see-submission") : t("no-submission") }} |                                     :go-to-group-submission-link="goToGroupSubmissionLink" | ||||||
|                                 </v-btn> |                                 /> | ||||||
|                             </template> |                             </template> | ||||||
|                         </v-data-table> |                         </v-data-table> | ||||||
|                     </div> |                     </div> | ||||||
|  | @ -184,7 +209,7 @@ import {computed, type Ref, ref, watchEffect} from "vue"; | ||||||
|                                 > |                                 > | ||||||
|                                     <v-list-item-content> |                                     <v-list-item-content> | ||||||
|                                         <v-list-item-title |                                         <v-list-item-title | ||||||
|                                             >{{ member.firstName + " " + member.lastName }} |                                         >{{ member.firstName + " " + member.lastName }} | ||||||
|                                         </v-list-item-title> |                                         </v-list-item-title> | ||||||
|                                     </v-list-item-content> |                                     </v-list-item-content> | ||||||
|                                 </v-list-item> |                                 </v-list-item> | ||||||
|  | @ -194,7 +219,8 @@ import {computed, type Ref, ref, watchEffect} from "vue"; | ||||||
|                             <v-btn |                             <v-btn | ||||||
|                                 color="primary" |                                 color="primary" | ||||||
|                                 @click="dialog = false" |                                 @click="dialog = false" | ||||||
|                                 >Close</v-btn |                             >Close | ||||||
|  |                             </v-btn | ||||||
|                             > |                             > | ||||||
|                         </v-card-actions> |                         </v-card-actions> | ||||||
|                     </v-card> |                     </v-card> | ||||||
|  | @ -216,10 +242,10 @@ import {computed, type Ref, ref, watchEffect} from "vue"; | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
|     @import "@/assets/assignment.css"; | @import "@/assets/assignment.css"; | ||||||
| 
 | 
 | ||||||
|     .table-scroll { | .table-scroll { | ||||||
|         overflow-x: auto; |     overflow-x: auto; | ||||||
|         -webkit-overflow-scrolling: touch; |     -webkit-overflow-scrolling: touch; | ||||||
|     } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Joyelle Ndagijimana
						Joyelle Ndagijimana