diff --git a/client/src/components/dueDates/dueDatesUtils.ts b/client/src/components/dueDates/dueDatesUtils.ts
index 0c305f05..850377f8 100644
--- a/client/src/components/dueDates/dueDatesUtils.ts
+++ b/client/src/components/dueDates/dueDatesUtils.ts
@@ -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]"
)}`;
diff --git a/client/src/pages/learningPath/learningContentPage/blocks/EdoniqTestBlock.vue b/client/src/pages/learningPath/learningContentPage/blocks/EdoniqTestBlock.vue
index 22b47398..47abd727 100644
--- a/client/src/pages/learningPath/learningContentPage/blocks/EdoniqTestBlock.vue
+++ b/client/src/pages/learningPath/learningContentPage/blocks/EdoniqTestBlock.vue
@@ -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"
>
-
-
-
-
- {{ $t("a.Bewertung") }}:
- {{ assignmentCompletion?.evaluation_points }}
- {{ $t("assignment.von x Punkten", { x: assignment?.max_points }) }}
-
-
Ergebnisse abgeben, Bewertung ausstehend
-
-
-
+
+
+ {{ $t("a.Aufgabe") }}
+ {{ $t("edoniqTest.testDescription") }}
+
-
+
+ {{ $t("a.Abgabetermin") }}
+
+ {{
+ $t("edoniqTest.submitDateDescription", {
+ x: formatDueDate(courseSessionEdoniqTest.deadline_start),
+ })
+ }}
+
+
+
+
+ {{ $t("a.Kompetenznachweis") }}
+
+ {{
+ $t("circlePage.Dieser Inhalt gehört zu x", {
+ x: content.competence_certificate?.title,
+ })
+ }}.
+
+
+
+ {{ $t("circlePage.Im KompetenzNavi anschauen") }}
+
+
+
+
+
+
+
{{ $t("edoniqTest.checkboxTitle") }}
-
+
{{ $t("edoniqTest.qualifiesForExtendedTimeTitle") }}
-
+
+
+
+
+ {{ $t("edoniqTest.deadlineInPast") }}
+
+
+ {{ $t("a.Abgabetermin") }}:
+ {{ getDateString(dayjs(courseSessionEdoniqTest?.deadline_start)) }}
+
+
-
+
+
+
+
+
+
+ {{ $t("a.Resultat") }}:
+
+ {{ assignmentCompletion.evaluation_points }}
+
+ {{
+ $t("assignment.von x Punkten", {
+ x: assignmentCompletion.evaluation_max_points,
+ })
+ }}
+
+
+
+
+
+
+
diff --git a/client/src/services/assignmentService.ts b/client/src/services/assignmentService.ts
index 439dfb5d..dc6bb4da 100644
--- a/client/src/services/assignmentService.ts
+++ b/client/src/services/assignmentService.ts
@@ -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;
+}
diff --git a/client/src/stores/courseSessions.ts b/client/src/stores/courseSessions.ts
index 7c6bb476..d5fd89d2 100644
--- a/client/src/stores/courseSessions.ts
+++ b/client/src/stores/courseSessions.ts
@@ -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
diff --git a/client/src/types.ts b/client/src/types.ts
index 18b8e04d..4ea6bda2 100644
--- a/client/src/types.ts
+++ b/client/src/types.ts
@@ -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[];
diff --git a/server/vbv_lernwelt/course/creators/test_course.py b/server/vbv_lernwelt/course/creators/test_course.py
index ac32e2fa..4582f963 100644
--- a/server/vbv_lernwelt/course/creators/test_course.py
+++ b/server/vbv_lernwelt/course/creators/test_course.py
@@ -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",
diff --git a/server/vbv_lernwelt/course/serializers.py b/server/vbv_lernwelt/course/serializers.py
index 69d326b2..a1b1ba56 100644
--- a/server/vbv_lernwelt/course/serializers.py
+++ b/server/vbv_lernwelt/course/serializers.py
@@ -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",
diff --git a/server/vbv_lernwelt/course_session/serializers.py b/server/vbv_lernwelt/course_session/serializers.py
index 76c9d9f8..7bec74a2 100644
--- a/server/vbv_lernwelt/course_session/serializers.py
+++ b/server/vbv_lernwelt/course_session/serializers.py
@@ -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