Merged in feature/VBV-291-kn-frontend-teilnehmer (pull request #56)
VBV-291 Auftrag Frontend * Fixes in SubmissionView * Change closing button tag * Delete client cypress folder * Add eslint cypress plugin * Add Cypress tests * Reformat de.json * Fix type errors * Fix cypress tests * Add cypress commands * Disable assignment task inputs after submission
This commit is contained in:
parent
f1ab753515
commit
e4b8d7c301
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"body": "Fixtures are a great way to mock data for responses to routes",
|
||||
"email": "hello@cypress.io",
|
||||
"name": "Using fixtures to represent data"
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/// <reference types="cypress" />
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
// declare global {
|
||||
// namespace Cypress {
|
||||
// interface Chainable {
|
||||
// login(email: string, password: string): Chainable<void>
|
||||
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>Components App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div data-cy-root></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
// ***********************************************************
|
||||
// This example support/component.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import "../../tailwind.css";
|
||||
import "./commands";
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
import { setupI18n } from "@/i18n.ts";
|
||||
import router from "@/router";
|
||||
import { mount } from "cypress/vue";
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
// Augment the Cypress namespace to include type definitions for
|
||||
// your custom command.
|
||||
// Alternatively, can be defined in cypress/support/component.d.ts
|
||||
// with a <reference path="./component" /> at the top of your spec.
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
mount: typeof mount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add("mount", (component, options = {}) => {
|
||||
options.global = options.global || {};
|
||||
options.global.plugins = options.global.plugins || [];
|
||||
options.global.plugins.push(setupI18n());
|
||||
options.global.plugins.push(createPinia());
|
||||
|
||||
if (!options.router) {
|
||||
options.router = router;
|
||||
}
|
||||
|
||||
return mount(component, options);
|
||||
});
|
||||
|
||||
// Example use:
|
||||
// cy.mount(MyComponent)
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es5", "dom"],
|
||||
"target": "es5",
|
||||
"types": ["cypress", "node"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
|
|
@ -60,6 +60,7 @@
|
|||
"autoprefixer": "^10.4.8",
|
||||
"eslint": "8.37",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-cypress": "^2.13.3",
|
||||
"eslint-plugin-storybook": "^0.6.11",
|
||||
"eslint-plugin-vue": "^9.4.0",
|
||||
"jsdom": "^21.1.1",
|
||||
|
|
@ -10824,6 +10825,18 @@
|
|||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-cypress": {
|
||||
"version": "2.13.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.13.3.tgz",
|
||||
"integrity": "sha512-nAPjZE5WopCsgJwl3vHm5iafpV+ZRO76Z9hMyRygWhmg5ODXDPd+9MaPl7kdJ2azj+sO87H3P1PRnggIrz848g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"globals": "^11.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">= 3.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
|
||||
|
|
@ -27863,6 +27876,15 @@
|
|||
"integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-plugin-cypress": {
|
||||
"version": "2.13.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.13.3.tgz",
|
||||
"integrity": "sha512-nAPjZE5WopCsgJwl3vHm5iafpV+ZRO76Z9hMyRygWhmg5ODXDPd+9MaPl7kdJ2azj+sO87H3P1PRnggIrz848g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"globals": "^11.12.0"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-prettier": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@
|
|||
"autoprefixer": "^10.4.8",
|
||||
"eslint": "8.37",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-cypress": "^2.13.3",
|
||||
"eslint-plugin-storybook": "^0.6.11",
|
||||
"eslint-plugin-vue": "^9.4.0",
|
||||
"jsdom": "^21.1.1",
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
:show-exit-button="stepNo + 1 === numSteps"
|
||||
:current-step="stepNo"
|
||||
:steps-count="numSteps"
|
||||
:start-badge-text="$t('feedback.introduction')"
|
||||
:end-badge-text="$t('feedback.submission')"
|
||||
:start-badge-text="$t('general.introduction')"
|
||||
:end-badge-text="$t('general.submission')"
|
||||
@previous="previousStep()"
|
||||
@next="nextStep()"
|
||||
>
|
||||
|
|
@ -141,7 +141,7 @@ const title = computed(
|
|||
);
|
||||
|
||||
const stepLabels = [
|
||||
t("feedback.introduction"),
|
||||
t("general.introduction"),
|
||||
t("feedback.recommendLabel"),
|
||||
t("feedback.satisfactionLabel"),
|
||||
t("feedback.goalAttainmentLabel"),
|
||||
|
|
@ -152,7 +152,7 @@ const stepLabels = [
|
|||
t("feedback.instructorOpenFeedbackLabel"),
|
||||
t("feedback.courseNegativeFeedbackLabel"),
|
||||
t("feedback.coursePositiveFeedbackLabel"),
|
||||
t("feedback.submission"),
|
||||
t("general.submission"),
|
||||
];
|
||||
|
||||
const numSteps = stepLabels.length;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import { RadioGroup, RadioGroupLabel, RadioGroupOption } from "@headlessui/vue";
|
|||
interface Props {
|
||||
modelValue: any;
|
||||
items: RadioItem<any>[];
|
||||
label?: string;
|
||||
label: string | undefined;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
||||
import type { Meta, StoryObj } from "@storybook/vue3";
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/vue/writing-stories/introduction
|
||||
const meta: Meta<typeof ItSuccessAlert> = {
|
||||
title: "VBV/SuccessAlert",
|
||||
component: ItSuccessAlert,
|
||||
// This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/7.0/vue/writing-docs/docs-page
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ItSuccessAlert>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
text: "Deiner Praxisauftrag wurde abgegeben",
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
text: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-row items-center space-x-2 bg-green-200 px-6">
|
||||
<it-icon-check class="it-icon"></it-icon-check>
|
||||
<p class="text-large py-4">{{ text }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
<textarea
|
||||
:value="modelValue"
|
||||
class="h-40 w-full border-gray-500"
|
||||
:data-cy="`it-textarea-${cyKey}`"
|
||||
:disabled="disabled"
|
||||
@input="onInput"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -12,11 +14,14 @@
|
|||
<script setup lang="ts">
|
||||
interface Props {
|
||||
modelValue: string;
|
||||
label?: string;
|
||||
label: string | undefined;
|
||||
cyKey?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
label: undefined,
|
||||
cyKey: "",
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,26 @@
|
|||
{
|
||||
"assignment": {
|
||||
"acceptConditionsDisclaimer": "Bedingungen akzeptieren und Ergebnisse abgeben",
|
||||
"assessmentDocumentDisclaimer": "Diese geleitete Fallarbeit wird auf Grund des folgenden Beurteilungsinstrument bewertet:",
|
||||
"assessmentTitle": "Bewertung",
|
||||
"assignmentSubmitted": "Du hast deine Ergebnisse erfolgreich abgegeben.",
|
||||
"confirmSubmitPerson": "Hiermit bestätige ich, dass die folgende Person meine Ergebnisse bewerten soll.",
|
||||
"confirmSubmitResults": "Hiermit bestätige ich, dass ich die Zusammenfassung meiner Ergebnisse überprüft habe und so abgeben will.",
|
||||
"dueDateIntroduction": "Reiche deine Ergebnisse pünktlich ein bis am {date} um {time} Uhr ein.",
|
||||
"dueDateNotSet": "Keine Abgabedaten wurden erfasst für diese Durchführung",
|
||||
"dueDateSubmission": "Einreichungstermin: {date}",
|
||||
"dueDateTitle": "Abgabetermin",
|
||||
"edit": "Bearbeiten",
|
||||
"effortTitle": "Zeitaufwand",
|
||||
"initialSituationTitle": "Ausgangslage",
|
||||
"lastChangesNotSaved": "Die letzte Änderung konnte nicht gespeichert werden.",
|
||||
"performanceObjectivesTitle": "Leistungsziele",
|
||||
"showAssessmentDocument": "Bewertungsinstrument anzeigen",
|
||||
"submissionNotificationDisclaimer": "{name} wird deine Ergebnisse bewerten. Du wirst per Benachrichtigung informiert, sobald die Bewertung für dich freigegeben wurde.",
|
||||
"submitAssignment": "Ergebnisse abgeben",
|
||||
"taskDefinition": "Bearbeite die Teilaufgaben und dokumentiere deine Ergebnisse.",
|
||||
"taskDefinitionTitle": "Aufgabenstellung"
|
||||
},
|
||||
"circlePage": {
|
||||
"circleContentBoxTitle": "Das lernst du in diesem Circle.",
|
||||
"contactExpertButton": "Fachexpertin kontaktieren",
|
||||
|
|
@ -70,7 +92,6 @@
|
|||
"instructorOpenFeedbackLabel": "Was ich dem Kursleiter sonst noch sagen wollte:",
|
||||
"instructorRespectLabel": "Fragen und Anregungen der Kursteilnehmenden wurden ernst genommen und aufgegriffen.",
|
||||
"intro": "{name}, dein/e Trainer/-in, bittet dich, ihm/ihr Feedback zu geben. Das ist freiwillig, würde aber ihm/ihr helfen, deine Lernerlebniss zu verbessern.",
|
||||
"introduction": "Einleitung",
|
||||
"materialsRatingLabel": "Falls ja: Wie beurteilen Sie die Vorbereitungsunterlagen (z.B. eLearning)?",
|
||||
"noFeedbacks": "Es wurden noch keine Feedbacks abgegeben",
|
||||
"proficiencyLabel": "Wie beurteilen Sie Ihre Sicherheit bezüglichen den Themen nach dem Kurs?",
|
||||
|
|
@ -81,7 +102,6 @@
|
|||
"sendFeedback": "Feedback abschicken",
|
||||
"sentByUsers": "Von {count} Teilnehmern ausgefüllt",
|
||||
"showDetails": "Details anzeigen",
|
||||
"submission": "Abgabe",
|
||||
"unhappy": "Unzufrieden",
|
||||
"veryHappy": "Sehr zufrieden",
|
||||
"veryUnhappy": "Sehr unzufrieden"
|
||||
|
|
@ -99,9 +119,11 @@
|
|||
"backToLearningPath": "zurück zum Lernpfad",
|
||||
"certificate": "Zertifikat | Zertifikate",
|
||||
"circles": "Circles",
|
||||
"close": "Schliessen",
|
||||
"exam": "Prüfung | Prüfungen",
|
||||
"examResult": "Prüfungsresultat | Prüfungsresultate",
|
||||
"feedback": "Feedback | Feedbacks",
|
||||
"introduction": "Einleitung",
|
||||
"learningPath": "Lernpfad",
|
||||
"learningSequence": "Lernsequenz",
|
||||
"learningUnit": "Lerneinheit",
|
||||
|
|
@ -117,6 +139,7 @@
|
|||
"show": "Anschauen",
|
||||
"showAll": "Alle anschauen",
|
||||
"start": "Los geht's",
|
||||
"submission": "Abgabe",
|
||||
"title": "myVBV",
|
||||
"transferTask": "Transferauftrag | Transferaufträge",
|
||||
"yes": "Ja"
|
||||
|
|
@ -126,7 +149,7 @@
|
|||
"fr": "Französisch"
|
||||
},
|
||||
"learningContent": {
|
||||
"completeAndContinue": "Als erledigt markieren"
|
||||
"markAsDone": "Als erledigt markieren"
|
||||
},
|
||||
"learningPathPage": {
|
||||
"currentCircle": "Aktueller Circle",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
<script setup lang="ts">
|
||||
import type { Assignment } from "@/types";
|
||||
import type { Dayjs } from "dayjs";
|
||||
|
||||
interface Props {
|
||||
assignment: Assignment;
|
||||
dueDate?: Dayjs;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
dueDate: undefined,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3 class="mt-8">{{ $t("assignment.initialSituationTitle") }}</h3>
|
||||
<p class="text-large">{{ props.assignment.starting_position }}</p>
|
||||
|
||||
<h3 class="mt-8">{{ $t("assignment.taskDefinitionTitle") }}</h3>
|
||||
<p class="text-large">
|
||||
{{ $t("assignment.taskDefinition") }}
|
||||
</p>
|
||||
<ul>
|
||||
<li v-for="task in props.assignment.tasks" :key="task.id">
|
||||
-
|
||||
<span class="text-large underline">{{ task.value.title }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="mt-8">{{ $t("assignment.dueDateTitle") }}</h3>
|
||||
<p v-if="props.dueDate" class="text-large">
|
||||
{{
|
||||
$t("assignment.dueDateIntroduction", {
|
||||
date: dueDate!.format("DD.MM.YYYY"),
|
||||
time: dueDate!.format("HH:mm"),
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<p v-else class="text-large">
|
||||
{{ $t("assignment.dueDateNotSet") }}
|
||||
</p>
|
||||
|
||||
<h3 class="mt-8">{{ $t("assignment.effortTitle") }}</h3>
|
||||
<p class="text-large">{{ props.assignment.effort_required }}</p>
|
||||
|
||||
<h3 class="mt-8 border-b border-gray-500 pb-2">
|
||||
{{ $t("assignment.performanceObjectivesTitle") }}
|
||||
</h3>
|
||||
<p
|
||||
v-for="performance_objective in props.assignment.performance_objectives"
|
||||
:key="performance_objective.id"
|
||||
class="text-large border-b border-gray-500 py-4"
|
||||
>
|
||||
{{ performance_objective.value.text }}
|
||||
</p>
|
||||
|
||||
<h3 class="mt-8">{{ $t("assignment.assessmentTitle") }}</h3>
|
||||
<p class="text-large">{{ props.assignment.assessment_description }}</p>
|
||||
<a :href="props.assignment.assessment_document_url" class="text-large underline">
|
||||
{{ $t("assignment.showAssessmentDocument") }}
|
||||
</a>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<script setup lang="ts">
|
||||
import type { UserDataText } from "@/stores/assignmentStore";
|
||||
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||
import type { AssignmentTask } from "@/types";
|
||||
import { computed } from "vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "editTask", task: AssignmentTask): void;
|
||||
}>();
|
||||
|
||||
const assignmentStore = useAssignmentStore();
|
||||
|
||||
const completionData = computed(() => assignmentStore.completion_data);
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-for="task in assignmentStore.assignment?.tasks ?? []"
|
||||
:key="task.id"
|
||||
class="mb-6 border-t border-gray-400"
|
||||
>
|
||||
<div class="flex flex-row justify-between pt-8">
|
||||
<p class="text-sm text-gray-900">{{ task.value.title }}</p>
|
||||
<button
|
||||
class="link whitespace-nowrap pl-2 text-sm"
|
||||
@click="emit('editTask', task)"
|
||||
>
|
||||
{{ $t("assignment.edit") }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="taskBlock in task.value.content" :key="taskBlock.id">
|
||||
<p class="pt-6 text-base font-bold">{{ taskBlock.value.text }}</p>
|
||||
<p v-if="completionData && taskBlock.id in completionData" class="font-normal">
|
||||
{{ (completionData[taskBlock.id].user_data as UserDataText).text }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
<script setup lang="ts">
|
||||
import ItButton from "@/components/ui/ItButton.vue";
|
||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
||||
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
||||
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import type { Assignment, AssignmentTask } from "@/types";
|
||||
import type { Dayjs } from "dayjs";
|
||||
import log from "loglevel";
|
||||
import { computed, reactive } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const props = defineProps<{
|
||||
assignment: Assignment;
|
||||
courseSessionId: number;
|
||||
dueDate: Dayjs;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "editTask", task: AssignmentTask): void;
|
||||
}>();
|
||||
|
||||
const assignmentStore = useAssignmentStore();
|
||||
const courseSessionStore = useCourseSessionsStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const state = reactive({
|
||||
confirmInput: false,
|
||||
confirmPerson: false,
|
||||
});
|
||||
|
||||
const circleExpert = computed(() => {
|
||||
return courseSessionStore.circleExperts[0];
|
||||
});
|
||||
|
||||
const circleExpertName = computed(() => {
|
||||
return `${circleExpert.value?.first_name} ${circleExpert.value?.last_name}`;
|
||||
});
|
||||
|
||||
const onEditTask = (task: AssignmentTask) => {
|
||||
emit("editTask", task);
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
const courseSessionId = courseSessionStore.currentCourseSession?.id;
|
||||
if (!courseSessionId) {
|
||||
log.error("Invalid courseSessionId");
|
||||
return;
|
||||
}
|
||||
await assignmentStore.upsertAssignmentCompletion(
|
||||
props.assignment.id,
|
||||
{},
|
||||
courseSessionId,
|
||||
true
|
||||
);
|
||||
} catch (error) {
|
||||
log.error("Could not submit assignment", error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="w-full border border-gray-400 p-8">
|
||||
<h3 class="heading-3 border-b border-gray-400 pb-6">
|
||||
{{ $t("assignment.acceptConditionsDisclaimer") }}
|
||||
</h3>
|
||||
|
||||
<div v-if="!assignmentStore.submitted">
|
||||
<ItCheckbox
|
||||
class="w-full border-b border-gray-400 py-6"
|
||||
:checkbox-item="{
|
||||
label: $t('assignment.confirmSubmitResults'),
|
||||
value: 'value',
|
||||
checked: state.confirmInput,
|
||||
}"
|
||||
@toggle="state.confirmInput = !state.confirmInput"
|
||||
></ItCheckbox>
|
||||
<div class="w-full border-b border-gray-400 py-6">
|
||||
<ItCheckbox
|
||||
:checkbox-item="{
|
||||
label: $t('assignment.confirmSubmitPerson'),
|
||||
value: 'value',
|
||||
checked: state.confirmPerson,
|
||||
}"
|
||||
@toggle="state.confirmPerson = !state.confirmPerson"
|
||||
></ItCheckbox>
|
||||
<div class="flex flex-row items-center pl-[49px] pt-3">
|
||||
<img
|
||||
alt="Notification icon"
|
||||
class="mr-2 h-[45px] min-w-[45px] rounded-full"
|
||||
:src="circleExpert.avatar_url"
|
||||
/>
|
||||
<p class="text-base font-bold">
|
||||
{{ circleExpertName }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- TODO: find way to find user that will do the corrections -->
|
||||
</div>
|
||||
<div class="flex flex-col space-x-2 pt-6 text-base sm:flex-row">
|
||||
<p>{{ $t("assignment.assessmentDocumentDisclaimer") }}</p>
|
||||
<a :href="props.assignment.assessment_document_url" class="underline">
|
||||
{{ $t("assignment.showAssessmentDocument") }}
|
||||
</a>
|
||||
</div>
|
||||
<p class="pt-6">
|
||||
{{ $t("assignment.dueDateSubmission", { date: dueDate.format("DD.MM.YYYY") }) }}
|
||||
</p>
|
||||
<ItButton
|
||||
class="mt-6"
|
||||
variant="primary"
|
||||
size="large"
|
||||
:disabled="!state.confirmInput || !state.confirmPerson"
|
||||
@click="onSubmit"
|
||||
>
|
||||
<p>{{ $t("assignment.submitAssignment") }}</p>
|
||||
</ItButton>
|
||||
</div>
|
||||
<div v-else class="pt-6">
|
||||
<ItSuccessAlert :text="t('assignment.assignmentSubmitted')"></ItSuccessAlert>
|
||||
<p class="pt-6">
|
||||
{{
|
||||
$t("assignment.submissionNotificationDisclaimer", { name: circleExpertName })
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<AssignmentSubmissionResponses
|
||||
@edit-task="onEditTask"
|
||||
></AssignmentSubmissionResponses>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<script setup lang="ts">
|
||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
||||
import type {
|
||||
AssignmentCompletionData,
|
||||
BlockId,
|
||||
UserDataConfirmation,
|
||||
UserDataText,
|
||||
} from "@/stores/assignmentStore";
|
||||
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import type { AssignmentTask } from "@/types";
|
||||
import { useDebounceFn } from "@vueuse/core";
|
||||
import dayjs from "dayjs";
|
||||
import { reactive, ref } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
assignmentId: number;
|
||||
task: AssignmentTask;
|
||||
}>();
|
||||
|
||||
const lastSaved = ref(dayjs());
|
||||
const lastSaveUnsuccessful = ref(false);
|
||||
|
||||
const checkboxState = reactive({} as Record<BlockId, boolean>);
|
||||
|
||||
const courseSessionStore = useCourseSessionsStore();
|
||||
const assignmentStore = useAssignmentStore();
|
||||
|
||||
async function upsertAssignmentCompletion(completion_data: AssignmentCompletionData) {
|
||||
try {
|
||||
const courseSessionId = courseSessionStore.currentCourseSession?.id;
|
||||
if (!courseSessionId) {
|
||||
console.error("Invalid courseSessionId");
|
||||
return;
|
||||
}
|
||||
await assignmentStore.upsertAssignmentCompletion(
|
||||
props.assignmentId,
|
||||
completion_data,
|
||||
courseSessionId,
|
||||
false
|
||||
);
|
||||
lastSaved.value = dayjs();
|
||||
lastSaveUnsuccessful.value = false;
|
||||
console.debug("Saved user input");
|
||||
} catch (error) {
|
||||
lastSaveUnsuccessful.value = true;
|
||||
console.error("Could not save user input", error);
|
||||
}
|
||||
}
|
||||
|
||||
const upsertAssignmentCompletionDebounced = useDebounceFn(
|
||||
upsertAssignmentCompletion,
|
||||
500
|
||||
);
|
||||
|
||||
const onUpdateText = (id: BlockId, value: string) => {
|
||||
const data: AssignmentCompletionData = {};
|
||||
data[id] = {
|
||||
user_data: {
|
||||
text: value,
|
||||
} as UserDataText,
|
||||
};
|
||||
upsertAssignmentCompletionDebounced(data);
|
||||
};
|
||||
|
||||
const onUpdateConfirmation = (id: BlockId, value: boolean) => {
|
||||
const data: AssignmentCompletionData = {};
|
||||
data[id] = {
|
||||
user_data: {
|
||||
confirmation: value,
|
||||
} as UserDataConfirmation,
|
||||
};
|
||||
upsertAssignmentCompletion(data);
|
||||
};
|
||||
|
||||
const getBlockData = (id: BlockId) => {
|
||||
const userData = assignmentStore.getCompletionDataForUserInput(id)?.user_data;
|
||||
if (userData && "text" in userData) {
|
||||
return userData.text;
|
||||
} else if (userData && "confirmation" in userData) {
|
||||
return userData.confirmation;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const onToggleCheckbox = (id: BlockId) => {
|
||||
checkboxState[id] = !checkboxState[id];
|
||||
onUpdateConfirmation(id, checkboxState[id]);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col space-y-10">
|
||||
<div v-for="(block, index) in props.task.value.content" :key="block.id">
|
||||
<div v-if="block.type === 'explanation'">
|
||||
<p class="text-large">{{ block.value.text }}</p>
|
||||
</div>
|
||||
<div v-if="block.type === 'user_confirmation'">
|
||||
<ItCheckbox
|
||||
:checkbox-item="{
|
||||
label: block.value.text,
|
||||
value: `confirmation-${index}`,
|
||||
checked: getBlockData(block.id) as boolean,
|
||||
}"
|
||||
:disabled="assignmentStore.submitted"
|
||||
@toggle="onToggleCheckbox(block.id)"
|
||||
></ItCheckbox>
|
||||
</div>
|
||||
<div v-if="block.type === 'user_text_input'">
|
||||
<p class="text-large pb-4">{{ block.value.text }}</p>
|
||||
<ItTextarea
|
||||
:model-value="(getBlockData(block.id) as string) ?? ''"
|
||||
:cy-key="`user-text-input-${index}`"
|
||||
:disabled="assignmentStore.submitted"
|
||||
label=""
|
||||
@update:model-value="onUpdateText(block.id, $event)"
|
||||
></ItTextarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="lastSaveUnsuccessful" class="text-red-600">
|
||||
{{ $t("assignment.lastChangesNotSaved") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="props.task.value.file_submission_required">
|
||||
<p class="text-large">Datei hochladen</p>
|
||||
|
||||
<p class="text-sm text-gray-900">
|
||||
Mögliche Formate: .JPG, .PNG, .PDF, .DOC, .MOV, .PPT
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,24 +1,39 @@
|
|||
<script setup lang="ts">
|
||||
import AssignmentIntroductionView from "@/pages/learningPath/learningContentPage/assignment/AssignmentIntroductionView.vue";
|
||||
import AssignmentSubmissionView from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue";
|
||||
import AssignmentTaskView from "@/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue";
|
||||
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
||||
import type { AssignmentCompletionData } from "@/stores/assignmentStore";
|
||||
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import type {
|
||||
Assignment,
|
||||
AssignmentTask,
|
||||
CourseSessionAssignmentDetails,
|
||||
LearningContent,
|
||||
} from "@/types";
|
||||
import dayjs from "dayjs";
|
||||
import * as log from "loglevel";
|
||||
import { onMounted, reactive } from "vue";
|
||||
import { computed, onMounted, reactive } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const courseSessionStore = useCourseSessionsStore();
|
||||
const assignmentStore = useAssignmentStore();
|
||||
|
||||
interface State {
|
||||
assignment: Assignment | undefined;
|
||||
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
|
||||
assignmentCompletionData: AssignmentCompletionData | undefined;
|
||||
pageIndex: number;
|
||||
}
|
||||
|
||||
const state: State = reactive({
|
||||
assignment: undefined,
|
||||
courseSessionAssignmentDetails: undefined,
|
||||
assignmentCompletionData: undefined,
|
||||
// 0 = introduction, 1 - n = tasks, n+1 = submission
|
||||
pageIndex: 0,
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -36,29 +51,104 @@ onMounted(async () => {
|
|||
state.courseSessionAssignmentDetails = courseSessionsStore.findAssignmentDetails(
|
||||
props.learningContent.id
|
||||
);
|
||||
state.assignmentCompletionData = await assignmentStore.loadAssignmentCompletion(
|
||||
props.assignmentId,
|
||||
courseSessionId.value
|
||||
);
|
||||
log.debug(state.assignment, state.courseSessionAssignmentDetails);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
const numTasks = computed(() => state.assignment?.tasks?.length ?? 0);
|
||||
const numPages = computed(() => numTasks.value + 2);
|
||||
const showPreviousButton = computed(() => state.pageIndex != 0);
|
||||
const showNextButton = computed(() => state.pageIndex + 1 < numPages.value);
|
||||
const showExitButton = computed(() => numPages.value === state.pageIndex + 1);
|
||||
const dueDate = computed(() =>
|
||||
dayjs(state.courseSessionAssignmentDetails?.deadlineDateTimeUtc)
|
||||
);
|
||||
const courseSessionId = computed(
|
||||
() => courseSessionStore.currentCourseSession?.id ?? 0
|
||||
);
|
||||
const currentTask = computed(() => {
|
||||
if (state.pageIndex > 0 && state.pageIndex <= numTasks.value) {
|
||||
return state.assignment?.tasks[state.pageIndex - 1];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const handleBack = () => {
|
||||
log.debug("handleBack");
|
||||
if (state.pageIndex > 0) {
|
||||
state.pageIndex -= 1;
|
||||
}
|
||||
log.debug(`pageIndex: ${state.pageIndex}`);
|
||||
};
|
||||
|
||||
const handleContinue = () => {
|
||||
log.debug("handleContinue");
|
||||
if (state.pageIndex + 1 < numPages.value) {
|
||||
state.pageIndex += 1;
|
||||
}
|
||||
log.debug(`pageIndex: ${state.pageIndex}`);
|
||||
};
|
||||
|
||||
const jumpToTask = (task: AssignmentTask) => {
|
||||
log.debug("jumpToTask", task);
|
||||
const index = state.assignment?.tasks.findIndex((t) => t.id === task.id);
|
||||
if (index && index >= 0) {
|
||||
state.pageIndex = index + 1;
|
||||
}
|
||||
log.debug(`pageIndex: ${state.pageIndex}`);
|
||||
};
|
||||
|
||||
const getTitle = () => {
|
||||
if (0 === state.pageIndex) {
|
||||
return t("general.introduction");
|
||||
} else if (state.pageIndex === numPages.value - 1) {
|
||||
return t("general.submission");
|
||||
}
|
||||
return currentTask?.value?.value.title ?? "Unknown";
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-medium">
|
||||
<div v-if="state.assignment" class="lg:mt-12">
|
||||
<h2>Einleitung</h2>
|
||||
|
||||
<h3 class="mt-8">Ausgangslage</h3>
|
||||
<p>{{ state.assignment.starting_position }}</p>
|
||||
|
||||
<h3 class="mt-8">Abgabetermin</h3>
|
||||
<p v-if="state.courseSessionAssignmentDetails">
|
||||
{{ state.courseSessionAssignmentDetails }}
|
||||
</p>
|
||||
<p v-else>Keine Abgabedaten erfasst für diese Durchführung</p>
|
||||
|
||||
<pre class="mt-16">
|
||||
{{ state.assignment }}
|
||||
</pre>
|
||||
</div>
|
||||
<LearningContentMultiLayout
|
||||
:current-step="state.pageIndex"
|
||||
:subtitle="state.assignment?.title ?? ''"
|
||||
:title="getTitle()"
|
||||
learning-content-type="assignment"
|
||||
:steps-count="numPages"
|
||||
:show-next-button="showNextButton"
|
||||
:show-exit-button="showExitButton"
|
||||
:show-start-button="false"
|
||||
:show-previous-button="showPreviousButton"
|
||||
start-badge-text="Einleitung"
|
||||
end-badge-text="Abgabe"
|
||||
close-button-variant="close"
|
||||
@previous="handleBack()"
|
||||
@next="handleContinue()"
|
||||
>
|
||||
<div>
|
||||
<AssignmentIntroductionView
|
||||
v-if="state.pageIndex === 0 && state.assignment"
|
||||
:due-date="dueDate"
|
||||
:assignment="state.assignment!"
|
||||
></AssignmentIntroductionView>
|
||||
<AssignmentTaskView
|
||||
v-if="currentTask"
|
||||
:task="currentTask"
|
||||
:assignment-id="props.assignmentId"
|
||||
></AssignmentTaskView>
|
||||
<AssignmentSubmissionView
|
||||
v-if="state.pageIndex + 1 === numPages && state.assignment && courseSessionId"
|
||||
:due-date="dueDate"
|
||||
:assignment="state.assignment!"
|
||||
:course-session-id="courseSessionId!"
|
||||
@edit-task="jumpToTask($event)"
|
||||
></AssignmentSubmissionView>
|
||||
</div>
|
||||
</LearningContentMultiLayout>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<AssignmentView
|
||||
class="container-medium"
|
||||
:assignment-id="props.value.assignment"
|
||||
:learning-content="props.content"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import eventBus from "@/utils/eventBus";
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
interface Props {
|
||||
showStartButton?: boolean;
|
||||
showPreviousButton?: boolean;
|
||||
showNextButton?: boolean;
|
||||
showExitButton?: boolean;
|
||||
export type ClosingButtonVariant = "close" | "mark_as_done";
|
||||
|
||||
const props = defineProps<{
|
||||
showStartButton: boolean;
|
||||
showPreviousButton: boolean;
|
||||
showNextButton: boolean;
|
||||
showExitButton: boolean;
|
||||
closingButtonVariant: ClosingButtonVariant;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// eslint-disable-next-line vue/return-in-computed-property
|
||||
const closingButtonText = computed(() => {
|
||||
switch (props.closingButtonVariant) {
|
||||
case "close":
|
||||
return t("general.close");
|
||||
case "mark_as_done":
|
||||
return t("learningContent.markAsDone");
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showStartButton: false,
|
||||
showPreviousButton: false,
|
||||
showNextButton: false,
|
||||
showExitButton: true,
|
||||
});
|
||||
|
||||
defineEmits(["start", "previous", "next"]);
|
||||
|
|
@ -56,8 +66,11 @@ defineEmits(["start", "previous", "next"]);
|
|||
data-cy="complete-and-continue"
|
||||
@click="eventBus.emit('finishedLearningContent', true)"
|
||||
>
|
||||
{{ "Als erledigt markieren" }}
|
||||
<it-icon-check class="ml-2"></it-icon-check>
|
||||
{{ closingButtonText }}
|
||||
<it-icon-check
|
||||
v-if="props.closingButtonVariant == 'mark_as_done'"
|
||||
class="ml-2"
|
||||
></it-icon-check>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
// Layout for learning contents with multiple steps
|
||||
import ItNavigationProgress from "@/components/ui/ItNavigationProgress.vue";
|
||||
import type { ClosingButtonVariant } from "@/pages/learningPath/learningContentPage/layouts/LearningContentFooter.vue";
|
||||
import LearningContentFooter from "@/pages/learningPath/learningContentPage/layouts/LearningContentFooter.vue";
|
||||
import type { LearningContentType } from "@/types";
|
||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||
|
|
@ -17,19 +18,21 @@ interface Props {
|
|||
stepsCount: number;
|
||||
startBadgeText?: string;
|
||||
endBadgeText?: string;
|
||||
closeButtonVariant?: ClosingButtonVariant;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: undefined,
|
||||
startBadgeText: undefined,
|
||||
endBadgeText: undefined,
|
||||
closeButtonVariant: "mark_as_done",
|
||||
});
|
||||
|
||||
const emit = defineEmits(["previous", "next", "exit"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-medium">
|
||||
<div class="container-large">
|
||||
<div
|
||||
v-if="props.learningContentType !== 'placeholder'"
|
||||
class="flex h-min w-min items-center gap-2 rounded-full pb-10"
|
||||
|
|
@ -38,12 +41,12 @@ const emit = defineEmits(["previous", "next", "exit"]);
|
|||
:is="learningContentTypeData(props.learningContentType).icon"
|
||||
class="h-6 w-6 text-gray-900"
|
||||
></component>
|
||||
<p class="whitespace-nowrap text-gray-900">
|
||||
<p class="whitespace-nowrap text-gray-900" data-cy="lc-subtitle">
|
||||
{{ props.subtitle }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 v-if="props.title" class="pb-6 text-3xl" data-cy="ln-title">
|
||||
<h2 v-if="props.title" class="pb-6 text-3xl" data-cy="lc-title">
|
||||
{{ props.title }}
|
||||
</h2>
|
||||
<ItNavigationProgress
|
||||
|
|
@ -61,6 +64,7 @@ const emit = defineEmits(["previous", "next", "exit"]);
|
|||
:show-next-button="props.showNextButton"
|
||||
:show-previous-button="props.showPreviousButton"
|
||||
:show-exit-button="props.showExitButton"
|
||||
:closing-button-variant="props.closeButtonVariant"
|
||||
@previous="emit('previous')"
|
||||
@next="emit('next')"
|
||||
@start="emit('next')"
|
||||
|
|
|
|||
|
|
@ -23,13 +23,19 @@ const type = learningContentTypeData(props.learningContentType);
|
|||
class="flex h-min w-full items-center gap-2 pb-8"
|
||||
>
|
||||
<component :is="type.icon" class="h-6 w-6 text-gray-900"></component>
|
||||
<p class="whitespace-nowrap text-gray-900">
|
||||
<p class="whitespace-nowrap text-gray-900" data-cy="lc-subtitle">
|
||||
{{ type.title }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 v-if="props.title" data-cy="ln-title">{{ props.title }}</h2>
|
||||
<h2 v-if="props.title" data-cy="lc-title">{{ props.title }}</h2>
|
||||
</div>
|
||||
<slot></slot>
|
||||
<LearningContentFooter></LearningContentFooter>
|
||||
<LearningContentFooter
|
||||
:show-next-button="false"
|
||||
:show-previous-button="false"
|
||||
:show-exit-button="true"
|
||||
:show-start-button="false"
|
||||
:closing-button-variant="'close'"
|
||||
></LearningContentFooter>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,39 @@
|
|||
import { itGet } from "@/fetchHelpers";
|
||||
import { itGet, itPost } from "@/fetchHelpers";
|
||||
import type { Assignment } from "@/types";
|
||||
import log from "loglevel";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export type AssignmentStoreState = {
|
||||
assignment: Assignment | undefined;
|
||||
completion_data: AssignmentCompletionData;
|
||||
submitted: boolean;
|
||||
};
|
||||
|
||||
export type BlockId = string;
|
||||
|
||||
export interface UserDataText {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface UserDataConfirmation {
|
||||
confirmation: boolean;
|
||||
}
|
||||
|
||||
export interface AssignmentCompletionData {
|
||||
// {
|
||||
// "<user_text_input:uuid>": {"user_data": {"text": "some text from user"}},
|
||||
// "<user_confirmation:uuid>": {"user_data": {"confirmation": true}},
|
||||
// }
|
||||
[key: BlockId]: { user_data: UserDataText | UserDataConfirmation };
|
||||
}
|
||||
|
||||
export const useAssignmentStore = defineStore({
|
||||
id: "assignmentStore",
|
||||
state: () => {
|
||||
return {
|
||||
assignment: undefined,
|
||||
completion_data: {},
|
||||
submitted: false,
|
||||
} as AssignmentStoreState;
|
||||
},
|
||||
getters: {},
|
||||
|
|
@ -23,9 +45,42 @@ export const useAssignmentStore = defineStore({
|
|||
if (!assignmentData) {
|
||||
throw `No assignment found with: ${assignmentId}`;
|
||||
}
|
||||
|
||||
this.assignment = assignmentData;
|
||||
return this.assignment;
|
||||
},
|
||||
async loadAssignmentCompletion(assignmentId: number, courseSessionId: number) {
|
||||
log.debug("load assignment completion", assignmentId, courseSessionId);
|
||||
try {
|
||||
const data = await itGet(`/api/assignment/${assignmentId}/${courseSessionId}/`);
|
||||
this.completion_data = data.completion_data;
|
||||
this.submitted = data.completion_status === "submitted";
|
||||
} catch (e) {
|
||||
log.debug("no completion data found ", e);
|
||||
return undefined;
|
||||
}
|
||||
return this.completion_data;
|
||||
},
|
||||
getCompletionDataForUserInput(id: BlockId) {
|
||||
return this.completion_data[id];
|
||||
},
|
||||
async upsertAssignmentCompletion(
|
||||
assignmentId: number,
|
||||
completion_data: AssignmentCompletionData,
|
||||
courseSessionId: number,
|
||||
submit: boolean
|
||||
) {
|
||||
const data = {
|
||||
assignment_id: assignmentId,
|
||||
completion_status: submit ? "submitted" : "in_progress",
|
||||
course_session_id: courseSessionId,
|
||||
completion_data: completion_data,
|
||||
};
|
||||
const responseData = await itPost(`/api/assignment/upsert/`, data);
|
||||
if (responseData) {
|
||||
this.completion_data = responseData.completion_data;
|
||||
this.submitted = responseData.completion_status === "submitted";
|
||||
}
|
||||
return responseData;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": [
|
||||
"plugin:cypress/recommended"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import { login } from "./helpers";
|
||||
|
||||
const navigateToAssignment = () => {
|
||||
cy.visit(
|
||||
"/course/überbetriebliche-kurse/learn/fahrzeug/überprüfen-einer-motorfahrzeug-versicherungspolice"
|
||||
);
|
||||
};
|
||||
|
||||
describe("assignment completion", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
login("admin", "test");
|
||||
navigateToAssignment();
|
||||
});
|
||||
|
||||
it("can open assignment", () => {
|
||||
cy.testLearningContentTitle("Einleitung");
|
||||
cy.testLearningContentSubtitle(
|
||||
"Überprüfen einer Motorfahrzeugs-Versicherungspolice"
|
||||
);
|
||||
});
|
||||
|
||||
it("can navigate through assignment", () => {
|
||||
// 1 Step forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 1: Beispiel einer Versicherungspolice finden"
|
||||
);
|
||||
|
||||
// 2 Steps forward, 1 step backwards
|
||||
for (let i = 0; i !== 2; i++) {
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
}
|
||||
cy.learningContentMultiLayoutPreviousStep();
|
||||
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 2: Kundensituation und Ausgangslage"
|
||||
);
|
||||
});
|
||||
|
||||
it("can save confirmation", () => {
|
||||
// 1 Step forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 1: Beispiel einer Versicherungspolice finden"
|
||||
);
|
||||
// Click confirmation
|
||||
cy.get('[data-cy="it-checkbox-confirmation-1"]').click({ force: true });
|
||||
cy.wait(250);
|
||||
cy.reload();
|
||||
// 1 Step forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.get('[data-cy="it-checkbox-confirmation-1"]').should("be.checked");
|
||||
});
|
||||
|
||||
it("can save text", () => {
|
||||
// 2 Steps forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 2: Kundensituation und Ausgangslage"
|
||||
);
|
||||
// Enter text
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallovelo");
|
||||
cy.wait(550);
|
||||
cy.reload();
|
||||
|
||||
// 2 Step forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]').should(
|
||||
"have.value",
|
||||
"Hallovelo"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -34,18 +34,18 @@ describe("circle page", () => {
|
|||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-fahrzeug-lc-verschaffe-dir-einen-überblick"]'
|
||||
).click();
|
||||
cy.get('[data-cy="ln-title"]').should(
|
||||
cy.get('[data-cy="lc-title"]').should(
|
||||
"contain",
|
||||
"Verschaffe dir einen Überblick"
|
||||
);
|
||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||
|
||||
cy.get('[data-cy="ls-continue-button"]').click();
|
||||
cy.get('[data-cy="ln-title"]').should("contain", "Mediathek Fahrzeug");
|
||||
cy.get('[data-cy="lc-title"]').should("contain", "Mediathek Fahrzeug");
|
||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||
|
||||
cy.get('[data-cy="ls-continue-button"]').click();
|
||||
cy.get('[data-cy="ln-title"]').should("contain", "Vorbereitungsauftrag");
|
||||
cy.get('[data-cy="lc-title"]').should("contain", "Vorbereitungsauftrag");
|
||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||
|
||||
cy.get(
|
||||
|
|
@ -64,7 +64,7 @@ describe("circle page", () => {
|
|||
cy.get('[data-cy="ls-continue-button"]').should("contain", "Los geht's");
|
||||
cy.get('[data-cy="ls-continue-button"]').click();
|
||||
|
||||
cy.get('[data-cy="ln-title"]').should(
|
||||
cy.get('[data-cy="lc-title"]').should(
|
||||
"contain",
|
||||
"Verschaffe dir einen Überblick"
|
||||
);
|
||||
|
|
@ -72,12 +72,12 @@ describe("circle page", () => {
|
|||
|
||||
cy.get('[data-cy="ls-continue-button"]').should("contain", "Weiter geht's");
|
||||
cy.get('[data-cy="ls-continue-button"]').click();
|
||||
cy.get('[data-cy="ln-title"]').should("contain", "Mediathek Fahrzeug");
|
||||
cy.get('[data-cy="lc-title"]').should("contain", "Mediathek Fahrzeug");
|
||||
});
|
||||
|
||||
it("can open learning content by url", () => {
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug/mediathek-fahrzeug");
|
||||
cy.get('[data-cy="ln-title"]').should("contain", "Mediathek Fahrzeug");
|
||||
cy.get('[data-cy="lc-title"]').should("contain", "Mediathek Fahrzeug");
|
||||
|
||||
cy.get('[data-cy="close-learning-content"]').click();
|
||||
cy.get('[data-cy="circle-title"]').should("contain", "Fahrzeug");
|
||||
|
|
|
|||
|
|
@ -121,3 +121,19 @@ Cypress.Commands.add('makeSelfEvaluation', (answers) => {
|
|||
// 'myservice.apps.apiclient.serializers.ApiClientRequestResponseLogSerializer'
|
||||
// );
|
||||
// });
|
||||
|
||||
Cypress.Commands.add("learningContentMultiLayoutNextStep", () => {
|
||||
return cy.get('[data-cy="next-step"]').click({ force: true });
|
||||
});
|
||||
|
||||
Cypress.Commands.add("learningContentMultiLayoutPreviousStep", () => {
|
||||
return cy.get('[data-cy="previous-step"]').click({ force: true });
|
||||
});
|
||||
|
||||
Cypress.Commands.add("testLearningContentTitle", (title) => {
|
||||
return cy.get('[data-cy="lc-title"]').should("contain", title);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("testLearningContentSubtitle", (subtitle) => {
|
||||
return cy.get('[data-cy="lc-subtitle"]').should("contain", subtitle);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue