Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
ad9c495c9c
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import {computed, onMounted} from "vue";
|
import {computed} from "vue";
|
||||||
import * as _ from 'underscore'
|
import * as _ from 'underscore'
|
||||||
|
import {useCircleStore} from '@/stores/circle';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
circleStore: {},
|
|
||||||
width: {
|
width: {
|
||||||
default: 500,
|
default: 500,
|
||||||
type: number,
|
type: number,
|
||||||
|
|
@ -18,28 +18,26 @@ const props = defineProps<{
|
||||||
},
|
},
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const circleStore = useCircleStore();
|
||||||
|
|
||||||
function someFinished(learningSequence) {
|
function someFinished(learningSequence) {
|
||||||
return props.circleStore.flatChildren.filter((lc) => {
|
if (circleStore.circle) {
|
||||||
return lc.completed && lc.parentLearningSequence?.translation_key === learningSequence.translation_key;
|
return circleStore.circle.someFinishedInLearningSequence(learningSequence.translation_key);
|
||||||
}).length > 0;
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const pieData = computed(() => {
|
const pieData = computed(() => {
|
||||||
const circle = props.circleStore.circleData, completionData = props.circleStore.completionData
|
const circle = circleStore.circle
|
||||||
console.log('initial of compute pie data ', circle, completionData)
|
console.log('initial of compute pie data ', circle)
|
||||||
|
|
||||||
if (circle && completionData) {
|
if (circle) {
|
||||||
console.log('initial of compute pie data ', circle, completionData)
|
console.log('initial of compute pie data ', circle)
|
||||||
let learningSequences = _.filter(circle.children, (child) => {
|
let learningSequences = _.filter(circle.children, (child) => {
|
||||||
return child.type === 'learnpath.LearningSequence';
|
return child.type === 'learnpath.LearningSequence';
|
||||||
})
|
})
|
||||||
|
|
||||||
const completionDataByPageId = _.object(_.map(completionData, function (item) {
|
|
||||||
return [item.page_key, item]
|
|
||||||
}))
|
|
||||||
|
|
||||||
let pieWeights = new Array(Math.max(learningSequences.length, 1)).fill(1)
|
let pieWeights = new Array(Math.max(learningSequences.length, 1)).fill(1)
|
||||||
let pieGenerator = d3.pie()
|
let pieGenerator = d3.pie()
|
||||||
let angles = pieGenerator(pieWeights)
|
let angles = pieGenerator(pieWeights)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {Circle} from '@/types';
|
import {Circle} from '@/services/circle';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
circleData: Circle
|
circle: Circle
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -17,7 +17,7 @@ const props = defineProps<{
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<h1 class="">Überblick: Circle "{{ circleData.title }}"</h1>
|
<h1 class="">Überblick: Circle "{{ circle.title }}"</h1>
|
||||||
|
|
||||||
<p class="mt-8 text-xl">Hier zeigen wir dir, was du in diesem Circle lernen wirst.</p>
|
<p class="mt-8 text-xl">Hier zeigen wir dir, was du in diesem Circle lernen wirst.</p>
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ const props = defineProps<{
|
||||||
<h3>Du wirst in der Lage sein, ... </h3>
|
<h3>Du wirst in der Lage sein, ... </h3>
|
||||||
|
|
||||||
<ul class="mt-4">
|
<ul class="mt-4">
|
||||||
<li class="text-xl flex items-center" v-for="goal in circleData.goals" :key="goal.id">
|
<li class="text-xl flex items-center" v-for="goal in circle.goals" :key="goal.id">
|
||||||
<it-icon-check class="mt-4 hidden lg:block w-12 h-12 text-sky-500 flex-none"></it-icon-check>
|
<it-icon-check class="mt-4 hidden lg:block w-12 h-12 text-sky-500 flex-none"></it-icon-check>
|
||||||
<div class="mt-4">{{ goal.value }}</div>
|
<div class="mt-4">{{ goal.value }}</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -38,7 +38,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
<ul class="grid grid-cols-1 lg:grid-cols-3 auto-rows-fr gap-6 mt-8">
|
<ul class="grid grid-cols-1 lg:grid-cols-3 auto-rows-fr gap-6 mt-8">
|
||||||
<li
|
<li
|
||||||
v-for="jobSituation in circleData.job_situations"
|
v-for="jobSituation in circle.job_situations"
|
||||||
:key="jobSituation.id"
|
:key="jobSituation.id"
|
||||||
class="job-situation border border-gray-500 p-4 text-xl flex items-center"
|
class="job-situation border border-gray-500 p-4 text-xl flex items-center"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import * as d3 from 'd3'
|
import * as d3 from 'd3';
|
||||||
import * as _ from 'underscore'
|
import { useLearningPathStore } from '../../stores/learningPath';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -51,23 +51,32 @@ export default {
|
||||||
type: Number,
|
type: Number,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const learningPathStore = useLearningPathStore()
|
||||||
|
|
||||||
|
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() {
|
||||||
|
if (this.learningPathStore.learningPath) {
|
||||||
let internalCircles = _.flatten(_.pluck(this.learningPathContents.topics, 'circles'))
|
let internalCircles = this.learningPathStore.learningPath.topics.flatMap(topic => topic.circles);
|
||||||
_.forEach(internalCircles, function (circle) {
|
// console.log(internalCircles);
|
||||||
let pieWeights = new Array(Math.max(circle.learning_sequences.length, 1)).fill(1)
|
internalCircles.forEach((circle) => {
|
||||||
let pieGenerator = d3.pie()
|
let pieWeights = new Array(Math.max(circle.learningSequences.length, 1)).fill(1)
|
||||||
let pieData = pieGenerator(pieWeights)
|
let pieGenerator = d3.pie()
|
||||||
_.forEach(pieData, function (pie) {
|
let pieData = pieGenerator(pieWeights)
|
||||||
pie.done = circle.learning_sequences.length === 0 ? false : circle.learning_sequences[parseInt(pie.index)].done
|
pieData.forEach((pie) => {
|
||||||
})
|
const lp = circle.learningSequences[parseInt(pie.index)];
|
||||||
circle.pieData = pieData
|
pie.done = circle.someFinishedInLearningSequence(lp.translation_key);
|
||||||
})
|
});
|
||||||
return internalCircles
|
circle.pieData = pieData
|
||||||
|
});
|
||||||
|
return internalCircles
|
||||||
|
}
|
||||||
|
return [];
|
||||||
},
|
},
|
||||||
svg() {
|
svg() {
|
||||||
return d3.select('.learning-path-visualization')
|
return d3.select('.learning-path-visualization')
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,8 @@ function toggleCompleted(learningContent: LearningContent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const someFinished = computed(() => {
|
const someFinished = computed(() => {
|
||||||
if (props.learningSequence) {
|
if (props.learningSequence && circleStore.circle) {
|
||||||
return circleStore.flatChildren.filter((lc) => {
|
return circleStore.circle.someFinishedInLearningSequence(props.learningSequence.translation_key);
|
||||||
return lc.completed && lc.parentLearningSequence?.translation_key === props.learningSequence.translation_key;
|
|
||||||
}).length > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
import type {CircleChild, LearningContent, LearningSequence, LearningUnit} from '@/types';
|
import type {
|
||||||
|
CircleChild,
|
||||||
|
CircleCompletion,
|
||||||
|
CircleGoal,
|
||||||
|
CircleJobSituation,
|
||||||
|
LearningContent,
|
||||||
|
LearningSequence,
|
||||||
|
LearningUnit,
|
||||||
|
LearningWagtailPage
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
|
||||||
function _createEmptyLearningUnit(parentLearningSequence: LearningSequence): LearningUnit {
|
function _createEmptyLearningUnit(parentLearningSequence: LearningSequence): LearningUnit {
|
||||||
|
|
@ -98,3 +107,74 @@ export function parseLearningSequences (children: CircleChild[]): LearningSequen
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Circle implements LearningWagtailPage {
|
||||||
|
readonly type = 'learnpath.Circle';
|
||||||
|
readonly learningSequences: LearningSequence[];
|
||||||
|
readonly completed: boolean;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly id: number,
|
||||||
|
public readonly slug: string,
|
||||||
|
public readonly title: string,
|
||||||
|
public readonly translation_key: string,
|
||||||
|
public description: string,
|
||||||
|
public children: CircleChild[],
|
||||||
|
public goals: CircleGoal[],
|
||||||
|
public job_situations: CircleJobSituation[],
|
||||||
|
) {
|
||||||
|
this.learningSequences = parseLearningSequences(this.children);
|
||||||
|
this.completed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromJson(json: any): Circle {
|
||||||
|
// TODO add error checking when the data does not conform to the schema
|
||||||
|
return new Circle(
|
||||||
|
json.id,
|
||||||
|
json.slug,
|
||||||
|
json.title,
|
||||||
|
json.translation_key,
|
||||||
|
json.description,
|
||||||
|
json.children,
|
||||||
|
json.goals,
|
||||||
|
json.job_situations,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get flatChildren(): CircleChild[] {
|
||||||
|
const result: CircleChild[] = [];
|
||||||
|
this.learningSequences.forEach((learningSequence) => {
|
||||||
|
learningSequence.learningUnits.forEach((learningUnit) => {
|
||||||
|
learningUnit.children.forEach((learningUnitQuestion) => {
|
||||||
|
result.push(learningUnitQuestion);
|
||||||
|
})
|
||||||
|
learningUnit.learningContents.forEach((learningContent) => {
|
||||||
|
result.push(learningContent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public someFinishedInLearningSequence(translationKey: string): boolean {
|
||||||
|
if (translationKey) {
|
||||||
|
return this.flatChildren.filter((lc) => {
|
||||||
|
return lc.completed && lc.parentLearningSequence?.translation_key === translationKey;
|
||||||
|
}).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseCompletionData(completionData: CircleCompletion[]) {
|
||||||
|
this.flatChildren.forEach((page) => {
|
||||||
|
const pageIndex = completionData.findIndex((e) => {
|
||||||
|
return e.page_key === page.translation_key;
|
||||||
|
});
|
||||||
|
if (pageIndex >= 0) {
|
||||||
|
page.completed = completionData[pageIndex].completed;
|
||||||
|
} else {
|
||||||
|
page.completed = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@ import * as log from 'loglevel';
|
||||||
|
|
||||||
import {defineStore} from 'pinia'
|
import {defineStore} from 'pinia'
|
||||||
|
|
||||||
import type {Circle, CircleChild, CircleCompletion, LearningContent, LearningUnit, LearningUnitQuestion} from '@/types'
|
import type {LearningContent, LearningUnit, LearningUnitQuestion} from '@/types'
|
||||||
|
import {Circle} from '@/services/circle'
|
||||||
import {itGet, itPost} from '@/fetchHelpers';
|
import {itGet, itPost} from '@/fetchHelpers';
|
||||||
import {parseLearningSequences} from '@/services/circle';
|
|
||||||
import {useAppStore} from '@/stores/app';
|
import {useAppStore} from '@/stores/app';
|
||||||
|
|
||||||
export type CircleStoreState = {
|
export type CircleStoreState = {
|
||||||
circleData: Circle;
|
circle: Circle | undefined;
|
||||||
completionData: CircleCompletion[];
|
|
||||||
currentLearningContent: LearningContent | undefined;
|
currentLearningContent: LearningContent | undefined;
|
||||||
currentSelfEvaluation: LearningUnit | undefined;
|
currentSelfEvaluation: LearningUnit | undefined;
|
||||||
page: 'INDEX' | 'OVERVIEW' | 'LEARNING_CONTENT' | 'SELF_EVALUATION';
|
page: 'INDEX' | 'OVERVIEW' | 'LEARNING_CONTENT' | 'SELF_EVALUATION';
|
||||||
|
|
@ -19,7 +18,7 @@ export const useCircleStore = defineStore({
|
||||||
id: 'circle',
|
id: 'circle',
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
circleData: {},
|
circle: undefined,
|
||||||
completionData: {},
|
completionData: {},
|
||||||
currentLearningContent: undefined,
|
currentLearningContent: undefined,
|
||||||
currentSelfEvaluation: undefined,
|
currentSelfEvaluation: undefined,
|
||||||
|
|
@ -28,27 +27,15 @@ export const useCircleStore = defineStore({
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
flatChildren: (state) => {
|
flatChildren: (state) => {
|
||||||
const result:CircleChild[] = [];
|
|
||||||
state.circleData.learningSequences.forEach((learningSequence) => {
|
|
||||||
learningSequence.learningUnits.forEach((learningUnit) => {
|
|
||||||
learningUnit.children.forEach((learningUnitQuestion) => {
|
|
||||||
result.push(learningUnitQuestion);
|
|
||||||
})
|
|
||||||
learningUnit.learningContents.forEach((learningContent) => {
|
|
||||||
result.push(learningContent);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async loadCircle(slug: string) {
|
async loadCircle(slug: string) {
|
||||||
try {
|
try {
|
||||||
this.circleData = await itGet(`/learnpath/api/circle/${slug}/`);
|
const circleData = await itGet(`/learnpath/api/circle/${slug}/`);
|
||||||
this.circleData.learningSequences = parseLearningSequences(this.circleData.children);
|
this.circle = Circle.fromJson(circleData);
|
||||||
this.completionData = await itGet(`/api/completion/circle/${this.circleData.translation_key}/`);
|
const completionData = await itGet(`/api/completion/circle/${this.circle.translation_key}/`);
|
||||||
this.parseCompletionData();
|
this.circle.parseCompletionData(completionData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
return error
|
return error
|
||||||
|
|
@ -57,28 +44,18 @@ export const useCircleStore = defineStore({
|
||||||
async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) {
|
async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) {
|
||||||
try {
|
try {
|
||||||
page.completed = flag;
|
page.completed = flag;
|
||||||
this.completionData = await itPost('/api/completion/circle/mark/', {
|
const completionData = await itPost('/api/completion/circle/mark/', {
|
||||||
page_key: page.translation_key,
|
page_key: page.translation_key,
|
||||||
completed: page.completed,
|
completed: page.completed,
|
||||||
});
|
});
|
||||||
this.parseCompletionData();
|
if (this.circle) {
|
||||||
|
this.circle.parseCompletionData(completionData);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parseCompletionData() {
|
|
||||||
this.flatChildren.forEach((page) => {
|
|
||||||
const pageIndex = this.completionData.findIndex((e) => {
|
|
||||||
return e.page_key === page.translation_key;
|
|
||||||
});
|
|
||||||
if (pageIndex >= 0) {
|
|
||||||
page.completed = this.completionData[pageIndex].completed;
|
|
||||||
} else {
|
|
||||||
page.completed = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
openLearningContent(learningContent: LearningContent) {
|
openLearningContent(learningContent: LearningContent) {
|
||||||
this.currentLearningContent = learningContent;
|
this.currentLearningContent = learningContent;
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import * as log from 'loglevel';
|
||||||
|
|
||||||
|
import {defineStore} from 'pinia'
|
||||||
|
|
||||||
|
import type {LearningPath, Topic} from '@/types'
|
||||||
|
import {itGet} from '@/fetchHelpers';
|
||||||
|
import {Circle} from '@/services/circle';
|
||||||
|
|
||||||
|
export type LearningPathStoreState = {
|
||||||
|
learningPath: LearningPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLearningPathStore = defineStore({
|
||||||
|
id: 'learningPath',
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
learningPath: undefined,
|
||||||
|
} as LearningPathStoreState;
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async loadLearningPath(slug: string) {
|
||||||
|
try {
|
||||||
|
const learningPathData = await itGet(`/learnpath/api/learningpath/${slug}/`);
|
||||||
|
const completionData = await itGet(`/api/completion/learning_path/${learningPathData.translation_key}/`);
|
||||||
|
|
||||||
|
this.learningPath = learningPathData;
|
||||||
|
this.learningPath.topics = [];
|
||||||
|
const emptyTopic: Topic = {
|
||||||
|
id: 0,
|
||||||
|
title: '',
|
||||||
|
slug: '',
|
||||||
|
type: 'learnpath.Topic',
|
||||||
|
translation_key: '',
|
||||||
|
is_visible: false,
|
||||||
|
circles: []
|
||||||
|
}
|
||||||
|
let topic = Object.assign({}, emptyTopic);
|
||||||
|
this.learningPath.children.forEach((page) => {
|
||||||
|
if (page.type === 'learnpath.Topic') {
|
||||||
|
if (topic.id !== 0) {
|
||||||
|
this.learningPath.topics.push(topic);
|
||||||
|
}
|
||||||
|
topic = Object.assign({}, emptyTopic, page);
|
||||||
|
}
|
||||||
|
if (page.type === 'learnpath.Circle') {
|
||||||
|
const circle = Circle.fromJson(page);
|
||||||
|
circle.parseCompletionData(completionData);
|
||||||
|
topic.circles.push(circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.learningPath.topics.push(topic);
|
||||||
|
})
|
||||||
|
console.log(this.learningPath);
|
||||||
|
return this.learningPath;
|
||||||
|
} catch (error) {
|
||||||
|
log.error(error);
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type {Circle} from '@/services/circle';
|
||||||
|
|
||||||
export interface LearningContentBlock {
|
export interface LearningContentBlock {
|
||||||
type: 'web-based-training' | 'competence' | 'exercise' | 'knowledge';
|
type: 'web-based-training' | 'competence' | 'exercise' | 'knowledge';
|
||||||
value: {
|
value: {
|
||||||
|
|
@ -46,10 +48,10 @@ export interface CircleJobSituation {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LearningWagtailPage {
|
export interface LearningWagtailPage {
|
||||||
id: number;
|
readonly id: number;
|
||||||
title: string;
|
readonly title: string;
|
||||||
slug: string;
|
readonly slug: string;
|
||||||
translation_key: string;
|
readonly translation_key: string;
|
||||||
completed?: boolean;
|
completed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,6 +95,20 @@ export interface WagtailCircle extends LearningWagtailPage {
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Topic extends LearningWagtailPage {
|
||||||
|
type: 'learnpath.Topic';
|
||||||
|
is_visible: boolean;
|
||||||
|
circles: Circle[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LearningPathChild = Topic | WagtailCircle;
|
||||||
|
|
||||||
|
export interface LearningPath extends LearningWagtailPage {
|
||||||
|
type: 'learnpath.LearningPath';
|
||||||
|
children: LearningPathChild[];
|
||||||
|
topics: Topic[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface CircleCompletion {
|
export interface CircleCompletion {
|
||||||
id: number;
|
id: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
|
|
@ -105,15 +121,6 @@ export interface CircleCompletion {
|
||||||
json_data: any;
|
json_data: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Circle extends LearningWagtailPage {
|
|
||||||
type: 'learnpath.Circle';
|
|
||||||
children: CircleChild[];
|
|
||||||
description: string;
|
|
||||||
learningSequences: LearningSequence[];
|
|
||||||
goals: CircleGoal[];
|
|
||||||
job_situations: CircleJobSituation[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CircleDiagramData {
|
export interface CircleDiagramData {
|
||||||
index: number
|
index: number
|
||||||
title: string
|
title: string
|
||||||
|
|
|
||||||
|
|
@ -35,15 +35,15 @@ onMounted(async () => {
|
||||||
<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-if="circleStore.circle">
|
||||||
<div class="circle">
|
<div class="circle">
|
||||||
<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:py-8">
|
<div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:py-8">
|
||||||
<h1 class="text-blue-dark text-7xl">
|
<h1 class="text-blue-dark text-7xl">
|
||||||
{{ circleStore.circleData.title }}
|
{{ circleStore.circle.title }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div v-if="circleStore.circleData.learningSequences && circleStore.flatChildren" class="w-full mt-8">
|
<div v-if="circleStore.circle.learningSequences && circleStore.circle.flatChildren" class="w-full mt-8">
|
||||||
<CircleDiagram :circle-store="circleStore"></CircleDiagram>
|
<CircleDiagram :circle-store="circleStore"></CircleDiagram>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ onMounted(async () => {
|
||||||
<div class="block border border-gray-500 mt-8 p-6">
|
<div class="block border border-gray-500 mt-8 p-6">
|
||||||
<h3 class="text-blue-dark">Das lernst du in diesem Circle.</h3>
|
<h3 class="text-blue-dark">Das lernst du in diesem Circle.</h3>
|
||||||
<div class="prose mt-4">
|
<div class="prose mt-4">
|
||||||
{{ circleStore.circleData.description }}
|
{{ circleStore.circle.description }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn-primary mt-4" @click="circleStore.page = 'OVERVIEW'">Erfahre mehr dazu</button>
|
<button class="btn-primary mt-4" @click="circleStore.page = 'OVERVIEW'">Erfahre mehr dazu</button>
|
||||||
|
|
@ -83,12 +83,11 @@ onMounted(async () => {
|
||||||
|
|
||||||
<div class="flex-auto bg-gray-100 px-4 py-8 lg:px-24">
|
<div class="flex-auto bg-gray-100 px-4 py-8 lg:px-24">
|
||||||
<div
|
<div
|
||||||
v-for="learningSequence in circleStore.circleData.learningSequences"
|
v-for="learningSequence in circleStore.circle.learningSequences"
|
||||||
:key="learningSequence.translation_key"
|
:key="learningSequence.translation_key"
|
||||||
>
|
>
|
||||||
<LearningSequence
|
<LearningSequence
|
||||||
:learning-sequence="learningSequence"
|
:learning-sequence="learningSequence"
|
||||||
:completion-data="circleStore.completionData"
|
|
||||||
></LearningSequence>
|
></LearningSequence>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,80 +1,40 @@
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import axios from 'axios';
|
|
||||||
import * as log from 'loglevel';
|
import * as log from 'loglevel';
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
|
|
||||||
import LearningPathDiagram from '../components/circle/LearningPathDiagram.vue';
|
import {onMounted} from 'vue'
|
||||||
import { useUserStore } from '../stores/user';
|
import {useLearningPathStore} from '@/stores/learningPath';
|
||||||
|
import {useUserStore} from '@/stores/user';
|
||||||
|
|
||||||
export default {
|
import LearningPathDiagram from '@/components/circle/LearningPathDiagram.vue';
|
||||||
|
|
||||||
|
|
||||||
components: {LearningPathDiagram},
|
log.debug('LearningPathView created');
|
||||||
props: ['learningPathSlug',],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
count: 0,
|
|
||||||
learningPathData: {},
|
|
||||||
learningPathContents: null,
|
|
||||||
circles: [],
|
|
||||||
learningSequences: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useUserStore)
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
log.debug('LearningPath mounted', this.learningPathSlug);
|
|
||||||
axios({
|
|
||||||
method: 'get',
|
|
||||||
url: `/learnpath/api/learningpath/${this.learningPathSlug}/`,
|
|
||||||
}).then((response) => {
|
|
||||||
|
|
||||||
this.learningPathData = response.data
|
const props = defineProps<{
|
||||||
|
learningPathSlug: string
|
||||||
|
}>()
|
||||||
|
|
||||||
let learningPathContents = {topics: []}
|
const learningPathStore = useLearningPathStore();
|
||||||
let topic = {
|
const userStore = useUserStore();
|
||||||
id: 0,
|
|
||||||
title: '',
|
|
||||||
slug: '',
|
|
||||||
type: 'learnpath.Topic',
|
|
||||||
translation_key: '',
|
|
||||||
is_visible: false,
|
|
||||||
cirlces: []
|
|
||||||
}
|
|
||||||
response.data.children.forEach((child) => {
|
|
||||||
if (child.type === 'learnpath.Topic') {
|
|
||||||
if (topic.id != 0) {
|
|
||||||
learningPathContents.topics.push(child)
|
|
||||||
}
|
|
||||||
topic = child
|
|
||||||
topic.circles = []
|
|
||||||
}
|
|
||||||
if (child.type === 'learnpath.Circle') {
|
|
||||||
topic.circles.push(child)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
learningPathContents.topics.push(topic)
|
|
||||||
this.learningPathContents = learningPathContents;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
log.info('LearningPathView mounted');
|
||||||
|
await learningPathStore.loadLearningPath(props.learningPathSlug);
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-gray-100" v-if="learningPathContents">
|
<div class="bg-gray-100" v-if="learningPathStore.learningPath">
|
||||||
|
|
||||||
<div class="learningpath flex flex-col">
|
<div class="learningpath flex flex-col">
|
||||||
<div class="flex flex-col h-max">
|
<div class="flex flex-col h-max">
|
||||||
<div class="bg-white h-80 pt-8">
|
<div class="bg-white h-80 pt-8">
|
||||||
<LearningPathDiagram :learning-path-contents="learningPathContents"></LearningPathDiagram>
|
<LearningPathDiagram></LearningPathDiagram>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="m-12">{{ learningPathData.title }}</h1>
|
<h1 class="m-12">{{ learningPathStore.learningPath.title }}</h1>
|
||||||
|
|
||||||
<div class="bg-white m-12 p-8 flex flex-row justify-start">
|
<div class="bg-white m-12 p-8 flex flex-row justify-start">
|
||||||
<div class="p-8 flex-auto">
|
<div class="p-8 flex-auto">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 3.2.13 on 2022-06-22 16:53
|
# Generated by Django 3.2.13 on 2022-07-04 09:58
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
@ -23,6 +23,7 @@ class Migration(migrations.Migration):
|
||||||
('page_key', models.UUIDField()),
|
('page_key', models.UUIDField()),
|
||||||
('page_type', models.CharField(blank=True, default='', max_length=255)),
|
('page_type', models.CharField(blank=True, default='', max_length=255)),
|
||||||
('circle_key', models.UUIDField()),
|
('circle_key', models.UUIDField()),
|
||||||
|
('learning_path_key', models.UUIDField()),
|
||||||
('completed', models.BooleanField(default=False)),
|
('completed', models.BooleanField(default=False)),
|
||||||
('json_data', models.JSONField(blank=True, default=dict)),
|
('json_data', models.JSONField(blank=True, default=dict)),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class CircleCompletion(models.Model):
|
||||||
page_key = models.UUIDField()
|
page_key = models.UUIDField()
|
||||||
page_type = models.CharField(max_length=255, default='', blank=True)
|
page_type = models.CharField(max_length=255, default='', blank=True)
|
||||||
circle_key = models.UUIDField()
|
circle_key = models.UUIDField()
|
||||||
|
learning_path_key = models.UUIDField()
|
||||||
|
|
||||||
completed = models.BooleanField(default=False)
|
completed = models.BooleanField(default=False)
|
||||||
json_data = models.JSONField(default=dict, blank=True)
|
json_data = models.JSONField(default=dict, blank=True)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,6 @@ class CircleCompletionSerializer(serializers.ModelSerializer):
|
||||||
model = CircleCompletion
|
model = CircleCompletion
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'created_at', 'updated_at', 'user', 'page_key', 'page_type', 'circle_key',
|
'id', 'created_at', 'updated_at', 'user', 'page_key', 'page_type', 'circle_key',
|
||||||
'completed', 'json_data',
|
'learning_path_key', 'completed', 'json_data',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from vbv_lernwelt.completion.views import request_circle_completion, mark_circle_completion
|
from vbv_lernwelt.completion.views import request_circle_completion, mark_circle_completion, \
|
||||||
|
request_learning_path_completion
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(r"circle/<uuid:circle_key>/", request_circle_completion, name="request_circle_completion"),
|
path(r"circle/<uuid:circle_key>/", request_circle_completion, name="request_circle_completion"),
|
||||||
|
path(r"learning_path/<uuid:learning_path_key>/", request_learning_path_completion, name="request_learning_path_completion"),
|
||||||
path(r"circle/mark/", mark_circle_completion, name="mark_circle_completion"),
|
path(r"circle/mark/", mark_circle_completion, name="mark_circle_completion"),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from wagtail.models import Page
|
||||||
|
|
||||||
from vbv_lernwelt.completion.models import CircleCompletion
|
from vbv_lernwelt.completion.models import CircleCompletion
|
||||||
from vbv_lernwelt.completion.serializers import CircleCompletionSerializer
|
from vbv_lernwelt.completion.serializers import CircleCompletionSerializer
|
||||||
from vbv_lernwelt.learnpath.models import Circle
|
from vbv_lernwelt.learnpath.models import Circle, LearningPath
|
||||||
from vbv_lernwelt.learnpath.utils import get_wagtail_type
|
from vbv_lernwelt.learnpath.utils import get_wagtail_type
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
@ -21,6 +21,16 @@ def request_circle_completion(request, circle_key):
|
||||||
return Response(status=200, data=response_data)
|
return Response(status=200, data=response_data)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
def request_learning_path_completion(request, learning_path_key):
|
||||||
|
response_data = CircleCompletionSerializer(
|
||||||
|
CircleCompletion.objects.filter(user=request.user, learning_path_key=learning_path_key),
|
||||||
|
many=True,
|
||||||
|
).data
|
||||||
|
|
||||||
|
return Response(status=200, data=response_data)
|
||||||
|
|
||||||
|
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def mark_circle_completion(request):
|
def mark_circle_completion(request):
|
||||||
page_key = request.data.get('page_key')
|
page_key = request.data.get('page_key')
|
||||||
|
|
@ -29,11 +39,13 @@ def mark_circle_completion(request):
|
||||||
page = Page.objects.get(translation_key=page_key, locale__language_code='de-CH')
|
page = Page.objects.get(translation_key=page_key, locale__language_code='de-CH')
|
||||||
page_type = get_wagtail_type(page.specific)
|
page_type = get_wagtail_type(page.specific)
|
||||||
circle = Circle.objects.ancestor_of(page).first()
|
circle = Circle.objects.ancestor_of(page).first()
|
||||||
|
learning_path = LearningPath.objects.ancestor_of(page).first()
|
||||||
|
|
||||||
cc, created = CircleCompletion.objects.get_or_create(
|
cc, created = CircleCompletion.objects.get_or_create(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
page_key=page_key,
|
page_key=page_key,
|
||||||
circle_key=circle.translation_key,
|
circle_key=circle.translation_key,
|
||||||
|
learning_path_key=learning_path.translation_key,
|
||||||
)
|
)
|
||||||
cc.page_type = page_type
|
cc.page_type = page_type
|
||||||
cc.completed = completed
|
cc.completed = completed
|
||||||
|
|
|
||||||
|
|
@ -98,16 +98,12 @@ class Circle(Page):
|
||||||
FieldPanel('experts'),
|
FieldPanel('experts'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
|
||||||
def learning_sequences(self):
|
|
||||||
return self.get_children().filter(content_type__model='learningsequence').values('id', 'title')
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_serializer_class(cls):
|
def get_serializer_class(cls):
|
||||||
return get_it_serializer_class(
|
return get_it_serializer_class(
|
||||||
cls,
|
cls,
|
||||||
field_names=[
|
field_names=[
|
||||||
'id', 'title', 'slug', 'type', 'translation_key', 'learning_sequences', 'children',
|
'id', 'title', 'slug', 'type', 'translation_key', 'children',
|
||||||
'description', 'job_situations', 'goals', 'experts',
|
'description', 'job_situations', 'goals', 'experts',
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,4 @@ class LearningPathSerializer(get_it_serializer_class(LearningPath, [])):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Circle
|
model = Circle
|
||||||
fields = ['id', 'title', 'slug', 'children', 'type']
|
fields = ['id', 'title', 'slug', 'type', 'translation_key', 'children']
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue