VBV-519: Anpassungen Darstellung Wissens- und Verständnisfragen für Lernende

This commit is contained in:
Daniel Egger 2023-09-28 16:38:23 +02:00
parent 589453a8dc
commit f8c6daf9eb
8 changed files with 182 additions and 39 deletions

View File

@ -3,23 +3,25 @@ import dayjs from "dayjs";
import LocalizedFormat from "dayjs/plugin/localizedFormat";
import i18next from "i18next";
export const formatDueDate = (start: string, end: string) => {
export const formatDueDate = (start: string, end?: string) => {
dayjs.extend(LocalizedFormat);
const startDayjs = dayjs(start);
const endDayjs = dayjs(end);
const startDateString = getDateString(startDayjs);
const endDateString = getDateString(endDayjs);
// if startDayjs isundefined, dont show the day twice
let endDayjs;
let endDateString;
if (end) {
endDayjs = dayjs(end);
endDateString = getDateString(endDayjs);
}
if (!startDayjs.isValid() && !endDayjs.isValid()) {
// at least `start` must be provided and valid
if (!startDayjs.isValid()) {
return i18next.t("Termin nicht festgelegt");
}
if (!startDayjs || (!startDayjs.isValid() && endDayjs.isValid())) {
return `${endDateString} ${getTimeString(endDayjs)} ${endDayjs.format("[Uhr]")}`;
}
if (!endDayjs || (!endDayjs.isValid() && startDayjs.isValid())) {
// when only `start` is provided, show only the start date with time
if (!endDayjs || !endDayjs.isValid()) {
return `${startDateString} ${getTimeString(startDayjs)} ${startDayjs.format(
"[Uhr]"
)}`;

View File

@ -2,17 +2,20 @@
import { useTranslation } from "i18next-vue";
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
import type {
Assignment,
AssignmentCompletion,
LearningContentEdoniqTest,
} from "@/types";
import type { AssignmentCompletion, LearningContentEdoniqTest } from "@/types";
import { computed, ref } from "vue";
import * as log from "loglevel";
import { itPost } from "@/fetchHelpers";
import { useQuery } from "@urql/vue";
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
import { useCurrentCourseSession } from "@/composables";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import dayjs from "dayjs";
import {
formatDueDate,
getDateString,
} from "../../../../components/dueDates/dueDatesUtils";
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
const { t } = useTranslation();
@ -21,6 +24,11 @@ const props = defineProps<{
}>();
const courseSession = useCurrentCourseSession();
const courseSessionsStore = useCourseSessionsStore();
const courseSessionEdoniqTest = computed(() => {
return courseSessionsStore.findCourseSessionEdoniqTest(props.content.id);
});
const queryResult = useQuery({
query: ASSIGNMENT_COMPLETION_QUERY,
@ -31,21 +39,25 @@ const queryResult = useQuery({
},
});
const assignment = computed(
() => queryResult.data.value?.assignment as Assignment | undefined
);
const assignmentCompletion = computed(
() =>
queryResult.data.value?.assignment_completion as AssignmentCompletion | undefined
);
const completionStatus = computed(() => {
return assignmentCompletion.value?.completion_status ?? "IN_PROGRESS";
return assignmentCompletion.value?.completion_status ?? "";
});
const termsAccepted = ref(false);
const extendedTimeTest = ref(false);
const deadlineInPast = computed(() => {
// with 16 minutes buffer
return dayjs(courseSessionEdoniqTest.value?.deadline_start)
.add(16, "minute")
.isBefore(dayjs());
});
async function startTest() {
log.info("start test", props.content);
const response = await itPost("/api/core/edoniq-test/redirect/", {
@ -62,24 +74,46 @@ async function startTest() {
:title="props.content.title"
:learning-content="props.content"
>
<!-- eslint-disable vue/no-v-html -->
<div v-if="queryResult.data" class="container-medium">
<div v-if="completionStatus !== 'IN_PROGRESS'">
<div v-if="completionStatus === 'EVALUATION_SUBMITTED'">
{{ $t("a.Bewertung") }}:
{{ assignmentCompletion?.evaluation_points }}
{{ $t("assignment.von x Punkten", { x: assignment?.max_points }) }}
</div>
<div v-else>Ergebnisse abgeben, Bewertung ausstehend</div>
</div>
<div v-else>
<p
v-if="props.content.description"
class="default-wagtail-rich-text text-large my-4"
v-html="props.content.description"
></p>
<div class="container-medium">
<section class="py-4">
<h3>{{ $t("a.Aufgabe") }}</h3>
<p class="mt-2 text-lg">{{ $t("edoniqTest.testDescription") }}</p>
</section>
<div class="my-8">
<section v-if="courseSessionEdoniqTest" class="py-4">
<h3>{{ $t("a.Abgabetermin") }}</h3>
<p class="mt-2 text-lg">
{{
$t("edoniqTest.submitDateDescription", {
x: formatDueDate(courseSessionEdoniqTest.deadline_start),
})
}}
</p>
</section>
<section class="py-4">
<h3>{{ $t("a.Kompetenznachweis") }}</h3>
<div class="mt-2 text-lg">
{{
$t("circlePage.Dieser Inhalt gehört zu x", {
x: content.competence_certificate?.title,
})
}}.
</div>
<div class="mt-2 text-lg">
<router-link
:to="content.competence_certificate?.frontend_url ?? ''"
class="link"
data-cy="show-results"
>
{{ $t("circlePage.Im KompetenzNavi anschauen") }}
</router-link>
</div>
</section>
<section v-if="!completionStatus">
<div class="mt-8 border p-8">
<h3 class="mb-4">{{ $t("edoniqTest.checkboxTitle") }}</h3>
<ItCheckbox
v-if="props.content.checkbox_text"
:checkbox-item="{
@ -90,7 +124,7 @@ async function startTest() {
@toggle="termsAccepted = !termsAccepted"
/>
</div>
<div class="my-8 border-t pt-8">
<div class="my-8 border-b border-t py-8">
<h3 class="mb-4">{{ $t("edoniqTest.qualifiesForExtendedTimeTitle") }}</h3>
<ItCheckbox
v-if="props.content.has_extended_time_test"
@ -103,17 +137,53 @@ async function startTest() {
/>
</div>
<div class="my-8">
<div>
<button
:disabled="!termsAccepted"
:disabled="!termsAccepted || deadlineInPast"
class="btn-primary inline-flex items-center"
@click="startTest()"
>
{{ $t("edoniqTest.startTest") }}
<it-icon-external-link class="it-icon ml-2 h-5 w-5"></it-icon-external-link>
</button>
<div class="mt-4">
<div v-if="deadlineInPast" class="text-red-700">
{{ $t("edoniqTest.deadlineInPast") }}
</div>
<div v-else>
{{ $t("a.Abgabetermin") }}:
{{ getDateString(dayjs(courseSessionEdoniqTest?.deadline_start)) }}
</div>
</div>
</div>
</div>
</section>
<section v-else>
<div v-if="assignmentCompletion" class="mt-8 border p-8">
<ItSuccessAlert :text="$t('edoniqTest.testSubmitted')" />
<div class="my-4">
{{ $t("a.Resultat") }}:
<span class="font-bold">
{{ assignmentCompletion.evaluation_points }}
</span>
{{
$t("assignment.von x Punkten", {
x: assignmentCompletion.evaluation_max_points,
})
}}
</div>
<div class="mt-4">
<button class="btn-primary inline-flex items-center" @click="startTest()">
{{ $t("edoniqTest.viewResults") }}
<it-icon-external-link
class="it-icon ml-2 h-5 w-5"
></it-icon-external-link>
</button>
</div>
</div>
</section>
</div>
</LearningContentSimpleLayout>
</template>

View File

@ -121,3 +121,10 @@ export function userAssignmentPoints(
)
);
}
export function pointsToGrade(points: number, maxPoints: number) {
// round to half-grades
const grade = Math.round((points / maxPoints) * 10);
const halfGrade = grade / 2;
return Math.min(halfGrade, 5) + 1;
}

View File

@ -5,6 +5,7 @@ import type {
CourseSession,
CourseSessionAssignment,
CourseSessionAttendanceCourse,
CourseSessionEdoniqTest,
CourseSessionUser,
DueDate,
ExpertSessionUser,
@ -264,6 +265,16 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
}
}
function findCourseSessionEdoniqTest(
contentId?: number
): CourseSessionEdoniqTest | undefined {
if (contentId && currentCourseSession.value) {
return currentCourseSession.value.edoniq_tests.find(
(a) => a.learning_content_id === contentId
);
}
}
return {
uniqueCourseSessionsByCourse,
allCurrentCourseSessions,
@ -280,6 +291,7 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
removeDocument,
findAttendanceCourse,
findCourseSessionAssignment,
findCourseSessionEdoniqTest,
allDueDates,
// use `useCurrentCourseSession` whenever possible

View File

@ -485,6 +485,14 @@ export interface CourseSessionAssignment {
evaluation_deadline_start: string;
}
export interface CourseSessionEdoniqTest {
id: number;
course_session_id: number;
learning_content_id: number;
deadline_id: number;
deadline_start: string;
}
export interface CourseSession {
id: number;
created_at: string;
@ -500,6 +508,7 @@ export interface CourseSession {
media_library_url: string;
attendance_courses: CourseSessionAttendanceCourse[];
assignments: CourseSessionAssignment[];
edoniq_tests: CourseSessionEdoniqTest[];
documents: CircleDocument[];
users: CourseSessionUser[];
due_dates: DueDate[];

View File

@ -49,12 +49,14 @@ from vbv_lernwelt.course.utils import get_wagtail_default_site
from vbv_lernwelt.course_session.models import (
CourseSessionAssignment,
CourseSessionAttendanceCourse,
CourseSessionEdoniqTest,
)
from vbv_lernwelt.feedback.services import update_feedback_response
from vbv_lernwelt.learnpath.models import (
Circle,
LearningContentAssignment,
LearningContentAttendanceCourse,
LearningContentEdoniqTest,
)
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
CircleFactory,
@ -168,6 +170,19 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
)
csa.submission_deadline.save()
cset = CourseSessionEdoniqTest.objects.create(
course_session=cs_bern,
learning_content=LearningContentEdoniqTest.objects.get(
slug="test-lehrgang-lp-circle-fahrzeug-lc-wissens-und-verständnisfragen"
),
)
cset.deadline.start = timezone.make_aware(
(next_monday + relativedelta(days=3)).replace(
hour=21, minute=0, second=0, microsecond=0
)
)
cset.deadline.save()
cs_zurich = CourseSession.objects.create(
course_id=COURSE_TEST_ID,
title="Test Zürich 2022 a",

View File

@ -10,10 +10,12 @@ from vbv_lernwelt.course.models import (
from vbv_lernwelt.course_session.models import (
CourseSessionAssignment,
CourseSessionAttendanceCourse,
CourseSessionEdoniqTest,
)
from vbv_lernwelt.course_session.serializers import (
CourseSessionAssignmentSerializer,
CourseSessionAttendanceCourseSerializer,
CourseSessionEdoniqTestSerializer,
)
from vbv_lernwelt.duedate.models import DueDate
from vbv_lernwelt.duedate.serializers import DueDateSerializer
@ -61,6 +63,7 @@ class CourseSessionSerializer(serializers.ModelSerializer):
documents = serializers.SerializerMethodField()
attendance_courses = serializers.SerializerMethodField()
assignments = serializers.SerializerMethodField()
edoniq_tests = serializers.SerializerMethodField()
due_dates = serializers.SerializerMethodField()
def get_course(self, obj):
@ -97,6 +100,11 @@ class CourseSessionSerializer(serializers.ModelSerializer):
CourseSessionAssignment.objects.filter(course_session=obj), many=True
).data
def get_edoniq_tests(self, obj):
return CourseSessionEdoniqTestSerializer(
CourseSessionEdoniqTest.objects.filter(course_session=obj), many=True
).data
def get_due_dates(self, obj):
due_dates = DueDate.objects.filter(course_session=obj)
return DueDateSerializer(due_dates, many=True).data
@ -114,6 +122,7 @@ class CourseSessionSerializer(serializers.ModelSerializer):
"additional_json_data",
"attendance_courses",
"assignments",
"edoniq_tests",
"learning_path_url",
"cockpit_url",
"competence_url",

View File

@ -3,6 +3,7 @@ from rest_framework import serializers
from vbv_lernwelt.course_session.models import (
CourseSessionAssignment,
CourseSessionAttendanceCourse,
CourseSessionEdoniqTest,
)
@ -61,3 +62,21 @@ class CourseSessionAssignmentSerializer(serializers.ModelSerializer):
def get_submission_deadline_start(self, obj):
if obj.submission_deadline:
return obj.submission_deadline.start
class CourseSessionEdoniqTestSerializer(serializers.ModelSerializer):
deadline_start = serializers.SerializerMethodField()
class Meta:
model = CourseSessionEdoniqTest
fields = [
"id",
"course_session_id",
"learning_content_id",
"deadline_id",
"deadline_start",
]
def get_deadline_start(self, obj):
if obj.deadline:
return obj.deadline.start