feat(backend): De meest recente indiening wordt automatisch ingeladen.

This commit is contained in:
Gerald Schmittinger 2025-04-18 12:37:17 +02:00
parent 1685c518b6
commit 63c3d6aaa0
18 changed files with 406 additions and 263 deletions

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1 @@
export type SubmissionData = (string | number | object | null)[];

View file

@ -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>

View file

@ -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>