Refactor to new url structure for learningPath and circle

This commit is contained in:
Daniel Egger 2022-08-31 15:28:17 +02:00
parent 0acdab60cd
commit 18acf10c9a
10 changed files with 201 additions and 320 deletions

View File

@ -1,27 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import * as log from 'loglevel'; import * as log from 'loglevel'
import {onMounted, reactive} from 'vue'; import { onMounted, reactive } from 'vue'
import {useUserStore} from '@/stores/user'; import { useUserStore } from '@/stores/user'
import {useLearningPathStore} from '@/stores/learningPath'; import { useLearningPathStore } from '@/stores/learningPath'
import {useRoute, useRouter} from 'vue-router'; import { useRoute, useRouter } from 'vue-router'
import {useAppStore} from '@/stores/app'; import { useAppStore } from '@/stores/app'
import IconLogout from "@/components/icons/IconLogout.vue"; import IconLogout from '@/components/icons/IconLogout.vue'
import IconSettings from "@/components/icons/IconSettings.vue"; import IconSettings from '@/components/icons/IconSettings.vue'
import ItDropdown from "@/components/ui/ItDropdown.vue"; import ItDropdown from '@/components/ui/ItDropdown.vue'
import MobileMenu from "@/components/MobileMenu.vue" import MobileMenu from '@/components/MobileMenu.vue'
log.debug('MainNavigationBar created'); log.debug('MainNavigationBar created')
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const userStore = useUserStore(); const userStore = useUserStore()
const appStore = useAppStore(); const appStore = useAppStore()
const learningPathStore = useLearningPathStore(); const learningPathStore = useLearningPathStore()
const state = reactive({showMenu: false}); const state = reactive({ showMenu: false })
function toggleNav() { function toggleNav() {
state.showMenu = !state.showMenu; state.showMenu = !state.showMenu
} }
function isInRoutePath(checkPaths: string[]) { function isInRoutePath(checkPaths: string[]) {
@ -29,28 +29,21 @@ function isInRoutePath(checkPaths: string[]) {
} }
function inLearningPath() { function inLearningPath() {
return isInRoutePath(['/learningpath/', '/circle/']); return isInRoutePath(['/learn/'])
} }
function getLearningPathStringProp (prop: 'title' | 'slug'): string { function getLearningPathStringProp(prop: 'title' | 'slug'): string {
return inLearningPath() && learningPathStore.learningPath ? learningPathStore.learningPath[prop] : ''; return inLearningPath() && learningPathStore.learningPath ? learningPathStore.learningPath[prop] : ''
} }
function learningPathName (): string { function learningPathName(): string {
return getLearningPathStringProp('title') return getLearningPathStringProp('title')
} }
function learninPathSlug (): string { function learninPathSlug(): string {
return getLearningPathStringProp('slug') return getLearningPathStringProp('slug')
} }
function backButtonUrl() {
if (route.path.startsWith('/circle/')) {
return '/learningpath/versicherungsvermittlerin';
}
return '/';
}
function handleDropdownSelect(data) { function handleDropdownSelect(data) {
log.debug('Selected action:', data.action) log.debug('Selected action:', data.action)
switch (data.action) { switch (data.action) {
@ -58,42 +51,41 @@ function handleDropdownSelect(data) {
router.push('/profile') router.push('/profile')
break break
case 'logout': case 'logout':
userStore.handleLogout(); userStore.handleLogout()
break break
default: default:
console.log('no action') console.log('no action')
} }
} }
function logout () { function logout() {
userStore.handleLogout(); userStore.handleLogout()
} }
onMounted(() => { onMounted(() => {
log.debug('MainNavigationBar mounted'); log.debug('MainNavigationBar mounted')
}) })
const profileDropdownData = [ const profileDropdownData = [
[ [
{ {
title: 'Kontoeinstellungen', title: 'Kontoeinstellungen',
icon: IconSettings, icon: IconSettings,
data: { data: {
action: 'settings' action: 'settings',
}
}
],
[
{
title: 'Abmelden',
icon: IconLogout,
data: {
action: 'logout'
}
}, },
] },
] ],
[
{
title: 'Abmelden',
icon: IconLogout,
data: {
action: 'logout',
},
},
],
]
</script> </script>
<template> <template>
@ -109,35 +101,14 @@ const profileDropdownData = [
</Teleport> </Teleport>
<Transition name="nav"> <Transition name="nav">
<div v-if="appStore.showMainNavigationBar" class="navigation bg-blue-900"> <div v-if="appStore.showMainNavigationBar" class="navigation bg-blue-900">
<nav <nav class="px-8 py-2 mx-auto lg:flex lg:justify-start lg:items-center lg:py-4">
class="
px-8
py-2
mx-auto
lg:flex lg:justify-start lg:items-center lg:py-4
"
>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center"> <div class="flex items-center">
<a <a href="https://www.vbv.ch" class="flex">
href="https://www.vbv.ch" <it-icon-vbv class="h-8 w-16 mr-3 -mt-6 -ml-3" />
class="flex">
<it-icon-vbv class="h-8 w-16 mr-3 -mt-6 -ml-3"/>
</a> </a>
<router-link <router-link to="/" class="flex">
to="/" <div class="text-white text-2xl pr-10 pl-3 ml-1 border-l border-white">myVBV</div>
class="flex">
<div class="
text-white
text-2xl
pr-10
pl-3
ml-1
border-l border-white
"
>
myVBV
</div>
</router-link> </router-link>
</div> </div>
@ -148,21 +119,15 @@ const profileDropdownData = [
class="nav-item flex flex-row items-center" class="nav-item flex flex-row items-center"
data-cy="messages-link" data-cy="messages-link"
> >
<it-icon-message class="w-8 h-8 mr-6"/> <it-icon-message class="w-8 h-8 mr-6" />
</router-link> </router-link>
<!-- Mobile menu button --> <!-- Mobile menu button -->
<div @click="toggleNav" class="flex"> <div @click="toggleNav" class="flex">
<button <button
type="button" type="button"
class=" class="w-8 h-8 text-white hover:text-sky-500 focus:outline-none focus:text-sky-500"
w-8
h-8
text-white
hover:text-sky-500
focus:outline-none focus:text-sky-500
"
> >
<it-icon-menu class="h-8 w-8"/> <it-icon-menu class="h-8 w-8" />
</button> </button>
</div> </div>
</div> </div>
@ -172,17 +137,13 @@ const profileDropdownData = [
<div <div
v-if="appStore.userLoaded && appStore.routingFinished && userStore.loggedIn" v-if="appStore.userLoaded && appStore.routingFinished && userStore.loggedIn"
:class="state.showMenu ? 'flex' : 'hidden'" :class="state.showMenu ? 'flex' : 'hidden'"
class=" class="flex-auto mt-8 lg:flex lg:space-y-0 lg:flex-row lg:items-center lg:space-x-10 lg:mt-0"
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="inLearningPath()" v-if="inLearningPath()"
to="/learningpath/versicherungsvermittlerin" to="/learningpath/versicherungsvermittlerin"
class="nav-item" class="nav-item"
:class="{'nav-item--active': inLearningPath()}" :class="{ 'nav-item--active': inLearningPath() }"
> >
Lernpfad Lernpfad
</router-link> </router-link>
@ -191,32 +152,24 @@ const profileDropdownData = [
v-if="inLearningPath()" v-if="inLearningPath()"
to="/competences/" to="/competences/"
class="nav-item" class="nav-item"
:class="{'nav-item--active': isInRoutePath(['/competences/'])}" :class="{ 'nav-item--active': isInRoutePath(['/competences/']) }"
> >
Kompetenzprofil Kompetenzprofil
</router-link> </router-link>
<div class="hidden lg:block flex-auto"></div> <div class="hidden lg:block flex-auto"></div>
<router-link <router-link to="/shop" class="nav-item" :class="{ 'nav-item--active': isInRoutePath(['/shop']) }">
to="/shop"
class="nav-item"
:class="{'nav-item--active': isInRoutePath(['/shop'])}"
>
Shop Shop
</router-link> </router-link>
<router-link <router-link
to="/mediacenter" to="/mediacenter"
class="nav-item" class="nav-item"
:class="{'nav-item--active': isInRoutePath(['/mediacenter'])}" :class="{ 'nav-item--active': isInRoutePath(['/mediacenter']) }"
> >
Mediathek Mediathek
</router-link> </router-link>
<router-link <router-link to="/messages" class="nav-item flex flex-row items-center" data-cy="messages-link">
to="/messages" <it-icon-message class="w-8 h-8 mr-6" />
class="nav-item flex flex-row items-center"
data-cy="messages-link"
>
<it-icon-message class="w-8 h-8 mr-6"/>
</router-link> </router-link>
<div class="nav-item flex items-center" v-if="userStore.loggedIn"> <div class="nav-item flex items-center" v-if="userStore.loggedIn">
<ItDropdown <ItDropdown
@ -226,9 +179,7 @@ const profileDropdownData = [
@select="handleDropdownSelect" @select="handleDropdownSelect"
> >
<div v-if="userStore.avatar_url"> <div v-if="userStore.avatar_url">
<img class="inline-block h-8 w-8 rounded-full" <img class="inline-block h-8 w-8 rounded-full" :src="userStore.avatar_url" alt="" />
:src="userStore.avatar_url"
alt=""/>
</div> </div>
<div v-else> <div v-else>
{{ userStore.getFullName }} {{ userStore.getFullName }}
@ -249,7 +200,7 @@ const profileDropdownData = [
} }
.nav-item--active { .nav-item--active {
@apply underline underline-offset-[21px] decoration-sky-500 decoration-4 @apply underline underline-offset-[21px] decoration-sky-500 decoration-4;
} }
.nav-enter-active, .nav-enter-active,
@ -262,5 +213,4 @@ const profileDropdownData = [
opacity: 0; opacity: 0;
transform: translateY(-80px); transform: translateY(-80px);
} }
</style> </style>

View File

@ -1,7 +1,7 @@
<script> <script>
import * as d3 from 'd3'; import * as d3 from 'd3'
import { useLearningPathStore } from '../../stores/learningPath'; import { useLearningPathStore } from '../../stores/learningPath'
import colors from '@/colors.json'; import colors from '@/colors.json'
export default { export default {
props: { props: {
@ -15,37 +15,35 @@ export default {
}, },
vertical: { vertical: {
default: false, default: false,
type: Boolean type: Boolean,
}, },
identifier: { identifier: {
required: true, required: true,
type: String type: String,
} },
}, },
setup() { setup() {
const learningPathStore = useLearningPathStore() const learningPathStore = useLearningPathStore()
return {learningPathStore} return { learningPathStore }
}, },
computed: { computed: {
viewBox() { viewBox() {
return `0 0 ${this.width} ${this.height * 1.5}` return `0 0 ${this.width} ${this.height * 1.5}`
}, },
circles() { circles() {
function someFinished(circle, learningSequence) { function someFinished(circle, learningSequence) {
if (circle) { if (circle) {
return circle.someFinishedInLearningSequence(learningSequence.translation_key); return circle.someFinishedInLearningSequence(learningSequence.translation_key)
} }
return false; return false
} }
function allFinished(circle, learningSequence) { function allFinished(circle, learningSequence) {
if (circle) { if (circle) {
return circle.allFinishedInLearningSequence(learningSequence.translation_key); return circle.allFinishedInLearningSequence(learningSequence.translation_key)
} }
return false; return false
} }
if (this.learningPathStore.learningPath) { if (this.learningPathStore.learningPath) {
@ -58,29 +56,28 @@ export default {
const thisLearningSequence = circle.learningSequences[parseInt(pie.index)] const thisLearningSequence = circle.learningSequences[parseInt(pie.index)]
pie.startAngle = pie.startAngle + Math.PI pie.startAngle = pie.startAngle + Math.PI
pie.endAngle = pie.endAngle + Math.PI pie.endAngle = pie.endAngle + Math.PI
pie.done = circle.someFinishedInLearningSequence(thisLearningSequence.translation_key); pie.done = circle.someFinishedInLearningSequence(thisLearningSequence.translation_key)
pie.someFinished = someFinished(circle, thisLearningSequence) pie.someFinished = someFinished(circle, thisLearningSequence)
pie.allFinished = allFinished(circle, thisLearningSequence) pie.allFinished = allFinished(circle, thisLearningSequence)
}); })
const newCircle = {} const newCircle = {}
newCircle.pieData = pieData.reverse() newCircle.pieData = pieData.reverse()
newCircle.title = circle.title newCircle.title = circle.title
newCircle.slug = circle.slug newCircle.slug = circle.slug
newCircle.id = circle.id newCircle.id = circle.id
internalCircles.push(newCircle) internalCircles.push(newCircle)
}); })
return internalCircles return internalCircles
} }
return []; return []
}, },
svg() { svg() {
return d3.select("#" + this.identifier) return d3.select('#' + this.identifier)
}, },
learningPath() { learningPath() {
return Object.assign({}, this.learningPathStore.learningPath) return Object.assign({}, this.learningPathStore.learningPath)
} },
}, },
mounted() { mounted() {
@ -111,7 +108,6 @@ export default {
const vueRouter = this.$router const vueRouter = this.$router
// Create append pie charts to the main svg // Create append pie charts to the main svg
const circle_groups = this.svg const circle_groups = this.svg
.selectAll('.circle') .selectAll('.circle')
@ -121,7 +117,7 @@ export default {
.attr('class', 'circle') .attr('class', 'circle')
.attr('data-cy', (d) => { .attr('data-cy', (d) => {
if (this.vertical) { if (this.vertical) {
return `circle-${d.slug}-vertical`; return `circle-${d.slug}-vertical`
} else { } else {
return `circle-${d.slug}` return `circle-${d.slug}`
} }
@ -144,8 +140,8 @@ export default {
return getColor(d) return getColor(d)
}) })
}) })
.on('click', function (d, i) { .on('click', (d, i) => {
vueRouter.push('/circle/' + i.slug) vueRouter.push(`/learn/${this.learningPathStore.learningPath.slug}/${i.slug}`)
}) })
.attr('role', 'button') .attr('role', 'button')
@ -178,7 +174,6 @@ export default {
//Draw arc paths //Draw arc paths
arcs.append('path').attr('d', arcGenerator) arcs.append('path').attr('d', arcGenerator)
const circlesText = circle_groups const circlesText = circle_groups
.append('text') .append('text')
.attr('fill', colors.blue[900]) .attr('fill', colors.blue[900])
@ -223,21 +218,13 @@ export default {
return y return y
} }
y += circleHeigth y += circleHeigth
} }
} }
} }
const topicGroups = this.svg const topicGroups = this.svg.selectAll('.topic').data(this.learningPath.topics).enter().append('g')
.selectAll('.topic')
.data(this.learningPath.topics)
.enter()
.append('g')
const topicLines = topicGroups const topicLines = topicGroups.append('line').attr('class', 'stroke-gray-500').attr('stroke-width', 1)
.append('line')
.attr('class', 'stroke-gray-500')
.attr('stroke-width', 1)
const topicTitles = topicGroups const topicTitles = topicGroups
.append('text') .append('text')
@ -245,47 +232,40 @@ export default {
.style('font-size', 16) .style('font-size', 16)
.text((d) => d.title) .text((d) => d.title)
// Calculate positions of objects // Calculate positions of objects
if (this.vertical) { if (this.vertical) {
const Circles_X = 60 const Circles_X = 60
const Topics_X = Circles_X - radius const Topics_X = Circles_X - radius
circle_groups.attr('transform', (d, i) => {
circle_groups return 'translate(' + Circles_X + ',' + getCircleVerticalPostion(i, d, this.learningPath.topics) + ')'
.attr('transform', (d, i) => { })
return 'translate(' + Circles_X + ',' + getCircleVerticalPostion(i, d, this.learningPath.topics) + ')'
})
circlesText circlesText
.attr('y', 7) .attr('y', 7)
.attr('x', radius + 40) .attr('x', radius + 40)
.attr('class', 'circlesText text-xl font-bold block') .attr('class', 'circlesText text-xl font-bold block')
topicGroups topicGroups
.attr('transform', (d, i) => { .attr('transform', (d, i) => {
return "translate(" + Topics_X + ", " + getTopicVerticalPosition(i, d, this.learningPath.topics) + ")" return 'translate(' + Topics_X + ', ' + getTopicVerticalPosition(i, d, this.learningPath.topics) + ')'
}) })
.attr('class', (d) => { .attr('class', (d) => {
return 'topic '.concat(d.is_visible ? "block" : "hidden") return 'topic '.concat(d.is_visible ? 'block' : 'hidden')
}) })
topicLines topicLines
.transition().duration('1000').attr('x2', this.width * 0.8) .transition()
.duration('1000')
topicTitles .attr('x2', this.width * 0.8)
.attr('y', 30)
topicTitles.attr('y', 30)
} else { } else {
circle_groups circle_groups.attr('transform', (d, i) => {
.attr('transform', (d, i) => { const x_coord = (i + 1) * circleWidth - radius
const x_coord = (i + 1) * circleWidth - radius return 'translate(' + x_coord + ', 200)'
return 'translate(' + x_coord + ', 200)' })
})
circlesText circlesText
.attr('y', radius + 30) .attr('y', radius + 30)
@ -293,30 +273,28 @@ export default {
.call(wrap, circleWidth - 20) .call(wrap, circleWidth - 20)
.attr('class', 'circlesText text-xl font-bold hidden lg:block') .attr('class', 'circlesText text-xl font-bold hidden lg:block')
topicGroups topicGroups
.attr('transform', (d, i) => { .attr('transform', (d, i) => {
return "translate(" + getTopicHorizontalPosition(i, d, this.learningPathStore.learningPath.topics) + ",0)" return 'translate(' + getTopicHorizontalPosition(i, d, this.learningPathStore.learningPath.topics) + ',0)'
}) })
.attr('class', (d) => { .attr('class', (d) => {
return 'topic '.concat(d.is_visible ? "hidden lg:block" : "hidden") return 'topic '.concat(d.is_visible ? 'hidden lg:block' : 'hidden')
}) })
topicLines topicLines
.attr('x1', -10) .attr('x1', -10)
.attr('y1', 0) .attr('y1', 0)
.attr('x2', -10) .attr('x2', -10)
.attr('y2', 0) .attr('y2', 0)
.transition().duration('1000').attr('y2', 350) .transition()
.duration('1000')
.attr('y2', 350)
topicTitles topicTitles
.attr('y', 20) .attr('y', 20)
.style('font-size', 19) .style('font-size', 19)
.call(wrap, circleWidth * 0.8) .call(wrap, circleWidth * 0.8)
.attr('class', 'topicTitles font-bold') .attr('class', 'topicTitles font-bold')
} }
function wrap(text, width) { function wrap(text, width) {
@ -357,12 +335,8 @@ export default {
} }
</script> </script>
<template> <template>
<div class="svg-container h-full content-start"> <div class="svg-container h-full content-start">
<svg class="learning-path-visualization h-full" :viewBox="viewBox" :id=identifier> <svg class="learning-path-visualization h-full" :viewBox="viewBox" :id="identifier"></svg>
</svg>
</div> </div>
</template> </template>

View File

@ -37,12 +37,12 @@ const router = createRouter({
component: () => import('@/views/ProfileView.vue'), component: () => import('@/views/ProfileView.vue'),
}, },
{ {
path: '/learningpath/:learningPathSlug', path: '/learn/:learningPathSlug',
component: () => import('../views/LearningPathView.vue'), component: () => import('../views/LearningPathView.vue'),
props: true, props: true,
}, },
{ {
path: '/circle/:circleSlug', path: '/learn/:learningPathSlug/:circleSlug',
component: () => import('../views/CircleView.vue'), component: () => import('../views/CircleView.vue'),
props: true, props: true,
}, },

View File

@ -4,7 +4,7 @@ import { defineStore } from 'pinia'
import type { LearningContent, LearningUnit, LearningUnitQuestion } from '@/types' import type { LearningContent, LearningUnit, LearningUnitQuestion } from '@/types'
import type { Circle } from '@/services/circle' import type { Circle } from '@/services/circle'
import { itGet, itPost } from '@/fetchHelpers' import { itPost } from '@/fetchHelpers'
import { useAppStore } from '@/stores/app' import { useAppStore } from '@/stores/app'
import { useLearningPathStore } from '@/stores/learningPath' import { useLearningPathStore } from '@/stores/learningPath'
@ -28,26 +28,19 @@ export const useCircleStore = defineStore({
getters: { getters: {
}, },
actions: { actions: {
async loadCircle(slug: string) { async loadCircle(learningPathSlug: string, circleSlug: string) {
this.circle = undefined; this.circle = undefined;
try { const learningPathStore = useLearningPathStore();
// const circleData = await itGet(`/learnpath/api/circle/${slug}/`); await learningPathStore.loadLearningPath(learningPathSlug);
// this.circle = Circle.fromJson(circleData); if (learningPathStore.learningPath) {
// this.circle.parseCompletionData(completionData); this.circle = learningPathStore.learningPath.circles.find(circle => circle.slug === circleSlug);
const learningPathStore = useLearningPathStore();
await learningPathStore.loadLearningPath('versicherungsvermittlerin');
if (learningPathStore.learningPath) {
this.circle = learningPathStore.learningPath.circles.find(circle => circle.slug === slug);
if (this.circle) {
const completionData = await itGet(`/api/completion/circle/${this.circle.translation_key}/`);
this.circle.parseCompletionData(completionData);
}
}
return Promise.resolve(this.circle)
} catch (error) {
log.error(error);
return error
} }
if (!this.circle) {
throw `No circle found with slug: ${circleSlug}`;
}
return this.circle
}, },
async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) { async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) {
try { try {

View File

@ -1,5 +1,3 @@
import * as log from 'loglevel'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { itGet } from '@/fetchHelpers' import { itGet } from '@/fetchHelpers'
import { LearningPath } from '@/services/learningPath' import { LearningPath } from '@/services/learningPath'
@ -21,18 +19,15 @@ export const useLearningPathStore = defineStore({
if (this.learningPath && !reload) { if (this.learningPath && !reload) {
return this.learningPath; return this.learningPath;
} }
try { const learningPathData = await itGet(`/learnpath/api/page/${slug}/`);
const learningPathData = await itGet(`/learnpath/api/page/${slug}/`); const completionData = await itGet(`/api/completion/learning_path/${learningPathData.translation_key}/`);
const completionData = await itGet(`/api/completion/learning_path/${learningPathData.translation_key}/`);
if (learningPathData) { if (!learningPathData) {
this.learningPath = LearningPath.fromJson(learningPathData, completionData); throw `No learning path found with: ${slug}`;
}
return this.learningPath;
} catch (error) {
log.error(error);
return error
} }
this.learningPath = LearningPath.fromJson(learningPathData, completionData);
return this.learningPath;
}, },
} }
}) })

View File

@ -1,27 +1,32 @@
<script setup lang="ts"> <script setup lang="ts">
import * as log from 'loglevel'; import * as log from 'loglevel'
import LearningSequence from '@/components/circle/LearningSequence.vue'; import LearningSequence from '@/components/circle/LearningSequence.vue'
import CircleOverview from '@/components/circle/CircleOverview.vue'; import CircleOverview from '@/components/circle/CircleOverview.vue'
import CircleDiagram from '@/components/circle/CircleDiagram.vue'; import CircleDiagram from '@/components/circle/CircleDiagram.vue'
import LearningContent from '@/components/circle/LearningContent.vue'; import LearningContent from '@/components/circle/LearningContent.vue'
import {onMounted} from 'vue' import { onMounted } from 'vue'
import {useCircleStore} from '@/stores/circle'; import { useCircleStore } from '@/stores/circle'
import SelfEvaluation from '@/components/circle/SelfEvaluation.vue'; import SelfEvaluation from '@/components/circle/SelfEvaluation.vue'
log.debug('CircleView.vue created'); log.debug('CircleView.vue created')
const props = defineProps<{ const props = defineProps<{
learningPathSlug: string
circleSlug: string circleSlug: string
}>() }>()
const circleStore = useCircleStore(); const circleStore = useCircleStore()
circleStore.loadCircle(props.circleSlug);
onMounted(async () => { onMounted(async () => {
log.info('CircleView.vue mounted'); log.debug('CircleView.vue mounted', props.learningPathSlug, props.circleSlug)
});
try {
await circleStore.loadCircle(props.learningPathSlug, props.circleSlug)
} catch (error) {
log.error(error)
}
})
</script> </script>
<template> <template>
@ -35,10 +40,10 @@ onMounted(async () => {
</Teleport> </Teleport>
<Transition mode="out-in"> <Transition mode="out-in">
<div v-if="circleStore.page === 'LEARNING_CONTENT'"> <div v-if="circleStore.page === 'LEARNING_CONTENT'">
<LearningContent :key="circleStore.currentLearningContent.translation_key"/> <LearningContent :key="circleStore.currentLearningContent.translation_key" />
</div> </div>
<div v-else-if="circleStore.page === 'SELF_EVALUATION'"> <div v-else-if="circleStore.page === 'SELF_EVALUATION'">
<SelfEvaluation :key="circleStore.currentSelfEvaluation.translation_key"/> <SelfEvaluation :key="circleStore.currentSelfEvaluation.translation_key" />
</div> </div>
<div v-else> <div v-else>
<div class="circle-container"> <div class="circle-container">
@ -46,7 +51,7 @@ onMounted(async () => {
<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
to="/learningpath/versicherungsvermittlerin" :to="`/learn/${props.learningPathSlug}`"
class="btn-text inline-flex items-center px-3 py-4 font-normal" class="btn-text inline-flex items-center px-3 py-4 font-normal"
> >
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left> <it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
@ -62,15 +67,12 @@ onMounted(async () => {
</div> </div>
<div class="border-t-2 border-gray-500 mt-4 lg:hidden"> <div class="border-t-2 border-gray-500 mt-4 lg:hidden">
<div <div class="mt-4 inline-flex items-center" @click="circleStore.page = 'OVERVIEW'">
class="mt-4 inline-flex items-center" <it-icon-info class="mr-2" />
@click="circleStore.page = 'OVERVIEW'"
>
<it-icon-info class="mr-2"/>
Das lernst du in diesem Circle Das lernst du in diesem Circle
</div> </div>
<div class="inline-flex items-center"> <div class="inline-flex items-center">
<it-icon-message class="mr-2"/> <it-icon-message class="mr-2" />
Fachexpertin kontaktieren Fachexpertin kontaktieren
</div> </div>
</div> </div>
@ -82,16 +84,15 @@ onMounted(async () => {
{{ circleStore.circle?.description }} {{ circleStore.circle?.description }}
</div> </div>
<button class="btn-primary mt-4 text-xl" @click="circleStore.page = 'OVERVIEW'">Erfahre mehr dazu <button class="btn-primary mt-4 text-xl" @click="circleStore.page = 'OVERVIEW'">
Erfahre mehr dazu
</button> </button>
</div> </div>
<div class="expert border border-gray-500 mt-8 p-6"> <div class="expert border border-gray-500 mt-8 p-6">
<h3 class="text-blue-dark">Hast du Fragen?</h3> <h3 class="text-blue-dark">Hast du Fragen?</h3>
<div class="prose mt-4">Tausche dich mit der Fachexpertin aus für den Circle Analyse aus.</div> <div class="prose mt-4">Tausche dich mit der Fachexpertin aus für den Circle Analyse aus.</div>
<button class="btn-secondary mt-4 text-xl"> <button class="btn-secondary mt-4 text-xl">Fachexpertin kontaktieren</button>
Fachexpertin kontaktieren
</button>
</div> </div>
</div> </div>
</div> </div>
@ -101,14 +102,10 @@ onMounted(async () => {
v-for="learningSequence in circleStore.circle?.learningSequences || []" v-for="learningSequence in circleStore.circle?.learningSequences || []"
:key="learningSequence.translation_key" :key="learningSequence.translation_key"
> >
<LearningSequence <LearningSequence :learning-sequence="learningSequence"></LearningSequence>
:learning-sequence="learningSequence"
></LearningSequence>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -117,15 +114,8 @@ onMounted(async () => {
</template> </template>
<style lang="postcss" scoped> <style lang="postcss" scoped>
.circle-container { .circle-container {
background: linear-gradient( background: linear-gradient(to right, white 0%, white 50%, theme(colors.gray.200) 50%, theme(colors.gray.200) 100%);
to right,
white 0%,
white 50%,
theme(colors.gray.200) 50%,
theme(colors.gray.200) 100%
);
} }
.circle { .circle {
@ -142,5 +132,4 @@ onMounted(async () => {
.v-leave-to { .v-leave-to {
opacity: 0; opacity: 0;
} }
</style> </style>

View File

@ -1,31 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import * as log from 'loglevel'; import * as log from 'loglevel'
import {useUserStore} from '@/stores/user'; import { useUserStore } from '@/stores/user'
log.debug('CockpitView created'); log.debug('CockpitView created')
const userStore = useUserStore();
const userStore = useUserStore()
</script> </script>
<template> <template>
<main class="px-8 py-8 lg:px-12 lg:py-12 bg-gray-200"> <main class="px-8 py-8 lg:px-12 lg:py-12 bg-gray-200">
<h1 data-cy="welcome-message">Willkommen, {{userStore.first_name}}</h1> <h1 data-cy="welcome-message">Willkommen, {{ userStore.first_name }}</h1>
<h2 class="mt-12">Deine Kurse</h2> <h2 class="mt-12">Deine Kurse</h2>
<div class="mt-8 p-8 break-words bg-white max-w-xl"> <div class="mt-8 p-8 break-words bg-white max-w-xl">
<h3>Versicherungsvermittler/in</h3> <h3>Versicherungsvermittler/in</h3>
<div class="mt-4"> <div class="mt-4">
<router-link class="btn-blue" to="/learningpath/versicherungsvermittlerin"> <router-link class="btn-blue" to="/learn/versicherungsvermittlerin"> Weiter gehts </router-link>
Weiter gehts
</router-link>
</div> </div>
</div> </div>
</main> </main>
</template> </template>
<style scoped> <style scoped></style>
</style>

View File

@ -1,18 +0,0 @@
<script setup lang="ts"></script>
<template>
<main class="px-8 py-8">
<h1>myVBV Start Page</h1>
<div class="mt-8 flex flex-col lg:flex-row justify-start gap-4">
<router-link class="link text-xl" to="/styleguide">Styelguide</router-link>
<a class="link text-xl" href="/login">Login</a>
<router-link class="link text-xl" to="/learningpath/versicherungsvermittlerin">Lernpfad "Versicherungsvermittlerin"</router-link>
<router-link class="link text-xl" to="/circle/analyse">Circle "Analyse"</router-link>
</div>
</main>
</template>
<style scoped>
</style>

View File

@ -1,31 +1,31 @@
<script setup lang="ts"> <script setup lang="ts">
import * as log from 'loglevel'
import * as log from 'loglevel'; import { onMounted } from 'vue'
import { useLearningPathStore } from '@/stores/learningPath'
import { useUserStore } from '@/stores/user'
import {onMounted} from 'vue' import LearningPathDiagram from '@/components/circle/LearningPathDiagram.vue'
import {useLearningPathStore} from '@/stores/learningPath'; import LearningPathViewVertical from '@/views/LearningPathViewVertical.vue'
import {useUserStore} from '@/stores/user';
import LearningPathDiagram from '@/components/circle/LearningPathDiagram.vue'; log.debug('LearningPathView created')
import LearningPathViewVertical from "@/views/LearningPathViewVertical.vue";
log.debug('LearningPathView created');
const props = defineProps<{ const props = defineProps<{
learningPathSlug: string learningPathSlug: string
}>() }>()
const learningPathStore = useLearningPathStore(); const learningPathStore = useLearningPathStore()
const userStore = useUserStore(); const userStore = useUserStore()
onMounted(async () => { onMounted(async () => {
log.info('LearningPathView mounted'); log.debug('LearningPathView mounted')
await learningPathStore.loadLearningPath(props.learningPathSlug);
console.log(learningPathStore)
});
try {
await learningPathStore.loadLearningPath(props.learningPathSlug)
} catch (error) {
log.error(error)
}
})
</script> </script>
<template> <template>
@ -42,12 +42,8 @@ onMounted(async () => {
<div class="flex flex-col h-max"> <div class="flex flex-col h-max">
<div class="bg-white py-8 flex flex-col"> <div class="bg-white py-8 flex flex-col">
<div class="flex justify-end p-3"> <div class="flex justify-end p-3">
<button <button class="flex items-center" @click="learningPathStore.page = 'OVERVIEW'" data-cy="show-list-view">
class="flex items-center" <it-icon-list />
@click="learningPathStore.page = 'OVERVIEW'"
data-cy="show-list-view"
>
<it-icon-list/>
Listen Ansicht anzeigen Listen Ansicht anzeigen
</button> </button>
</div> </div>
@ -61,19 +57,21 @@ onMounted(async () => {
<h1 data-cy="learning-path-title" class="m-12">{{ learningPathStore.learningPath.title }}</h1> <h1 data-cy="learning-path-title" class="m-12">{{ learningPathStore.learningPath.title }}</h1>
<div <div
class="bg-white m-12 p-8 flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-gray-500 justify-start"> class="bg-white m-12 p-8 flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-gray-500 justify-start"
>
<div class="p-8 flex-auto"> <div class="p-8 flex-auto">
<h2 translate>Willkommmen zurück, {{ userStore.first_name }}</h2> <h2 translate>Willkommmen zurück, {{ userStore.first_name }}</h2>
<p class="mt-4 text-xl"> <p class="mt-4 text-xl"></p>
</p>
</div> </div>
<div class="p-8 flex-2" v-if="learningPathStore.learningPath.nextLearningContent" translate> <div class="p-8 flex-2" v-if="learningPathStore.learningPath.nextLearningContent" translate>
Nächster Schirtt Nächster Schirtt
<h3>{{ learningPathStore.learningPath.nextLearningContent.parentCircle.title }}: {{ learningPathStore.learningPath.nextLearningContent.parentLearningSequence.title }}</h3> <h3>
{{ learningPathStore.learningPath.nextLearningContent.parentCircle.title }}:
{{ learningPathStore.learningPath.nextLearningContent.parentLearningSequence.title }}
</h3>
<router-link <router-link
class="mt-4 btn-blue" class="mt-4 btn-blue"
:to="`/circle/${learningPathStore.learningPath.nextLearningContent.parentCircle.slug}/`" :to="`/learn/${learningPathStore.learningPath.slug}/${learningPathStore.learningPath.nextLearningContent.parentCircle.slug}`"
translate translate
> >
Los geht's Los geht's
@ -86,5 +84,4 @@ onMounted(async () => {
</div> </div>
</template> </template>
<style scoped> <style scoped></style>
</style>

View File

@ -22,7 +22,7 @@ class LearningPath(Page):
verbose_name = "Learning Path" verbose_name = "Learning Path"
def full_clean(self, *args, **kwargs): def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(Page, slugify(self.title, allow_unicode=True)) self.slug = find_available_slug(slugify(self.title, allow_unicode=True))
super(LearningPath, self).full_clean(*args, **kwargs) super(LearningPath, self).full_clean(*args, **kwargs)
def __str__(self): def __str__(self):
@ -54,8 +54,7 @@ class Topic(Page):
# subpage_types = ['learnpath.Circle'] # subpage_types = ['learnpath.Circle']
def full_clean(self, *args, **kwargs): def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(Topic, slugify(self.title, allow_unicode=True)) self.slug = find_available_slug(slugify(f'topic-{self.title}', allow_unicode=True))
print(self.slug)
super(Topic, self).full_clean(*args, **kwargs) super(Topic, self).full_clean(*args, **kwargs)
@classmethod @classmethod
@ -115,8 +114,7 @@ class Circle(Page):
) )
def full_clean(self, *args, **kwargs): def full_clean(self, *args, **kwargs):
# TODO: why own slug function? self.slug = find_available_slug(slugify(self.title, allow_unicode=True))
self.slug = find_available_slug(Page, slugify(self.title, allow_unicode=True))
super(Circle, self).full_clean(*args, **kwargs) super(Circle, self).full_clean(*args, **kwargs)
class Meta: class Meta:
@ -157,6 +155,7 @@ class LearningSequence(Page):
</span>''' </span>'''
def full_clean(self, *args, **kwargs): def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(slugify(f'ls-{self.title}', allow_unicode=True))
super(LearningSequence, self).full_clean(*args, **kwargs) super(LearningSequence, self).full_clean(*args, **kwargs)
@ -170,6 +169,10 @@ class LearningUnit(Page):
def __str__(self): def __str__(self):
return f"{self.title}" return f"{self.title}"
def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(slugify(f'lu-{self.title}', allow_unicode=True))
super(LearningUnit, self).full_clean(*args, **kwargs)
@classmethod @classmethod
def get_serializer_class(cls): def get_serializer_class(cls):
return get_it_serializer_class(cls, field_names=['id', 'title', 'slug', 'type', 'translation_key', 'children']) return get_it_serializer_class(cls, field_names=['id', 'title', 'slug', 'type', 'translation_key', 'children'])
@ -188,6 +191,10 @@ class LearningUnitQuestion(Page):
def __str__(self): def __str__(self):
return f"{self.title}" return f"{self.title}"
def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(slugify(f'luq-{self.title}', allow_unicode=True))
super(LearningUnitQuestion, self).full_clean(*args, **kwargs)
@classmethod @classmethod
def get_serializer_class(cls): def get_serializer_class(cls):
return get_it_serializer_class(cls, field_names=['id', 'title', 'slug', 'type', 'translation_key', ]) return get_it_serializer_class(cls, field_names=['id', 'title', 'slug', 'type', 'translation_key', ])
@ -240,7 +247,8 @@ class LearningContent(Page):
verbose_name = "Learning Content" verbose_name = "Learning Content"
def full_clean(self, *args, **kwargs): def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(LearningContent, slugify(self.title, allow_unicode=True)) self.slug = find_available_slug(slugify(f'lc-{self.title}', allow_unicode=True))
print(self.slug)
super(LearningContent, self).full_clean(*args, **kwargs) super(LearningContent, self).full_clean(*args, **kwargs)
@classmethod @classmethod
@ -253,7 +261,7 @@ class LearningContent(Page):
return f"{self.title}" return f"{self.title}"
def find_available_slug(model, requested_slug, ignore_page_id=None): def find_available_slug(requested_slug, ignore_page_id=None):
""" """
Finds an available slug within the specified parent. Finds an available slug within the specified parent.
@ -270,8 +278,7 @@ def find_available_slug(model, requested_slug, ignore_page_id=None):
treated as in use by another page. treated as in use by another page.
""" """
# TODO: In comparison ot wagtails own function, I look for the same model instead of the parent pages = Page.objects.filter(slug__startswith=requested_slug)
pages = model.objects.filter(slug__startswith=requested_slug)
if ignore_page_id: if ignore_page_id:
pages = pages.exclude(id=ignore_page_id) pages = pages.exclude(id=ignore_page_id)