{{ learningUnit.title }}
{{ learningUnit.minutes }} Minuten
@@ -79,48 +71,36 @@ const learningSequenceBorderClass = computed(() => {
- {{ learningContent.contents[0].type }}: {{ learningContent.title }}
+ {{ learningContent.contents[0].type }}: {{ learningContent.title }}
-
-
-
+
+
+
Selbsteinschätzung: Ich kann das.
-
+
Selbsteinschätzung: Muss ich nochmals anschauen
-
-
-
+
-
-
+
diff --git a/client/src/components/circle/SimpleCircleDiagram.vue b/client/src/components/circle/SimpleCircleDiagram.vue
deleted file mode 100644
index 89034d4b..00000000
--- a/client/src/components/circle/SimpleCircleDiagram.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/components/ui/ItFullScreenModal.vue b/client/src/components/ui/ItFullScreenModal.vue
index 22fbbf96..4aa10be1 100644
--- a/client/src/components/ui/ItFullScreenModal.vue
+++ b/client/src/components/ui/ItFullScreenModal.vue
@@ -2,7 +2,6 @@
// inspiration https://vuejs.org/examples/#modal
import {onMounted, watch} from "vue";
-import {HTMLElement} from "happy-dom";
const props = defineProps<{
show: boolean
diff --git a/client/src/router/index.ts b/client/src/router/index.ts
index e6683230..764f3508 100644
--- a/client/src/router/index.ts
+++ b/client/src/router/index.ts
@@ -37,18 +37,18 @@ 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,
},
{
path: '/styleguide',
- component: () => import('../views/StyelGuideView.vue'),
+ component: () => import('../views/StyleGuideView.vue'),
meta: {
public: true,
},
diff --git a/client/src/services/__tests__/circle.spec.ts b/client/src/services/__tests__/circle.spec.ts
index 31c4536c..cc321a72 100644
--- a/client/src/services/__tests__/circle.spec.ts
+++ b/client/src/services/__tests__/circle.spec.ts
@@ -1,130 +1,12 @@
-import {describe, it} from 'vitest'
-import {parseLearningSequences} from '../circle';
-import type {WagtailCircle} from '@/types';
+import { describe, it } from 'vitest'
+import data from './learning_path_json.json'
+import { Circle } from '../circle'
-describe('circleService.parseLearningSequences', () => {
- it('can parse learning sequences from api response', () => {
- const input = {
- "id": 10,
- "title": "Analyse",
- "slug": "analyse",
- "type": "learnpath.Circle",
- "translation_key": "c9832aaf-02b2-47af-baeb-bde60d8ec1f5",
- "children": [
- {
- "id": 18,
- "title": "Anwenden",
- "slug": "anwenden",
- "type": "learnpath.LearningSequence",
- "translation_key": "2e4c431a-9602-4398-ad18-20dd4bb189fa",
- "icon": "it-icon-ls-apply"
- },
- {
- "id": 19,
- "title": "Prämien einsparen",
- "slug": "pramien-einsparen",
- "type": "learnpath.LearningUnit",
- "translation_key": "75c1f31a-ae25-4d9c-9206-a4e7fdae8c13",
- "questions": []
- },
- {
- "id": 20,
- "title": "Versicherungsbedarf für Familien",
- "slug": "versicherungsbedarf-für-familien",
- "type": "learnpath.LearningContent",
- "translation_key": "2a422da3-a3ad-468a-831e-9141c122ffef",
- "minutes": 60,
- "contents": [
- {
- "type": "exercise",
- "value": {
- "description": "Beispiel Aufgabe"
- },
- "id": "ee0bcef7-702b-42f3-a891-88a0332fce6f"
- }
- ]
- },
- {
- "id": 21,
- "title": "Alles klar?",
- "slug": "alles-klar",
- "type": "learnpath.LearningContent",
- "translation_key": "7dc9d96d-07f9-4b9f-bec1-43ba67cf9010",
- "minutes": 60,
- "contents": [
- {
- "type": "exercise",
- "value": {
- "description": "Beispiel Aufgabe"
- },
- "id": "a556ebb2-f902-4d78-9b76-38b7933118b8"
- }
- ]
- },
- {
- "id": 22,
- "title": "Sich selbständig machen",
- "slug": "sich-selbstandig-machen",
- "type": "learnpath.LearningUnit",
- "translation_key": "c40d5266-3c94-4b9b-8469-9ac6b32a6231",
- "questions": []
- },
- {
- "id": 23,
- "title": "GmbH oder AG",
- "slug": "gmbh-oder-ag",
- "type": "learnpath.LearningContent",
- "translation_key": "59331843-9f52-4b41-9cd1-2293a8d90064",
- "minutes": 120,
- "contents": [
- {
- "type": "video",
- "value": {
- "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
- "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
- },
- "id": "a4974834-f404-4fb8-af94-a24c6db56bb8"
- }
- ]
- },
- {
- "id": 24,
- "title": "Tiertherapie Patrizia Feller",
- "slug": "tiertherapie-patrizia-feller",
- "type": "learnpath.LearningContent",
- "translation_key": "13f6d661-1d10-4b59-b8e5-01fcec47a38f",
- "minutes": 120,
- "contents": [
- {
- "type": "exercise",
- "value": {
- "description": "Beispiel Aufgabe"
- },
- "id": "5947c947-8656-44b5-826c-1787057c2df2"
- }
- ]
- },
- {
- "id": 25,
- "title": "Auto verkaufen",
- "slug": "auto-verkaufen",
- "type": "learnpath.LearningUnit",
- "translation_key": "3b42e514-0bbe-4c23-9c88-3f5263e47cf9",
- "questions": []
- },
- ],
- "description": "Nach dem Gespräch werten sie die Analyse aus...",
- "job_situations": [],
- "goals": [],
- "experts": []
- } as WagtailCircle;
-
- const learningSequences = parseLearningSequences(input.children);
- expect(learningSequences.length).toBe(1);
- console.log(learningSequences[0].learningUnits[0].learningContents[0]);
-
- expect(
- learningSequences[0].learningUnits[1].learningContents[0].previousLearningContent.translation_key
- ).toEqual(learningSequences[0].learningUnits[0].learningContents[1].translation_key);
+describe('Circle.parseJson', () => {
+ it('can parse circle from api response', () => {
+ const cirleData = data.children.find((c) => c.slug === 'unit-test-circle')
+ const circle = Circle.fromJson(cirleData, undefined)
+ expect(circle.learningSequences.length).toBe(3)
+ expect(circle.flatLearningContents.length).toBe(8)
})
})
diff --git a/client/src/services/__tests__/learningPath.spec.ts b/client/src/services/__tests__/learningPath.spec.ts
new file mode 100644
index 00000000..a2a32dfe
--- /dev/null
+++ b/client/src/services/__tests__/learningPath.spec.ts
@@ -0,0 +1,15 @@
+import { describe, it } from 'vitest'
+import data from './learning_path_json.json'
+import { LearningPath } from '../learningPath'
+
+describe('LearningPath.parseJson', () => {
+ it('can parse learning sequences from api response', () => {
+ const learningPath = LearningPath.fromJson(data, [])
+
+ expect(learningPath.circles.length).toBe(2)
+ expect(learningPath.circles[0].title).toBe('Basis')
+ expect(learningPath.circles[1].title).toBe('Unit-Test Circle')
+
+ expect(learningPath.topics.length).toBe(2)
+ })
+})
diff --git a/client/src/services/__tests__/learning_path_json.json b/client/src/services/__tests__/learning_path_json.json
new file mode 100644
index 00000000..e583623e
--- /dev/null
+++ b/client/src/services/__tests__/learning_path_json.json
@@ -0,0 +1,356 @@
+{
+ "id": 409,
+ "title": "Unit-Test Lernpfad",
+ "slug": "unit-test-lernpfad",
+ "type": "learnpath.LearningPath",
+ "translation_key": "9f50de84-036c-4986-ab3e-1a83a374910a",
+ "children": [
+ {
+ "id": 410,
+ "title": "Basis",
+ "slug": "basis-1",
+ "type": "learnpath.Topic",
+ "translation_key": "fbc1431c-46b0-4f77-93ee-4f10e0e59c03",
+ "is_visible": false
+ },
+ {
+ "id": 411,
+ "title": "Basis",
+ "slug": "basis-2",
+ "type": "learnpath.Circle",
+ "translation_key": "d30cb8f8-6bb5-4e7a-8123-a370b7668a85",
+ "children": [
+ {
+ "id": 412,
+ "title": "Starten",
+ "slug": "starten",
+ "type": "learnpath.LearningSequence",
+ "translation_key": "1c5cd2a1-a39e-496e-b856-342f34d2b21c",
+ "icon": "it-icon-ls-start"
+ },
+ {
+ "id": 413,
+ "title": "Einleitung Circle \"Basis\"",
+ "slug": "einleitung-circle-basis-1",
+ "type": "learnpath.LearningContent",
+ "translation_key": "48d4ace9-b0cf-4e23-98d2-012c1b91100e",
+ "minutes": 15,
+ "contents": [
+ {
+ "type": "video",
+ "value": {
+ "description": "Basis Video",
+ "url": "https://www.youtube.com/embed/qhPIfxS2hvI"
+ },
+ "id": "ee431ded-edc4-4984-9dd8-ab1d869d82ae"
+ }
+ ]
+ },
+ {
+ "id": 414,
+ "title": "Beenden",
+ "slug": "beenden",
+ "type": "learnpath.LearningSequence",
+ "translation_key": "eaeaf0c7-b2b7-41a9-a77f-b392f83291eb",
+ "icon": "it-icon-ls-end"
+ },
+ {
+ "id": 415,
+ "title": "Kompetenzprofil anschauen",
+ "slug": "kompetenzprofil-anschauen-8",
+ "type": "learnpath.LearningContent",
+ "translation_key": "784772fc-d2ac-4df2-8ca1-61a45fbfe001",
+ "minutes": 30,
+ "contents": [
+ {
+ "type": "document",
+ "value": {
+ "description": "Beispiel Kompetenz"
+ },
+ "id": "09acb23d-cb20-4d0f-963b-61db9ac0b037"
+ }
+ ]
+ },
+ {
+ "id": 416,
+ "title": "Circle \"Analyse\" abschliessen",
+ "slug": "circle-analyse-abschliessen-8",
+ "type": "learnpath.LearningContent",
+ "translation_key": "e1bf9081-cf6b-4426-a16d-8213aba9795e",
+ "minutes": 30,
+ "contents": [
+ {
+ "type": "document",
+ "value": {
+ "description": "Beispiel Kompetenz"
+ },
+ "id": "fa835da9-6238-40fb-a718-2d21d420926f"
+ }
+ ]
+ }
+ ],
+ "description": "Basis von Unit-Test Lernpfad",
+ "job_situations": [],
+ "goals": [],
+ "experts": []
+ },
+ {
+ "id": 417,
+ "title": "Gewinnen von Kunden",
+ "slug": "gewinnen-von-kunden-1",
+ "type": "learnpath.Topic",
+ "translation_key": "4b2aa669-4cd9-43f1-9605-8575e5e7e760",
+ "is_visible": true
+ },
+ {
+ "id": 418,
+ "title": "Unit-Test Circle",
+ "slug": "unit-test-circle",
+ "type": "learnpath.Circle",
+ "translation_key": "8433f8fe-7074-4c8a-a93a-b62e042f06ca",
+ "children": [
+ {
+ "id": 419,
+ "title": "Starten",
+ "slug": "starten",
+ "type": "learnpath.LearningSequence",
+ "translation_key": "065ab931-122a-4e4d-a570-f8e6352a0550",
+ "icon": "it-icon-ls-start"
+ },
+ {
+ "id": 420,
+ "title": "Einleitung Circle \"Unit-Test Circle\"",
+ "slug": "einleitung-circle-unit-test-circle",
+ "type": "learnpath.LearningContent",
+ "translation_key": "ec97ed44-a2ee-46b4-b6ba-3cce4c6f627e",
+ "minutes": 15,
+ "contents": [
+ {
+ "type": "video",
+ "value": {
+ "description": "In dieser Circle zeigt dir ein Fachexperte anhand von Kundensituationen, wie du erfolgreichden Kundenbedarf ermitteln, analysieren, priorisieren und anschliessend zusammenfassen kannst.",
+ "url": "https://www.youtube.com/embed/qhPIfxS2hvI"
+ },
+ "id": "01ed1388-e82f-49a4-aafc-2d24891ec64a"
+ }
+ ]
+ },
+ {
+ "id": 421,
+ "title": "Beobachten",
+ "slug": "beobachten",
+ "type": "learnpath.LearningSequence",
+ "translation_key": "8fed5f78-2d39-4a78-9dfc-f65551a81a7b",
+ "icon": "it-icon-ls-watch"
+ },
+ {
+ "id": 422,
+ "title": "Absicherung der Familie",
+ "slug": "absicherung-der-familie",
+ "type": "learnpath.LearningUnit",
+ "translation_key": "fe50e509-b679-40f8-bddf-844c473e1e8a",
+ "children": [
+ {
+ "id": 423,
+ "title": "Ich bin in der Lage, mit geeigneten Fragestellungen die Deckung von Versicherungen zu erfassen.",
+ "slug": "ich-bin-in-der-lage-mit-geeigneten-fragestellungen-die-deckung-von-versicherungen-zu-erfassen",
+ "type": "learnpath.LearningUnitQuestion",
+ "translation_key": "7a1631e9-56b2-48fd-b9ff-1eafba9f96da"
+ },
+ {
+ "id": 424,
+ "title": "Zweite passende Frage zu 'Absicherung der Familie'",
+ "slug": "zweite-passende-frage-zu-absicherung-der-familie",
+ "type": "learnpath.LearningUnitQuestion",
+ "translation_key": "f5aea045-f428-4b06-8b51-1857626250a8"
+ }
+ ]
+ },
+ {
+ "id": 425,
+ "title": "Ermittlung des Kundenbedarfs",
+ "slug": "ermittlung-des-kundenbedarfs-14",
+ "type": "learnpath.LearningContent",
+ "translation_key": "ffd613f5-830c-4bc0-860b-fc194e2d7d1c",
+ "minutes": 30,
+ "contents": [
+ {
+ "type": "podcast",
+ "value": {
+ "description": "Die Ermittlung des Kundenbedarfs muss in einem eingehenden Gespr\u00e4ch herausgefunden werden. H\u00f6re dazu auch diesen Podcast an.",
+ "url": "https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/325190984&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true"
+ },
+ "id": "642c0906-3bd3-4030-be3f-8b1acce08930"
+ }
+ ]
+ },
+ {
+ "id": 426,
+ "title": "Kundenbed\u00fcrfnisse erkennen",
+ "slug": "kundenbed\u00fcrfnisse-erkennen-7",
+ "type": "learnpath.LearningContent",
+ "translation_key": "b36bd615-053c-4054-a5be-080005140a98",
+ "minutes": 30,
+ "contents": [
+ {
+ "type": "competence",
+ "value": {
+ "description": "Beispiel Kompetenz"
+ },
+ "id": "6b85361b-cc27-4454-aa20-72b31ad92a3f"
+ }
+ ]
+ },
+ {
+ "id": 427,
+ "title": "Was braucht eine Familie?",
+ "slug": "was-braucht-eine-familie-7",
+ "type": "learnpath.LearningContent",
+ "translation_key": "b4d2ec6c-12b3-48bc-b159-f5b9e06637cf",
+ "minutes": 60,
+ "contents": [
+ {
+ "type": "exercise",
+ "value": {
+ "description": "Beispiel Aufgabe",
+ "url": "/media/web_based_trainings/story-01-a-01-patrizia-marco-sichern-sich-ab-einstieg/scormcontent/index.html"
+ },
+ "id": "b7e661b1-9e39-4482-8b23-c24dad1ef648"
+ }
+ ]
+ },
+ {
+ "id": 428,
+ "title": "Reisen",
+ "slug": "reisen",
+ "type": "learnpath.LearningUnit",
+ "translation_key": "07a52671-a50e-46fc-b685-947aadb3e4d4",
+ "children": [
+ {
+ "id": 429,
+ "title": "Passende Frage zu \"Reisen\"",
+ "slug": "passende-frage-zu-reisen",
+ "type": "learnpath.LearningUnitQuestion",
+ "translation_key": "00491fd6-f1f5-4a52-b13d-0197bc875296"
+ }
+ ]
+ },
+ {
+ "id": 430,
+ "title": "Reiseversicherung",
+ "slug": "reiseversicherung-7",
+ "type": "learnpath.LearningContent",
+ "translation_key": "08dacac1-1853-4e07-8a6d-b7e2ee610398",
+ "minutes": 240,
+ "contents": [
+ {
+ "type": "competence",
+ "value": {
+ "description": "Beispiel Kompetenz"
+ },
+ "id": "6532e206-8737-45d9-9c2a-3ad44c372449"
+ }
+ ]
+ },
+ {
+ "id": 431,
+ "title": "Sorgenfrei reisen",
+ "slug": "sorgenfrei-reisen-7",
+ "type": "learnpath.LearningContent",
+ "translation_key": "d8e9ec02-cae6-4494-91e0-707591456afb",
+ "minutes": 120,
+ "contents": [
+ {
+ "type": "exercise",
+ "value": {
+ "description": "Beispiel Aufgabe",
+ "url": "/media/web_based_trainings/story-06-a-01-emma-und-ayla-campen-durch-amerika-einstieg/scormcontent/index.html"
+ },
+ "id": "87333833-ad07-4c86-a846-46232668e8e1"
+ }
+ ]
+ },
+ {
+ "id": 432,
+ "title": "Beenden",
+ "slug": "beenden",
+ "type": "learnpath.LearningSequence",
+ "translation_key": "a3ee459e-ab98-483f-95c9-ba85eb10c105",
+ "icon": "it-icon-ls-end"
+ },
+ {
+ "id": 433,
+ "title": "Kompetenzprofil anschauen",
+ "slug": "kompetenzprofil-anschauen-9",
+ "type": "learnpath.LearningContent",
+ "translation_key": "0e16dd46-14cf-43ac-888f-f03beded7fa1",
+ "minutes": 30,
+ "contents": [
+ {
+ "type": "document",
+ "value": {
+ "description": "Beispiel Kompetenz"
+ },
+ "id": "4b729c72-aee8-4944-b5fb-d0bfd317a339"
+ }
+ ]
+ },
+ {
+ "id": 434,
+ "title": "Circle \"Analyse\" abschliessen",
+ "slug": "circle-analyse-abschliessen-9",
+ "type": "learnpath.LearningContent",
+ "translation_key": "53703784-c71f-4bad-a3e7-e014f0fded12",
+ "minutes": 30,
+ "contents": [
+ {
+ "type": "document",
+ "value": {
+ "description": "Beispiel Kompetenz"
+ },
+ "id": "8bc53dfd-bb9b-4ae5-bd3c-74b7eef0eafd"
+ }
+ ]
+ }
+ ],
+ "description": "Unit-Test Circle",
+ "job_situations": [
+ {
+ "type": "job_situation",
+ "value": "Absicherung der Familie",
+ "id": "f715a46f-53df-4205-8257-30cff62f337c"
+ },
+ {
+ "type": "job_situation",
+ "value": "Reisen",
+ "id": "f2174789-eab4-4059-961d-699b3c333110"
+ }
+ ],
+ "goals": [
+ {
+ "type": "goal",
+ "value": "... die heutige Versicherungssituation von Privat- oder Gesch\u00e4ftskunden einzusch\u00e4tzen.",
+ "id": "41acaebc-38de-4929-a4af-aaed43a1e5f3"
+ },
+ {
+ "type": "goal",
+ "value": "... deinem Kunden seine optimale L\u00f6sung aufzuzeigen",
+ "id": "cb1d556b-dac1-4edc-a3e5-97307b49c55c"
+ }
+ ],
+ "experts": [
+ {
+ "type": "person",
+ "value": {
+ "first_name": "Patrizia",
+ "last_name": "Huggel",
+ "email": "patrizia.huggel@example.com",
+ "photo": null,
+ "biography": ""
+ },
+ "id": "479878e7-2d30-46a4-8d6b-bfe77268bbae"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/client/src/services/__tests__/request_learning_path_json.py b/client/src/services/__tests__/request_learning_path_json.py
new file mode 100644
index 00000000..20088cb7
--- /dev/null
+++ b/client/src/services/__tests__/request_learning_path_json.py
@@ -0,0 +1,29 @@
+import json
+
+import requests
+
+
+def main():
+ client = requests.session()
+ client.get('http://localhost:8000/')
+
+ client.post(
+ 'http://localhost:8000/core/login/',
+ json={
+ 'username': 'admin',
+ 'password': 'test',
+ }
+ )
+
+ response = client.get(
+ 'http://localhost:8000/learnpath/api/page/unit-test-lernpfad/',
+ )
+ print(response.status_code)
+ print(response.json())
+
+ with open('learning_path_json.json', 'w') as f:
+ f.write(json.dumps(response.json(), indent=4))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/client/src/services/circle.ts b/client/src/services/circle.ts
index e467b1b9..87c0e7c4 100644
--- a/client/src/services/circle.ts
+++ b/client/src/services/circle.ts
@@ -6,9 +6,10 @@ import type {
LearningContent,
LearningSequence,
LearningUnit,
- LearningWagtailPage
-} from '@/types';
-
+ LearningUnitQuestion,
+ LearningWagtailPage,
+} from '@/types'
+import type { LearningPath } from '@/services/learningPath'
function _createEmptyLearningUnit(parentLearningSequence: LearningSequence): LearningUnit {
return {
@@ -22,10 +23,10 @@ function _createEmptyLearningUnit(parentLearningSequence: LearningSequence): Lea
parentLearningSequence: parentLearningSequence,
children: [],
last: true,
- };
+ }
}
-export function parseLearningSequences (children: CircleChild[]): LearningSequence[] {
+export function parseLearningSequences (circle: Circle, children: CircleChild[]): LearningSequence[] {
let learningSequence:LearningSequence | undefined;
let learningUnit:LearningUnit | undefined;
let learningContent:LearningContent | undefined;
@@ -70,6 +71,7 @@ export function parseLearningSequences (children: CircleChild[]): LearningSequen
previousLearningContent = learningContent;
learningContent = Object.assign(child, {
+ parentCircle: circle,
parentLearningSequence: learningSequence,
parentLearningUnit: learningUnit,
previousLearningContent: previousLearningContent,
@@ -112,6 +114,9 @@ export class Circle implements LearningWagtailPage {
readonly learningSequences: LearningSequence[];
readonly completed: boolean;
+ nextCircle?: Circle;
+ previousCircle?: Circle;
+
constructor(
public readonly id: number,
public readonly slug: string,
@@ -121,12 +126,13 @@ export class Circle implements LearningWagtailPage {
public children: CircleChild[],
public goals: CircleGoal[],
public job_situations: CircleJobSituation[],
+ public readonly parentLearningPath?: LearningPath,
) {
- this.learningSequences = parseLearningSequences(this.children);
+ this.learningSequences = parseLearningSequences(this, this.children);
this.completed = false;
}
- public static fromJson(json: any): Circle {
+ public static fromJson(json: any, learningPath?: LearningPath): Circle {
// TODO add error checking when the data does not conform to the schema
return new Circle(
json.id,
@@ -137,11 +143,12 @@ export class Circle implements LearningWagtailPage {
json.children,
json.goals,
json.job_situations,
+ learningPath,
)
}
- public get flatChildren(): CircleChild[] {
- const result: CircleChild[] = [];
+ public get flatChildren(): (LearningContent | LearningUnitQuestion)[] {
+ const result: (LearningContent | LearningUnitQuestion)[] = [];
this.learningSequences.forEach((learningSequence) => {
learningSequence.learningUnits.forEach((learningUnit) => {
learningUnit.children.forEach((learningUnitQuestion) => {
@@ -155,6 +162,18 @@ export class Circle implements LearningWagtailPage {
return result;
}
+ public get flatLearningContents(): LearningContent[] {
+ const result: LearningContent[] = [];
+ this.learningSequences.forEach((learningSequence) => {
+ learningSequence.learningUnits.forEach((learningUnit) => {
+ learningUnit.learningContents.forEach((learningContent) => {
+ result.push(learningContent);
+ });
+ });
+ });
+ return result;
+ }
+
public someFinishedInLearningSequence(translationKey: string): boolean {
if (translationKey) {
return this.flatChildren.filter((lc) => {
@@ -191,5 +210,9 @@ export class Circle implements LearningWagtailPage {
page.completed = undefined;
}
});
+
+ if (this.parentLearningPath) {
+ this.parentLearningPath.calcNextLearningContent(completionData);
+ }
}
}
diff --git a/client/src/services/learningPath.ts b/client/src/services/learningPath.ts
new file mode 100644
index 00000000..c3b38fa4
--- /dev/null
+++ b/client/src/services/learningPath.ts
@@ -0,0 +1,92 @@
+import * as _ from 'lodash'
+
+import type { CircleCompletion, LearningContent, LearningPathChild, LearningWagtailPage, Topic } from '@/types'
+import { Circle } from '@/services/circle'
+
+function getLastCompleted(learningPathKey: string, completionData: CircleCompletion[]) {
+ return _.orderBy(completionData, ['updated_at'], 'desc').find((c: CircleCompletion) => {
+ return c.completed && c.learning_path_key === learningPathKey && c.page_type === 'learnpath.LearningContent'
+ })
+}
+
+export class LearningPath implements LearningWagtailPage {
+ readonly type = 'learnpath.LearningPath'
+ public topics: Topic[]
+ public circles: Circle[]
+ public nextLearningContent?: LearningContent
+
+ public static fromJson(json: any, completionData: CircleCompletion[]): LearningPath {
+ return new LearningPath(json.id, json.slug, json.title, json.translation_key, json.children, completionData)
+ }
+
+ constructor(
+ public readonly id: number,
+ public readonly slug: string,
+ public readonly title: string,
+ public readonly translation_key: string,
+ public children: LearningPathChild[],
+ completionData?: any
+ ) {
+ // parse children
+ this.topics = []
+ this.circles = []
+
+ let topic: Topic | undefined
+
+ this.children.forEach((page) => {
+ if (page.type === 'learnpath.Topic') {
+ if (topic) {
+ this.topics.push(topic)
+ }
+ topic = Object.assign(page, { circles: [] })
+ }
+ if (page.type === 'learnpath.Circle') {
+ const circle = Circle.fromJson(page, this)
+ circle.parseCompletionData(completionData)
+ if (topic) {
+ topic.circles.push(circle)
+ }
+
+ circle.previousCircle = this.circles[this.circles.length - 1]
+ if (circle.previousCircle) {
+ circle.previousCircle.nextCircle = circle
+ }
+ this.circles.push(circle)
+ }
+ })
+
+ if (topic) {
+ this.topics.push(topic)
+ }
+
+ this.calcNextLearningContent(completionData)
+ }
+
+ public calcNextLearningContent(completionData: CircleCompletion[]): void {
+ this.nextLearningContent = undefined
+
+ const lastCompletedLearningContent = getLastCompleted(this.translation_key, completionData)
+
+ if (lastCompletedLearningContent) {
+ const lastCircle = this.circles.find(
+ (circle) => circle.translation_key === lastCompletedLearningContent.circle_key
+ )
+ if (lastCircle) {
+ const lastLearningContent = lastCircle.flatLearningContents.find(
+ (learningContent) => learningContent.translation_key === lastCompletedLearningContent.page_key
+ )
+ if (lastLearningContent && lastLearningContent.nextLearningContent) {
+ this.nextLearningContent = lastLearningContent.nextLearningContent
+ } else {
+ if (lastCircle.nextCircle) {
+ this.nextLearningContent = lastCircle.nextCircle.flatLearningContents[0]
+ }
+ }
+ }
+ } else {
+ if (this.circles[0]) {
+ this.nextLearningContent = this.circles[0].flatLearningContents[0]
+ }
+ }
+ }
+}
diff --git a/client/src/stores/circle.ts b/client/src/stores/circle.ts
index 023b689c..f729b2c9 100644
--- a/client/src/stores/circle.ts
+++ b/client/src/stores/circle.ts
@@ -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');
- 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
+ const learningPathStore = useLearningPathStore();
+ await learningPathStore.loadLearningPath(learningPathSlug);
+ if (learningPathStore.learningPath) {
+ this.circle = learningPathStore.learningPath.circles.find(circle => circle.slug === circleSlug);
}
+
+ if (!this.circle) {
+ throw `No circle found with slug: ${circleSlug}`;
+ }
+
+ return this.circle
},
async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) {
try {
diff --git a/client/src/stores/learningPath.ts b/client/src/stores/learningPath.ts
index 0f3c5668..ec3930c2 100644
--- a/client/src/stores/learningPath.ts
+++ b/client/src/stores/learningPath.ts
@@ -1,66 +1,16 @@
-import * as log from 'loglevel';
-
-import {defineStore} from 'pinia'
-import * as _ from 'lodash';
-
-import type {LearningPath, Topic} from '@/types'
-import {itGet} from '@/fetchHelpers';
-import {Circle} from '@/services/circle';
-import learningPathDiagram from "@/components/circle/LearningPathDiagram.vue";
+import { defineStore } from 'pinia'
+import { itGet } from '@/fetchHelpers'
+import { LearningPath } from '@/services/learningPath'
export type LearningPathStoreState = {
- learningPath: LearningPath | undefined;
+ learningPath: LearningPath | undefined
}
-
-function getLastCompleted(completionData: any) {
- return _.filter(_.orderBy(completionData, ['updated_at'], 'desc'), c =>{return c.completed && c.page_type === "learnpath.LearningContent" })[0]
-}
-
-
-function getFirstLearningContent(lastCopleted, learningPathData) {
- const circles = _.filter(learningPathData.children, {'type': 'learnpath.Circle'})
-
- let currentCircle = Circle.fromJson(circles[0])
- const currentLearningUnit = currentCircle.flatChildren[0]
- let currentLearningSequence = currentLearningUnit.parentLearningSequence
- return [currentCircle, currentLearningSequence, currentLearningUnit]
-}
-
-function getNextLearningContent(lastCopleted, learningPathData) {
-
- let currentCircle, currentLearningSequence, currentLearningUnit
-
- currentLearningUnit = getFirstLearningContent(lastCopleted, learningPathData)
-
- if (lastCopleted) {
- const circles = _.filter(learningPathData.children, {'type': 'learnpath.Circle'})
- _.forEach(circles, circle => {
- _.forEach(Circle.fromJson(circle).learningSequences, learningSequence => {
- _.forEach(learningSequence.learningUnits, learningUnit => {
- _.forEach(learningUnit.learningContents, content => {
- if (lastCopleted.page_key === content.translation_key) {
- currentCircle = Circle.fromJson(circle)
- currentLearningSequence = learningSequence
- currentLearningUnit = content
-
- }
- })
- })
- })
- })
- currentLearningUnit = [currentCircle, currentLearningSequence, currentLearningUnit]
- }
- return currentLearningUnit
-}
-
-
export const useLearningPathStore = defineStore({
id: 'learningPath',
state: () => {
return {
learningPath: undefined,
-
} as LearningPathStoreState;
},
getters: {},
@@ -69,52 +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}/`);
+ const learningPathData = await itGet(`/learnpath/api/page/${slug}/`);
+ const completionData = await itGet(`/api/completion/learning_path/${learningPathData.translation_key}/`);
- this.learningPath = learningPathData;
-
-
- if (this.learningPath) {
- this.learningPath.lastCompleted = getLastCompleted(completionData)
- const nextLearningContent = getNextLearningContent(this.learningPath.lastCompleted, learningPathData)
-
- console.log('nextLearningContent', nextLearningContent)
- this.learningPath.nextCircle = nextLearningContent[0]
- this.learningPath.nextLearningSequence = nextLearningContent[1]
- this.learningPath.nextLearningUnit = nextLearningContent[2]
-
-
- this.learningPath.topics = [];
- this.learningPath.circles = [];
-
- let topic: Topic | undefined;
-
- this.learningPath.children.forEach((page) => {
- if (page.type === 'learnpath.Topic') {
- if (topic) {
- this.learningPath.topics.push(topic);
- }
- topic = Object.assign(page, {circles: []});
- }
- if (page.type === 'learnpath.Circle') {
- const circle = Circle.fromJson(page);
- circle.parseCompletionData(completionData);
- if (topic) {
- topic.circles.push(circle);
- }
- this.learningPath.circles.push(circle);
- }
-
- })
- this.learningPath.topics.push(topic);
- }
- return this.learningPath;
- } catch (error) {
- log.error(error);
- return error
+ if (!learningPathData) {
+ throw `No learning path found with: ${slug}`;
}
+
+ this.learningPath = LearningPath.fromJson(learningPathData, completionData);
+ return this.learningPath;
},
}
})
diff --git a/client/src/types.ts b/client/src/types.ts
index 3a8ff19b..420c1984 100644
--- a/client/src/types.ts
+++ b/client/src/types.ts
@@ -1,11 +1,11 @@
-import type {Circle} from '@/services/circle';
+import type { Circle } from '@/services/circle'
export interface LearningContentBlock {
- type: 'web-based-training' | 'competence' | 'exercise' | 'knowledge';
+ type: 'web-based-training' | 'competence' | 'exercise' | 'knowledge'
value: {
- description: string;
- },
- id: string;
+ description: string
+ }
+ id: string
}
export interface VideoBlock {
@@ -59,6 +59,7 @@ export interface LearningContent extends LearningWagtailPage {
type: 'learnpath.LearningContent';
minutes: number;
contents: (LearningContentBlock | VideoBlock | PodcastBlock | DocumentBlock)[];
+ parentCircle: Circle;
parentLearningSequence?: LearningSequence;
parentLearningUnit?: LearningUnit;
nextLearningContent?: LearningContent;
@@ -103,18 +104,6 @@ export interface Topic extends LearningWagtailPage {
export type LearningPathChild = Topic | WagtailCircle;
-export interface LearningPath extends LearningWagtailPage {
- type: 'learnpath.LearningPath';
- children: LearningPathChild[];
- topics: Topic[];
- circles: Circle[];
- lastCompleted: CircleCompletion;
- nextCircle: Circle;
- nextLearningSequence: LearningSequence;
- nextLearningUnit: LearningContent;
-
-}
-
export interface CircleCompletion {
id: number;
created_at: string;
@@ -123,6 +112,7 @@ export interface CircleCompletion {
page_key: string;
page_type: string;
circle_key: string;
+ learning_path_key: string;
completed: boolean;
json_data: any;
}
diff --git a/client/src/views/CircleView.vue b/client/src/views/CircleView.vue
index f97e0390..358301c0 100644
--- a/client/src/views/CircleView.vue
+++ b/client/src/views/CircleView.vue
@@ -1,27 +1,32 @@
@@ -35,10 +40,10 @@ onMounted(async () => {
-
+
-
+
@@ -46,8 +51,9 @@ onMounted(async () => {
zurück zum Lernpfad
@@ -62,15 +68,12 @@ onMounted(async () => {
-
-
+
+
Das lernst du in diesem Circle
-
+
Fachexpertin kontaktieren
@@ -82,16 +85,15 @@ onMounted(async () => {
{{ circleStore.circle?.description }}
-
Hast du Fragen?
Tausche dich mit der Fachexpertin aus für den Circle Analyse aus.
-
- Fachexpertin kontaktieren
-
+
Fachexpertin kontaktieren
@@ -101,14 +103,10 @@ onMounted(async () => {
v-for="learningSequence in circleStore.circle?.learningSequences || []"
:key="learningSequence.translation_key"
>
-
+