feat(backend): De meest recente indiening wordt automatisch ingeladen.
This commit is contained in:
		
							parent
							
								
									1685c518b6
								
							
						
					
					
						commit
						63c3d6aaa0
					
				
					 18 changed files with 406 additions and 263 deletions
				
			
		|  | @ -1,5 +1,5 @@ | ||||||
| import { Submission } from '../entities/assignments/submission.entity.js'; | import { Submission } from '../entities/assignments/submission.entity.js'; | ||||||
| import { mapToGroupDTO } from './group.js'; | import { mapToGroupDTOId } from './group.js'; | ||||||
| import { mapToStudentDTO } from './student.js'; | import { mapToStudentDTO } from './student.js'; | ||||||
| import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; | import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; | ||||||
| import { getSubmissionRepository } from '../data/repositories'; | import { getSubmissionRepository } from '../data/repositories'; | ||||||
|  | @ -13,11 +13,10 @@ export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { | ||||||
|             language: submission.learningObjectLanguage, |             language: submission.learningObjectLanguage, | ||||||
|             version: submission.learningObjectVersion, |             version: submission.learningObjectVersion, | ||||||
|         }, |         }, | ||||||
| 
 |  | ||||||
|         submissionNumber: submission.submissionNumber, |         submissionNumber: submission.submissionNumber, | ||||||
|         submitter: mapToStudentDTO(submission.submitter), |         submitter: mapToStudentDTO(submission.submitter), | ||||||
|         time: submission.submissionTime, |         time: submission.submissionTime, | ||||||
|         group: mapToGroupDTO(submission.onBehalfOf), |         group: mapToGroupDTOId(submission.onBehalfOf), | ||||||
|         content: submission.content, |         content: submission.content, | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ export function errorHandler(err: unknown, _req: Request, res: Response, _: Next | ||||||
|         logger.warn(`An error occurred while handling a request: ${err} (-> HTTP ${err.status})`); |         logger.warn(`An error occurred while handling a request: ${err} (-> HTTP ${err.status})`); | ||||||
|         res.status(err.status).json(err); |         res.status(err.status).json(err); | ||||||
|     } else { |     } else { | ||||||
|         logger.error(`Unexpected error occurred while handing a request: ${JSON.stringify(err)}`); |         logger.error(`Unexpected error occurred while handing a request: ${(err as {stack: string})?.stack ?? JSON.stringify(err)}`); | ||||||
|         res.status(500).json(err); |         res.status(500).json(err); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,13 +1,13 @@ | ||||||
| { | { | ||||||
|     "welcome": "Willkommen", |     "welcome": "Willkommen", | ||||||
|     "student": "schüler", |     "student": "Schüler", | ||||||
|     "teacher": "lehrer", |     "teacher": "Lehrer", | ||||||
|     "assignments": "Aufgaben", |     "assignments": "Aufgaben", | ||||||
|     "classes": "Klasses", |     "classes": "Klassen", | ||||||
|     "discussions": "Diskussionen", |     "discussions": "Diskussionen", | ||||||
|     "login": "einloggen", |     "login": "einloggen", | ||||||
|     "logout": "ausloggen", |     "logout": "ausloggen", | ||||||
|     "cancel": "kündigen", |     "cancel": "abbrechen", | ||||||
|     "logoutVerification": "Sind Sie sicher, dass Sie sich abmelden wollen?", |     "logoutVerification": "Sind Sie sicher, dass Sie sich abmelden wollen?", | ||||||
|     "homeTitle": "Unsere Stärken", |     "homeTitle": "Unsere Stärken", | ||||||
|     "homeIntroduction1": "Wir entwickeln innovative Workshops und Bildungsressourcen, die wir in Zusammenarbeit mit Lehrern und Freiwilligen Schülern auf der ganzen Welt zur Verfügung stellen. Unsere Train-the-Trainer-Sitzungen ermöglichen es ihnen, unsere praktischen Workshops an die Schüler weiterzugeben.", |     "homeIntroduction1": "Wir entwickeln innovative Workshops und Bildungsressourcen, die wir in Zusammenarbeit mit Lehrern und Freiwilligen Schülern auf der ganzen Welt zur Verfügung stellen. Unsere Train-the-Trainer-Sitzungen ermöglichen es ihnen, unsere praktischen Workshops an die Schüler weiterzugeben.", | ||||||
|  | @ -23,10 +23,10 @@ | ||||||
|     "submitCode": "senden", |     "submitCode": "senden", | ||||||
|     "members": "Mitglieder", |     "members": "Mitglieder", | ||||||
|     "themes": "Themen", |     "themes": "Themen", | ||||||
|     "choose-theme": "Wähle ein thema", |     "choose-theme": "Wählen Sie ein Thema", | ||||||
|     "choose-age": "Alter auswählen", |     "choose-age": "Alter auswählen", | ||||||
|     "theme-options": { |     "theme-options": { | ||||||
|         "all": "Alle themen", |         "all": "Alle Themen", | ||||||
|         "culture": "Kultur", |         "culture": "Kultur", | ||||||
|         "electricity-and-mechanics": "Elektrizität und Mechanik", |         "electricity-and-mechanics": "Elektrizität und Mechanik", | ||||||
|         "nature-and-climate": "Natur und Klima", |         "nature-and-climate": "Natur und Klima", | ||||||
|  | @ -37,11 +37,11 @@ | ||||||
|         "algorithms": "Algorithmisches Denken" |         "algorithms": "Algorithmisches Denken" | ||||||
|     }, |     }, | ||||||
|     "age-options": { |     "age-options": { | ||||||
|         "all": "Alle altersgruppen", |         "all": "Alle Altersgruppen", | ||||||
|         "primary-school": "Grundschule", |         "primary-school": "Grundschule", | ||||||
|         "lower-secondary": "12-14 jahre alt", |         "lower-secondary": "12-14 Jahre alt", | ||||||
|         "upper-secondary": "14-16 jahre alt", |         "upper-secondary": "14-16 Jahre alt", | ||||||
|         "high-school": "16-18 jahre alt", |         "high-school": "16-18 Jahre alt", | ||||||
|         "older": "18 und älter" |         "older": "18 und älter" | ||||||
|     }, |     }, | ||||||
|     "read-more": "Mehr lesen", |     "read-more": "Mehr lesen", | ||||||
|  | @ -73,7 +73,9 @@ | ||||||
|     "accept": "akzeptieren", |     "accept": "akzeptieren", | ||||||
|     "deny": "ablehnen", |     "deny": "ablehnen", | ||||||
|     "sent": "sent", |     "sent": "sent", | ||||||
|     "failed": "gescheitert", |     "failed": "fehlgeschlagen", | ||||||
|     "wrong": "etwas ist schief gelaufen", |     "wrong": "etwas ist schief gelaufen", | ||||||
|     "created": "erstellt" |     "created": "erstellt", | ||||||
|  |     "submit": "Einreichen", | ||||||
|  |     "markAsDone": "Als fertig markieren" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -75,5 +75,7 @@ | ||||||
|     "sent": "sent", |     "sent": "sent", | ||||||
|     "failed": "failed", |     "failed": "failed", | ||||||
|     "wrong": "something went wrong", |     "wrong": "something went wrong", | ||||||
|     "created": "created" |     "created": "created", | ||||||
|  |     "submit": "Submit", | ||||||
|  |     "markAsDone": "Mark as done" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -75,5 +75,7 @@ | ||||||
|     "sent": "verzonden", |     "sent": "verzonden", | ||||||
|     "failed": "mislukt", |     "failed": "mislukt", | ||||||
|     "wrong": "er ging iets verkeerd", |     "wrong": "er ging iets verkeerd", | ||||||
|     "created": "gecreëerd" |     "created": "gecreëerd", | ||||||
|  |     "submit": "Indienen", | ||||||
|  |     "markAsDone": "Markeren als afgewerkt" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -10,13 +10,11 @@ const learningPathController = getLearningPathController(); | ||||||
| export function useGetLearningPathQuery( | export function useGetLearningPathQuery( | ||||||
|     hruid: MaybeRefOrGetter<string>, |     hruid: MaybeRefOrGetter<string>, | ||||||
|     language: MaybeRefOrGetter<Language>, |     language: MaybeRefOrGetter<Language>, | ||||||
|     forGroup?: MaybeRefOrGetter<{forGroup: number, assignmentNo: number, classId: string}>, |     forGroup?: MaybeRefOrGetter<{forGroup: number, assignmentNo: number, classId: string} | undefined>, | ||||||
| ): UseQueryReturnType<LearningPath, Error> { | ): UseQueryReturnType<LearningPath, Error> { | ||||||
|     return useQuery({ |     return useQuery({ | ||||||
|         queryKey: [LEARNING_PATH_KEY, "get", toValue(hruid), toValue(language), toValue(forGroup)], |         queryKey: [LEARNING_PATH_KEY, "get", toValue(hruid), toValue(language), toValue(forGroup)], | ||||||
|         queryFn: async () => { |         queryFn: async () => { | ||||||
|             console.log("queryKey"); |  | ||||||
|             console.log([LEARNING_PATH_KEY, "get", toValue(hruid), toValue(language), toValue(forGroup)]); |  | ||||||
|             const [hruidVal, languageVal, forGroupVal] = [toValue(hruid), toValue(language), toValue(forGroup)]; |             const [hruidVal, languageVal, forGroupVal] = [toValue(hruid), toValue(language), toValue(forGroup)]; | ||||||
|             return learningPathController.getBy(hruidVal, languageVal, forGroupVal); |             return learningPathController.getBy(hruidVal, languageVal, forGroupVal); | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { SubmissionController, type SubmissionResponse, type SubmissionsResponse } from "@/controllers/submissions"; | import { SubmissionController, type SubmissionResponse } from "@/controllers/submissions"; | ||||||
| import type { SubmissionDTO } from "@dwengo-1/common/interfaces/submission"; | import type { SubmissionDTO } from "@dwengo-1/common/interfaces/submission"; | ||||||
| import { | import { | ||||||
|     QueryClient, |     QueryClient, | ||||||
|  | @ -13,17 +13,7 @@ import {LEARNING_PATH_KEY} from "@/queries/learning-paths.ts"; | ||||||
| import {LEARNING_OBJECT_KEY} from "@/queries/learning-objects.ts"; | import {LEARNING_OBJECT_KEY} from "@/queries/learning-objects.ts"; | ||||||
| import type {Language} from "@dwengo-1/common/util/language"; | import type {Language} from "@dwengo-1/common/util/language"; | ||||||
| 
 | 
 | ||||||
| function submissionsQueryKey( | export const SUBMISSION_KEY = "submissions"; | ||||||
|     hruid: string, |  | ||||||
|     language: Language, |  | ||||||
|     version: number, |  | ||||||
|     classid: string, |  | ||||||
|     assignmentNumber: number, |  | ||||||
|     groupNumber?: number, |  | ||||||
|     full?: boolean |  | ||||||
| ) { |  | ||||||
|     return ["submissions", hruid, language, version, classid, assignmentNumber, groupNumber, full ?? false]; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| function submissionQueryKey( | function submissionQueryKey( | ||||||
|     hruid: string, |     hruid: string, | ||||||
|  | @ -72,19 +62,8 @@ export async function invalidateAllSubmissionKeys( | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function checkEnabled( | function checkEnabled(properties: MaybeRefOrGetter<unknown>[]): boolean { | ||||||
|     classid: string | undefined, |     return properties.every(prop => !!toValue(prop)); | ||||||
|     assignmentNumber: number | undefined, |  | ||||||
|     groupNumber: number | undefined, |  | ||||||
|     submissionNumber?: number | undefined, |  | ||||||
|     submissionNumberRequired: boolean = false |  | ||||||
| ): boolean { |  | ||||||
|     return ( |  | ||||||
|         Boolean(classid) && |  | ||||||
|         !isNaN(Number(groupNumber)) && |  | ||||||
|         !isNaN(Number(assignmentNumber)) && |  | ||||||
|         (!isNaN(Number(submissionNumber)) || !submissionNumberRequired) |  | ||||||
|     ); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function toValues( | function toValues( | ||||||
|  | @ -110,31 +89,24 @@ export function useSubmissionsQuery( | ||||||
|     assignmentNumber: MaybeRefOrGetter<number | undefined>, |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|     groupNumber: MaybeRefOrGetter<number | undefined>, |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|     full: MaybeRefOrGetter<boolean> = true, |     full: MaybeRefOrGetter<boolean> = true, | ||||||
| ): UseQueryReturnType<SubmissionsResponse, Error> { | ): UseQueryReturnType<SubmissionDTO[], Error> { | ||||||
|     const hruidVal = toValue(hruid); |  | ||||||
|     const languageVal = toValue(language); |  | ||||||
|     const versionVal = toValue(version); |  | ||||||
|     const classIdVal = toValue(classid); |  | ||||||
|     const assignmentNumberVal = toValue(assignmentNumber); |  | ||||||
|     const groupNumberVal = toValue(groupNumber); |  | ||||||
|     const fullVal = toValue(full); |  | ||||||
| 
 |  | ||||||
|     return useQuery({ |     return useQuery({ | ||||||
|         queryKey: computed(() => |         queryKey: ["submissions", hruid, language, version, classid, assignmentNumber, groupNumber, full], | ||||||
|             submissionsQueryKey( |         queryFn: async () => { | ||||||
|                 hruidVal!, |             const hruidVal = toValue(hruid); | ||||||
|                 languageVal!, |             const languageVal = toValue(language); | ||||||
|                 versionVal!, |             const versionVal = toValue(version); | ||||||
|                 classIdVal!, |             const classIdVal = toValue(classid); | ||||||
|                 assignmentNumberVal!, |             const assignmentNumberVal = toValue(assignmentNumber); | ||||||
|                 groupNumberVal, |             const groupNumberVal = toValue(groupNumber); | ||||||
|                 fullVal |             const fullVal = toValue(full); | ||||||
|             ) | 
 | ||||||
|         ), |             const response = await new SubmissionController(hruidVal!).getAll( | ||||||
|         queryFn: async () => new SubmissionController(hruidVal!).getAll( |                 languageVal!, versionVal!, classIdVal!, assignmentNumberVal!, groupNumberVal, fullVal | ||||||
|             languageVal!, versionVal!, classIdVal!, assignmentNumberVal!, groupNumberVal, fullVal |             ); | ||||||
|         ), |             return response ? response.submissions as SubmissionDTO[] : undefined; | ||||||
|         enabled: () => !!hruidVal && !!languageVal && !!versionVal && !!classIdVal && !!assignmentNumberVal, |         }, | ||||||
|  |         enabled: () => checkEnabled([hruid, language, version, classid, assignmentNumber]), | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -147,8 +119,6 @@ export function useSubmissionQuery( | ||||||
|     groupNumber: MaybeRefOrGetter<number | undefined>, |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|     submissionNumber: MaybeRefOrGetter<number | undefined>, |     submissionNumber: MaybeRefOrGetter<number | undefined>, | ||||||
| ): UseQueryReturnType<SubmissionResponse, Error> { | ): UseQueryReturnType<SubmissionResponse, Error> { | ||||||
|     const { cid, an, gn, sn, f } = toValues(classid, assignmentNumber, groupNumber, submissionNumber, true); |  | ||||||
| 
 |  | ||||||
|     const hruidVal = toValue(hruid); |     const hruidVal = toValue(hruid); | ||||||
|     const languageVal = toValue(language); |     const languageVal = toValue(language); | ||||||
|     const versionVal = toValue(version); |     const versionVal = toValue(version); | ||||||
|  | @ -192,11 +162,6 @@ export function useCreateSubmissionMutation(): UseMutationReturnType< | ||||||
|                 const {hruid, language, version} = response.submission.learningObjectIdentifier; |                 const {hruid, language, version} = response.submission.learningObjectIdentifier; | ||||||
|                 await invalidateAllSubmissionKeys(queryClient, hruid, language, version, cid, an, gn); |                 await invalidateAllSubmissionKeys(queryClient, hruid, language, version, cid, an, gn); | ||||||
| 
 | 
 | ||||||
|                 console.log("INVALIDATE"); |  | ||||||
|                 console.log([ |  | ||||||
|                     LEARNING_PATH_KEY, "get", |  | ||||||
|                     response.submission.learningObjectIdentifier.hruid, |  | ||||||
|                 ]); |  | ||||||
|                 await queryClient.invalidateQueries({queryKey: [LEARNING_PATH_KEY, "get"]}); |                 await queryClient.invalidateQueries({queryKey: [LEARNING_PATH_KEY, "get"]}); | ||||||
| 
 | 
 | ||||||
|                 await queryClient.invalidateQueries({ |                 await queryClient.invalidateQueries({ | ||||||
|  | @ -216,7 +181,7 @@ export function useDeleteSubmissionMutation(): UseMutationReturnType< | ||||||
|     const queryClient = useQueryClient(); |     const queryClient = useQueryClient(); | ||||||
| 
 | 
 | ||||||
|     return useMutation({ |     return useMutation({ | ||||||
|         mutationFn: async ({ cid, an, gn, sn }) => new SubmissionController(cid).deleteSubmission(sn), |         mutationFn: async ({ cid, sn }) => new SubmissionController(cid).deleteSubmission(sn), | ||||||
|         onSuccess: async (response) => { |         onSuccess: async (response) => { | ||||||
|             if (!response.submission.group) { |             if (!response.submission.group) { | ||||||
|                 await invalidateAllSubmissionKeys(queryClient); |                 await invalidateAllSubmissionKeys(queryClient); | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ import LearningPathPage from "@/views/learning-paths/LearningPathPage.vue"; | ||||||
| import LearningPathSearchPage from "@/views/learning-paths/LearningPathSearchPage.vue"; | import LearningPathSearchPage from "@/views/learning-paths/LearningPathSearchPage.vue"; | ||||||
| import UserHomePage from "@/views/homepage/UserHomePage.vue"; | import UserHomePage from "@/views/homepage/UserHomePage.vue"; | ||||||
| import SingleTheme from "@/views/SingleTheme.vue"; | import SingleTheme from "@/views/SingleTheme.vue"; | ||||||
| import LearningObjectView from "@/views/learning-paths/LearningObjectView.vue"; | import LearningObjectView from "@/views/learning-paths/learning-object/LearningObjectView.vue"; | ||||||
| 
 | 
 | ||||||
| const router = createRouter({ | const router = createRouter({ | ||||||
|     history: createWebHistory(import.meta.env.BASE_URL), |     history: createWebHistory(import.meta.env.BASE_URL), | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								frontend/src/utils/array-utils.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								frontend/src/utils/array-utils.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | export function copyArrayWith<T>(index: number, newValue: T, array: T[]) { | ||||||
|  |     const copy = [...array]; | ||||||
|  |     copy[index] = newValue; | ||||||
|  |     return copy; | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								frontend/src/utils/deep-equals.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								frontend/src/utils/deep-equals.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | export function deepEquals<T>(a: T, b: T): boolean { | ||||||
|  |     if (a === b) return true; | ||||||
|  | 
 | ||||||
|  |     if (typeof a !== 'object' || typeof b !== 'object' || a == null || b == null) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     if (Array.isArray(a) !== Array.isArray(b)) return false; | ||||||
|  | 
 | ||||||
|  |     if (Array.isArray(a) && Array.isArray(b)) { | ||||||
|  |         if (a.length !== b.length) return false; | ||||||
|  |         return a.every((val, i) => deepEquals(val, b[i])); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const keysA = Object.keys(a) as (keyof T)[]; | ||||||
|  |     const keysB = Object.keys(b) as (keyof T)[]; | ||||||
|  | 
 | ||||||
|  |     if (keysA.length !== keysB.length) return false; | ||||||
|  | 
 | ||||||
|  |     return keysA.every(key => deepEquals(a[key], b[key])); | ||||||
|  | } | ||||||
|  | @ -1,174 +0,0 @@ | ||||||
| <script setup lang="ts"> |  | ||||||
|     import { Language } from "@/data-objects/language.ts"; |  | ||||||
|     import type { UseQueryReturnType } from "@tanstack/vue-query"; |  | ||||||
|     import { useLearningObjectHTMLQuery } from "@/queries/learning-objects.ts"; |  | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; |  | ||||||
|     import {computed, nextTick, onMounted, reactive, watch} from "vue"; |  | ||||||
|     import {getGiftAdapterForType} from "@/views/learning-paths/gift-adapters/gift-adapters.ts"; |  | ||||||
|     import authService from "@/services/auth/auth-service.ts"; |  | ||||||
|     import {useCreateSubmissionMutation, useSubmissionsQuery} from "@/queries/submissions.ts"; |  | ||||||
|     import type {SubmissionDTO} from "@dwengo-1/common/dist/interfaces/submission.d.ts"; |  | ||||||
|     import type {GroupDTO} from "@dwengo-1/common/interfaces/group"; |  | ||||||
|     import type {StudentDTO} from "@dwengo-1/common/interfaces/student"; |  | ||||||
|     import type {LearningObjectIdentifierDTO} from "@dwengo-1/common/interfaces/learning-content"; |  | ||||||
|     import type {UserProfile} from "oidc-client-ts"; |  | ||||||
| 
 |  | ||||||
|     const isStudent = computed(() => authService.authState.activeRole === "student"); |  | ||||||
| 
 |  | ||||||
|     const props = defineProps<{ |  | ||||||
|         hruid: string; |  | ||||||
|         language: Language; |  | ||||||
|         version: number, |  | ||||||
|         group?: {forGroup: number, assignmentNo: number, classId: string} |  | ||||||
|     }>(); |  | ||||||
| 
 |  | ||||||
|     const learningObjectHtmlQueryResult: UseQueryReturnType<Document, Error> = useLearningObjectHTMLQuery( |  | ||||||
|         () => props.hruid, |  | ||||||
|         () => props.language, |  | ||||||
|         () => props.version, |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     const currentAnswer = reactive(<(string | number | object)[]>[]); |  | ||||||
| 
 |  | ||||||
|     const { |  | ||||||
|         isPending: submissionIsPending, |  | ||||||
|         isError: submissionFailed, |  | ||||||
|         error: submissionError, |  | ||||||
|         isSuccess: submissionSuccess, |  | ||||||
|         mutate: submitSolution |  | ||||||
|     } = useCreateSubmissionMutation(); |  | ||||||
| 
 |  | ||||||
|     const { |  | ||||||
|         isPending: existingSubmissionsIsPending, |  | ||||||
|         isError: existingSubmissionsFailed, |  | ||||||
|         error: existingSubmissionsError, |  | ||||||
|         isSuccess: existingSubmissionsSuccess, |  | ||||||
|         data: existingSubmissions |  | ||||||
|     } = useSubmissionsQuery( |  | ||||||
|         props.hruid, |  | ||||||
|         props.language, |  | ||||||
|         props.version, |  | ||||||
|         props.group?.classId, |  | ||||||
|         props.group?.assignmentNo, |  | ||||||
|         props.group?.forGroup, |  | ||||||
|         true |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     function submitCurrentAnswer(): void { |  | ||||||
|         const { forGroup, assignmentNo, classId } = props.group!; |  | ||||||
|         const currentUser: UserProfile = authService.authState.user!.profile; |  | ||||||
|         const learningObjectIdentifier: LearningObjectIdentifierDTO = { |  | ||||||
|             hruid: props.hruid, |  | ||||||
|             language: props.language as Language, |  | ||||||
|             version: props.version |  | ||||||
|         }; |  | ||||||
|         const submitter: StudentDTO = { |  | ||||||
|             id: currentUser.preferred_username!, |  | ||||||
|             username: currentUser.preferred_username!, |  | ||||||
|             firstName: currentUser.given_name!, |  | ||||||
|             lastName: currentUser.family_name! |  | ||||||
|         }; |  | ||||||
|         const group: GroupDTO = { |  | ||||||
|             class: classId, |  | ||||||
|             assignment: assignmentNo, |  | ||||||
|             groupNumber: forGroup |  | ||||||
|         } |  | ||||||
|         const submission: SubmissionDTO = { |  | ||||||
|             learningObjectIdentifier, |  | ||||||
|             submitter, |  | ||||||
|             group, |  | ||||||
|             content: JSON.stringify(currentAnswer) |  | ||||||
|         } |  | ||||||
|         submitSolution({ data: submission }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function forEachQuestion( |  | ||||||
|         doAction: (questionIndex: number, questionName: string, questionType: string, questionElement: Element) => void |  | ||||||
|     ) { |  | ||||||
|         const questions = document.querySelectorAll(".gift-question"); |  | ||||||
|         questions.forEach(question => { |  | ||||||
|             const name = question.id.match(/gift-q(\d+)/)?.[1] |  | ||||||
|             const questionType = question.className.split(" ") |  | ||||||
|                 .find(it => it.startsWith("gift-question-type")) |  | ||||||
|                 ?.match(/gift-question-type-([^ ]*)/)?.[1]; |  | ||||||
| 
 |  | ||||||
|             if (!name || isNaN(parseInt(name)) || !questionType) return; |  | ||||||
| 
 |  | ||||||
|             const index = parseInt(name) - 1; |  | ||||||
| 
 |  | ||||||
|             doAction(index, name, questionType, question); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function attachQuestionListeners() { |  | ||||||
|         forEachQuestion((index, name, type, element) => { |  | ||||||
|             getGiftAdapterForType(type)?.installListener( |  | ||||||
|                 element, (newAnswer) => currentAnswer[index] = newAnswer |  | ||||||
|             ); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function setAnswers(answers: (object | string | number)[]) { |  | ||||||
|         forEachQuestion((index, name, type, element) => { |  | ||||||
|             getGiftAdapterForType(type)?.setAnswer(element, answers[index]); |  | ||||||
|         }); |  | ||||||
|         currentAnswer.splice(0, currentAnswer.length, ...answers); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     onMounted(() => nextTick(() => attachQuestionListeners())); |  | ||||||
| 
 |  | ||||||
|     watch(learningObjectHtmlQueryResult.data, async () => { |  | ||||||
|         await nextTick(); |  | ||||||
|         attachQuestionListeners(); |  | ||||||
|         setAnswers([1]); |  | ||||||
|     }); |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| <template> |  | ||||||
|     <using-query-result |  | ||||||
|         :query-result="learningObjectHtmlQueryResult as UseQueryReturnType<Document, Error>" |  | ||||||
|         v-slot="learningPathHtml: { data: Document }" |  | ||||||
|     > |  | ||||||
|         <div |  | ||||||
|             class="learning-object-container" |  | ||||||
|             v-html="learningPathHtml.data.body.innerHTML" |  | ||||||
|         ></div> |  | ||||||
|         <p>Last submissions: {{ existingSubmissions }}</p> |  | ||||||
|         <p>Your answer: {{ currentAnswer }}</p> |  | ||||||
|         <v-btn v-if="isStudent && props.group" |  | ||||||
|                prepend-icon="mdi-check" |  | ||||||
|                variant="elevated" |  | ||||||
|                :loading="submissionIsPending" |  | ||||||
|                @click="submitCurrentAnswer()" |  | ||||||
|         > |  | ||||||
|             Submit |  | ||||||
|         </v-btn> |  | ||||||
|     </using-query-result> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <style scoped> |  | ||||||
|     .learning-object-container { |  | ||||||
|         padding: 20px; |  | ||||||
|     } |  | ||||||
|     :deep(hr) { |  | ||||||
|         margin-top: 10px; |  | ||||||
|         margin-bottom: 10px; |  | ||||||
|     } |  | ||||||
|     :deep(li) { |  | ||||||
|         margin-left: 30px; |  | ||||||
|         margin-top: 5px; |  | ||||||
|         margin-bottom: 5px; |  | ||||||
|     } |  | ||||||
|     :deep(img) { |  | ||||||
|         max-width: 80%; |  | ||||||
|     } |  | ||||||
|     :deep(h2), |  | ||||||
|     :deep(h3), |  | ||||||
|     :deep(h4), |  | ||||||
|     :deep(h5), |  | ||||||
|     :deep(h6) { |  | ||||||
|         margin-top: 10px; |  | ||||||
|     } |  | ||||||
| </style> |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
|     import { computed, type ComputedRef, ref } from "vue"; |     import { computed, type ComputedRef, ref } from "vue"; | ||||||
|     import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts"; |     import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts"; | ||||||
|     import { useRoute } from "vue-router"; |     import { useRoute } from "vue-router"; | ||||||
|     import LearningObjectView from "@/views/learning-paths/LearningObjectView.vue"; |     import LearningObjectView from "@/views/learning-paths/learning-object/LearningObjectView.vue"; | ||||||
|     import { useI18n } from "vue-i18n"; |     import { useI18n } from "vue-i18n"; | ||||||
|     import LearningPathSearchField from "@/components/LearningPathSearchField.vue"; |     import LearningPathSearchField from "@/components/LearningPathSearchField.vue"; | ||||||
|     import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; |     import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; | ||||||
|  | @ -185,13 +185,15 @@ | ||||||
|                 <learning-path-search-field></learning-path-search-field> |                 <learning-path-search-field></learning-path-search-field> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|         <learning-object-view |         <div class="learning-object-view-container"> | ||||||
|             :hruid="currentNode.learningobjectHruid" |             <learning-object-view | ||||||
|             :language="currentNode.language" |                 :hruid="currentNode.learningobjectHruid" | ||||||
|             :version="currentNode.version" |                 :language="currentNode.language" | ||||||
|             :group="forGroup" |                 :version="currentNode.version" | ||||||
|             v-if="currentNode" |                 :group="forGroup" | ||||||
|         ></learning-object-view> |                 v-if="currentNode" | ||||||
|  |             ></learning-object-view> | ||||||
|  |         </div> | ||||||
|         <div class="navigation-buttons-container"> |         <div class="navigation-buttons-container"> | ||||||
|             <v-btn |             <v-btn | ||||||
|                 prepend-icon="mdi-chevron-left" |                 prepend-icon="mdi-chevron-left" | ||||||
|  | @ -227,6 +229,11 @@ | ||||||
|         display: flex; |         display: flex; | ||||||
|         justify-content: space-between; |         justify-content: space-between; | ||||||
|     } |     } | ||||||
|  |     .learning-object-view-container { | ||||||
|  |         padding-left: 20px; | ||||||
|  |         padding-right: 20px; | ||||||
|  |         padding-bottom: 20px; | ||||||
|  |     } | ||||||
|     .navigation-buttons-container { |     .navigation-buttons-container { | ||||||
|         padding: 20px; |         padding: 20px; | ||||||
|         display: flex; |         display: flex; | ||||||
|  |  | ||||||
|  | @ -18,9 +18,7 @@ export const multipleChoiceQuestionAdapter: GiftAdapter = { | ||||||
|     setAnswer(questionElement: Element, answer: string | number | object): void { |     setAnswer(questionElement: Element, answer: string | number | object): void { | ||||||
|         questionElement.querySelectorAll('input[type=radio]').forEach(element => { |         questionElement.querySelectorAll('input[type=radio]').forEach(element => { | ||||||
|             const input = element as HTMLInputElement; |             const input = element as HTMLInputElement; | ||||||
|             console.log(`input: ${input.value}, answer: ${answer}`); |  | ||||||
|             input.checked = String(answer) === String(input.value); |             input.checked = String(answer) === String(input.value); | ||||||
|             console.log(input.checked); |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,73 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { Language } from "@/data-objects/language.ts"; | ||||||
|  | import type { UseQueryReturnType } from "@tanstack/vue-query"; | ||||||
|  | import { useLearningObjectHTMLQuery } from "@/queries/learning-objects.ts"; | ||||||
|  | import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|  | import {computed, ref} from "vue"; | ||||||
|  | import authService from "@/services/auth/auth-service.ts"; | ||||||
|  | import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data"; | ||||||
|  | import LearningObjectContentView from "@/views/learning-paths/learning-object/content/LearningObjectContentView.vue"; | ||||||
|  | import LearningObjectSubmissionsView from "@/views/learning-paths/learning-object/submissions/LearningObjectSubmissionsView.vue"; | ||||||
|  | 
 | ||||||
|  | const isStudent = computed(() => authService.authState.activeRole === "student"); | ||||||
|  | 
 | ||||||
|  | const props = defineProps<{ | ||||||
|  |     hruid: string; | ||||||
|  |     language: Language; | ||||||
|  |     version: number, | ||||||
|  |     group?: {forGroup: number, assignmentNo: number, classId: string} | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | const learningObjectHtmlQueryResult: UseQueryReturnType<Document, Error> = useLearningObjectHTMLQuery( | ||||||
|  |     () => props.hruid, | ||||||
|  |     () => props.language, | ||||||
|  |     () => props.version, | ||||||
|  | ); | ||||||
|  | const currentSubmission = ref<SubmissionData>([]); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <using-query-result | ||||||
|  |         :query-result="learningObjectHtmlQueryResult as UseQueryReturnType<Document, Error>" | ||||||
|  |         v-slot="learningPathHtml: { data: Document }" | ||||||
|  |     > | ||||||
|  |         <learning-object-content-view | ||||||
|  |             :learning-object-content="learningPathHtml.data" | ||||||
|  |             v-model:submission-data="currentSubmission" | ||||||
|  |         /> | ||||||
|  |         <div class="content-submissions-spacer"/> | ||||||
|  |         <learning-object-submissions-view | ||||||
|  |             v-if="props.group" | ||||||
|  |             :group="props.group" | ||||||
|  |             :hruid="props.hruid" | ||||||
|  |             :language="props.language" | ||||||
|  |             :version="props.version" | ||||||
|  |             v-model:submission-data="currentSubmission" | ||||||
|  |         /> | ||||||
|  |     </using-query-result> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | :deep(hr) { | ||||||
|  |     margin-top: 10px; | ||||||
|  |     margin-bottom: 10px; | ||||||
|  | } | ||||||
|  | :deep(li) { | ||||||
|  |     margin-left: 30px; | ||||||
|  |     margin-top: 5px; | ||||||
|  |     margin-bottom: 5px; | ||||||
|  | } | ||||||
|  | :deep(img) { | ||||||
|  |     max-width: 80%; | ||||||
|  | } | ||||||
|  | :deep(h2), | ||||||
|  | :deep(h3), | ||||||
|  | :deep(h4), | ||||||
|  | :deep(h5), | ||||||
|  | :deep(h6) { | ||||||
|  |     margin-top: 10px; | ||||||
|  | } | ||||||
|  | .content-submissions-spacer { | ||||||
|  |     height: 20px; | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  | 
 | ||||||
|  |     import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data"; | ||||||
|  |     import {getGiftAdapterForType} from "@/views/learning-paths/gift-adapters/gift-adapters.ts"; | ||||||
|  |     import {computed, nextTick, onMounted, watch} from "vue"; | ||||||
|  |     import {copyArrayWith} from "@/utils/array-utils.ts"; | ||||||
|  | 
 | ||||||
|  |     const props = defineProps<{ | ||||||
|  |         learningObjectContent: Document | ||||||
|  |         submissionData?: SubmissionData | ||||||
|  |     }>(); | ||||||
|  | 
 | ||||||
|  |     const emit = defineEmits<{ | ||||||
|  |         (e: "update:submissionData", value: SubmissionData): void | ||||||
|  |     }>(); | ||||||
|  | 
 | ||||||
|  |     const submissionData = computed<SubmissionData | undefined>({ | ||||||
|  |         get: () => props.submissionData, | ||||||
|  |         set: (v?: SubmissionData) => v ? emit('update:submissionData', v) : undefined, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     function forEachQuestion( | ||||||
|  |         doAction: (questionIndex: number, questionName: string, questionType: string, questionElement: Element) => void | ||||||
|  |     ) { | ||||||
|  |         const questions = document.querySelectorAll(".gift-question"); | ||||||
|  |         questions.forEach(question => { | ||||||
|  |             const name = question.id.match(/gift-q(\d+)/)?.[1] | ||||||
|  |             const questionType = question.className.split(" ") | ||||||
|  |                 .find(it => it.startsWith("gift-question-type")) | ||||||
|  |                 ?.match(/gift-question-type-([^ ]*)/)?.[1]; | ||||||
|  | 
 | ||||||
|  |             if (!name || isNaN(parseInt(name)) || !questionType) return; | ||||||
|  | 
 | ||||||
|  |             const index = parseInt(name) - 1; | ||||||
|  | 
 | ||||||
|  |             doAction(index, name, questionType, question); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     function attachQuestionListeners(): void { | ||||||
|  |         let counter = 0; | ||||||
|  |         forEachQuestion((index, _name, type, element) => { | ||||||
|  |             getGiftAdapterForType(type)?.installListener( | ||||||
|  |                 element, | ||||||
|  |                 (newAnswer) => { | ||||||
|  |                     submissionData.value = copyArrayWith(index, newAnswer, submissionData.value ?? []) | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |             counter++; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     function setAnswers(answers: SubmissionData) { | ||||||
|  |         forEachQuestion((index, name, type, element) => { | ||||||
|  |             const answer = answers[index]; | ||||||
|  |             if (answer !== null && answer !== undefined) { | ||||||
|  |                 getGiftAdapterForType(type)?.setAnswer(element, answer); | ||||||
|  |             } else if (answer === undefined) { | ||||||
|  |                 answers[index] = null; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         submissionData.value = answers; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     onMounted(() => nextTick(() => attachQuestionListeners())); | ||||||
|  | 
 | ||||||
|  |     watch(() => props.learningObjectContent, async () => { | ||||||
|  |         await nextTick(); | ||||||
|  |         attachQuestionListeners(); | ||||||
|  |     }); | ||||||
|  |     watch(() => props.submissionData, async () => { | ||||||
|  |         await nextTick(); | ||||||
|  |         setAnswers(props.submissionData ?? []); | ||||||
|  |     }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <div | ||||||
|  |         class="learning-object-container" | ||||||
|  |         v-html="learningObjectContent.body.innerHTML" | ||||||
|  |     ></div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | </style> | ||||||
							
								
								
									
										1
									
								
								frontend/src/views/learning-paths/learning-object/submission-data.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								frontend/src/views/learning-paths/learning-object/submission-data.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | export type SubmissionData = (string | number | object | null)[]; | ||||||
|  | @ -0,0 +1,58 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  |     import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data"; | ||||||
|  |     import type {SubmissionDTO} from "@dwengo-1/common/interfaces/submission"; | ||||||
|  |     import {Language} from "@/data-objects/language.ts"; | ||||||
|  |     import {useSubmissionsQuery} from "@/queries/submissions.ts"; | ||||||
|  |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|  |     import SubmitButton from "@/views/learning-paths/learning-object/submissions/SubmitButton.vue"; | ||||||
|  |     import {watch} from "vue"; | ||||||
|  | 
 | ||||||
|  |     const props = defineProps<{ | ||||||
|  |         submissionData?: SubmissionData, | ||||||
|  |         hruid: string; | ||||||
|  |         language: Language; | ||||||
|  |         version: number, | ||||||
|  |         group: {forGroup: number, assignmentNo: number, classId: string} | ||||||
|  |     }>(); | ||||||
|  |     const emit = defineEmits<{ | ||||||
|  |         (e: "update:submissionData", value: SubmissionData): void | ||||||
|  |     }>(); | ||||||
|  | 
 | ||||||
|  |     const submissionQuery = useSubmissionsQuery( | ||||||
|  |         () => props.hruid, | ||||||
|  |         () => props.language, | ||||||
|  |         () => props.version, | ||||||
|  |         () => props.group.classId, | ||||||
|  |         () => props.group.assignmentNo, | ||||||
|  |         () => props.group.forGroup, | ||||||
|  |         () => true | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     function loadSubmission(submission: SubmissionDTO) { | ||||||
|  |         emit("update:submissionData", JSON.parse(submission.content)); | ||||||
|  |         console.log(`emitted: ${JSON.parse(submission.content)}`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     watch(submissionQuery.data, () => { | ||||||
|  |         const submissions = submissionQuery.data.value; | ||||||
|  |         if (submissions && submissions.length > 0) { | ||||||
|  |             loadSubmission(submissions[submissions.length - 1]); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <using-query-result :query-result="submissionQuery" v-slot="submissions: { data: SubmissionDTO[] }"> | ||||||
|  |         <submit-button | ||||||
|  |             :hruid="props.hruid" | ||||||
|  |             :language="props.language" | ||||||
|  |             :version="props.version" | ||||||
|  |             :group="props.group" | ||||||
|  |             :submission-data="props.submissionData" | ||||||
|  |             :submissions="submissions.data" | ||||||
|  |         /> | ||||||
|  |     </using-query-result> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,102 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  |     import {computed} from "vue"; | ||||||
|  |     import authService from "@/services/auth/auth-service.ts"; | ||||||
|  |     import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data"; | ||||||
|  |     import {Language} from "@/data-objects/language.ts"; | ||||||
|  |     import type {SubmissionDTO} from "@dwengo-1/common/interfaces/submission"; | ||||||
|  |     import {useCreateSubmissionMutation} from "@/queries/submissions.ts"; | ||||||
|  |     import {deepEquals} from "@/utils/deep-equals.ts"; | ||||||
|  |     import type {UserProfile} from "oidc-client-ts"; | ||||||
|  |     import type {LearningObjectIdentifierDTO} from "@dwengo-1/common/interfaces/learning-content"; | ||||||
|  |     import type {StudentDTO} from "@dwengo-1/common/interfaces/student"; | ||||||
|  |     import type {GroupDTO} from "@dwengo-1/common/interfaces/group"; | ||||||
|  |     import {useI18n} from "vue-i18n"; | ||||||
|  | 
 | ||||||
|  |     const { t } = useI18n(); | ||||||
|  | 
 | ||||||
|  |     const props = defineProps<{ | ||||||
|  |         submissionData?: SubmissionData, | ||||||
|  |         submissions: SubmissionDTO[], | ||||||
|  |         hruid: string; | ||||||
|  |         language: Language; | ||||||
|  |         version: number, | ||||||
|  |         group: {forGroup: number, assignmentNo: number, classId: string} | ||||||
|  |     }>(); | ||||||
|  | 
 | ||||||
|  |     const { | ||||||
|  |         isPending: submissionIsPending, | ||||||
|  |         isError: submissionFailed, | ||||||
|  |         error: submissionError, | ||||||
|  |         isSuccess: submissionSuccess, | ||||||
|  |         mutate: submitSolution | ||||||
|  |     } = useCreateSubmissionMutation(); | ||||||
|  | 
 | ||||||
|  |     const isStudent = computed(() => authService.authState.activeRole === "student"); | ||||||
|  | 
 | ||||||
|  |     const isSubmitDisabled = computed(() => { | ||||||
|  |         if (!props.submissionData || props.submissions === undefined) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         if (props.submissionData.some(answer => answer === null)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         if (props.submissions.length === 0) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return deepEquals( | ||||||
|  |             JSON.parse(props.submissions[props.submissions.length - 1].content), | ||||||
|  |             props.submissionData | ||||||
|  |         ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     function submitCurrentAnswer(): void { | ||||||
|  |         const { forGroup, assignmentNo, classId } = props.group!; | ||||||
|  |         const currentUser: UserProfile = authService.authState.user!.profile; | ||||||
|  |         const learningObjectIdentifier: LearningObjectIdentifierDTO = { | ||||||
|  |             hruid: props.hruid, | ||||||
|  |             language: props.language as Language, | ||||||
|  |             version: props.version | ||||||
|  |         }; | ||||||
|  |         const submitter: StudentDTO = { | ||||||
|  |             id: currentUser.preferred_username!, | ||||||
|  |             username: currentUser.preferred_username!, | ||||||
|  |             firstName: currentUser.given_name!, | ||||||
|  |             lastName: currentUser.family_name! | ||||||
|  |         }; | ||||||
|  |         const group: GroupDTO = { | ||||||
|  |             class: classId, | ||||||
|  |             assignment: assignmentNo, | ||||||
|  |             groupNumber: forGroup | ||||||
|  |         } | ||||||
|  |         const submission: SubmissionDTO = { | ||||||
|  |             learningObjectIdentifier, | ||||||
|  |             submitter, | ||||||
|  |             group, | ||||||
|  |             content: JSON.stringify(props.submissionData) | ||||||
|  |         } | ||||||
|  |         submitSolution({ data: submission }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const buttonText = computed(() => { | ||||||
|  |         if (props.submissionData && props.submissionData.length === 0) { | ||||||
|  |             return t("markAsDone"); | ||||||
|  |         } | ||||||
|  |         return t("submit"); | ||||||
|  |     }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <v-btn v-if="isStudent" | ||||||
|  |            prepend-icon="mdi-check" | ||||||
|  |            variant="elevated" | ||||||
|  |            :loading="submissionIsPending" | ||||||
|  |            :disabled="isSubmitDisabled" | ||||||
|  |            @click="submitCurrentAnswer()" | ||||||
|  |     > | ||||||
|  |         {{ buttonText }} | ||||||
|  |     </v-btn> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | 
 | ||||||
|  | </style> | ||||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger