From 63c3d6aaa0b1ac45c2e7b51fda0331a6c1d23e53 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Fri, 18 Apr 2025 12:37:17 +0200 Subject: [PATCH] feat(backend): De meest recente indiening wordt automatisch ingeladen. --- backend/src/interfaces/submission.ts | 5 +- .../error-handling/error-handler.ts | 2 +- frontend/src/i18n/locale/de.json | 26 +-- frontend/src/i18n/locale/en.json | 4 +- frontend/src/i18n/locale/nl.json | 4 +- frontend/src/queries/learning-paths.ts | 4 +- frontend/src/queries/submissions.ts | 79 +++----- frontend/src/router/index.ts | 2 +- frontend/src/utils/array-utils.ts | 5 + frontend/src/utils/deep-equals.ts | 20 ++ .../learning-paths/LearningObjectView.vue | 174 ------------------ .../views/learning-paths/LearningPathPage.vue | 23 ++- .../multiple-choice-question-adapter.ts | 2 - .../learning-object/LearningObjectView.vue | 73 ++++++++ .../content/LearningObjectContentView.vue | 85 +++++++++ .../learning-object/submission-data.d.ts | 1 + .../LearningObjectSubmissionsView.vue | 58 ++++++ .../submissions/SubmitButton.vue | 102 ++++++++++ 18 files changed, 406 insertions(+), 263 deletions(-) create mode 100644 frontend/src/utils/array-utils.ts create mode 100644 frontend/src/utils/deep-equals.ts delete mode 100644 frontend/src/views/learning-paths/LearningObjectView.vue create mode 100644 frontend/src/views/learning-paths/learning-object/LearningObjectView.vue create mode 100644 frontend/src/views/learning-paths/learning-object/content/LearningObjectContentView.vue create mode 100644 frontend/src/views/learning-paths/learning-object/submission-data.d.ts create mode 100644 frontend/src/views/learning-paths/learning-object/submissions/LearningObjectSubmissionsView.vue create mode 100644 frontend/src/views/learning-paths/learning-object/submissions/SubmitButton.vue diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index aa88f4a1..15e2fc31 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -1,5 +1,5 @@ import { Submission } from '../entities/assignments/submission.entity.js'; -import { mapToGroupDTO } from './group.js'; +import { mapToGroupDTOId } from './group.js'; import { mapToStudentDTO } from './student.js'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; import { getSubmissionRepository } from '../data/repositories'; @@ -13,11 +13,10 @@ export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { language: submission.learningObjectLanguage, version: submission.learningObjectVersion, }, - submissionNumber: submission.submissionNumber, submitter: mapToStudentDTO(submission.submitter), time: submission.submissionTime, - group: mapToGroupDTO(submission.onBehalfOf), + group: mapToGroupDTOId(submission.onBehalfOf), content: submission.content, }; } diff --git a/backend/src/middleware/error-handling/error-handler.ts b/backend/src/middleware/error-handling/error-handler.ts index d7315603..12de68be 100644 --- a/backend/src/middleware/error-handling/error-handler.ts +++ b/backend/src/middleware/error-handling/error-handler.ts @@ -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})`); res.status(err.status).json(err); } 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); } } diff --git a/frontend/src/i18n/locale/de.json b/frontend/src/i18n/locale/de.json index d7bded04..0b82d707 100644 --- a/frontend/src/i18n/locale/de.json +++ b/frontend/src/i18n/locale/de.json @@ -1,13 +1,13 @@ { "welcome": "Willkommen", - "student": "schüler", - "teacher": "lehrer", + "student": "Schüler", + "teacher": "Lehrer", "assignments": "Aufgaben", - "classes": "Klasses", + "classes": "Klassen", "discussions": "Diskussionen", "login": "einloggen", "logout": "ausloggen", - "cancel": "kündigen", + "cancel": "abbrechen", "logoutVerification": "Sind Sie sicher, dass Sie sich abmelden wollen?", "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.", @@ -23,10 +23,10 @@ "submitCode": "senden", "members": "Mitglieder", "themes": "Themen", - "choose-theme": "Wähle ein thema", + "choose-theme": "Wählen Sie ein Thema", "choose-age": "Alter auswählen", "theme-options": { - "all": "Alle themen", + "all": "Alle Themen", "culture": "Kultur", "electricity-and-mechanics": "Elektrizität und Mechanik", "nature-and-climate": "Natur und Klima", @@ -37,11 +37,11 @@ "algorithms": "Algorithmisches Denken" }, "age-options": { - "all": "Alle altersgruppen", + "all": "Alle Altersgruppen", "primary-school": "Grundschule", - "lower-secondary": "12-14 jahre alt", - "upper-secondary": "14-16 jahre alt", - "high-school": "16-18 jahre alt", + "lower-secondary": "12-14 Jahre alt", + "upper-secondary": "14-16 Jahre alt", + "high-school": "16-18 Jahre alt", "older": "18 und älter" }, "read-more": "Mehr lesen", @@ -73,7 +73,9 @@ "accept": "akzeptieren", "deny": "ablehnen", "sent": "sent", - "failed": "gescheitert", + "failed": "fehlgeschlagen", "wrong": "etwas ist schief gelaufen", - "created": "erstellt" + "created": "erstellt", + "submit": "Einreichen", + "markAsDone": "Als fertig markieren" } diff --git a/frontend/src/i18n/locale/en.json b/frontend/src/i18n/locale/en.json index 171eb1f8..655ab634 100644 --- a/frontend/src/i18n/locale/en.json +++ b/frontend/src/i18n/locale/en.json @@ -75,5 +75,7 @@ "sent": "sent", "failed": "failed", "wrong": "something went wrong", - "created": "created" + "created": "created", + "submit": "Submit", + "markAsDone": "Mark as done" } diff --git a/frontend/src/i18n/locale/nl.json b/frontend/src/i18n/locale/nl.json index 1fbed08d..0f4aca6a 100644 --- a/frontend/src/i18n/locale/nl.json +++ b/frontend/src/i18n/locale/nl.json @@ -75,5 +75,7 @@ "sent": "verzonden", "failed": "mislukt", "wrong": "er ging iets verkeerd", - "created": "gecreëerd" + "created": "gecreëerd", + "submit": "Indienen", + "markAsDone": "Markeren als afgewerkt" } diff --git a/frontend/src/queries/learning-paths.ts b/frontend/src/queries/learning-paths.ts index 290cd284..b9d3624a 100644 --- a/frontend/src/queries/learning-paths.ts +++ b/frontend/src/queries/learning-paths.ts @@ -10,13 +10,11 @@ const learningPathController = getLearningPathController(); export function useGetLearningPathQuery( hruid: MaybeRefOrGetter, language: MaybeRefOrGetter, - forGroup?: MaybeRefOrGetter<{forGroup: number, assignmentNo: number, classId: string}>, + forGroup?: MaybeRefOrGetter<{forGroup: number, assignmentNo: number, classId: string} | undefined>, ): UseQueryReturnType { return useQuery({ queryKey: [LEARNING_PATH_KEY, "get", toValue(hruid), toValue(language), toValue(forGroup)], 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)]; return learningPathController.getBy(hruidVal, languageVal, forGroupVal); }, diff --git a/frontend/src/queries/submissions.ts b/frontend/src/queries/submissions.ts index 7458a013..ec2bcf23 100644 --- a/frontend/src/queries/submissions.ts +++ b/frontend/src/queries/submissions.ts @@ -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 { QueryClient, @@ -13,17 +13,7 @@ import {LEARNING_PATH_KEY} from "@/queries/learning-paths.ts"; import {LEARNING_OBJECT_KEY} from "@/queries/learning-objects.ts"; import type {Language} from "@dwengo-1/common/util/language"; -function submissionsQueryKey( - hruid: string, - language: Language, - version: number, - classid: string, - assignmentNumber: number, - groupNumber?: number, - full?: boolean -) { - return ["submissions", hruid, language, version, classid, assignmentNumber, groupNumber, full ?? false]; -} +export const SUBMISSION_KEY = "submissions"; function submissionQueryKey( hruid: string, @@ -72,19 +62,8 @@ export async function invalidateAllSubmissionKeys( }); } -function checkEnabled( - classid: string | undefined, - 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 checkEnabled(properties: MaybeRefOrGetter[]): boolean { + return properties.every(prop => !!toValue(prop)); } function toValues( @@ -110,31 +89,24 @@ export function useSubmissionsQuery( assignmentNumber: MaybeRefOrGetter, groupNumber: MaybeRefOrGetter, full: MaybeRefOrGetter = true, -): UseQueryReturnType { - 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); - +): UseQueryReturnType { return useQuery({ - queryKey: computed(() => - submissionsQueryKey( - hruidVal!, - languageVal!, - versionVal!, - classIdVal!, - assignmentNumberVal!, - groupNumberVal, - fullVal - ) - ), - queryFn: async () => new SubmissionController(hruidVal!).getAll( - languageVal!, versionVal!, classIdVal!, assignmentNumberVal!, groupNumberVal, fullVal - ), - enabled: () => !!hruidVal && !!languageVal && !!versionVal && !!classIdVal && !!assignmentNumberVal, + queryKey: ["submissions", hruid, language, version, classid, assignmentNumber, groupNumber, full], + queryFn: async () => { + 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); + + const response = await new SubmissionController(hruidVal!).getAll( + languageVal!, versionVal!, classIdVal!, assignmentNumberVal!, groupNumberVal, fullVal + ); + return response ? response.submissions as SubmissionDTO[] : undefined; + }, + enabled: () => checkEnabled([hruid, language, version, classid, assignmentNumber]), }); } @@ -147,8 +119,6 @@ export function useSubmissionQuery( groupNumber: MaybeRefOrGetter, submissionNumber: MaybeRefOrGetter, ): UseQueryReturnType { - const { cid, an, gn, sn, f } = toValues(classid, assignmentNumber, groupNumber, submissionNumber, true); - const hruidVal = toValue(hruid); const languageVal = toValue(language); const versionVal = toValue(version); @@ -192,11 +162,6 @@ export function useCreateSubmissionMutation(): UseMutationReturnType< const {hruid, language, version} = response.submission.learningObjectIdentifier; 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({ @@ -216,7 +181,7 @@ export function useDeleteSubmissionMutation(): UseMutationReturnType< const queryClient = useQueryClient(); 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) => { if (!response.submission.group) { await invalidateAllSubmissionKeys(queryClient); diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 23695680..5211d5f8 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -15,7 +15,7 @@ import LearningPathPage from "@/views/learning-paths/LearningPathPage.vue"; import LearningPathSearchPage from "@/views/learning-paths/LearningPathSearchPage.vue"; import UserHomePage from "@/views/homepage/UserHomePage.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({ history: createWebHistory(import.meta.env.BASE_URL), diff --git a/frontend/src/utils/array-utils.ts b/frontend/src/utils/array-utils.ts new file mode 100644 index 00000000..cba3291e --- /dev/null +++ b/frontend/src/utils/array-utils.ts @@ -0,0 +1,5 @@ +export function copyArrayWith(index: number, newValue: T, array: T[]) { + const copy = [...array]; + copy[index] = newValue; + return copy; +} diff --git a/frontend/src/utils/deep-equals.ts b/frontend/src/utils/deep-equals.ts new file mode 100644 index 00000000..84afee2e --- /dev/null +++ b/frontend/src/utils/deep-equals.ts @@ -0,0 +1,20 @@ +export function deepEquals(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])); +} diff --git a/frontend/src/views/learning-paths/LearningObjectView.vue b/frontend/src/views/learning-paths/LearningObjectView.vue deleted file mode 100644 index bfa7a6b9..00000000 --- a/frontend/src/views/learning-paths/LearningObjectView.vue +++ /dev/null @@ -1,174 +0,0 @@ - - - - - diff --git a/frontend/src/views/learning-paths/LearningPathPage.vue b/frontend/src/views/learning-paths/LearningPathPage.vue index 12103712..2412cee0 100644 --- a/frontend/src/views/learning-paths/LearningPathPage.vue +++ b/frontend/src/views/learning-paths/LearningPathPage.vue @@ -4,7 +4,7 @@ import { computed, type ComputedRef, ref } from "vue"; import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts"; 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 LearningPathSearchField from "@/components/LearningPathSearchField.vue"; import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; @@ -185,13 +185,15 @@ - +
+ +