Merged develop into feature/use_currents_dev

This commit is contained in:
Christian Cueni 2022-12-15 08:32:12 +00:00
commit 819985f858
43 changed files with 553 additions and 151 deletions

View File

@ -3,5 +3,6 @@
"singleQuote": false, "singleQuote": false,
"tabWidth": 2, "tabWidth": 2,
"printWidth": 88, "printWidth": 88,
"organizeImportsSkipDestructiveCodeActions": true "organizeImportsSkipDestructiveCodeActions": true,
"htmlWhitespaceSensitivity": "ignore"
} }

View File

@ -168,6 +168,19 @@ const profileDropdownData: DropdownListItem[] = [
<!-- Cockpit--> <!-- Cockpit-->
<!-- </router-link>--> <!-- </router-link>-->
<router-link
v-if="
inCourse() &&
courseSessionsStore.courseSessionForRoute &&
courseSessionsStore.hasCockpit
"
:to="`${courseSessionsStore.courseSessionForRoute.course_url}/cockpit`"
class="nav-item"
:class="{ 'nav-item--active': inCockpit() }"
>
{{ $t("cockpit.title") }}
</router-link>
<router-link <router-link
v-if="inCourse() && courseSessionsStore.courseSessionForRoute" v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
:to="courseSessionsStore.courseSessionForRoute.learning_path_url" :to="courseSessionsStore.courseSessionForRoute.learning_path_url"
@ -186,28 +199,16 @@ const profileDropdownData: DropdownListItem[] = [
{{ $t("competences.title") }} {{ $t("competences.title") }}
</router-link> </router-link>
<router-link
v-if="
inCourse() &&
courseSessionsStore.courseSessionForRoute &&
courseSessionsStore.hasCockpit
"
:to="`${courseSessionsStore.courseSessionForRoute.course_url}/cockpit`"
class="nav-item"
:class="{ 'nav-item--active': inCockpit() }"
>
{{ $t("cockpit.title") }}
</router-link>
<div class="hidden lg:block flex-auto"></div> <div class="hidden lg:block flex-auto"></div>
<a <a
class="nav-item" class="nav-item"
target="_blank" target="_blank"
href="https://bildung.vbv.ch/ilp/pages/catalogsearch.jsf" href="https://bildung.vbv.ch/ilp/pages/catalogsearch.jsf"
>Shop</a
> >
Shop
</a>
<router-link <router-link
to="/media/versicherungsvermittlerin-media" to="/media/versicherungsvermittler-in-media"
class="nav-item" class="nav-item"
:class="{ 'nav-item--active': inMediaLibrary() }" :class="{ 'nav-item--active': inMediaLibrary() }"
data-cy="medialibrary-link" data-cy="medialibrary-link"

View File

@ -44,7 +44,7 @@ const clickLink = (to: string | undefined) => {
@click="clickLink('/profile')" @click="clickLink('/profile')"
> >
<IconSettings class="inline-block" /> <IconSettings class="inline-block" />
<span class="ml-3"> {{ $t("mainNavigation.settings") }} </span> <span class="ml-3">{{ $t("mainNavigation.settings") }}</span>
</button> </button>
</div> </div>
</div> </div>
@ -72,11 +72,12 @@ const clickLink = (to: string | undefined) => {
class="nav-item" class="nav-item"
target="_blank" target="_blank"
href="https://bildung.vbv.ch/ilp/pages/catalogsearch.jsf" href="https://bildung.vbv.ch/ilp/pages/catalogsearch.jsf"
>Shop</a
> >
Shop
</a>
</li> </li>
<li class="mt-6"> <li class="mt-6">
<button @click="clickLink(`/media/versicherungsvermittlerin-media`)"> <button @click="clickLink(`/media/versicherungsvermittler-in-media`)">
{{ $t("mediaLibrary.title") }} {{ $t("mediaLibrary.title") }}
</button> </button>
</li> </li>

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useCircleStore } from "@/stores/circle"; import { useCircleStore } from "@/stores/circle";
import type { DefaultArcObject } from "d3";
import * as d3 from "d3"; import * as d3 from "d3";
import * as _ from "lodash"; import * as _ from "lodash";
import * as log from "loglevel"; import * as log from "loglevel";
@ -8,7 +9,6 @@ import { computed, onMounted } from "vue";
// @ts-ignore // @ts-ignore
import colors from "@/colors.json"; import colors from "@/colors.json";
import type { LearningSequence } from "@/types"; import type { LearningSequence } from "@/types";
import type { DefaultArcObject } from "d3";
const circleStore = useCircleStore(); const circleStore = useCircleStore();
@ -48,11 +48,8 @@ interface CirclePie extends d3.PieArcDatum<number> {
const pieData = computed(() => { const pieData = computed(() => {
const circle = circleStore.circle; const circle = circleStore.circle;
console.log("initial of compute pie data ", circle);
if (circle) { if (circle) {
console.log("initial of compute pie data ", circle);
const pieWeights = new Array(Math.max(circle.learningSequences.length, 1)).fill(1); const pieWeights = new Array(Math.max(circle.learningSequences.length, 1)).fill(1);
const pieGenerator = d3.pie(); const pieGenerator = d3.pie();
const angles = pieGenerator(pieWeights); const angles = pieGenerator(pieWeights);
@ -177,7 +174,7 @@ function render() {
}); });
}) })
.on("click", function (d, elm) { .on("click", function (d, elm) {
console.log("clicked on ", d, elm); log.info("clicked on ", d, elm);
document.getElementById(elm.slug)?.scrollIntoView({ behavior: "smooth" }); document.getElementById(elm.slug)?.scrollIntoView({ behavior: "smooth" });
}); });

View File

@ -55,8 +55,7 @@ const block = computed(() => {
frameborder="0" frameborder="0"
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture" allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen allowfullscreen
> ></iframe>
</iframe>
</div> </div>
<div v-else-if="block.type === 'media_library'" class="mt-4 lg:mt-12"> <div v-else-if="block.type === 'media_library'" class="mt-4 lg:mt-12">

View File

@ -17,14 +17,18 @@ export type DiagramType = "horizontal" | "vertical" | "horizontalSmall";
export interface Props { export interface Props {
diagramType?: DiagramType; diagramType?: DiagramType;
postfix?: string; postfix?: string;
profileUserId?: string;
learningPath: LearningPath; learningPath: LearningPath;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
diagramType: "horizontal", diagramType: "horizontal",
postfix: "", postfix: "",
profileUserId: "",
}); });
log.debug("LearningPathDiagram created", props.postfix, props.profileUserId);
const state = reactive({ width: 1640, height: 384 }); const state = reactive({ width: 1640, height: 384 });
const svgId = computed(() => { const svgId = computed(() => {
@ -38,8 +42,7 @@ const viewBox = computed(() => {
const vueRouter = useRouter(); const vueRouter = useRouter();
onMounted(async () => { onMounted(async () => {
log.debug("LearningPathDiagram mounted"); log.debug("LearningPathDiagram mounted", props.postfix, props.profileUserId);
console.log(props.learningPath);
render(); render();
}); });
@ -57,6 +60,14 @@ function allFinished(circle: Circle, learningSequence: LearningSequence) {
return false; return false;
} }
function circleUrl(circle: InternalCircle) {
let circleUrl = circle.frontend_url;
if (props.profileUserId) {
circleUrl = `/course/${props.learningPath.course.slug}/cockpit/profile/${props.profileUserId}/${circle.slug}`;
}
return circleUrl;
}
interface CirclePie extends d3.PieArcDatum<number> { interface CirclePie extends d3.PieArcDatum<number> {
someFinished: boolean; someFinished: boolean;
allFinished: boolean; allFinished: boolean;
@ -90,6 +101,7 @@ const circles = computed(() => {
pie.someFinished = someFinished(circle, thisLearningSequence); pie.someFinished = someFinished(circle, thisLearningSequence);
pie.allFinished = allFinished(circle, thisLearningSequence); pie.allFinished = allFinished(circle, thisLearningSequence);
}); });
internalCircles.push({ internalCircles.push({
pieData: pieData.reverse() as CirclePie[], pieData: pieData.reverse() as CirclePie[],
title: circle.title, title: circle.title,
@ -181,7 +193,7 @@ function render() {
}); });
}) })
.on("click", (d, i) => { .on("click", (d, i) => {
vueRouter.push(i.frontend_url); vueRouter.push(circleUrl(i));
}) })
.attr("role", "button"); .attr("role", "button");

View File

@ -11,9 +11,14 @@ import _ from "lodash";
import { computed } from "vue"; import { computed } from "vue";
import LearningContentBadge from "./LearningContentTypeBadge.vue"; import LearningContentBadge from "./LearningContentTypeBadge.vue";
const props = defineProps<{ interface Props {
learningSequence: LearningSequence; learningSequence: LearningSequence;
}>(); readonly?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
readonly: false,
});
const circleStore = useCircleStore(); const circleStore = useCircleStore();
@ -112,14 +117,26 @@ const learningSequenceBorderClass = computed(() => {
:key="learningContent.id" :key="learningContent.id"
class="flex gap-4 pb-3 lg:pb-6 items-center" class="flex gap-4 pb-3 lg:pb-6 items-center"
> >
<div v-if="props.readonly">
<it-icon-check
v-if="learningContent.completion_status === 'success'"
class="block w-8 h-8"
></it-icon-check>
<div v-else class="w-8 h-8"></div>
</div>
<ItCheckbox <ItCheckbox
v-else
:model-value="learningContent.completion_status === 'success'" :model-value="learningContent.completion_status === 'success'"
:on-toggle="() => toggleCompleted(learningContent)" :on-toggle="() => toggleCompleted(learningContent)"
:data-cy="`${learningContent.slug}-checkbox`" :data-cy="`${learningContent.slug}-checkbox`"
/> />
<div class="flex-auto pt-1 sm:pt-0"> <div class="flex-auto pt-1 sm:pt-0">
<span class="flex gap-4 items-center xl:h-10"> <span class="flex gap-4 items-center xl:h-10">
<div v-if="props.readonly" class="w-full sm:w-auto text-left">
{{ learningContent.title }}
</div>
<button <button
v-else
class="cursor-pointer w-full sm:w-auto text-left" class="cursor-pointer w-full sm:w-auto text-left"
:data-cy="`${learningContent.slug}`" :data-cy="`${learningContent.slug}`"
@click.stop="circleStore.openLearningContent(learningContent)" @click.stop="circleStore.openLearningContent(learningContent)"
@ -132,7 +149,8 @@ const learningSequenceBorderClass = computed(() => {
> >
<button <button
v-if=" v-if="
learningContent.translation_key === continueTranslationKeyTuple[0] learningContent.translation_key ===
continueTranslationKeyTuple[0] && !props.readonly
" "
class="btn-blue order-1 sm:order-none" class="btn-blue order-1 sm:order-none"
data-cy="ls-continue-button" data-cy="ls-continue-button"
@ -162,9 +180,9 @@ const learningSequenceBorderClass = computed(() => {
<div <div
v-if="learningUnit.children.length" v-if="learningUnit.children.length"
class="hover:cursor-pointer" :class="{ 'cursor-pointer': !props.readonly }"
:data-cy="`${learningUnit.slug}`" :data-cy="`${learningUnit.slug}`"
@click="circleStore.openSelfEvaluation(learningUnit)" @click="!props.readonly && circleStore.openSelfEvaluation(learningUnit)"
> >
<div <div
v-if="circleStore.calcSelfEvaluationStatus(learningUnit) === 'success'" v-if="circleStore.calcSelfEvaluationStatus(learningUnit) === 'success'"

View File

@ -65,7 +65,8 @@ function handleBack() {
<p class="text-large mt-4"> <p class="text-large mt-4">
{{ $t("selfEvaluation.instruction[0]") }} {{ $t("selfEvaluation.instruction[0]") }}
<span class="font-bold">«{{ learningUnit.title }}»</span> <span class="font-bold">«{{ learningUnit.title }}»</span>
{{ $t("selfEvaluation.instruction[1]") }}<br /> {{ $t("selfEvaluation.instruction[1]") }}
<br />
{{ $t("selfEvaluation.instruction[2]") }} {{ $t("selfEvaluation.instruction[2]") }}
</p> </p>
@ -85,9 +86,7 @@ function handleBack() {
@click="circleStore.markCompletion(currentQuestion, 'success')" @click="circleStore.markCompletion(currentQuestion, 'success')"
> >
<it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy> <it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy>
<span class="font-bold text-large"> <span class="font-bold text-large">{{ $t("selfEvaluation.yes") }}.</span>
{{ $t("selfEvaluation.yes") }}.
</span>
</button> </button>
<button <button
class="flex-1 inline-flex items-center text-left p-4 border" class="flex-1 inline-flex items-center text-left p-4 border"
@ -99,7 +98,7 @@ function handleBack() {
@click="circleStore.markCompletion(currentQuestion, 'fail')" @click="circleStore.markCompletion(currentQuestion, 'fail')"
> >
<it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking> <it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking>
<span class="font-bold text-xl"> {{ $t("selfEvaluation.no") }}. </span> <span class="font-bold text-xl">{{ $t("selfEvaluation.no") }}.</span>
</button> </button>
</div> </div>

View File

@ -0,0 +1,19 @@
<script setup lang="ts"></script>
<template>
<li
class="py-4 leading-[45px] border-b border-gray-500 last:border-0 flex flex-row justify-between"
>
<div class="flex flex-row">
<slot name="left"></slot>
</div>
<div class="leading-[45px]">
<slot name="center"></slot>
</div>
<div class="leading-[45px]">
<slot name="link"></slot>
</div>
</li>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "@headlessui/vue";
import { ref } from "vue";
interface Props {
modelValue: boolean;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
});
const emits = defineEmits(["update:modelValue"]);
function setIsOpen(value: boolean) {
emits("update:modelValue", value);
}
</script>
<template>
<Dialog :open="modelValue" class="relative z-50" @close="setIsOpen">
<div class="fixed inset-0 bg-black/30" aria-hidden="true"></div>
<div class="fixed inset-0 flex items-center justify-center p-4">
<DialogPanel class="w-full max-w-2xl bg-white px-8 pt-8 pb-4">
<DialogTitle class="flex flex-row relative mb-8">
<slot name="title"></slot>
<button
type="button"
class="w-4 h-4 absolute right-4 cursor-pointer"
@click="setIsOpen(false)"
>
<it-icon-close></it-icon-close>
</button>
</DialogTitle>
<DialogDescription></DialogDescription>
<slot name="body"></slot>
</DialogPanel>
</div>
</Dialog>
</template>

View File

@ -10,13 +10,15 @@
"save": "Speichern", "save": "Speichern",
"learningUnit": "Lerneinheit", "learningUnit": "Lerneinheit",
"learningPath": "Lernpfad", "learningPath": "Lernpfad",
"learningSequence": "Lernsequenz",
"show": "Anschauen", "show": "Anschauen",
"circles": "Circles", "circles": "Circles",
"transferTask": "Transferauftrag | Transferaufträge", "transferTask": "Transferauftrag | Transferaufträge",
"feedback": "Feedback | Feedbacks", "feedback": "Feedback | Feedbacks",
"exam": "Prüfung | Prüfungen", "exam": "Prüfung | Prüfungen",
"examResult": "Prüfungsresultat | Prüfungsresultate", "examResult": "Prüfungsresultat | Prüfungsresultate",
"certificate": "Zertifikat | Zertifikate" "certificate": "Zertifikat | Zertifikate",
"notification": "Benachrichtigung | Benachrichtigungen"
}, },
"mainNavigation": { "mainNavigation": {
"logout": "Abmelden", "logout": "Abmelden",
@ -32,7 +34,18 @@
}, },
"circlePage": { "circlePage": {
"duration": "Dauer", "duration": "Dauer",
"circleContentBoxTitle": "Das lernst du in diesem Circle." "circleContentBoxTitle": "Das lernst du in diesem Circle.",
"gotQuestions": "Hast du Fragen?",
"documents": {
"title": "Unterlagen",
"description": "Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.",
"action": "Unterlagen hochladen",
"modalAction": "Datei auswählen",
"fileLabel": "Datei",
"modalFileName": "Name",
"modalNameInformation": "Max. 70 Zeichen",
"chooseSequence": "Wähle eine Lernsequenz aus"
}
}, },
"learningContent": { "learningContent": {
"completeAndContinue": "Abschliessen und weiter" "completeAndContinue": "Abschliessen und weiter"

View File

@ -112,7 +112,8 @@ onMounted(async () => {
<p class="text-bold">Check-up</p> <p class="text-bold">Check-up</p>
<p>Vermittler/-in VBV</p> <p>Vermittler/-in VBV</p>
<p> <p>
Gültig bis: <span class="text-green-500 text-bold">31.12.2026</span> Gültig bis:
<span class="text-green-500 text-bold">31.12.2026</span>
</p> </p>
</div> </div>
</div> </div>
@ -122,7 +123,10 @@ onMounted(async () => {
<div> <div>
<p class="text-bold">Zulassungsprüfung</p> <p class="text-bold">Zulassungsprüfung</p>
<p>Vermittler/-in VBV</p> <p>Vermittler/-in VBV</p>
<p>Bestanden: <span class="text-bold">14.11.2022</span></p> <p>
Bestanden:
<span class="text-bold">14.11.2022</span>
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,78 @@
<script setup lang="ts">
import ItListRow from "@/components/ui/ItListRow.vue";
import * as log from "loglevel";
log.debug("ProfileView created");
const fakeData = {
notifications: [
{
id: 1,
source: {
name: "Daniel",
title: "EFZ Kaufmann/-frau",
course: "üK",
avatar: "https://picsum.photos/200",
},
ago: "Vor 1 Stunde",
title: "hat den Transferauftrag «Die Motorhaftpflicht» mit dir geteilt.",
unread: true,
},
{
id: 2,
source: {
name: "Sofia",
title: "EFZ Kaufmann/-frau",
course: "üK",
avatar: "https://picsum.photos/200",
},
ago: "Vor 1 Tag",
title: "hat dir ein Feedback zum Kreis «Analyse» mit dir geteilt.",
unread: false,
},
],
};
</script>
<template>
<div class="bg-gray-200">
<div class="container-large">
<header class="mt-12 mb-8">
<h1>{{ $t("general.notification", 2) }}</h1>
</header>
<main>
<div class="bg-white px-8 py-2">
<ItListRow
v-for="notification in fakeData.notifications"
:key="notification.id"
>
<template #left>
<img
class="h-[45px] rounded-full mr-2"
:src="notification.source.avatar"
/>
<div class="ml-1">
<p class="leading-6">
{{ `${notification.source.name} ${notification.title}` }}
</p>
<p class="leading-6 text-sm text-gray-800">
{{ `${notification.source.title} &hyphen; ${notification.ago}` }}
</p>
</div>
</template>
<template #link>
<div
v-if="notification.unread"
class="flex items-center h-[45px] flex-row"
>
<div class="w-[10px] h-[10px] bg-blue-500 rounded-full"></div>
</div>
</template>
</ItListRow>
</div>
</main>
</div>
</div>
</template>
<style scoped></style>

View File

@ -356,14 +356,13 @@ function log(data: any) {
v-model="state.dropdownSelected" v-model="state.dropdownSelected"
class="w-full lg:w-96 mt-4 lg:mt-0" class="w-full lg:w-96 mt-4 lg:mt-0"
:items="state.dropdownValues" :items="state.dropdownValues"
> ></ItDropdownSelect>
</ItDropdownSelect>
{{ state.dropdownSelected }} {{ state.dropdownSelected }}
<h2 class="mt-8 mb-8">Checkbox</h2> <h2 class="mt-8 mb-8">Checkbox</h2>
<ItCheckbox v-model="state.checkboxValue" :disabled="false" class="" <ItCheckbox v-model="state.checkboxValue" :disabled="false" class="">
>Label Label
</ItCheckbox> </ItCheckbox>
<ItCheckbox disabled class="mt-4">Disabled</ItCheckbox> <ItCheckbox disabled class="mt-4">Disabled</ItCheckbox>
@ -376,7 +375,8 @@ function log(data: any) {
:list-items="dropdownData" :list-items="dropdownData"
:align="'left'" :align="'left'"
@select="log" @select="log"
>Click Me >
Click Me
</ItDropdown> </ItDropdown>
</div> </div>
</main> </main>

View File

@ -8,7 +8,6 @@ import { useCockpitStore } from "@/stores/cockpit";
import { useCompetenceStore } from "@/stores/competence"; import { useCompetenceStore } from "@/stores/competence";
import { useLearningPathStore } from "@/stores/learningPath"; import { useLearningPathStore } from "@/stores/learningPath";
import * as log from "loglevel"; import * as log from "loglevel";
import { ref } from "vue";
const props = defineProps<{ const props = defineProps<{
courseSlug: string; courseSlug: string;
@ -27,7 +26,6 @@ function userCountStatus(userId: number) {
} }
const data = { const data = {
circles: ["KMU Teil 1", "KMU Teil 2", "3-Säuli-Prinzip"],
transferProgress: { transferProgress: {
fail: 0, fail: 0,
success: 3, success: 3,
@ -35,17 +33,11 @@ const data = {
}, },
}; };
const selectedCircle = ref(1);
function setActiveClasses(id: number) { function setActiveClasses(id: number) {
return cockpitStore.selectedCircle === id return cockpitStore.selectedCircles.indexOf(id) > -1
? ["bg-blue-900", "text-white"] ? ["bg-blue-900", "text-white"]
: ["text-bg-900"]; : ["text-bg-900"];
} }
function setActiveCircle(id: number) {
cockpitStore.selectedCircle = id;
}
</script> </script>
<template> <template>
@ -62,7 +54,7 @@ function setActiveCircle(id: number) {
<button <button
class="border-2 border-blue-900 rounded-full px-4 mr-4 last:mr-0" class="border-2 border-blue-900 rounded-full px-4 mr-4 last:mr-0"
:class="setActiveClasses(circle.id)" :class="setActiveClasses(circle.id)"
@click="setActiveCircle(circle.id)" @click="cockpitStore.toggleCourseSelection(circle.id)"
> >
{{ circle.title }} {{ circle.title }}
</button> </button>
@ -134,10 +126,18 @@ function setActiveCircle(id: number) {
) as LearningPath ) as LearningPath
" "
:postfix="`cockpit-${csu.user_id}`" :postfix="`cockpit-${csu.user_id}`"
:profile-user-id="`${csu.user_id}`"
diagram-type="horizontalSmall" diagram-type="horizontalSmall"
></LearningPathDiagram> ></LearningPathDiagram>
</div> </div>
<div>KMU Teil 1</div> <div>
<span
v-for="title in cockpitStore.selectedCirclesTitles"
:key="title"
>
{{ title }}
</span>
</div>
</div> </div>
<div class="ml-4 flex flex-row items-center"> <div class="ml-4 flex flex-row items-center">
<div class="flex flex-row items-center mr-6"> <div class="flex flex-row items-center mr-6">

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import CirclePage from "@/pages/learningPath/CirclePage.vue";
import { useCockpitStore } from "@/stores/cockpit";
import * as log from "loglevel";
import { computed, onMounted } from "vue";
const props = defineProps<{
userId: string;
courseSlug: string;
circleSlug: string;
}>();
log.debug("CockpitUserCirclePage created", props.userId, props.circleSlug);
const cockpitStore = useCockpitStore();
onMounted(async () => {
log.debug("CockpitUserCirclePage mounted");
});
const user = computed(() => {
return cockpitStore.courseSessionUsers?.find(
(csu) => csu.user_id === Number(props.userId)
);
});
</script>
<template>
<CirclePage
v-if="user"
:course-slug="props.courseSlug"
:circle-slug="props.circleSlug"
:profile-user="user"
:readonly="true"
></CirclePage>
</template>
<style scoped></style>

View File

@ -67,6 +67,7 @@ function setActiveClasses(isActive: boolean) {
diagram-type="horizontal" diagram-type="horizontal"
:learning-path="learningPath" :learning-path="learningPath"
:postfix="userId" :postfix="userId"
:profile-user-id="userId"
></LearningPathDiagram> ></LearningPathDiagram>
</div> </div>
<ul class="flex flex-row border-b-2 mb-5"> <ul class="flex flex-row border-b-2 mb-5">
@ -85,8 +86,8 @@ function setActiveClasses(isActive: boolean) {
</ul> </ul>
<div> <div>
<ul <ul
class="px-8 bg-white"
v-if="competenceStore.competenceProfilePage(user.user_id)" v-if="competenceStore.competenceProfilePage(user.user_id)"
class="px-8 bg-white"
> >
<li <li
v-for="competence in competenceStore.competences(user.user_id)" v-for="competence in competenceStore.competences(user.user_id)"

View File

@ -80,7 +80,7 @@ findCriteria();
@click="circleStore.markCompletion(currentQuestion, 'success')" @click="circleStore.markCompletion(currentQuestion, 'success')"
> >
<it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy> <it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy>
<span class="font-bold text-large"> {{ $t("selfEvaluation.yes") }} </span> <span class="font-bold text-large">{{ $t("selfEvaluation.yes") }}</span>
</button> </button>
<button <button
class="flex-1 inline-flex items-center text-left p-4 border" class="flex-1 inline-flex items-center text-left p-4 border"
@ -92,7 +92,7 @@ findCriteria();
@click="circleStore.markCompletion(currentQuestion, 'fail')" @click="circleStore.markCompletion(currentQuestion, 'fail')"
> >
<it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking> <it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking>
<span class="font-bold text-xl"> {{ $t("selfEvaluation.no") }} </span> <span class="font-bold text-xl">{{ $t("selfEvaluation.no") }}</span>
</button> </button>
</div> </div>
</div> </div>

View File

@ -2,23 +2,47 @@
import CircleDiagram from "@/components/learningPath/CircleDiagram.vue"; import CircleDiagram from "@/components/learningPath/CircleDiagram.vue";
import CircleOverview from "@/components/learningPath/CircleOverview.vue"; import CircleOverview from "@/components/learningPath/CircleOverview.vue";
import LearningSequence from "@/components/learningPath/LearningSequence.vue"; import LearningSequence from "@/components/learningPath/LearningSequence.vue";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import ItModal from "@/components/ui/ItModal.vue";
import * as log from "loglevel"; import * as log from "loglevel";
import { computed, onMounted, reactive, ref } from "vue";
import { useAppStore } from "@/stores/app"; import { useAppStore } from "@/stores/app";
import { useCircleStore } from "@/stores/circle"; import { useCircleStore } from "@/stores/circle";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import type { CourseSessionUser } from "@/types";
import { humanizeDuration } from "@/utils/humanizeDuration"; import { humanizeDuration } from "@/utils/humanizeDuration";
import _ from "lodash"; import _ from "lodash";
import { computed, onMounted } from "vue"; import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
const { t } = useI18n();
const courseSessionsStore = useCourseSessionsStore();
log.debug("CircleView.vue created", route); interface Props {
const props = defineProps<{
courseSlug: string; courseSlug: string;
circleSlug: string; circleSlug: string;
}>(); profileUser?: CourseSessionUser;
readonly?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
readonly: false,
profileUser: undefined,
});
log.debug("CirclePage created", props.readonly, props.profileUser);
const showUploadModal = ref(false);
const formData = reactive({
file: "",
name: "",
learningSequence: {
id: -1,
name: t("circlePage.documents.chooseSequence"),
},
});
const appStore = useAppStore(); const appStore = useAppStore();
appStore.showMainNavigationBar = true; appStore.showMainNavigationBar = true;
@ -34,11 +58,31 @@ const duration = computed(() => {
return ""; return "";
}); });
const dropdownLearningSequences = computed(() =>
circleStore.circle?.learningSequences.map((sequence) => ({
id: sequence.id,
name: sequence.title,
}))
);
onMounted(async () => { onMounted(async () => {
log.debug("CircleView.vue mounted", props.courseSlug, props.circleSlug); log.debug(
"CirclePage mounted",
props.courseSlug,
props.circleSlug,
props.profileUser
);
try { try {
await circleStore.loadCircle(props.courseSlug, props.circleSlug); if (props.profileUser) {
await circleStore.loadCircle(
props.courseSlug,
props.circleSlug,
props.profileUser.user_id
);
} else {
await circleStore.loadCircle(props.courseSlug, props.circleSlug);
}
if (route.hash.startsWith("#ls-") || route.hash.startsWith("#lu-")) { if (route.hash.startsWith("#ls-") || route.hash.startsWith("#lu-")) {
const slugEnd = route.hash.replace("#", ""); const slugEnd = route.hash.replace("#", "");
@ -83,9 +127,33 @@ onMounted(async () => {
<div> <div>
<div class="circle-container bg-gray-200"> <div class="circle-container bg-gray-200">
<div class="circle max-w-9xl"> <div class="circle max-w-9xl">
<div v-if="profileUser" class="user-profile">
<header
class="flex flex-row items-center p-8 bg-white relative shadow-xl"
>
<img
class="w-32 h-32 rounded-full mr-8"
:src="profileUser.avatar_url"
/>
<div>
<h1 class="mb-2">
{{ profileUser.first_name }} {{ profileUser.last_name }}
</h1>
<div>
<router-link
class="link"
:to="`/course/${courseSlug}/cockpit/profile/${profileUser.user_id}`"
>
Profil anzeigen
</router-link>
</div>
</div>
</header>
</div>
<div class="flex flex-col lg:flex-row"> <div class="flex flex-col lg:flex-row">
<div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:pt-4 bg-white"> <div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:pt-4 bg-white">
<router-link <router-link
v-if="!props.readonly"
:to="`/course/${props.courseSlug}/learn`" :to="`/course/${props.courseSlug}/learn`"
class="btn-text inline-flex items-center px-3 py-4" class="btn-text inline-flex items-center px-3 py-4"
data-cy="back-to-learning-path-button" data-cy="back-to-learning-path-button"
@ -104,15 +172,15 @@ onMounted(async () => {
<CircleDiagram></CircleDiagram> <CircleDiagram></CircleDiagram>
</div> </div>
<div class="border-t-2 mt-4 lg:hidden"> <div v-if="!props.readonly" class="border-t-2 mt-4 lg:hidden">
<div <div
class="mt-4 inline-flex items-center" class="mt-4 inline-flex items-center"
@click="circleStore.page = 'OVERVIEW'" @click="circleStore.page = 'OVERVIEW'"
> >
<it-icon-info class="mr-2" /> <it-icon-info class="mr-2" />
Das lernst du in diesem Circle {{ $t("circlePage.circleContentBoxTitle") }}
</div> </div>
<div class="inline-flex items-center"> <div v-if="!props.readonly" class="inline-flex items-center">
<it-icon-message class="mr-2" /> <it-icon-message class="mr-2" />
Fachexpertin kontaktieren Fachexpertin kontaktieren
</div> </div>
@ -135,8 +203,25 @@ onMounted(async () => {
</button> </button>
</div> </div>
<div class="expert border mt-8 p-6"> <div v-if="!props.readonly" class="block border mt-8 p-6">
<h3 class="text-blue-dark">Hast du Fragen?</h3> <h3 class="text-blue-dark">
{{ $t("circlePage.documents.title") }}
</h3>
<div v-if="courseSessionsStore.hasCockpit">
<div class="leading-relaxed mt-4">
{{ $t("circlePage.documents.description") }}
</div>
<button
class="btn-primary mt-4 text-xl"
@click="showUploadModal = true"
>
{{ $t("circlePage.documents.action") }}
</button>
</div>
</div>
<div v-if="!props.readonly" class="expert border mt-8 p-6">
<h3 class="text-blue-dark">{{ $t("circlePage.gotQuestions") }}</h3>
<div class="leading-relaxed mt-4"> <div class="leading-relaxed mt-4">
Tausche dich mit der Fachexpertin aus für den Circle Analyse aus. Tausche dich mit der Fachexpertin aus für den Circle Analyse aus.
</div> </div>
@ -155,12 +240,50 @@ onMounted(async () => {
> >
<LearningSequence <LearningSequence
:learning-sequence="learningSequence" :learning-sequence="learningSequence"
:readonly="props.readonly"
></LearningSequence> ></LearningSequence>
</li> </li>
</ol> </ol>
</div> </div>
</div> </div>
</div> </div>
<ItModal v-model="showUploadModal">
<template #title>{{ $t("circlePage.documents.action") }}</template>
<template #body>
<form>
<label class="block text-bold" for="upload">
{{ $t("circlePage.documents.fileLabel") }}
</label>
<div class="btn-secondary mt-4 mb-8 text-xl relative cursor-pointer">
<input id="upload" type="file" class="absolute opacity-0" />
{{ $t("circlePage.documents.modalAction") }}
</div>
<!--p>{{ $t("circlePage.documentsModalInformation") }}</p-->
<div class="mb-8">
<label class="block text-bold mb-4" for="name">
{{ $t("circlePage.documents.modalFileName") }}
</label>
<input id="name" type="text" class="w-1/2 mb-2" />
<p>{{ $t("circlePage.documents.modalNameInformation") }}</p>
</div>
<div class="mb-8">
<label class="block text-bold mb-4" for="learningsequnce">
{{ $t("general.learningSequence") }}
</label>
<ItDropdownSelect
v-model="formData.learningSequence"
class="w-full lg:w-96 mt-4 lg:mt-0"
:items="dropdownLearningSequences"
></ItDropdownSelect>
</div>
<div class="-mx-8 px-8 pt-4 border-t">
<button class="btn-primary text-xl mb-0">
{{ $t("general.save") }}
</button>
</div>
</form>
</template>
</ItModal>
</div> </div>
</Transition> </Transition>
</div> </div>

View File

@ -50,8 +50,7 @@ watch(dropdownSelected, (newValue) =>
:description="$t('mediaLibrary.learningMedia.description')" :description="$t('mediaLibrary.learningMedia.description')"
icon="lernmedien-overview" icon="lernmedien-overview"
class="mb-6" class="mb-6"
> ></OverviewCard>
</OverviewCard>
</div> </div>
</template> </template>

View File

@ -74,7 +74,8 @@ const mediaList = computed(() => {
:to="item.value.url" :to="item.value.url"
:blank="item.value.open_window" :blank="item.value.open_window"
class="link" class="link"
>{{ item.value.link_display_text }} >
{{ item.value.link_display_text }}
</media-link> </media-link>
</div> </div>
</li> </li>

View File

@ -1,13 +1,13 @@
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import type { NavigationGuardWithThis, RouteLocationNormalized } from "vue-router"; import type { NavigationGuardWithThis, RouteLocationNormalized } from "vue-router";
export const updateLoggedIn: NavigationGuardWithThis<undefined> = (_to) => { export const updateLoggedIn: NavigationGuardWithThis<undefined> = async (_to) => {
const loggedIn = getCookieValue("loginStatus") === "true"; const loggedIn = getCookieValue("loginStatus") === "true";
const userStore = useUserStore(); const userStore = useUserStore();
userStore.$patch({ loggedIn }); userStore.$patch({ loggedIn });
if (loggedIn && !userStore.email) { if (loggedIn && !userStore.id) {
userStore.fetchUser(); await userStore.fetchUser();
} }
}; };

View File

@ -114,6 +114,11 @@ const router = createRouter({
component: () => import("@/pages/cockpit/CockpitUserProfilePage.vue"), component: () => import("@/pages/cockpit/CockpitUserProfilePage.vue"),
props: true, props: true,
}, },
{
path: "profile/:userId/:circleSlug",
component: () => import("@/pages/cockpit/CockpitUserCirclePage.vue"),
props: true,
},
], ],
}, },
{ {
@ -128,6 +133,10 @@ const router = createRouter({
path: "/profile", path: "/profile",
component: () => import("@/pages/ProfilePage.vue"), component: () => import("@/pages/ProfilePage.vue"),
}, },
{
path: "/notifications",
component: () => import("@/pages/NotificationsPage.vue"),
},
{ {
path: "/styleguide", path: "/styleguide",
component: () => import("../pages/StyleGuidePage.vue"), component: () => import("../pages/StyleGuidePage.vue"),

View File

@ -7,7 +7,7 @@ import { defineStore } from "pinia";
export type CockpitStoreState = { export type CockpitStoreState = {
courseSessionUsers: CourseSessionUser[] | undefined; courseSessionUsers: CourseSessionUser[] | undefined;
cockpitSessionUser: ExpertSessionUser | undefined; cockpitSessionUser: ExpertSessionUser | undefined;
selectedCircle: number; selectedCircles: number[];
}; };
export const useCockpitStore = defineStore({ export const useCockpitStore = defineStore({
@ -16,11 +16,15 @@ export const useCockpitStore = defineStore({
return { return {
courseSessionUsers: undefined, courseSessionUsers: undefined,
cockpitSessionUser: undefined, cockpitSessionUser: undefined,
selectedCircle: -1, selectedCircles: [],
} as CockpitStoreState; } as CockpitStoreState;
}, },
getters: { getters: {
circles: (state) => state.cockpitSessionUser?.circles, circles: (state) => state.cockpitSessionUser?.circles,
selectedCirclesTitles: (state) =>
state.cockpitSessionUser?.circles
.filter((circle) => state.selectedCircles.indexOf(circle.id) > -1)
.map((circle) => circle.title),
}, },
actions: { actions: {
async loadCourseSessionUsers(courseSlug: string, reload = false) { async loadCourseSessionUsers(courseSlug: string, reload = false) {
@ -32,8 +36,8 @@ export const useCockpitStore = defineStore({
this.courseSessionUsers = data.users; this.courseSessionUsers = data.users;
this.cockpitSessionUser = data.cockpit_user; this.cockpitSessionUser = data.cockpit_user;
if (this.cockpitSessionUser.circles?.length > 0) { if (this.cockpitSessionUser && this.cockpitSessionUser.circles?.length > 0) {
this.selectedCircle = this.cockpitSessionUser.circles[0].id; this.selectedCircles = [this.cockpitSessionUser.circles[0].id];
} }
if (!this.courseSessionUsers) { if (!this.courseSessionUsers) {
@ -41,5 +45,16 @@ export const useCockpitStore = defineStore({
} }
return this.courseSessionUsers; return this.courseSessionUsers;
}, },
toggleCourseSelection(id: number) {
if (this.selectedCircles.indexOf(id) < 0) {
this.selectedCircles.push(id);
} else {
if (this.selectedCircles.length === 1) {
return;
}
const index = this.selectedCircles.indexOf(id);
this.selectedCircles.splice(index, 1);
}
},
}, },
}); });

View File

@ -34,7 +34,8 @@ export const useCourseSessionsStore = defineStore({
hasCockpit() { hasCockpit() {
const userStore = useUserStore(); const userStore = useUserStore();
return ( return (
this.courseSessionForRoute.experts.filter( this.courseSessionForRoute &&
(this.courseSessionForRoute as unknown as CourseSession).experts.filter(
(expert) => expert.user_id === userStore.id (expert) => expert.user_id === userStore.id
).length > 0 ).length > 0
); );

View File

@ -17,7 +17,7 @@ export const useMediaLibraryStore = defineStore({
selectedLearningPath: { id: 1, name: "Alle Lehrgänge" }, selectedLearningPath: { id: 1, name: "Alle Lehrgänge" },
availableLearningPaths: [ availableLearningPaths: [
{ id: 1, name: "Alle Lehrgänge" }, { id: 1, name: "Alle Lehrgänge" },
{ id: 2, name: "Versicherungsvermittler/in" }, { id: 2, name: "Versicherungsvermittler/-in" },
], ],
} as MediaLibraryStoreState; } as MediaLibraryStoreState;
}, },

View File

@ -71,20 +71,14 @@ export const useUserStore = defineStore({
window.location.href = redirectUrl; window.location.href = redirectUrl;
}); });
}, },
fetchUser() { async fetchUser() {
const appStore = useAppStore(); const appStore = useAppStore();
itGetCached("/api/core/me/") const data = await itGetCached("/api/core/me/");
.then((data) => { this.$state = data;
this.$state = data; this.loggedIn = true;
this.loggedIn = true; appStore.userLoaded = true;
appStore.userLoaded = true; const courseSessionsStore = useCourseSessionsStore();
const courseSessionsStore = useCourseSessionsStore(); await courseSessionsStore.loadCourseSessionsData();
courseSessionsStore.loadCourseSessionsData();
})
.catch(() => {
this.loggedIn = false;
appStore.userLoaded = true;
});
}, },
}, },
}); });

View File

@ -5,7 +5,7 @@ describe("circle page", () => {
cy.manageCommand("cypress_reset"); cy.manageCommand("cypress_reset");
login("admin", "test"); login("admin", "test");
cy.visit("/learn/test-lehrgang-lp/analyse"); cy.visit("/course/test-lehrgang/learn/analyse");
}); });
it("can open circle page", () => { it("can open circle page", () => {
@ -92,7 +92,7 @@ describe("circle page", () => {
}); });
it("can open learning content by url", () => { it("can open learning content by url", () => {
cy.visit("/learn/test-lehrgang-lp/analyse/reiseversicherung"); cy.visit("/course/test-lehrgang/learn/analyse/reiseversicherung");
cy.get('[data-cy="ln-title"]').should("contain", "Reiseversicherung"); cy.get('[data-cy="ln-title"]').should("contain", "Reiseversicherung");
cy.get('[data-cy="close-learning-content"]').click(); cy.get('[data-cy="close-learning-content"]').click();

View File

@ -5,28 +5,50 @@ describe("Competence", () => {
cy.manageCommand("cypress_reset"); cy.manageCommand("cypress_reset");
login("admin", "test"); login("admin", "test");
cy.visit("/learn/test-lehrgang-lp/analyse"); cy.visit("/course/versicherungsvermittler-in/learn/analyse");
}); });
it("self evaluation should be neutral", () => { it("self evaluation should be neutral", () => {
cy.get('[data-cy="test-lehrgang-lp-circle-analyse-lu-fahrzeug"]').find('[data-cy="unknown"]').should('exist'); cy.get(
'[data-cy="versicherungsvermittler-in-lp-circle-analyse-lu-fahrzeug"]'
)
.find('[data-cy="unknown"]')
.should("exist");
}); });
it("should be able to make a happy self evaluation", () => { it("should be able to make a happy self evaluation", () => {
cy.get('[data-cy="test-lehrgang-lp-circle-analyse-lu-fahrzeug"]').click(); cy.get(
cy.makeSelfEvaluation([true, true]) '[data-cy="versicherungsvermittler-in-lp-circle-analyse-lu-fahrzeug"]'
cy.get('[data-cy="test-lehrgang-lp-circle-analyse-lu-fahrzeug"]').find('[data-cy="success"]').should('exist'); ).click();
cy.makeSelfEvaluation([true, true, true]);
cy.get(
'[data-cy="versicherungsvermittler-in-lp-circle-analyse-lu-fahrzeug"]'
)
.find('[data-cy="success"]')
.should("exist");
}); });
it("should be able to make a fail self evaluation", () => { it("should be able to make a fail self evaluation", () => {
cy.get('[data-cy="test-lehrgang-lp-circle-analyse-lu-fahrzeug"]').click(); cy.get(
cy.makeSelfEvaluation([false, false]) '[data-cy="versicherungsvermittler-in-lp-circle-analyse-lu-fahrzeug"]'
cy.get('[data-cy="test-lehrgang-lp-circle-analyse-lu-fahrzeug"]').find('[data-cy="fail"]').should('exist'); ).click();
cy.makeSelfEvaluation([false, false, false]);
cy.get(
'[data-cy="versicherungsvermittler-in-lp-circle-analyse-lu-fahrzeug"]'
)
.find('[data-cy="fail"]')
.should("exist");
}); });
it("should be able to make a mixed self evaluation", () => { it("should be able to make a mixed self evaluation", () => {
cy.get('[data-cy="test-lehrgang-lp-circle-analyse-lu-fahrzeug"]').click(); cy.get(
cy.makeSelfEvaluation([false, true]) '[data-cy="versicherungsvermittler-in-lp-circle-analyse-lu-fahrzeug"]'
cy.get('[data-cy="test-lehrgang-lp-circle-analyse-lu-fahrzeug"]').find('[data-cy="fail"]').should('exist'); ).click();
cy.makeSelfEvaluation([false, true, true]);
cy.get(
'[data-cy="versicherungsvermittler-in-lp-circle-analyse-lu-fahrzeug"]'
)
.find('[data-cy="fail"]')
.should("exist");
}); });
}); });

View File

@ -7,40 +7,46 @@ describe("learningPath page", () => {
it("can open learningPath page", () => { it("can open learningPath page", () => {
login("admin", "test"); login("admin", "test");
cy.visit("/learn/versicherungsvermittlerin-lp"); cy.visit("/course/versicherungsvermittler-in/learn");
cy.get('[data-cy="learning-path-title"]').should( cy.get('[data-cy="learning-path-title"]').should(
"contain", "contain",
"Versicherungsvermittler/in" "Versicherungsvermittler/-in"
); );
}); });
it("click on circle on learningPath page will open circle", () => { it("/course/versicherungsvermittler-in/learn", () => {
login("admin", "test"); login("admin", "test");
cy.visit("/learn/test-lehrgang-lp"); cy.visit("/course/versicherungsvermittler-in/learn");
cy.get('[data-cy="circle-analyse"]').click({ force: true }); cy.get('[data-cy="circle-analyse"]').click({ force: true });
cy.url().should("include", "/learn/test-lehrgang-lp/analyse"); cy.url().should(
"include",
"/course/versicherungsvermittler-in/learn/analyse"
);
cy.get('[data-cy="circle-title"]').should("contain", "Analyse"); cy.get('[data-cy="circle-title"]').should("contain", "Analyse");
}); });
it("open listView and click on circle will open circle", () => { it("open listView and click on circle will open circle", () => {
login("admin", "test"); login("admin", "test");
cy.visit("/learn/test-lehrgang-lp"); cy.visit("/course/versicherungsvermittler-in/learn");
cy.get('[data-cy="show-list-view"]').click(); cy.get('[data-cy="show-list-view"]').click();
cy.get('[data-cy="full-screen-modal"]').should("be.visible"); cy.get('[data-cy="full-screen-modal"]').should("be.visible");
cy.get('[data-cy="circle-analyse-vertical"]').click({ force: true }); cy.get('[data-cy="circle-analyse-vertical"]').click({ force: true });
cy.url().should("include", "/learn/test-lehrgang-lp/analyse"); cy.url().should(
"include",
"/course/versicherungsvermittler-in/learn/analyse"
);
cy.get('[data-cy="circle-title"]').should("contain", "Analyse"); cy.get('[data-cy="circle-title"]').should("contain", "Analyse");
}); });
it("weiter gehts button will open next circle", () => { it("weiter gehts button will open next circle", () => {
login("admin", "test"); login("admin", "test");
cy.visit("/learn/test-lehrgang-lp"); cy.visit("/course/versicherungsvermittler-in/learn");
// first click will open first circle // first click will open first circle
cy.get('[data-cy="lp-continue-button"]').should("contain", "Los geht's"); cy.get('[data-cy="lp-continue-button"]').should("contain", "Los geht's");
@ -51,7 +57,7 @@ describe("learningPath page", () => {
// mark a learning content in second circle // mark a learning content in second circle
cy.get('[data-cy="circle-analyse"]').click({ force: true }); cy.get('[data-cy="circle-analyse"]').click({ force: true });
cy.get( cy.get(
'[data-cy="test-lehrgang-lp-circle-analyse-lc-fachcheck-fahrzeug-checkbox"] > .cy-checkbox' '[data-cy="versicherungsvermittler-in-lp-circle-analyse-lc-fachcheck-fahrzeug-checkbox"] > .cy-checkbox'
).click(); ).click();
cy.get('[data-cy="back-to-learning-path-button"]').click(); cy.get('[data-cy="back-to-learning-path-button"]').click();

View File

@ -1,6 +1,11 @@
import { login } from "./helpers"; import { login } from "./helpers";
describe("login", () => { describe("login", () => {
Cypress.on("uncaught:exception", (err, runnable) => {
// do not fail on failed requests during tests
return false;
});
beforeEach(() => { beforeEach(() => {
cy.manageCommand("cypress_reset"); cy.manageCommand("cypress_reset");
}); });
@ -31,7 +36,7 @@ describe("login", () => {
}); });
it("login will redirect to requestet page", () => { it("login will redirect to requestet page", () => {
cy.visit("/learn/versicherungsvermittlerin-lp"); cy.visit("/course/versicherungsvermittler-in/learn");
cy.get("h1").should("contain", "Login"); cy.get("h1").should("contain", "Login");
cy.get("#username").type("admin"); cy.get("#username").type("admin");

View File

@ -5,13 +5,13 @@ describe("MediaLibrary", () => {
cy.manageCommand("cypress_reset"); cy.manageCommand("cypress_reset");
login("admin", "test"); login("admin", "test");
cy.visit("/learn/test-lehrgang-lp/analyse"); cy.visit("/");
}); });
it("should be accessible", () => { it("should be accessible", () => {
cy.get('[data-cy="medialibrary-link"]').click(); cy.get('[data-cy="medialibrary-link"]').click();
cy.get('[data-cy="Handlungsfelder-link"]').click(); cy.get('[data-cy="Handlungsfelder-link"]').click();
cy.get('[data-cy="Fahrzeug-link"]').click(); cy.get('[data-cy="Fahrzeug-link"]').click();
cy.get('[data-cy="hf-title"]').should('contain', 'Fahrzeug') cy.get('[data-cy="hf-title"]').should("contain", "Fahrzeug");
}); });
}); });

View File

@ -122,10 +122,11 @@ def create_default_users(user_model=User, group_model=Group, default_password=No
last_name="VV", last_name="VV",
) )
_create_student_user( _create_student_user(
email="patrizia.huggel@eiger-versicherung.ch", email="patrizia.huggel@eiger-versicherungen.ch",
first_name="Patrizia", first_name="Patrizia",
last_name="Huggel", last_name="Huggel",
avatar_url="/static/avatars/uk1.patrizia.huggel.jpg", avatar_url="/static/avatars/uk1.patrizia.huggel.jpg",
password="myvbv1234",
) )
_create_student_user( _create_student_user(
email="daniel.tanaka@eiger-versicherung.ch", email="daniel.tanaka@eiger-versicherung.ch",

View File

@ -6,5 +6,4 @@ from vbv_lernwelt.course.models import CourseCompletion
@click.command() @click.command()
def command(): def command():
print("cypress reset data") print("cypress reset data")
CourseCompletion.objects.all().delete() CourseCompletion.objects.all().delete()

View File

@ -10,7 +10,7 @@ def create_versicherungsvermittlerin_with_categories(
apps=None, apps=None,
schema_editor=None, schema_editor=None,
course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID,
title="Versicherungsvermittler/in", title="Versicherungsvermittler/-in",
): ):
if apps is not None: if apps is not None:
Course = apps.get_model("course", "Course") Course = apps.get_model("course", "Course")

View File

@ -8,12 +8,12 @@ class CourseFactory(DjangoModelFactory):
class Meta: class Meta:
model = Course model = Course
title = "Versicherungsvermittler/in" title = "Versicherungsvermittler/-in"
category_name = "Handlungsfeld" category_name = "Handlungsfeld"
class CoursePageFactory(wagtail_factories.PageFactory): class CoursePageFactory(wagtail_factories.PageFactory):
title = "Versicherungsvermittler/in" title = "Versicherungsvermittler/-in"
class Meta: class Meta:
model = CoursePage model = CoursePage

View File

@ -63,7 +63,7 @@ def command():
# course session Versicherungsvermittler/in # course session Versicherungsvermittler/in
cs = CourseSession.objects.create( cs = CourseSession.objects.create(
course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID, course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID,
title="Versicherungsvermittler/in", title="Versicherungsvermittler/-in",
) )
for user_data in default_users: for user_data in default_users:
CourseSessionUser.objects.create( CourseSessionUser.objects.create(
@ -80,7 +80,7 @@ def command():
role=CourseSessionUser.Role.EXPERT, role=CourseSessionUser.Role.EXPERT,
) )
csu.expert.add( csu.expert.add(
Circle.objects.get(slug="versicherungsvermittlerin-lp-circle-einstieg") Circle.objects.get(slug="versicherungsvermittler-in-lp-circle-einstieg")
) )
csu = CourseSessionUser.objects.create( csu = CourseSessionUser.objects.create(
course_session=cs, course_session=cs,
@ -88,7 +88,7 @@ def command():
role=CourseSessionUser.Role.EXPERT, role=CourseSessionUser.Role.EXPERT,
) )
csu.expert.add( csu.expert.add(
Circle.objects.get(slug="versicherungsvermittlerin-lp-circle-analyse") Circle.objects.get(slug="versicherungsvermittler-in-lp-circle-analyse")
) )
# course session Überbetriebliche Kurse Lehrjahr 1 - Region Bern # course session Überbetriebliche Kurse Lehrjahr 1 - Region Bern
@ -119,7 +119,7 @@ def command():
# figma demo users and data # figma demo users and data
csu = CourseSessionUser.objects.create( csu = CourseSessionUser.objects.create(
course_session=cs, course_session=cs,
user=User.objects.get(username="patrizia.huggel@eiger-versicherung.ch"), user=User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch"),
role=CourseSessionUser.Role.EXPERT, role=CourseSessionUser.Role.EXPERT,
) )
csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-einstieg")) csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-einstieg"))

View File

@ -227,6 +227,7 @@ class CourseSessionUser(models.Model):
def to_dict(self): def to_dict(self):
return { return {
"session_id": self.course_session.id,
"session_title": self.course_session.title, "session_title": self.course_session.title,
"user_id": self.user.id, "user_id": self.user.id,
"first_name": self.user.first_name, "first_name": self.user.first_name,

View File

@ -138,7 +138,11 @@ def get_course_session_users(request, course_slug):
data = { data = {
"cockpit_user": cockpit_user_csu[0].to_dict() "cockpit_user": cockpit_user_csu[0].to_dict()
| {"circles": cockpit_user_csu[0].expert.all().values("id", "title")}, | {
"circles": cockpit_user_csu[0]
.expert.all()
.values("id", "title", "slug", "translation_key")
},
"users": user_data, "users": user_data,
} }

View File

@ -811,7 +811,7 @@ def create_standard_learning_unit(
( (
"media_library", "media_library",
MediaLibraryBlockFactory( MediaLibraryBlockFactory(
url=f"/media/versicherungsvermittlerin-media/category/{slugify(category_name)}" url=f"/media/versicherungsvermittler-in-media/category/{slugify(category_name)}"
), ),
) )
], ],

View File

@ -24,7 +24,7 @@ from vbv_lernwelt.learnpath.models_learning_unit_content import (
class LearningPathFactory(wagtail_factories.PageFactory): class LearningPathFactory(wagtail_factories.PageFactory):
title = "Versicherungsvermittler/in" title = "Versicherungsvermittler/-in"
class Meta: class Meta:
model = LearningPath model = LearningPath

View File

@ -131,25 +131,25 @@ die der Fahrzeugbesitzer und die Fahrzeugbesitzerin in einem grösseren Schadenf
create_internal_link_block( create_internal_link_block(
InternalLinkBlockFactory( InternalLinkBlockFactory(
title="Circle: Einstieg Lernsequenz: Anwenden", title="Circle: Einstieg Lernsequenz: Anwenden",
url="/learn/versicherungsvermittlerin-lp/einstieg#lu-fahrzeug", url="/learn/versicherungsvermittler-in-lp/einstieg#lu-fahrzeug",
) )
), ),
create_internal_link_block( create_internal_link_block(
InternalLinkBlockFactory( InternalLinkBlockFactory(
title="Circle: Analyse Lernsequenz: Anwenden", title="Circle: Analyse Lernsequenz: Anwenden",
url="/learn/versicherungsvermittlerin-lp/analyse#lu-fahrzeug", url="/learn/versicherungsvermittler-in-lp/analyse#lu-fahrzeug",
) )
), ),
create_internal_link_block( create_internal_link_block(
InternalLinkBlockFactory( InternalLinkBlockFactory(
title="Circle: Lösung Lernsequenz: Anwenden", title="Circle: Lösung Lernsequenz: Anwenden",
url="/learn/versicherungsvermittlerin-lp/lösung#lu-fahrzeug", url="/learn/versicherungsvermittler-in-lp/lösung#lu-fahrzeug",
) )
), ),
create_internal_link_block( create_internal_link_block(
InternalLinkBlockFactory( InternalLinkBlockFactory(
title="Circle: Abschluss Lernsequenz: Anwenden", title="Circle: Abschluss Lernsequenz: Anwenden",
url="/learn/versicherungsvermittlerin-lp/abschluss#lu-fahrzeug", url="/learn/versicherungsvermittler-in-lp/abschluss#lu-fahrzeug",
) )
), ),
], ],
@ -161,14 +161,14 @@ die der Fahrzeugbesitzer und die Fahrzeugbesitzerin in einem grösseren Schadenf
RelativeLinkBlockFactory( RelativeLinkBlockFactory(
title="Rechtsstreitigkeiten", title="Rechtsstreitigkeiten",
description="VBV 303/12.3 Verkehrsrechtsschutz", description="VBV 303/12.3 Verkehrsrechtsschutz",
url="/media/versicherungsvermittlerin-media/category/rechtsstreitigkeiten", url="/media/versicherungsvermittler-in-media/category/rechtsstreitigkeiten",
) )
), ),
create_relative_link_block( create_relative_link_block(
RelativeLinkBlockFactory( RelativeLinkBlockFactory(
title="Reisen", title="Reisen",
description="VBV 303/13 Reiseversicherung", description="VBV 303/13 Reiseversicherung",
url="/media/versicherungsvermittlerin-media/category/reisen", url="/media/versicherungsvermittler-in-media/category/reisen",
) )
), ),
], ],
@ -236,25 +236,25 @@ Diese können negative Folgen verschiedener Art nach sich ziehen, darunter recht
create_internal_link_block( create_internal_link_block(
InternalLinkBlockFactory( InternalLinkBlockFactory(
title="Circle: Einstieg Lernsequenz: Anwenden", title="Circle: Einstieg Lernsequenz: Anwenden",
url="/learn/versicherungsvermittlerin-lp/einstieg#lu-reisen", url="/learn/versicherungsvermittler-in-lp/einstieg#lu-reisen",
) )
), ),
create_internal_link_block( create_internal_link_block(
InternalLinkBlockFactory( InternalLinkBlockFactory(
title="Circle: Analyse Lernsequenz: Anwenden", title="Circle: Analyse Lernsequenz: Anwenden",
url="/learn/versicherungsvermittlerin-lp/analyse#lu-reisen", url="/learn/versicherungsvermittler-in-lp/analyse#lu-reisen",
) )
), ),
create_internal_link_block( create_internal_link_block(
InternalLinkBlockFactory( InternalLinkBlockFactory(
title="Circle: Lösung Lernsequenz: Anwenden", title="Circle: Lösung Lernsequenz: Anwenden",
url="/learn/versicherungsvermittlerin-lp/lösung#lu-reisen", url="/learn/versicherungsvermittler-in-lp/lösung#lu-reisen",
) )
), ),
create_internal_link_block( create_internal_link_block(
InternalLinkBlockFactory( InternalLinkBlockFactory(
title="Circle: Abschluss Lernsequenz: Anwenden", title="Circle: Abschluss Lernsequenz: Anwenden",
url="/learn/versicherungsvermittlerin-lp/abschluss#lu-reisen", url="/learn/versicherungsvermittler-in-lp/abschluss#lu-reisen",
) )
), ),
], ],
@ -266,21 +266,21 @@ Diese können negative Folgen verschiedener Art nach sich ziehen, darunter recht
RelativeLinkBlockFactory( RelativeLinkBlockFactory(
title="Haushalt", title="Haushalt",
description="VBV 303/03 Hausratversicherung", description="VBV 303/03 Hausratversicherung",
url="/media/versicherungsvermittlerin-media/category/haushalt", url="/media/versicherungsvermittler-in-media/category/haushalt",
) )
), ),
create_relative_link_block( create_relative_link_block(
RelativeLinkBlockFactory( RelativeLinkBlockFactory(
title="Rechtsstreitigkeiten", title="Rechtsstreitigkeiten",
desciption="VBV 303/12 Rechtschutzversicherung", desciption="VBV 303/12 Rechtschutzversicherung",
url="/media/versicherungsvermittlerin-media/category/rechtsstreitigkeiten", url="/media/versicherungsvermittler-in-media/category/rechtsstreitigkeiten",
) )
), ),
create_relative_link_block( create_relative_link_block(
RelativeLinkBlockFactory( RelativeLinkBlockFactory(
title="Gesundheit", title="Gesundheit",
description="VBV 304/Teil E Obligatorische Krankenversicherung", description="VBV 304/Teil E Obligatorische Krankenversicherung",
url="/media/versicherungsvermittlerin-media/category/gesundheit", url="/media/versicherungsvermittler-in-media/category/gesundheit",
) )
), ),
], ],

View File

@ -81,7 +81,7 @@ class InternalLinkBlockFactory(wagtail_factories.StructBlockFactory):
title = "Platzhalter interner Link" title = "Platzhalter interner Link"
description = "Link to a Learning Content" description = "Link to a Learning Content"
link_display_text = "Lerneinheit anzeigen" link_display_text = "Lerneinheit anzeigen"
url = "/learn/versicherungsvermittlerin-lp/basis" url = "/learn/versicherungsvermittler-in-lp/basis"
# TODO: page = blocks.PageChooserBlock mit Titel etc # TODO: page = blocks.PageChooserBlock mit Titel etc
@ -103,7 +103,7 @@ class RelativeLinkBlockFactory(wagtail_factories.StructBlockFactory):
description = "Platzhalter Querverweis" description = "Platzhalter Querverweis"
link_display_text = "Handlungsfeld anzeigen" link_display_text = "Handlungsfeld anzeigen"
icon_url = "/static/icons/demo/icon-hf-reisen.svg" icon_url = "/static/icons/demo/icon-hf-reisen.svg"
url = "/media/versicherungsvermittlerin-media/category/fahrzeug" url = "/media/versicherungsvermittler-in-media/category/fahrzeug"
# TODO: page = blocks.PageChooserBlock zu Handlungsfeld # TODO: page = blocks.PageChooserBlock zu Handlungsfeld