VBV-519: Anpassungen Darstellung Wissens- und Verständnisfragen für Lernende
This commit is contained in:
parent
589453a8dc
commit
f8c6daf9eb
|
|
@ -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]"
|
||||
)}`;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue