Merge branch 'feature/competences' into develop
This commit is contained in:
commit
d57c341d8c
|
|
@ -28,12 +28,12 @@ function isInRoutePath(checkPaths: string[]) {
|
|||
return checkPaths.some((path) => route.path.startsWith(path));
|
||||
}
|
||||
|
||||
function inLearningPath() {
|
||||
return isInRoutePath(["/learn/"]);
|
||||
function inCourse() {
|
||||
return isInRoutePath(["/learn", "/competence"]);
|
||||
}
|
||||
|
||||
function getLearningPathStringProp(prop: "title" | "slug"): string {
|
||||
return inLearningPath() && learningPathStore.learningPath
|
||||
return inCourse() && learningPathStore.learningPath
|
||||
? learningPathStore.learningPath[prop]
|
||||
: "";
|
||||
}
|
||||
|
|
@ -144,19 +144,19 @@ const profileDropdownData = [
|
|||
class="flex-auto mt-8 lg:flex lg:space-y-0 lg:flex-row lg:items-center lg:space-x-10 lg:mt-0"
|
||||
>
|
||||
<router-link
|
||||
v-if="inLearningPath()"
|
||||
v-if="inCourse()"
|
||||
to="/learn/versicherungsvermittlerin-lp"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inLearningPath() }"
|
||||
:class="{ 'nav-item--active': isInRoutePath(['/learn']) }"
|
||||
>
|
||||
Lernpfad
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
v-if="inLearningPath()"
|
||||
to="/competences/"
|
||||
v-if="inCourse()"
|
||||
to="/competence/versicherungsvermittlerin-competence"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': isInRoutePath(['/competences/']) }"
|
||||
:class="{ 'nav-item--active': isInRoutePath(['/competence']) }"
|
||||
>
|
||||
Kompetenzprofil
|
||||
</router-link>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
<script setup lang="ts">
|
||||
import ComptenceProgress from "@/components/competences/CompetenceProgress.vue";
|
||||
import PerformanceCriteriaRow from "@/components/competences/PerformanceCriteriaRow.vue";
|
||||
import { useCompetenceStore } from "@/stores/competence";
|
||||
import type { CompetencePage } from "@/types";
|
||||
import { ref } from "vue";
|
||||
|
||||
const competenceStore = useCompetenceStore();
|
||||
|
||||
const props = defineProps<{
|
||||
competence: CompetencePage;
|
||||
}>();
|
||||
|
||||
const isOpen = ref(false);
|
||||
|
||||
const togglePerformanceCriteria = () => {
|
||||
isOpen.value = !isOpen.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div :class="{ 'pb-8 border-b border-gray-500 mb-4': isOpen }" class="-mx-8 px-8">
|
||||
<div class="mb-4 flex flex-row justify-between items-center">
|
||||
<h2 class="text-large">
|
||||
{{ competence.competence_id }} {{ competence.title }}
|
||||
</h2>
|
||||
<button class="transition-transform" :class="{ 'rotate-180': isOpen }">
|
||||
<it-icon-arrow-down
|
||||
class="h-10 w-10"
|
||||
aria-hidden="true"
|
||||
@click="togglePerformanceCriteria()"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<ComptenceProgress
|
||||
:status-count="competenceStore.calcStatusCount(competence.children)"
|
||||
></ComptenceProgress>
|
||||
</div>
|
||||
<ul v-if="isOpen">
|
||||
<li
|
||||
v-for="performanceCriteria in competence.children"
|
||||
:key="performanceCriteria.id"
|
||||
class="mb-4 pb-4 border-b border-gray-500"
|
||||
>
|
||||
<PerformanceCriteriaRow
|
||||
:criteria="performanceCriteria"
|
||||
:show-state="true"
|
||||
></PerformanceCriteriaRow>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
statusCount?: {
|
||||
fail: number;
|
||||
success: number;
|
||||
unknown: number;
|
||||
};
|
||||
}>();
|
||||
|
||||
const total = computed(() => {
|
||||
return (
|
||||
(props.statusCount?.fail || 0) +
|
||||
(props.statusCount?.success || 0) +
|
||||
(props.statusCount?.unknown || 0)
|
||||
);
|
||||
});
|
||||
|
||||
const done = computed(() => {
|
||||
if (total.value === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ((props.statusCount?.success || 0) / total.value) * 100;
|
||||
});
|
||||
|
||||
const notDone = computed(() => {
|
||||
if (total.value === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ((props.statusCount?.fail || 0) / total.value) * 100 + done.value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="relative w-full h-2 bg-gray-300 inline-block">
|
||||
<span
|
||||
v-if="notDone !== done"
|
||||
class="absolute bg-orange-500 h-full"
|
||||
:style="{ width: `${notDone}%` }"
|
||||
></span>
|
||||
<span
|
||||
v-if="done > 0"
|
||||
class="absolute bg-green-500 h-full"
|
||||
:style="{ width: `${done}%` }"
|
||||
></span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<script setup lang="ts">
|
||||
import type { PerformanceCriteria } from "@/types";
|
||||
|
||||
interface Props {
|
||||
criteria: PerformanceCriteria;
|
||||
showState?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
criteria: undefined,
|
||||
showState: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col lg:flex-row lg:items-center justify-between">
|
||||
<div class="flex flex-row items-center">
|
||||
<div v-if="showState" class="h-8 w-8 mr-4">
|
||||
<it-icon-smiley-happy
|
||||
v-if="criteria.completion_status === 'success'"
|
||||
></it-icon-smiley-happy>
|
||||
<it-icon-smiley-thinking
|
||||
v-else-if="criteria.completion_status === 'fail'"
|
||||
></it-icon-smiley-thinking>
|
||||
<it-icon-smiley-neutral v-else></it-icon-smiley-neutral>
|
||||
</div>
|
||||
<div class="pr-5 lg:mr-10 mb-4 lg:mb-0">
|
||||
<h4 class="text-bold mb-2">
|
||||
{{ criteria.competence_id }} {{ criteria.title }}
|
||||
</h4>
|
||||
<p>
|
||||
Lerneinheit:
|
||||
<a class="link" :href="criteria.learning_unit.frontend_url">
|
||||
{{ criteria.learning_unit.title }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="whitespace-nowrap">
|
||||
<a class="link" :href="criteria.learning_unit.evaluate_url">
|
||||
Sich nochmals einschätzen
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -72,7 +72,9 @@ function handleContinue() {
|
|||
</p>
|
||||
|
||||
<div class="mt-4 lg:mt-8 p-6 lg:p-12 border border-gray-500">
|
||||
<h2 class="heading-2">{{ currentQuestion.title }}</h2>
|
||||
<h2 class="heading-2">
|
||||
{{ currentQuestion.competence_id }} {{ currentQuestion.title }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-4 lg:mt-8 flex flex-col lg:flex-row justify-between gap-6">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
<script setup lang="ts">
|
||||
import CompetenceProgress from "@/components/competences/CompetenceProgress.vue";
|
||||
import PerformanceCriteriaRow from "@/components/competences/PerformanceCriteriaRow.vue";
|
||||
import { useCompetenceStore } from "@/stores/competence";
|
||||
import _ from "lodash";
|
||||
import * as log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
|
||||
log.debug("CompetenceIndexPage created");
|
||||
|
||||
const competenceStore = useCompetenceStore();
|
||||
|
||||
const failedCriteria = computed(() => {
|
||||
return competenceStore.flatPerformanceCriteria
|
||||
.filter((criteria) => {
|
||||
return criteria.completion_status === "fail";
|
||||
})
|
||||
.slice(0, 3);
|
||||
});
|
||||
|
||||
const lastUpdatedCompetences = computed(() => {
|
||||
if (competenceStore.competenceProfilePage?.children.length) {
|
||||
return _.orderBy(
|
||||
competenceStore.competenceProfilePage.children,
|
||||
[
|
||||
(competence) => {
|
||||
return (
|
||||
_.maxBy(competence.children, "completion_status_updated_at")
|
||||
?.completion_status_updated_at || ""
|
||||
);
|
||||
},
|
||||
],
|
||||
["desc"]
|
||||
).slice(0, 3);
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
const countStatus = computed(() => {
|
||||
return competenceStore.calcStatusCount(competenceStore.flatPerformanceCriteria);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-large lg:mt-4">
|
||||
<div
|
||||
v-if="competenceStore.competenceProfilePage"
|
||||
class="flex flex-col lg:flex-row items-center justify-between mb-10"
|
||||
>
|
||||
<h1>Kompetenzprofil</h1>
|
||||
<!-- <ItDropdownSelect-->
|
||||
<!-- v-model="dropdownSelected"-->
|
||||
<!-- :items="mediaStore.availableLearningPaths"></ItDropdownSelect>-->
|
||||
</div>
|
||||
<div class="bg-white p-8 mb-8">
|
||||
<div>
|
||||
<h3 class="pb-4 border-b border-gray-500">Letzte verbesserte Kompetenzen</h3>
|
||||
<ul>
|
||||
<li
|
||||
v-for="competence in lastUpdatedCompetences"
|
||||
:key="competence.id"
|
||||
class="py-4 flex flex-col lg:flex-row lg:items-center border-b border-gray-500 last:mb-8"
|
||||
>
|
||||
<p class="mb-4 lg:mb-0 lg:w-1/4 inline-block lg:mr-5">
|
||||
{{ competence.competence_id }} {{ competence.title }}
|
||||
</p>
|
||||
<CompetenceProgress
|
||||
:status-count="competenceStore.calcStatusCount(competence.children)"
|
||||
></CompetenceProgress>
|
||||
</li>
|
||||
</ul>
|
||||
<router-link
|
||||
:to="`${competenceStore.competenceProfilePage?.frontend_url}/competences`"
|
||||
class="flex items-center"
|
||||
>
|
||||
<span>Alle anschauen</span>
|
||||
<it-icon-arrow-right></it-icon-arrow-right>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white p-8 mb-8">
|
||||
<h3 class="mb-4">Einschätzungen</h3>
|
||||
<ul
|
||||
class="flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8 mb-6"
|
||||
>
|
||||
<li class="inline-block mb-4 lg:mb-0 lg:border-r lg:border-gray-500 flex-1">
|
||||
<h5 class="text-gray-700 mb-4">«Das muss ich nochmals anschauen»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-thinking class="w-16 h-16"></it-icon-smiley-thinking>
|
||||
<p class="text-7xl font-bold inline-block ml-4">{{ countStatus.fail }}</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="inline-block mb-4 lg:mb-0 lg:border-r lg:border-gray-500 flex-1">
|
||||
<h5 class="text-gray-700 mb-4">«Ja, ich kann das»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-happy class="w-16 h-16"></it-icon-smiley-happy>
|
||||
<p class="text-7xl font-bold inline-block ml-4">
|
||||
{{ countStatus.success }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex-1">
|
||||
<h5 class="text-gray-700 mb-4">Nicht eingeschätzt</h5>
|
||||
<div class="flex flex-row items-center border-r">
|
||||
<it-icon-smiley-neutral class="w-16 h-16"></it-icon-smiley-neutral>
|
||||
<p class="text-7xl font-bold inline-block ml-4">
|
||||
{{ countStatus.unknown }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<router-link
|
||||
:to="`${competenceStore.competenceProfilePage?.frontend_url}/criteria`"
|
||||
class="flex items-center"
|
||||
>
|
||||
<span>Alle anschauen</span>
|
||||
<it-icon-arrow-right></it-icon-arrow-right>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="bg-white p-8">
|
||||
<div class="border-b border-gray-500 flex flex-row items-center pb-4 mb-4">
|
||||
<it-icon-smiley-thinking class="w-11 h-11 mr-5"></it-icon-smiley-thinking>
|
||||
<h3>«Das muss ich nochmals anschauen»</h3>
|
||||
</div>
|
||||
<ul class="mb-6">
|
||||
<li
|
||||
v-for="criteria in failedCriteria"
|
||||
:key="criteria.id"
|
||||
class="mb-4 pb-4 border-b border-gray-500"
|
||||
>
|
||||
<PerformanceCriteriaRow :criteria="criteria"></PerformanceCriteriaRow>
|
||||
</li>
|
||||
</ul>
|
||||
<router-link
|
||||
:to="`${competenceStore.competenceProfilePage?.frontend_url}/criteria`"
|
||||
class="flex items-center"
|
||||
>
|
||||
<span>Alle anschauen</span>
|
||||
<it-icon-arrow-right></it-icon-arrow-right>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<script setup lang="ts">
|
||||
import CompetenceDetail from "@/components/competences/CompetenceDetail.vue";
|
||||
import { useCompetenceStore } from "@/stores/competence";
|
||||
import * as log from "loglevel";
|
||||
|
||||
log.debug("CompetencesMainView created");
|
||||
|
||||
const competenceStore = useCompetenceStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-large">
|
||||
<nav class="lg:mt-4">
|
||||
<a
|
||||
class="block mb-8 cursor-pointer flex items-center"
|
||||
:href="competenceStore.competenceProfilePage?.frontend_url"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>zurück</span></a
|
||||
>
|
||||
</nav>
|
||||
<div class="flex flex-col lg:flex-row items-center justify-between mb-10">
|
||||
<h1>Kompetenzen</h1>
|
||||
<!-- <ItDropdownSelect-->
|
||||
<!-- v-model="dropdownSelected"-->
|
||||
<!-- :items="mediaStore.availableLearningPaths"></ItDropdownSelect>-->
|
||||
</div>
|
||||
<ul v-if="competenceStore.competenceProfilePage">
|
||||
<li
|
||||
v-for="competence in competenceStore.competenceProfilePage.children"
|
||||
:key="competence.id"
|
||||
class="bg-white p-8 mb-8"
|
||||
>
|
||||
<CompetenceDetail :competence="competence"></CompetenceDetail>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<script setup lang="ts">
|
||||
import { useCompetenceStore } from "@/stores/competence";
|
||||
import * as log from "loglevel";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
log.debug("CompetenceParentPage created");
|
||||
|
||||
const props = defineProps<{
|
||||
competenceProfilePageSlug: string;
|
||||
}>();
|
||||
|
||||
const competenceStore = useCompetenceStore();
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("CompetencesView mounted", props.competenceProfilePageSlug);
|
||||
|
||||
try {
|
||||
await competenceStore.loadCompetenceProfilePage(props.competenceProfilePageSlug);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<main>
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<script setup lang="ts">
|
||||
import { default as PerformanceCriteriaRow } from "@/components/competences/PerformanceCriteriaRow.vue";
|
||||
import { useCompetenceStore } from "@/stores/competence";
|
||||
import type { CourseCompletionStatus } from "@/types";
|
||||
import * as log from "loglevel";
|
||||
import { computed, Ref, ref } from "vue";
|
||||
|
||||
log.debug("CompetencesMainView created");
|
||||
|
||||
const competenceStore = useCompetenceStore();
|
||||
|
||||
const activeState: Ref<CourseCompletionStatus> = ref("fail");
|
||||
|
||||
const shownCriteria = computed(() => {
|
||||
return competenceStore.flatPerformanceCriteria.filter((criteria) => {
|
||||
return criteria.completion_status === activeState.value;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-large">
|
||||
<nav class="lg:mt-4">
|
||||
<a
|
||||
class="block mb-8 cursor-pointer flex items-center"
|
||||
:href="`${competenceStore.competenceProfilePage?.frontend_url}`"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>zurück</span></a
|
||||
>
|
||||
</nav>
|
||||
<div class="flex flex-col lg:flex-row items-center justify-between mb-10">
|
||||
<h1>Einschätzungen</h1>
|
||||
<!-- <ItDropdownSelect-->
|
||||
<!-- v-model="dropdownSelected"-->
|
||||
<!-- :items="mediaStore.availableLearningPaths"></ItDropdownSelect>-->
|
||||
</div>
|
||||
<div class="bg-white p-8">
|
||||
<div
|
||||
class="border-b border-gray-500 flex flex-col lg:flex-row lg:items-center pb-4 mb-4"
|
||||
>
|
||||
<button
|
||||
:class="{ 'bg-gray-200': activeState === 'fail' }"
|
||||
class="flex flex-row items-center py-4 px-2 mr-6"
|
||||
@click="activeState = 'fail'"
|
||||
>
|
||||
<span class="inline-block mr-2">«Das muss ich nochmals anschauen»</span>
|
||||
<span><it-icon-smiley-thinking></it-icon-smiley-thinking></span>
|
||||
</button>
|
||||
<button
|
||||
:class="{ 'bg-gray-200': activeState === 'success' }"
|
||||
class="flex flex-row items-center py-4 px-2 mr-6"
|
||||
@click="activeState = 'success'"
|
||||
>
|
||||
<span class="inline-block mr-2">«Ja, ich kann das»</span>
|
||||
<span><it-icon-smiley-happy></it-icon-smiley-happy></span>
|
||||
</button>
|
||||
<button
|
||||
:class="{ 'bg-gray-200': activeState === 'unknown' }"
|
||||
class="flex flex-row items-center py-4 px-2"
|
||||
@click="activeState = 'unknown'"
|
||||
>
|
||||
<span class="inline-block mr-2">Nicht eingeschätzt</span>
|
||||
<span><it-icon-smiley-neutral></it-icon-smiley-neutral></span>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="mb-6">
|
||||
<li
|
||||
v-for="criteria in shownCriteria"
|
||||
:key="criteria.id"
|
||||
class="mb-4 pb-4 border-b border-gray-500"
|
||||
>
|
||||
<PerformanceCriteriaRow
|
||||
:criteria="criteria"
|
||||
:show-state="true"
|
||||
></PerformanceCriteriaRow>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import LinkCard from "@/components/mediaLibrary/LinkCard.vue";
|
||||
import MediaLink from "@/components/mediaLibrary/MediaLink.vue";
|
||||
import MLCategoryLayout from "@/pages/mediaLibrary/MLCategoryLayout.vue";
|
||||
import { useMediaLibraryStore } from "@/stores/mediaLibrary";
|
||||
import * as log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
|
|
@ -48,9 +47,21 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport v-if="mediaStore.mediaLibraryPage && mediaCategory" to="body">
|
||||
<MLCategoryLayout>
|
||||
<template #header>
|
||||
<div
|
||||
v-if="mediaCategory"
|
||||
class="fixed top-0 overflow-y-scroll bg-white h-full w-full"
|
||||
>
|
||||
<div class="bg-gray-200">
|
||||
<div class="container-large">
|
||||
<nav>
|
||||
<a
|
||||
class="block my-9 cursor-pointer flex items-center"
|
||||
:href="`${mediaStore.mediaLibraryPage.frontend_url}/category`"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>zurück</span></a
|
||||
>
|
||||
</nav>
|
||||
<div class="flex justify-between">
|
||||
<div class="lg:w-6/12">
|
||||
<h3 class="font-normal text-large mb-3">Handlungsfeld</h3>
|
||||
|
|
@ -64,91 +75,89 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<section class="mb-20 mt-8 lg:w-2/3">
|
||||
<h2 class="mb-4">{{ mediaCategory.description_title }}</h2>
|
||||
<p class="mb-4">{{ mediaCategory.description_text }}</p>
|
||||
<ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-large">
|
||||
<section class="mb-20 mt-8 lg:w-2/3">
|
||||
<h2 class="mb-4">{{ mediaCategory.description_title }}</h2>
|
||||
<p class="mb-4">{{ mediaCategory.description_text }}</p>
|
||||
<ul>
|
||||
<li
|
||||
v-for="item in mediaCategory.items"
|
||||
:key="item"
|
||||
class="mb-2 flex items-center"
|
||||
>
|
||||
<it-icon-check class="h-8 w-8 text-sky-500 mr-4 flex-none"></it-icon-check>
|
||||
{{ item.value }}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<template
|
||||
v-for="content_collection in mediaCategory.body"
|
||||
:key="content_collection.value.title"
|
||||
>
|
||||
<section v-if="content_collection.value?.contents?.length" class="mb-20">
|
||||
<h2 class="mb-4">{{ content_collection.value.title }}</h2>
|
||||
<p class="mb-4 lg:w-2/3">
|
||||
{{ content_collection.value.description }}
|
||||
</p>
|
||||
<ul
|
||||
:class="{
|
||||
'grid gap-4 grid-cols-1 lg:grid-cols-2': displayAsCard(
|
||||
content_collection.value.contents[0].type
|
||||
),
|
||||
'border-t': !displayAsCard(content_collection.value.contents[0].type),
|
||||
'mb-6': hasMoreItemsForType(
|
||||
content_collection.value.contents[0].type,
|
||||
content_collection.value.contents
|
||||
),
|
||||
}"
|
||||
>
|
||||
<li
|
||||
v-for="item in mediaCategory.items"
|
||||
:key="item"
|
||||
class="mb-2 flex items-center"
|
||||
v-for="mediaItem in getMaxDisplayItemsForType(
|
||||
content_collection.value.contents[0].type,
|
||||
content_collection.value.contents
|
||||
)"
|
||||
:key="mediaItem.id"
|
||||
>
|
||||
<it-icon-check
|
||||
class="h-8 w-8 text-sky-500 mr-4 flex-none"
|
||||
></it-icon-check>
|
||||
{{ item.value }}
|
||||
<LinkCard
|
||||
v-if="displayAsCard(mediaItem.type)"
|
||||
:title="mediaItem.value.title"
|
||||
:icon="mediaItem.value.icon_url"
|
||||
:description="mediaItem.value.description"
|
||||
:url="mediaItem.value.url"
|
||||
:link-text="mediaItem.value.link_display_text"
|
||||
:open-window="mediaItem.value.open_window"
|
||||
/>
|
||||
<div v-else class="flex items-center justify-between border-b py-4">
|
||||
<h4 class="text-bold">{{ mediaItem.value.title }}</h4>
|
||||
<media-link
|
||||
:blank="mediaItem.value.open_window"
|
||||
:to="mediaItem.value.url"
|
||||
class="link"
|
||||
>{{ mediaItem.value.link_display_text }}
|
||||
</media-link>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<router-link
|
||||
v-if="
|
||||
hasMoreItemsForType(
|
||||
content_collection.value.contents[0].type,
|
||||
content_collection.value.contents
|
||||
)
|
||||
"
|
||||
:to="`${mediaCategory.frontend_url}/media`"
|
||||
class="flex items-center"
|
||||
>
|
||||
<span>Alle anschauen</span>
|
||||
<it-icon-arrow-right></it-icon-arrow-right>
|
||||
</router-link>
|
||||
</section>
|
||||
|
||||
<template
|
||||
v-for="content_collection in mediaCategory.body"
|
||||
:key="content_collection.value.title"
|
||||
>
|
||||
<section v-if="content_collection.value?.contents?.length" class="mb-20">
|
||||
<h2 class="mb-4">{{ content_collection.value.title }}</h2>
|
||||
<p class="mb-4 lg:w-2/3">
|
||||
{{ content_collection.value.description }}
|
||||
</p>
|
||||
<ul
|
||||
:class="{
|
||||
'grid gap-4 grid-cols-1 lg:grid-cols-2': displayAsCard(
|
||||
content_collection.value.contents[0].type
|
||||
),
|
||||
'border-t': !displayAsCard(content_collection.value.contents[0].type),
|
||||
'mb-6': hasMoreItemsForType(
|
||||
content_collection.value.contents[0].type,
|
||||
content_collection.value.contents
|
||||
),
|
||||
}"
|
||||
>
|
||||
<li
|
||||
v-for="mediaItem in getMaxDisplayItemsForType(
|
||||
content_collection.value.contents[0].type,
|
||||
content_collection.value.contents
|
||||
)"
|
||||
:key="mediaItem.id"
|
||||
>
|
||||
<LinkCard
|
||||
v-if="displayAsCard(mediaItem.type)"
|
||||
:title="mediaItem.value.title"
|
||||
:icon="mediaItem.value.icon_url"
|
||||
:description="mediaItem.value.description"
|
||||
:url="mediaItem.value.url"
|
||||
:link-text="mediaItem.value.link_display_text"
|
||||
:open-window="mediaItem.value.open_window"
|
||||
/>
|
||||
<div v-else class="flex items-center justify-between border-b py-4">
|
||||
<h4 class="text-bold">{{ mediaItem.value.title }}</h4>
|
||||
<media-link
|
||||
:blank="mediaItem.value.open_window"
|
||||
:to="mediaItem.value.url"
|
||||
class="link"
|
||||
>{{ mediaItem.value.link_display_text }}
|
||||
</media-link>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<router-link
|
||||
v-if="
|
||||
hasMoreItemsForType(
|
||||
content_collection.value.contents[0].type,
|
||||
content_collection.value.contents
|
||||
)
|
||||
"
|
||||
:to="`${mediaCategory.frontend_url}/media`"
|
||||
class="flex items-center"
|
||||
>
|
||||
<span>Alle anschauen</span>
|
||||
<it-icon-arrow-right></it-icon-arrow-right>
|
||||
</router-link>
|
||||
</section>
|
||||
</template>
|
||||
</template>
|
||||
</MLCategoryLayout>
|
||||
</Teleport>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div class="fixed top-0 overflow-y-scroll bg-white h-full w-full">
|
||||
<div class="bg-gray-200">
|
||||
<div class="container-large">
|
||||
<nav>
|
||||
<a
|
||||
class="block my-9 cursor-pointer flex items-center"
|
||||
@click="router.go(-1)"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>zurück</span></a
|
||||
>
|
||||
</nav>
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-large">
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.it-icon-hf {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.it-icon-hf > * {
|
||||
@apply m-auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import MediaLink from "@/components/mediaLibrary/MediaLink.vue";
|
||||
import MLCategoryLayout from "@/pages/mediaLibrary/MLCategoryLayout.vue";
|
||||
import { useMediaLibraryStore } from "@/stores/mediaLibrary";
|
||||
import * as log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
|
|
@ -35,44 +34,56 @@ const mediaList = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport v-if="mediaList" to="body">
|
||||
<MLCategoryLayout>
|
||||
<template #header>
|
||||
<div
|
||||
v-if="mediaCategory"
|
||||
class="fixed top-0 overflow-y-scroll bg-white h-full w-full"
|
||||
>
|
||||
<div class="bg-gray-200">
|
||||
<div class="container-large">
|
||||
<nav>
|
||||
<a
|
||||
class="block my-9 cursor-pointer flex items-center"
|
||||
:href="mediaStore.mediaLibraryPage.frontend_url"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>zurück</span></a
|
||||
>
|
||||
</nav>
|
||||
<h1 class="mb-4">{{ mediaList.title }}</h1>
|
||||
</template>
|
||||
<template #body>
|
||||
<section class="mb-20">
|
||||
<ul class="border-t">
|
||||
<li
|
||||
v-for="item in mediaList.contents"
|
||||
:key="item.id"
|
||||
class="flex items-center justify-between border-b py-4"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div v-if="item.value.icon_url">
|
||||
<img class="mr-6 max-h-[70px]" :src="item.value.icon_url" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-bold">{{ item.value.title }}</h4>
|
||||
<p v-if="item.value.description" class="mb-2">
|
||||
{{ item.value.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-large">
|
||||
<section class="mb-20">
|
||||
<ul class="border-t">
|
||||
<li
|
||||
v-for="item in mediaList.contents"
|
||||
:key="item.id"
|
||||
class="flex items-center justify-between border-b py-4"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div v-if="item.value.icon_url">
|
||||
<img class="mr-6 max-h-[70px]" :src="item.value.icon_url" />
|
||||
</div>
|
||||
<div class="">
|
||||
<media-link
|
||||
:to="item.value.url"
|
||||
:blank="item.value.open_window"
|
||||
class="link"
|
||||
>{{ item.value.link_display_text }}
|
||||
</media-link>
|
||||
<div>
|
||||
<h4 class="text-bold">{{ item.value.title }}</h4>
|
||||
<p v-if="item.value.description" class="mb-2">
|
||||
{{ item.value.description }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
</MLCategoryLayout>
|
||||
</Teleport>
|
||||
</div>
|
||||
<div class="">
|
||||
<media-link
|
||||
:to="item.value.url"
|
||||
:blank="item.value.open_window"
|
||||
class="link"
|
||||
>{{ item.value.link_display_text }}
|
||||
</media-link>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,25 @@ const router = createRouter({
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/competence/:competenceProfilePageSlug",
|
||||
props: true,
|
||||
component: () => import("@/pages/competence/CompetenceParentPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/pages/competence/CompetenceIndexPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "competences",
|
||||
component: () => import("@/pages/competence/CompetenceListPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "criteria",
|
||||
component: () => import("@/pages/competence/PerformanceCriteriaPage.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/learn/:learningPathSlug",
|
||||
component: () => import("../pages/learningPath/LearningPathPage.vue"),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import * as log from "loglevel";
|
||||
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
import { itPost } from "@/fetchHelpers";
|
||||
import type { Circle } from "@/services/circle";
|
||||
import { useCompletionStore } from "@/stores/completion";
|
||||
import { useLearningPathStore } from "@/stores/learningPath";
|
||||
import type {
|
||||
CourseCompletionStatus,
|
||||
|
|
@ -11,6 +9,7 @@ import type {
|
|||
LearningUnit,
|
||||
LearningUnitPerformanceCriteria,
|
||||
} from "@/types";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export type CircleStoreState = {
|
||||
circle: Circle | undefined;
|
||||
|
|
@ -89,12 +88,11 @@ export const useCircleStore = defineStore({
|
|||
page: LearningContent | LearningUnitPerformanceCriteria,
|
||||
completion_status: CourseCompletionStatus = "success"
|
||||
) {
|
||||
const completionStore = useCompletionStore();
|
||||
|
||||
try {
|
||||
page.completion_status = completion_status;
|
||||
const completionData = await itPost("/api/course/completion/mark/", {
|
||||
page_key: page.translation_key,
|
||||
completion_status: page.completion_status,
|
||||
});
|
||||
const completionData = await completionStore.markPage(page);
|
||||
if (this.circle) {
|
||||
this.circle.parseCompletionData(completionData);
|
||||
}
|
||||
|
|
@ -116,7 +114,7 @@ export const useCircleStore = defineStore({
|
|||
},
|
||||
openSelfEvaluation(learningUnit: LearningUnit) {
|
||||
this.router.push({
|
||||
path: learningUnit.frontend_url,
|
||||
path: learningUnit.evaluate_url,
|
||||
});
|
||||
},
|
||||
closeSelfEvaluation(learningUnit: LearningUnit) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
import { itGet } from "@/fetchHelpers";
|
||||
import { useCompletionStore } from "@/stores/completion";
|
||||
import type { CompetenceProfilePage, PerformanceCriteria } from "@/types";
|
||||
import _ from "lodash";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export type CompetenceStoreState = {
|
||||
competenceProfilePage: CompetenceProfilePage | undefined;
|
||||
};
|
||||
|
||||
export const useCompetenceStore = defineStore({
|
||||
id: "competence",
|
||||
state: () => {
|
||||
return {
|
||||
competenceProfilePage: undefined,
|
||||
} as CompetenceStoreState;
|
||||
},
|
||||
getters: {
|
||||
flatPerformanceCriteria: (state, circleTitle = "") => {
|
||||
if (!state.competenceProfilePage) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let criteria = _.orderBy(
|
||||
state.competenceProfilePage.children.flatMap((competence) => {
|
||||
return competence.children;
|
||||
}),
|
||||
["competence_id"],
|
||||
["asc"]
|
||||
);
|
||||
|
||||
if (circleTitle) {
|
||||
criteria = criteria.filter((c) => c.circle === circleTitle);
|
||||
}
|
||||
|
||||
return criteria;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
calcStatusCount(criteria: PerformanceCriteria[]) {
|
||||
if (criteria) {
|
||||
const grouped = _.groupBy(criteria, "completion_status");
|
||||
return {
|
||||
fail: grouped?.fail?.length || 0,
|
||||
success: grouped?.success?.length || 0,
|
||||
unknown: grouped?.unknown?.length || 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: 0,
|
||||
fail: 0,
|
||||
unknown: 0,
|
||||
};
|
||||
},
|
||||
async loadCompetenceProfilePage(slug: string, reload = false) {
|
||||
if (this.competenceProfilePage && !reload) {
|
||||
await this.parseCompletionData();
|
||||
return this.competenceProfilePage;
|
||||
}
|
||||
const competenceProfilePageData = await itGet(`/api/course/page/${slug}/`);
|
||||
|
||||
if (!competenceProfilePageData) {
|
||||
throw `No competenceProfilePageData found with: ${slug}`;
|
||||
}
|
||||
|
||||
this.competenceProfilePage = competenceProfilePageData;
|
||||
await this.parseCompletionData();
|
||||
|
||||
return this.competenceProfilePage;
|
||||
},
|
||||
async parseCompletionData() {
|
||||
if (this.competenceProfilePage) {
|
||||
const completionStore = useCompletionStore();
|
||||
const completionData = await completionStore.loadCompletionData(
|
||||
this.competenceProfilePage.course.id
|
||||
);
|
||||
|
||||
if (completionData) {
|
||||
this.competenceProfilePage.children.forEach((competence) => {
|
||||
competence.children.forEach((performanceCriteria) => {
|
||||
const completion = completionData.find(
|
||||
(c) => c.page_key === performanceCriteria.translation_key
|
||||
);
|
||||
if (completion) {
|
||||
performanceCriteria.completion_status = completion.completion_status;
|
||||
performanceCriteria.completion_status_updated_at =
|
||||
completion.updated_at;
|
||||
} else {
|
||||
performanceCriteria.completion_status = "unknown";
|
||||
performanceCriteria.completion_status_updated_at = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { itGet, itPost } from "@/fetchHelpers";
|
||||
import type { CourseCompletion, CourseWagtailPage } from "@/types";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export type CompletionStoreState = {
|
||||
completionData: CourseCompletion[] | undefined;
|
||||
};
|
||||
|
||||
export const useCompletionStore = defineStore({
|
||||
id: "completion",
|
||||
state: () => {
|
||||
return {
|
||||
completionData: undefined,
|
||||
} as CompletionStoreState;
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
async loadCompletionData(courseId: number, reload = false) {
|
||||
if (this.completionData && !reload) {
|
||||
return this.completionData;
|
||||
}
|
||||
const completionData = await itGet(`/api/course/completion/${courseId}/`);
|
||||
|
||||
if (!completionData) {
|
||||
throw `No completionData found with: ${courseId}`;
|
||||
}
|
||||
|
||||
this.completionData = completionData;
|
||||
return this.completionData || [];
|
||||
},
|
||||
async markPage(page: CourseWagtailPage) {
|
||||
const completionData = await itPost("/api/course/completion/mark/", {
|
||||
page_key: page.translation_key,
|
||||
completion_status: page.completion_status,
|
||||
});
|
||||
|
||||
if (completionData) {
|
||||
this.completionData = completionData;
|
||||
}
|
||||
|
||||
return this.completionData || [];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { itGet } from "@/fetchHelpers";
|
||||
import { LearningPath } from "@/services/learningPath";
|
||||
import { useCompletionStore } from "@/stores/completion";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export type LearningPathStoreState = {
|
||||
|
|
@ -18,12 +19,13 @@ export const useLearningPathStore = defineStore({
|
|||
getters: {},
|
||||
actions: {
|
||||
async loadLearningPath(slug: string, reload = false) {
|
||||
const completionStore = useCompletionStore();
|
||||
if (this.learningPath && !reload) {
|
||||
return this.learningPath;
|
||||
}
|
||||
const learningPathData = await itGet(`/api/course/page/${slug}/`);
|
||||
const completionData = await itGet(
|
||||
`/api/course/completion/${learningPathData.course.id}/`
|
||||
const completionData = await completionStore.loadCompletionData(
|
||||
learningPathData.course.id
|
||||
);
|
||||
|
||||
if (!learningPathData) {
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ export interface CourseWagtailPage {
|
|||
readonly translation_key: string;
|
||||
readonly frontend_url: string;
|
||||
completion_status: CourseCompletionStatus;
|
||||
completion_status_updated_at: string;
|
||||
}
|
||||
|
||||
export interface LearningContent extends CourseWagtailPage {
|
||||
|
|
@ -154,6 +155,7 @@ export interface LearningUnitPerformanceCriteria extends CourseWagtailPage {
|
|||
export interface LearningUnit extends CourseWagtailPage {
|
||||
type: "learnpath.LearningUnit";
|
||||
learningContents: LearningContent[];
|
||||
evaluate_url: string;
|
||||
minutes: number;
|
||||
parentLearningSequence?: LearningSequence;
|
||||
parentCircle?: Circle;
|
||||
|
|
@ -290,3 +292,23 @@ export interface MediaLibraryPage extends CourseWagtailPage {
|
|||
course: Course;
|
||||
children: MediaCategoryPage[];
|
||||
}
|
||||
|
||||
export interface PerformanceCriteria extends CourseWagtailPage {
|
||||
type: "competence.PerformanceCriteria";
|
||||
competence_id: string;
|
||||
circle: string;
|
||||
course_category: CourseCategory;
|
||||
learning_unit: CourseWagtailPage;
|
||||
}
|
||||
|
||||
export interface CompetencePage extends CourseWagtailPage {
|
||||
type: "competence.CompetencePage";
|
||||
competence_id: string;
|
||||
children: PerformanceCriteria[];
|
||||
}
|
||||
|
||||
export interface CompetenceProfilePage extends CourseWagtailPage {
|
||||
type: "competence.CompetenceProfilePage";
|
||||
course: Course;
|
||||
children: CompetencePage[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2021", "DOM", "DOM.Iterable"],
|
||||
"composite": true,
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ def create_default_competence_profile():
|
|||
competence_id="B4.2",
|
||||
title="Innerhalb des Handlungsfelds «Fahrzeug» bin ich fähig, dem Kunden die Vorschläge verständlich zu erläutern und die entsprechenden Informationspflichten zu erfüllen",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-fahrzeug"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-fahrzeug"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -381,7 +381,7 @@ def create_default_competence_profile():
|
|||
competence_id="B4.3",
|
||||
title="Innerhalb des Handlungsfelds «Fahrzeug» bin ich fähig, auf Vorbehalte und/oder Fragen sachlich korrekt und (verhandlungs-)sicher einzugehen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-fahrzeug"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-fahrzeug"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -389,7 +389,7 @@ def create_default_competence_profile():
|
|||
competence_id="B4.4",
|
||||
title="Innerhalb des Handlungsfelds «Fahrzeug» bin ich fähig, nötige Anpassungen flexibel vorzunehmen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-fahrzeug"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-fahrzeug"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -397,7 +397,7 @@ def create_default_competence_profile():
|
|||
competence_id="C1.1",
|
||||
title="Innerhalb des Handlungsfelds «Fahrzeug» bin ich fähig durch eine Bestandesaufnahme der aktuellen Policen zu prüfen, ob die Leistungen dem Bedarf des Kunden entsprechen",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-fahrzeug"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-fahrzeug"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -405,7 +405,7 @@ def create_default_competence_profile():
|
|||
competence_id="C1.2",
|
||||
title="Innerhalb des Handlungsfelds «Fahrzeug» bin ich fähig, den Kunden bedarfsgerechte Vorschläge für Anpassungen der Versicherungslösung zu unterbreiten (Up-Selling).",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-fahrzeug"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-fahrzeug"
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -415,7 +415,7 @@ def create_default_competence_profile():
|
|||
competence_id="B3.2",
|
||||
title="Innerhalb des Handlungsfelds «Reisen» bin ich fähig, eine Unterversicherung, eine Doppel- oder Überversicherung, einen fehlenden Versicherungsschutz und mögliches Optimierungspotential festzustellen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-reisen"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-reisen"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -423,7 +423,7 @@ def create_default_competence_profile():
|
|||
competence_id="B4.2",
|
||||
title="Innerhalb des Handlungsfelds «Reisen» bin ich fähig, dem Kunden die Vorschläge verständlich zu erläutern und die entsprechenden Informationspflichten zu erfüllen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-reisen"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-reisen"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -431,7 +431,7 @@ def create_default_competence_profile():
|
|||
competence_id="B4.3",
|
||||
title="Innerhalb des Handlungsfelds «Reisen» bin ich fähig, auf Vorbehalte und/oder Fragen sachlich korrekt und (verhandlungs-)sicher einzugehen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-reisen"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-reisen"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -439,7 +439,7 @@ def create_default_competence_profile():
|
|||
competence_id="B4.4",
|
||||
title="Innerhalb des Handlungsfelds «Reisen» bin ich fähig, nötige Anpassungen flexibel vorzunehmen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-reisen"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-reisen"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -447,7 +447,7 @@ def create_default_competence_profile():
|
|||
competence_id="C1.2",
|
||||
title="Innerhalb des Handlungsfelds «Reisen» bin ich fähig, den Kunden bedarfsgerechte Vorschläge für Anpassungen der Versicherungslösung zu unterbreiten (Up-Selling).",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-reisen"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-reisen"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -455,7 +455,7 @@ def create_default_competence_profile():
|
|||
competence_id="C1.3",
|
||||
title="Innerhalb des Handlungsfelds «Reisen» bin ich fähig aufgrund des Portfolios passende Zusatzprodukte anzubieten (Cross-Selling).",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-reisen"
|
||||
slug="versicherungsvermittlerin-lp-circle-lösung-lu-reisen"
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -465,7 +465,7 @@ def create_default_competence_profile():
|
|||
competence_id="A1.2",
|
||||
title="Innerhalb des Handlungsfelds «Fahrzeug» bin ich fähig, geeignete Personen wie z.B. Garagisten, Architekten, Treuhänder auf die Vermittlung/Zusammenarbeit anzusprechen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-fahrzeug"
|
||||
slug="versicherungsvermittlerin-lp-circle-abschluss-lu-fahrzeug"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -473,7 +473,7 @@ def create_default_competence_profile():
|
|||
competence_id="A4.1",
|
||||
title="Innerhalb des Handlungsfelds «Fahrzeug» bin ich fähig, Kundendaten in Datenbanken (CRM) korrekt zu erfassen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-fahrzeug"
|
||||
slug="versicherungsvermittlerin-lp-circle-abschluss-lu-fahrzeug"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -481,7 +481,7 @@ def create_default_competence_profile():
|
|||
competence_id="B4.6",
|
||||
title="Innerhalb des Handlungsfelds «Fahrzeug» bin ich fähig, Anträge korrekt auszufüllen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-fahrzeug"
|
||||
slug="versicherungsvermittlerin-lp-circle-abschluss-lu-fahrzeug"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -489,7 +489,7 @@ def create_default_competence_profile():
|
|||
competence_id="C1.3",
|
||||
title="Innerhalb des Handlungsfelds «Fahrzeug» bin ich fähig, aufgrund des Portfolios passende Zusatzprodukte anzubieten (Cross-Selling).",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-fahrzeug"
|
||||
slug="versicherungsvermittlerin-lp-circle-abschluss-lu-fahrzeug"
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -499,7 +499,7 @@ def create_default_competence_profile():
|
|||
competence_id="B4.6",
|
||||
title="Innerhalb des Handlungsfelds «Reisen» bin ich fähig, Anträge korrekt auszufüllen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-reisen"
|
||||
slug="versicherungsvermittlerin-lp-circle-abschluss-lu-reisen"
|
||||
),
|
||||
)
|
||||
PerformanceCriteriaFactory(
|
||||
|
|
@ -507,6 +507,6 @@ def create_default_competence_profile():
|
|||
competence_id="C3.1",
|
||||
title="Innerhalb des Handlungsfelds «Reisen» bin ich fähig, Kunden die Vorgehensweise für die Meldung des Schadens nachvollziehbar zu erläutern und sie bei Bedarf zu unterstützen.",
|
||||
learning_unit=LearningUnit.objects.get(
|
||||
slug="versicherungsvermittlerin-lp-circle-analyse-lu-reisen"
|
||||
slug="versicherungsvermittlerin-lp-circle-abschluss-lu-reisen"
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ class CompetenceProfilePage(Page):
|
|||
)
|
||||
super(CompetenceProfilePage, self).full_clean(*args, **kwargs)
|
||||
|
||||
def get_frontend_url(self):
|
||||
return f"/competence/{self.slug}"
|
||||
|
||||
@classmethod
|
||||
def get_serializer_class(cls):
|
||||
return get_it_serializer_class(
|
||||
|
|
@ -64,6 +67,7 @@ class CompetencePage(Page):
|
|||
return get_it_serializer_class(
|
||||
cls,
|
||||
[
|
||||
"competence_id",
|
||||
"children",
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ from rest_framework import serializers
|
|||
|
||||
from vbv_lernwelt.competence.models import PerformanceCriteria
|
||||
from vbv_lernwelt.course.serializers import CourseCategorySerializer
|
||||
from vbv_lernwelt.learnpath.models import LearningUnit
|
||||
from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
|
||||
|
||||
|
||||
|
|
@ -27,17 +26,11 @@ class PerformanceCriteriaSerializer(
|
|||
course_category = serializers.SerializerMethodField()
|
||||
|
||||
def get_learning_unit(self, obj):
|
||||
learning_unit_serializer = get_it_serializer_class(
|
||||
LearningUnit,
|
||||
[
|
||||
"id",
|
||||
"title",
|
||||
"slug",
|
||||
"type",
|
||||
"translation_key",
|
||||
],
|
||||
from vbv_lernwelt.learnpath.serializers import (
|
||||
LearningUnitPerformanceCriteriaSerializer,
|
||||
)
|
||||
return learning_unit_serializer(obj.learning_unit).data
|
||||
|
||||
return LearningUnitPerformanceCriteriaSerializer(obj.learning_unit).data
|
||||
|
||||
def get_circle(self, obj):
|
||||
return obj.learning_unit.get_parent().specific.title
|
||||
|
|
|
|||
|
|
@ -250,6 +250,10 @@ class LearningUnit(Page):
|
|||
super(LearningUnit, self).full_clean(*args, **kwargs)
|
||||
|
||||
def get_frontend_url(self):
|
||||
short_slug = self.slug.replace(f"{self.get_parent().slug}-lu-", "")
|
||||
return f"{self.get_parent().specific.get_frontend_url()}#lu-{short_slug}"
|
||||
|
||||
def get_evaluate_url(self):
|
||||
short_slug = self.slug.replace(f"{self.get_parent().slug}-lu-", "")
|
||||
return f"{self.get_parent().specific.get_frontend_url()}/evaluate/{short_slug}"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from rest_framework.fields import SerializerMethodField
|
||||
|
||||
from vbv_lernwelt.competence.serializers import (
|
||||
PerformanceCriteriaLearningPathSerializer,
|
||||
)
|
||||
|
|
@ -9,13 +11,34 @@ class LearningUnitSerializer(
|
|||
get_it_serializer_class(
|
||||
LearningUnit,
|
||||
[
|
||||
"evaluate_url",
|
||||
"course_category",
|
||||
"children",
|
||||
],
|
||||
)
|
||||
):
|
||||
evaluate_url = SerializerMethodField()
|
||||
|
||||
def get_children(self, obj):
|
||||
return [
|
||||
PerformanceCriteriaLearningPathSerializer(child).data
|
||||
for child in obj.performancecriteria_set.all()
|
||||
]
|
||||
|
||||
def get_evaluate_url(self, obj):
|
||||
return obj.get_evaluate_url()
|
||||
|
||||
|
||||
class LearningUnitPerformanceCriteriaSerializer(
|
||||
get_it_serializer_class(
|
||||
LearningUnit,
|
||||
[
|
||||
"evaluate_url",
|
||||
"course_category",
|
||||
],
|
||||
)
|
||||
):
|
||||
evaluate_url = SerializerMethodField()
|
||||
|
||||
def get_evaluate_url(self, obj):
|
||||
return obj.get_evaluate_url()
|
||||
|
|
|
|||
Loading…
Reference in New Issue