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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@ class LearningPath(Page):
verbose_name = "Learning Path"
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)
def __str__(self):
@ -54,8 +54,7 @@ class Topic(Page):
# subpage_types = ['learnpath.Circle']
def full_clean(self, *args, **kwargs):
self.slug = find_available_slug(Topic, slugify(self.title, allow_unicode=True))
print(self.slug)
self.slug = find_available_slug(slugify(f'topic-{self.title}', allow_unicode=True))
super(Topic, self).full_clean(*args, **kwargs)
@classmethod
@ -115,8 +114,7 @@ class Circle(Page):
)
def full_clean(self, *args, **kwargs):
# TODO: why own slug function?
self.slug = find_available_slug(Page, slugify(self.title, allow_unicode=True))
self.slug = find_available_slug(slugify(self.title, allow_unicode=True))
super(Circle, self).full_clean(*args, **kwargs)
class Meta:
@ -157,6 +155,7 @@ class LearningSequence(Page):
</span>'''
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)
@ -170,6 +169,10 @@ class LearningUnit(Page):
def __str__(self):
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
def get_serializer_class(cls):
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):
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
def get_serializer_class(cls):
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"
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)
@classmethod
@ -253,7 +261,7 @@ class LearningContent(Page):
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.
@ -270,8 +278,7 @@ def find_available_slug(model, requested_slug, ignore_page_id=None):
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 = model.objects.filter(slug__startswith=requested_slug)
pages = Page.objects.filter(slug__startswith=requested_slug)
if ignore_page_id:
pages = pages.exclude(id=ignore_page_id)