Merge branch 'feature/course-permissions-frontend' into develop
This commit is contained in:
commit
ca195c0b4a
|
|
@ -6,7 +6,7 @@ import IconSettings from "@/components/icons/IconSettings.vue";
|
||||||
import MobileMenu from "@/components/MobileMenu.vue";
|
import MobileMenu from "@/components/MobileMenu.vue";
|
||||||
import ItDropdown from "@/components/ui/ItDropdown.vue";
|
import ItDropdown from "@/components/ui/ItDropdown.vue";
|
||||||
import { useAppStore } from "@/stores/app";
|
import { useAppStore } from "@/stores/app";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import type { DropdownListItem } from "@/types";
|
import type { DropdownListItem } from "@/types";
|
||||||
import type { Component } from "vue";
|
import type { Component } from "vue";
|
||||||
|
|
@ -26,8 +26,9 @@ const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const learningPathStore = useLearningPathStore();
|
|
||||||
const state = reactive({ showMenu: false });
|
const state = reactive({ showMenu: false });
|
||||||
|
|
||||||
function toggleNav() {
|
function toggleNav() {
|
||||||
|
|
@ -35,6 +36,7 @@ function toggleNav() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInRoutePath(checkPaths: string[]) {
|
function isInRoutePath(checkPaths: string[]) {
|
||||||
|
log.debug("isInRoutePath", checkPaths, route.path);
|
||||||
return checkPaths.some((path) => route.path.startsWith(path));
|
return checkPaths.some((path) => route.path.startsWith(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,20 +44,6 @@ function inCourse() {
|
||||||
return isInRoutePath(["/learn", "/competence"]);
|
return isInRoutePath(["/learn", "/competence"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLearningPathStringProp(prop: "title" | "slug"): string {
|
|
||||||
return inCourse() && learningPathStore.learningPath
|
|
||||||
? learningPathStore.learningPath[prop]
|
|
||||||
: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function learningPathName(): string {
|
|
||||||
return getLearningPathStringProp("title");
|
|
||||||
}
|
|
||||||
|
|
||||||
function learninPathSlug(): string {
|
|
||||||
return getLearningPathStringProp("slug");
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDropdownSelect(data: DropdownData) {
|
function handleDropdownSelect(data: DropdownData) {
|
||||||
switch (data.action) {
|
switch (data.action) {
|
||||||
case "settings":
|
case "settings":
|
||||||
|
|
@ -75,6 +63,9 @@ function logout() {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
log.debug("MainNavigationBar mounted");
|
log.debug("MainNavigationBar mounted");
|
||||||
|
if (userStore.loggedIn) {
|
||||||
|
courseSessionsStore.loadCourseSessionsData();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const profileDropdownData: DropdownListItem[] = [
|
const profileDropdownData: DropdownListItem[] = [
|
||||||
|
|
@ -100,8 +91,7 @@ const profileDropdownData: DropdownListItem[] = [
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<MobileMenu
|
<MobileMenu
|
||||||
:show="state.showMenu"
|
:show="state.showMenu"
|
||||||
:learning-path-slug="learninPathSlug()"
|
:course-session="courseSessionsStore.courseSessionForRoute"
|
||||||
:learning-path-name="learningPathName()"
|
|
||||||
@closemodal="state.showMenu = false"
|
@closemodal="state.showMenu = false"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
|
@ -156,8 +146,8 @@ const profileDropdownData: DropdownListItem[] = [
|
||||||
class="flex-auto mt-8 lg:flex lg:space-y-0 lg:flex-row lg:items-center lg:space-x-10 lg:mt-0"
|
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
|
<router-link
|
||||||
v-if="inCourse()"
|
v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
|
||||||
to="/learn/versicherungsvermittlerin-lp"
|
:to="courseSessionsStore.courseSessionForRoute.learning_path_url"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
:class="{ 'nav-item--active': isInRoutePath(['/learn']) }"
|
:class="{ 'nav-item--active': isInRoutePath(['/learn']) }"
|
||||||
>
|
>
|
||||||
|
|
@ -165,8 +155,8 @@ const profileDropdownData: DropdownListItem[] = [
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
v-if="inCourse()"
|
v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
|
||||||
to="/competence/versicherungsvermittlerin-competence"
|
:to="courseSessionsStore.courseSessionForRoute.competence_url"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
:class="{ 'nav-item--active': isInRoutePath(['/competence']) }"
|
:class="{ 'nav-item--active': isInRoutePath(['/competence']) }"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import IconLogout from "@/components/icons/IconLogout.vue";
|
||||||
import IconSettings from "@/components/icons/IconSettings.vue";
|
import IconSettings from "@/components/icons/IconSettings.vue";
|
||||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import type { CourseSession } from "@/types";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -10,15 +11,16 @@ const userStore = useUserStore();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
show: boolean;
|
show: boolean;
|
||||||
learningPathName: string;
|
courseSession: CourseSession | undefined;
|
||||||
learningPathSlug: string;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emits = defineEmits(["closemodal"]);
|
const emits = defineEmits(["closemodal"]);
|
||||||
|
|
||||||
const clickLink = (to: string) => {
|
const clickLink = (to: string | undefined) => {
|
||||||
router.push(to);
|
if (to) {
|
||||||
emits("closemodal");
|
router.push(to);
|
||||||
|
emits("closemodal");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -48,18 +50,16 @@ const clickLink = (to: string) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="true" class="mt-6 pb-6 border-b">
|
<div v-if="courseSession" class="mt-6 pb-6 border-b">
|
||||||
<h4 class="text-gray-900 text-sm">Kurs: Versicherungsvermittler/in</h4>
|
<h4 class="text-gray-900 text-sm">{{ courseSession.course.title }}</h4>
|
||||||
<ul class="mt-6">
|
<ul class="mt-6">
|
||||||
<li>
|
<li>
|
||||||
<button @click="clickLink(`/learn/versicherungsvermittlerin-lp`)">
|
<button @click="clickLink(courseSession?.learning_path_url)">
|
||||||
{{ $t("general.learningPath") }}
|
{{ $t("general.learningPath") }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="mt-6">
|
<li class="mt-6">
|
||||||
<button
|
<button @click="clickLink(courseSession?.competence_url)">
|
||||||
@click="clickLink(`/competence/versicherungsvermittlerin-competence`)"
|
|
||||||
>
|
|
||||||
KompetenzNavi
|
KompetenzNavi
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,21 @@
|
||||||
<script>
|
<script>
|
||||||
import colors from "@/colors.json";
|
import colors from "@/colors.json";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
|
||||||
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";
|
||||||
|
|
||||||
|
// type DiagramType = "horizontal" | "vertical" | "horizontalSmall";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
vertical: {
|
diagramType: {
|
||||||
default: false,
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
identifier: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
type: String,
|
||||||
|
default: "horizontal",
|
||||||
|
},
|
||||||
|
learningPath: {
|
||||||
|
required: true,
|
||||||
|
type: Object,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const learningPathStore = useLearningPathStore();
|
|
||||||
return { learningPathStore };
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -27,6 +24,9 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
svgId() {
|
||||||
|
return `learningpath-diagram-${this.learningPath.slug}-${this.diagramType}`;
|
||||||
|
},
|
||||||
viewBox() {
|
viewBox() {
|
||||||
return `0 0 ${this.width} ${this.height}`;
|
return `0 0 ${this.width} ${this.height}`;
|
||||||
},
|
},
|
||||||
|
|
@ -47,9 +47,9 @@ export default {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.learningPathStore.learningPath) {
|
if (this.learningPath) {
|
||||||
const internalCircles = [];
|
const internalCircles = [];
|
||||||
this.learningPathStore.learningPath.circles.forEach((circle) => {
|
this.learningPath.circles.forEach((circle) => {
|
||||||
const pieWeights = new Array(
|
const pieWeights = new Array(
|
||||||
Math.max(circle.learningSequences.length, 1)
|
Math.max(circle.learningSequences.length, 1)
|
||||||
).fill(1);
|
).fill(1);
|
||||||
|
|
@ -78,21 +78,27 @@ export default {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
svg() {
|
svg() {
|
||||||
return d3.select("#" + this.identifier);
|
const result = d3.select("#" + this.svgId);
|
||||||
},
|
result.selectAll("*").remove();
|
||||||
|
return result;
|
||||||
learningPath() {
|
|
||||||
return Object.assign({}, this.learningPathStore.learningPath);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
log.debug("LearningPathDiagram mounted");
|
log.debug("LearningPathDiagram mounted");
|
||||||
|
|
||||||
const circleWidth = this.vertical ? 60 : 200;
|
// clean old svg
|
||||||
|
d3.select("#" + this.svgId)
|
||||||
|
.selectAll("*")
|
||||||
|
.remove();
|
||||||
|
|
||||||
|
let circleWidth = 200;
|
||||||
|
if (this.diagramType === "vertical") {
|
||||||
|
circleWidth = 60;
|
||||||
|
}
|
||||||
const radius = (circleWidth * 0.8) / 2;
|
const radius = (circleWidth * 0.8) / 2;
|
||||||
|
|
||||||
if (this.vertical) {
|
if (this.diagramType === "vertical") {
|
||||||
this.width = Math.min(960, window.innerWidth - 32);
|
this.width = Math.min(960, window.innerWidth - 32);
|
||||||
this.height = 860;
|
this.height = 860;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -131,7 +137,7 @@ export default {
|
||||||
.append("g")
|
.append("g")
|
||||||
.attr("class", "circle")
|
.attr("class", "circle")
|
||||||
.attr("data-cy", (d) => {
|
.attr("data-cy", (d) => {
|
||||||
if (this.vertical) {
|
if (this.diagramType === "vertical") {
|
||||||
return `circle-${d.slug}-vertical`;
|
return `circle-${d.slug}-vertical`;
|
||||||
} else {
|
} else {
|
||||||
return `circle-${d.slug}`;
|
return `circle-${d.slug}`;
|
||||||
|
|
@ -195,7 +201,7 @@ export default {
|
||||||
.style("font-size", "18px")
|
.style("font-size", "18px")
|
||||||
.style("overflow-wrap", "break-word")
|
.style("overflow-wrap", "break-word")
|
||||||
.text((d) => {
|
.text((d) => {
|
||||||
if (!this.vertical) {
|
if (this.diagramType === "horizontal") {
|
||||||
return d.title.replace("Prüfungsvorbereitung", "Prüfungs- vorbereitung");
|
return d.title.replace("Prüfungsvorbereitung", "Prüfungs- vorbereitung");
|
||||||
}
|
}
|
||||||
return d.title;
|
return d.title;
|
||||||
|
|
@ -266,7 +272,7 @@ export default {
|
||||||
|
|
||||||
// Calculate positions of objects
|
// Calculate positions of objects
|
||||||
|
|
||||||
if (this.vertical) {
|
if (this.diagramType === "vertical") {
|
||||||
const Circles_X = radius;
|
const Circles_X = radius;
|
||||||
const Topics_X = Circles_X - 20;
|
const Topics_X = Circles_X - 20;
|
||||||
|
|
||||||
|
|
@ -312,22 +318,28 @@ export default {
|
||||||
.attr("y", radius + 30)
|
.attr("y", radius + 30)
|
||||||
.style("text-anchor", "middle")
|
.style("text-anchor", "middle")
|
||||||
.call(wrap, circleWidth - 20)
|
.call(wrap, circleWidth - 20)
|
||||||
.attr("class", "circlesText text-xl font-bold hidden lg:block");
|
.attr("class", () => {
|
||||||
|
let classes = "circlesText text-xl font-bold hidden";
|
||||||
|
if (this.diagramType === "horizontal") {
|
||||||
|
classes += " lg:block";
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
});
|
||||||
|
|
||||||
topicGroups
|
topicGroups
|
||||||
.attr("transform", (d, i) => {
|
.attr("transform", (d, i) => {
|
||||||
return (
|
return (
|
||||||
"translate(" +
|
"translate(" +
|
||||||
getTopicHorizontalPosition(
|
getTopicHorizontalPosition(i, d, this.learningPath.topics) +
|
||||||
i,
|
|
||||||
d,
|
|
||||||
this.learningPathStore.learningPath.topics
|
|
||||||
) +
|
|
||||||
",0)"
|
",0)"
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.attr("class", (d) => {
|
.attr("class", (d) => {
|
||||||
return "topic ".concat(d.is_visible ? "hidden lg:block" : "hidden");
|
let classes = "topic hidden";
|
||||||
|
if (this.diagramType === "horizontal" && d.is_visible) {
|
||||||
|
classes += " lg:block";
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
});
|
});
|
||||||
|
|
||||||
topicLines
|
topicLines
|
||||||
|
|
@ -343,7 +355,7 @@ export default {
|
||||||
.attr("y", 20)
|
.attr("y", 20)
|
||||||
.style("font-size", "18px")
|
.style("font-size", "18px")
|
||||||
.call(wrap, circleWidth * 0.8)
|
.call(wrap, circleWidth * 0.8)
|
||||||
.attr("class", "topicTitles font-bold");
|
.attr("class", "topic-title font-bold");
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrap(text, width) {
|
function wrap(text, width) {
|
||||||
|
|
@ -387,10 +399,11 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<div class="svg-container h-full content-start">
|
<div class="svg-container h-full content-start">
|
||||||
<svg
|
<svg
|
||||||
:id="identifier"
|
:id="svgId"
|
||||||
class="learning-path-visualization h-full mx-auto -mt-6 lg:mt-0"
|
class="learning-path-visualization h-full mx-auto -mt-6 lg:mt-0"
|
||||||
:class="{
|
:class="{
|
||||||
'max-h-[384px]': !vertical,
|
'max-h-[90px]': ['horizontalSmall'].includes(diagramType),
|
||||||
|
'max-h-[90px] lg:max-h-[380px]': ['horizontal'].includes(diagramType),
|
||||||
}"
|
}"
|
||||||
:viewBox="viewBox"
|
:viewBox="viewBox"
|
||||||
></svg>
|
></svg>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
|
import * as log from "loglevel";
|
||||||
|
|
||||||
|
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
||||||
|
import type { LearningPath } from "@/services/learningPath";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
log.debug("LearningPathDiagramSmall created");
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
learningPathUrl: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const learningPathData = ref<LearningPath | undefined>(undefined);
|
||||||
|
|
||||||
|
const learningPathStore = useLearningPathStore();
|
||||||
|
|
||||||
|
learningPathStore
|
||||||
|
.loadLearningPath(props.learningPathUrl.replace("/learn/", ""), false, false)
|
||||||
|
.then((data) => {
|
||||||
|
learningPathData.value = data;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<LearningPathDiagram
|
||||||
|
v-if="learningPathData"
|
||||||
|
:learning-path="learningPathData"
|
||||||
|
diagram-type="horizontalSmall"
|
||||||
|
></LearningPathDiagram>
|
||||||
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import { useUserStore } from "@/stores/user";
|
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
|
|
||||||
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
||||||
|
|
@ -14,7 +13,6 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
const userStore = useUserStore();
|
|
||||||
|
|
||||||
const emits = defineEmits(["closemodal"]);
|
const emits = defineEmits(["closemodal"]);
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -25,9 +23,10 @@ const emits = defineEmits(["closemodal"]);
|
||||||
<div class="learningpath flex flex-col">
|
<div class="learningpath flex flex-col">
|
||||||
<div class="flex flex-col h-max">
|
<div class="flex flex-col h-max">
|
||||||
<LearningPathDiagram
|
<LearningPathDiagram
|
||||||
|
v-if="learningPathStore.learningPath"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
identifier="verticalVisualization"
|
:learning-path="learningPathStore.learningPath"
|
||||||
:vertical="true"
|
diagram-type="vertical"
|
||||||
></LearningPathDiagram>
|
></LearningPathDiagram>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import { computed } from "vue";
|
||||||
import { RouterLink } from "vue-router";
|
import { RouterLink } from "vue-router";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
// Vue-TypeScript make special things with `defineProps` so it's hard
|
||||||
|
// to make this without @ts-ignore...
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
...RouterLink.props,
|
...RouterLink.props,
|
||||||
|
|
|
||||||
|
|
@ -56,3 +56,26 @@ export const itPost = (url: RequestInfo, data: unknown, options: RequestInit = {
|
||||||
export const itGet = (url: RequestInfo) => {
|
export const itGet = (url: RequestInfo) => {
|
||||||
return itPost(url, {}, { method: "GET" });
|
return itPost(url, {}, { method: "GET" });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const itGetPromiseCache = new Map<string, Promise<any>>();
|
||||||
|
|
||||||
|
export function bustItGetCache(key?: string) {
|
||||||
|
if (key) {
|
||||||
|
itGetPromiseCache.delete(key);
|
||||||
|
} else {
|
||||||
|
itGetPromiseCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const itGetCached = (
|
||||||
|
url: RequestInfo,
|
||||||
|
options = {
|
||||||
|
reload: false,
|
||||||
|
}
|
||||||
|
): Promise<any> => {
|
||||||
|
if (!itGetPromiseCache.has(url.toString()) || options.reload) {
|
||||||
|
itGetPromiseCache.set(url.toString(), itGet(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
return itGetPromiseCache.get(url.toString()) as Promise<any>;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,25 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import LearningPathDiagramSmall from "@/components/learningPath/LearningPathDiagramSmall.vue";
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import * as log from "loglevel";
|
import log from "loglevel";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
log.debug("CockpitView created");
|
log.debug("CockpitView created");
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
|
||||||
function employer() {
|
function employer() {
|
||||||
return userStore.email === "bianca.muster@eiger-versicherungen.ch"
|
return userStore.email === "bianca.muster@eiger-versicherungen.ch"
|
||||||
? "Eiger Versicherungen"
|
? "Eiger Versicherungen"
|
||||||
: "VBV";
|
: "VBV";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
log.debug("CockpitView mounted");
|
||||||
|
await courseSessionsStore.loadCourseSessionsData();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -22,22 +31,26 @@ function employer() {
|
||||||
</h1>
|
</h1>
|
||||||
<div class="mb-14">
|
<div class="mb-14">
|
||||||
<h2 class="mt-12 mb-3">Kurse</h2>
|
<h2 class="mt-12 mb-3">Kurse</h2>
|
||||||
<div class="flex flex-col md:flex-row justify-between">
|
|
||||||
<div class="bg-white p-6 md:w-[48%] mb-4 md:mb-0">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 auto-rows-fr">
|
||||||
<h3 class="mb-4">Versicherungsvermittler/in</h3>
|
<div
|
||||||
<img class="mb-8 block" :src="'/static/icons/demo/vm-lernpfad.svg'" />
|
v-for="courseSession in courseSessionsStore.courseSessions"
|
||||||
<router-link class="btn-blue" to="/learn/versicherungsvermittlerin-lp">
|
:key="courseSession.id"
|
||||||
{{ $t("general.nextStep") }}
|
>
|
||||||
</router-link>
|
<div class="bg-white p-6 md:h-full">
|
||||||
</div>
|
<h3 class="mb-4">{{ courseSession.title }}</h3>
|
||||||
<div class="bg-white p-6 md:w-[48%]">
|
<div>
|
||||||
<h3 class="mb-4">Überbetriebliche Kurse</h3>
|
<LearningPathDiagramSmall
|
||||||
<img class="mb-8 block" :src="'/static/icons/demo/uk-lernpfad.svg'" />
|
class="mb-4"
|
||||||
<button
|
:learning-path-url="courseSession.learning_path_url"
|
||||||
class="bg-green-500 font-semibold py-2 px-4 align-middle inline-block text-blue-900 border-2 border-green-500"
|
></LearningPathDiagramSmall>
|
||||||
>
|
</div>
|
||||||
Abgeschlossen
|
<div>
|
||||||
</button>
|
<router-link class="btn-blue" :to="courseSession.learning_path_url">
|
||||||
|
{{ $t("general.nextStep") }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,9 @@ const createContinueUrl = (learningPath: LearningPath): [string, boolean] => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<LearningPathDiagram
|
<LearningPathDiagram
|
||||||
class="mx-auto max-w-[1920px] max-h-[380px] w-full px-4"
|
class="mx-auto max-w-[1920px] max-h-[90px] lg:max-h-[380px] w-full px-4"
|
||||||
identifier="mainVisualization"
|
diagram-type="horizontal"
|
||||||
:vertical="false"
|
:learning-path="learningPathStore.learningPath"
|
||||||
></LearningPathDiagram>
|
></LearningPathDiagram>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { itGet } from "@/fetchHelpers";
|
import { itGetCached } from "@/fetchHelpers";
|
||||||
import { useCompletionStore } from "@/stores/completion";
|
import { useCompletionStore } from "@/stores/completion";
|
||||||
import type {
|
import type {
|
||||||
BaseCourseWagtailPage,
|
CircleLight,
|
||||||
CompetencePage,
|
CompetencePage,
|
||||||
CompetenceProfilePage,
|
CompetenceProfilePage,
|
||||||
PerformanceCriteria,
|
PerformanceCriteria,
|
||||||
|
|
@ -91,23 +91,17 @@ export const useCompetenceStore = defineStore({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async loadCompetenceProfilePage(slug: string, reload = false) {
|
async loadCompetenceProfilePage(slug: string, reload = false) {
|
||||||
if (this.competenceProfilePage && !reload) {
|
this.competenceProfilePage = await itGetCached(`/api/course/page/${slug}/`, {
|
||||||
await this.parseCompletionData();
|
reload: reload,
|
||||||
return this.competenceProfilePage;
|
});
|
||||||
}
|
|
||||||
const competenceProfilePageData = await itGet(`/api/course/page/${slug}/`);
|
|
||||||
|
|
||||||
if (!competenceProfilePageData) {
|
if (!this.competenceProfilePage) {
|
||||||
throw `No competenceProfilePageData found with: ${slug}`;
|
throw `No competenceProfilePageData found with: ${slug}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.competenceProfilePage = competenceProfilePageData;
|
const circles = this.competenceProfilePage.circles.map((c: CircleLight) => {
|
||||||
|
return { id: c.translation_key, name: `Circle: ${c.title}` };
|
||||||
const circles = competenceProfilePageData.circles.map(
|
});
|
||||||
(c: BaseCourseWagtailPage) => {
|
|
||||||
return { id: c.translation_key, name: `Circle: ${c.title}` };
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.availableCircles = [{ id: "all", name: "Circle: Alle" }, ...circles];
|
this.availableCircles = [{ id: "all", name: "Circle: Alle" }, ...circles];
|
||||||
|
|
||||||
await this.parseCompletionData();
|
await this.parseCompletionData();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { itGet, itPost } from "@/fetchHelpers";
|
import { bustItGetCache, itGetCached, itPost } from "@/fetchHelpers";
|
||||||
import type { BaseCourseWagtailPage, CourseCompletion } from "@/types";
|
import type { BaseCourseWagtailPage, CourseCompletion } from "@/types";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
|
@ -16,16 +16,14 @@ export const useCompletionStore = defineStore({
|
||||||
getters: {},
|
getters: {},
|
||||||
actions: {
|
actions: {
|
||||||
async loadCompletionData(courseId: number, reload = false) {
|
async loadCompletionData(courseId: number, reload = false) {
|
||||||
if (this.completionData && !reload) {
|
this.completionData = await itGetCached(`/api/course/completion/${courseId}/`, {
|
||||||
return this.completionData;
|
reload: reload,
|
||||||
}
|
});
|
||||||
const completionData = await itGet(`/api/course/completion/${courseId}/`);
|
|
||||||
|
|
||||||
if (!completionData) {
|
if (!this.completionData) {
|
||||||
throw `No completionData found with: ${courseId}`;
|
throw `No completionData found with: ${courseId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.completionData = completionData;
|
|
||||||
return this.completionData || [];
|
return this.completionData || [];
|
||||||
},
|
},
|
||||||
async markPage(page: BaseCourseWagtailPage) {
|
async markPage(page: BaseCourseWagtailPage) {
|
||||||
|
|
@ -34,8 +32,9 @@ export const useCompletionStore = defineStore({
|
||||||
completion_status: page.completion_status,
|
completion_status: page.completion_status,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (completionData) {
|
if (completionData && completionData.length > 0) {
|
||||||
this.completionData = completionData;
|
this.completionData = completionData;
|
||||||
|
bustItGetCache(`/api/course/completion/${completionData[0].course}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.completionData || [];
|
return this.completionData || [];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { itGetCached } from "@/fetchHelpers";
|
||||||
|
import type { CourseSession } from "@/types";
|
||||||
|
import log from "loglevel";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
export type CourseSessionsStoreState = {
|
||||||
|
courseSessions: CourseSession[] | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCourseSessionsStore = defineStore({
|
||||||
|
id: "courseSessions",
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
courseSessions: undefined,
|
||||||
|
} as CourseSessionsStoreState;
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
courseSessionForRoute: (state) => {
|
||||||
|
const route = useRoute();
|
||||||
|
return state.courseSessions?.find((cs) => {
|
||||||
|
return (
|
||||||
|
route.path.startsWith(cs.learning_path_url) ||
|
||||||
|
route.path.startsWith(cs.competence_url)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async loadCourseSessionsData(reload = false) {
|
||||||
|
log.debug("loadCourseSessionsData called");
|
||||||
|
this.courseSessions = await itGetCached(`/api/course/sessions/`, {
|
||||||
|
reload: reload,
|
||||||
|
});
|
||||||
|
if (!this.courseSessions) {
|
||||||
|
throw `No courseSessionData found for user`;
|
||||||
|
}
|
||||||
|
return this.courseSessions;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { itGet } from "@/fetchHelpers";
|
import { itGetCached } from "@/fetchHelpers";
|
||||||
import { LearningPath } from "@/services/learningPath";
|
import { LearningPath } from "@/services/learningPath";
|
||||||
import { useCompletionStore } from "@/stores/completion";
|
import { useCompletionStore } from "@/stores/completion";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
@ -6,34 +6,45 @@ import { defineStore } from "pinia";
|
||||||
export type LearningPathStoreState = {
|
export type LearningPathStoreState = {
|
||||||
learningPath: LearningPath | undefined;
|
learningPath: LearningPath | undefined;
|
||||||
page: "INDEX" | "OVERVIEW";
|
page: "INDEX" | "OVERVIEW";
|
||||||
|
loading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let lastSlug = "";
|
||||||
|
|
||||||
export const useLearningPathStore = defineStore({
|
export const useLearningPathStore = defineStore({
|
||||||
id: "learningPath",
|
id: "learningPath",
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
learningPath: undefined,
|
learningPath: undefined,
|
||||||
page: "INDEX",
|
page: "INDEX",
|
||||||
|
loading: false,
|
||||||
} as LearningPathStoreState;
|
} as LearningPathStoreState;
|
||||||
},
|
},
|
||||||
getters: {},
|
getters: {},
|
||||||
actions: {
|
actions: {
|
||||||
async loadLearningPath(slug: string, reload = false) {
|
async loadLearningPath(slug: string, reload = false, fail = true) {
|
||||||
|
this.loading = true;
|
||||||
const completionStore = useCompletionStore();
|
const completionStore = useCompletionStore();
|
||||||
if (this.learningPath && !reload) {
|
if (this.learningPath && !reload && slug === lastSlug) {
|
||||||
return this.learningPath;
|
return this.learningPath;
|
||||||
}
|
}
|
||||||
const learningPathData = await itGet(`/api/course/page/${slug}/`);
|
this.learningPath = undefined;
|
||||||
const completionData = await completionStore.loadCompletionData(
|
const learningPathData = await itGetCached(`/api/course/page/${slug}/`);
|
||||||
learningPathData.course.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!learningPathData) {
|
if (!learningPathData && fail) {
|
||||||
throw `No learning path found with: ${slug}`;
|
throw `No learning path found with: ${slug}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.learningPath = LearningPath.fromJson(learningPathData, completionData);
|
if (learningPathData) {
|
||||||
return this.learningPath;
|
lastSlug = slug;
|
||||||
|
const completionData = await completionStore.loadCompletionData(
|
||||||
|
learningPathData.course.id
|
||||||
|
);
|
||||||
|
|
||||||
|
this.learningPath = LearningPath.fromJson(learningPathData, completionData);
|
||||||
|
this.loading = false;
|
||||||
|
return this.learningPath;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
|
|
||||||
import { itGet, itPost } from "@/fetchHelpers";
|
import { bustItGetCache, itGetCached, itPost } from "@/fetchHelpers";
|
||||||
import { useAppStore } from "@/stores/app";
|
import { useAppStore } from "@/stores/app";
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
const logoutRedirectUrl = import.meta.env.VITE_LOGOUT_REDIRECT;
|
const logoutRedirectUrl = import.meta.env.VITE_LOGOUT_REDIRECT;
|
||||||
|
|
@ -43,6 +44,8 @@ export const useUserStore = defineStore({
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.$state = data;
|
this.$state = data;
|
||||||
this.loggedIn = true;
|
this.loggedIn = true;
|
||||||
|
log.debug("bust cache");
|
||||||
|
bustItGetCache();
|
||||||
log.debug(`redirect to ${next}`);
|
log.debug(`redirect to ${next}`);
|
||||||
window.location.href = next;
|
window.location.href = next;
|
||||||
})
|
})
|
||||||
|
|
@ -68,11 +71,13 @@ export const useUserStore = defineStore({
|
||||||
},
|
},
|
||||||
fetchUser() {
|
fetchUser() {
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
itGet("/api/core/me/")
|
itGetCached("/api/core/me/")
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.$state = data;
|
this.$state = data;
|
||||||
this.loggedIn = true;
|
this.loggedIn = true;
|
||||||
appStore.userLoaded = true;
|
appStore.userLoaded = true;
|
||||||
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
courseSessionsStore.loadCourseSessionsData();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.loggedIn = false;
|
this.loggedIn = false;
|
||||||
|
|
|
||||||
|
|
@ -301,3 +301,28 @@ export interface DropdownListItem {
|
||||||
icon: Component;
|
icon: Component;
|
||||||
data: object;
|
data: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CircleExpert {
|
||||||
|
user_id: number;
|
||||||
|
user_email: string;
|
||||||
|
user_first_name: string;
|
||||||
|
user_last_name: string;
|
||||||
|
circle_id: number;
|
||||||
|
circle_slug: string;
|
||||||
|
circle_translation_key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CourseSession {
|
||||||
|
id: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
course: Course;
|
||||||
|
title: string;
|
||||||
|
start_date: string;
|
||||||
|
end_date: string;
|
||||||
|
learning_path_url: string;
|
||||||
|
competence_url: string;
|
||||||
|
media_library_url: string;
|
||||||
|
additional_json_data: unknown;
|
||||||
|
experts: CircleExpert[];
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,26 +107,36 @@ def create_default_users(user_model=User, group_model=Group, default_password=No
|
||||||
_create_student_user(**user_data)
|
_create_student_user(**user_data)
|
||||||
|
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="expertvv.analyse@vbv-afa.ch",
|
email="expert-vv.analyse@eiger-versicherungen.ch",
|
||||||
first_name="Expert",
|
first_name="Expert",
|
||||||
last_name="Analyse",
|
last_name="Analyse",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="expertvv.einstieg@vbv-afa.ch",
|
email="expert-vv.einstieg@eiger-versicherungen.ch",
|
||||||
first_name="Expert",
|
first_name="Expert",
|
||||||
last_name="Einstieg",
|
last_name="Einstieg",
|
||||||
)
|
)
|
||||||
|
_create_student_user(
|
||||||
|
email="student-vv@eiger-versicherungen.ch",
|
||||||
|
first_name="Student",
|
||||||
|
last_name="VV",
|
||||||
|
)
|
||||||
|
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="trainer-uk1.analyse@vbv-afa.ch",
|
email="trainer-uk1-bern.analyse@eiger-versicherungen.ch",
|
||||||
first_name="Trainer",
|
first_name="Trainer",
|
||||||
last_name="Analyse",
|
last_name="Analyse",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="trainer-uk1.einstieg@vbv-afa.ch",
|
email="trainer-uk1-bern.einstieg@eiger-versicherungen.ch",
|
||||||
first_name="Trainer",
|
first_name="Trainer",
|
||||||
last_name="Einstieg",
|
last_name="Einstieg",
|
||||||
)
|
)
|
||||||
|
_create_student_user(
|
||||||
|
email="student-uk1-bern@eiger-versicherungen.ch",
|
||||||
|
first_name="Student",
|
||||||
|
last_name="UK1-Bern",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_or_create_user(user_model, *args, **kwargs):
|
def _get_or_create_user(user_model, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -70,15 +70,18 @@ def command():
|
||||||
)
|
)
|
||||||
csu = CourseSessionUser.objects.create(
|
csu = CourseSessionUser.objects.create(
|
||||||
course_session=cs,
|
course_session=cs,
|
||||||
user=User.objects.get(username="expertvv.einstieg@vbv-afa.ch"),
|
user=User.objects.get(username="student-vv@eiger-versicherungen.ch"),
|
||||||
|
)
|
||||||
|
csu = CourseSessionUser.objects.create(
|
||||||
|
course_session=cs,
|
||||||
|
user=User.objects.get(username="expert-vv.einstieg@eiger-versicherungen.ch"),
|
||||||
)
|
)
|
||||||
csu.expert.add(
|
csu.expert.add(
|
||||||
Circle.objects.get(slug="versicherungsvermittlerin-lp-circle-einstieg")
|
Circle.objects.get(slug="versicherungsvermittlerin-lp-circle-einstieg")
|
||||||
)
|
)
|
||||||
|
|
||||||
csu = CourseSessionUser.objects.create(
|
csu = CourseSessionUser.objects.create(
|
||||||
course_session=cs,
|
course_session=cs,
|
||||||
user=User.objects.get(username="expertvv.analyse@vbv-afa.ch"),
|
user=User.objects.get(username="expert-vv.analyse@eiger-versicherungen.ch"),
|
||||||
)
|
)
|
||||||
csu.expert.add(
|
csu.expert.add(
|
||||||
Circle.objects.get(slug="versicherungsvermittlerin-lp-circle-analyse")
|
Circle.objects.get(slug="versicherungsvermittlerin-lp-circle-analyse")
|
||||||
|
|
@ -94,15 +97,21 @@ def command():
|
||||||
course_session=cs,
|
course_session=cs,
|
||||||
user=User.objects.get(username=user_data["email"]),
|
user=User.objects.get(username=user_data["email"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
csu = CourseSessionUser.objects.create(
|
csu = CourseSessionUser.objects.create(
|
||||||
course_session=cs,
|
course_session=cs,
|
||||||
user=User.objects.get(username="trainer-uk1.einstieg@vbv-afa.ch"),
|
user=User.objects.get(
|
||||||
|
username="trainer-uk1-bern.einstieg@eiger-versicherungen.ch"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-einstieg"))
|
csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-einstieg"))
|
||||||
|
|
||||||
csu = CourseSessionUser.objects.create(
|
csu = CourseSessionUser.objects.create(
|
||||||
course_session=cs,
|
course_session=cs,
|
||||||
user=User.objects.get(username="trainer-uk1.analyse@vbv-afa.ch"),
|
user=User.objects.get(
|
||||||
|
username="trainer-uk1-bern.analyse@eiger-versicherungen.ch"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-analyse"))
|
csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-analyse"))
|
||||||
|
csu = CourseSessionUser.objects.create(
|
||||||
|
course_session=cs,
|
||||||
|
user=User.objects.get(username="student-uk1-bern@eiger-versicherungen.ch"),
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue