Merge branch 'develop'
This commit is contained in:
commit
34e59a433c
|
|
@ -1,7 +1,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { loadLocaleMessages, setI18nLanguage } from "@/i18n";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
|
|
||||||
log.debug("AppFooter created");
|
log.debug("AppFooter created");
|
||||||
|
|
||||||
|
async function changeLocale(event: Event) {
|
||||||
|
const target = event.target as HTMLSelectElement;
|
||||||
|
await loadLocaleMessages(target.value);
|
||||||
|
setI18nLanguage(target.value);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -13,6 +20,11 @@ log.debug("AppFooter created");
|
||||||
<div class="flex-grow"></div>
|
<div class="flex-grow"></div>
|
||||||
<div>VBV_VERSION_BUILD_NUMBER_VBV</div>
|
<div>VBV_VERSION_BUILD_NUMBER_VBV</div>
|
||||||
<div class="lg:ml-8">Deutsch</div>
|
<div class="lg:ml-8">Deutsch</div>
|
||||||
|
<!--div class="locale-changer">
|
||||||
|
<select @change="changeLocale($event)">
|
||||||
|
<option v-for="locale in ['de', 'fr']" :key="`locale-${locale}`" :value="locale">{{ locale }}</option>
|
||||||
|
</select>
|
||||||
|
</div-->
|
||||||
<div class="lg:ml-8">{{ $t("footer.contact") }}</div>
|
<div class="lg:ml-8">{{ $t("footer.contact") }}</div>
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { useCircleStore } from "@/stores/circle";
|
import { useCircleStore } from "@/stores/circle";
|
||||||
import type { DefaultArcObject } from "d3";
|
import type { DefaultArcObject } from "d3";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import * as _ from "lodash";
|
import pick from "lodash/pick";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
import { computed, onMounted } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ const pieData = computed(() => {
|
||||||
{
|
{
|
||||||
startAngle: angle.startAngle,
|
startAngle: angle.startAngle,
|
||||||
endAngle: angle.endAngle,
|
endAngle: angle.endAngle,
|
||||||
..._.pick(thisLearningSequence, ["title", "icon", "translation_key", "slug"]),
|
...pick(thisLearningSequence, ["title", "icon", "translation_key", "slug"]),
|
||||||
arrowStartAngle: angle.endAngle + (angle.startAngle - angle.endAngle) / 2,
|
arrowStartAngle: angle.endAngle + (angle.startAngle - angle.endAngle) / 2,
|
||||||
arrowEndAngle: angle.startAngle + (angle.startAngle - angle.endAngle) / 2,
|
arrowEndAngle: angle.startAngle + (angle.startAngle - angle.endAngle) / 2,
|
||||||
someFinished: someFinished(thisLearningSequence),
|
someFinished: someFinished(thisLearningSequence),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import * as _ from "lodash";
|
import kebabCase from "lodash/kebabCase";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -12,7 +12,7 @@ import type { LearningSequence, Topic } from "@/types";
|
||||||
import { computed, onMounted, reactive, watch } from "vue";
|
import { computed, onMounted, reactive, watch } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
export type DiagramType = "horizontal" | "vertical" | "horizontalSmall";
|
export type DiagramType = "horizontal" | "vertical" | "horizontalSmall" | "singleSmall";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
diagramType?: DiagramType;
|
diagramType?: DiagramType;
|
||||||
|
|
@ -21,6 +21,7 @@ export interface Props {
|
||||||
learningPath: LearningPath;
|
learningPath: LearningPath;
|
||||||
// set to undefined (default) to show all circles
|
// set to undefined (default) to show all circles
|
||||||
showCircleTranslationKeys: string[];
|
showCircleTranslationKeys: string[];
|
||||||
|
pullUp?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
|
@ -28,6 +29,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
postfix: "",
|
postfix: "",
|
||||||
profileUserId: "",
|
profileUserId: "",
|
||||||
showCircleTranslationKeys: undefined,
|
showCircleTranslationKeys: undefined,
|
||||||
|
pullUp: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
|
|
@ -37,14 +39,14 @@ log.debug(
|
||||||
props.showCircleTranslationKeys
|
props.showCircleTranslationKeys
|
||||||
);
|
);
|
||||||
|
|
||||||
const state = reactive({ width: 1640, height: 384 });
|
const state = reactive({ width: 1640, height: 384, startY: 0 });
|
||||||
|
|
||||||
const svgId = computed(() => {
|
const svgId = computed(() => {
|
||||||
return `learningpath-diagram-${props.learningPath?.slug}-${props.diagramType}${props.postfix}`;
|
return `learningpath-diagram-${props.learningPath?.slug}-${props.diagramType}${props.postfix}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const viewBox = computed(() => {
|
const viewBox = computed(() => {
|
||||||
return `0 0 ${state.width} ${state.height}`;
|
return `0 ${state.startY} ${state.width} ${state.height}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const vueRouter = useRouter();
|
const vueRouter = useRouter();
|
||||||
|
|
@ -135,7 +137,7 @@ const circles = computed(() => {
|
||||||
frontend_url: circle.frontend_url,
|
frontend_url: circle.frontend_url,
|
||||||
id: circle.id,
|
id: circle.id,
|
||||||
translation_key: circle.translation_key,
|
translation_key: circle.translation_key,
|
||||||
slug: _.kebabCase(circle.title),
|
slug: kebabCase(circle.title),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -164,6 +166,10 @@ function render() {
|
||||||
if (props.diagramType === "vertical") {
|
if (props.diagramType === "vertical") {
|
||||||
state.width = Math.min(960, window.innerWidth - 32);
|
state.width = Math.min(960, window.innerWidth - 32);
|
||||||
state.height = 860;
|
state.height = 860;
|
||||||
|
} else if (props.diagramType === "singleSmall") {
|
||||||
|
state.height = 260;
|
||||||
|
state.startY = 60;
|
||||||
|
state.width = circleWidth * circles.value.length;
|
||||||
} else {
|
} else {
|
||||||
state.width = circleWidth * circles.value.length;
|
state.width = circleWidth * circles.value.length;
|
||||||
}
|
}
|
||||||
|
|
@ -466,10 +472,11 @@ function render() {
|
||||||
<div class="svg-container h-full content-start">
|
<div class="svg-container h-full content-start">
|
||||||
<svg
|
<svg
|
||||||
:id="svgId"
|
:id="svgId"
|
||||||
class="learning-path-visualization mx-auto -mt-6 h-full lg:mt-0"
|
class="learning-path-visualization mx-auto h-full lg:mt-0"
|
||||||
:class="{
|
:class="{
|
||||||
'max-h-[90px]': ['horizontalSmall'].includes(diagramType),
|
'max-h-[90px]': ['horizontalSmall', 'singleSmall'].includes(diagramType),
|
||||||
'max-h-[90px] lg:max-h-[380px]': ['horizontal'].includes(diagramType),
|
'max-h-[90px] lg:max-h-[380px]': ['horizontal'].includes(diagramType),
|
||||||
|
'-mt-6': pullUp,
|
||||||
}"
|
}"
|
||||||
:viewBox="viewBox"
|
:viewBox="viewBox"
|
||||||
></svg>
|
></svg>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
LearningSequence,
|
LearningSequence,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import { humanizeDuration } from "@/utils/humanizeDuration";
|
import { humanizeDuration } from "@/utils/humanizeDuration";
|
||||||
import _ from "lodash";
|
import findLast from "lodash/findLast";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import LearningContentBadge from "./LearningContentTypeBadge.vue";
|
import LearningContentBadge from "./LearningContentTypeBadge.vue";
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ const allFinished = computed(() => {
|
||||||
|
|
||||||
const continueTranslationKeyTuple = computed(() => {
|
const continueTranslationKeyTuple = computed(() => {
|
||||||
if (props.learningSequence && circleStore.circle) {
|
if (props.learningSequence && circleStore.circle) {
|
||||||
const lastFinished = _.findLast(
|
const lastFinished = findLast(
|
||||||
circleStore.circle.flatLearningContents,
|
circleStore.circle.flatLearningContents,
|
||||||
(learningContent) => {
|
(learningContent) => {
|
||||||
return learningContent.completion_status === "success";
|
return learningContent.completion_status === "success";
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<div class="flex flex-1 items-center">
|
<div class="flex flex-1 items-center">
|
||||||
<slot name="center"></slot>
|
<slot name="center"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center md:w-1/4">
|
<div class="flex items-center lg:w-1/4">
|
||||||
<slot name="link"></slot>
|
<slot name="link"></slot>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@ import { createI18n } from "vue-i18n";
|
||||||
|
|
||||||
// https://vue-i18n.intlify.dev/guide/advanced/lazy.html
|
// https://vue-i18n.intlify.dev/guide/advanced/lazy.html
|
||||||
export const SUPPORT_LOCALES = ["de", "fr", "it"];
|
export const SUPPORT_LOCALES = ["de", "fr", "it"];
|
||||||
|
let i18n: any = null;
|
||||||
|
|
||||||
export function setupI18n(options = { locale: "de", legacy: false }) {
|
export function setupI18n(options = { locale: "de", legacy: false }) {
|
||||||
const i18n = createI18n(options);
|
i18n = createI18n(options);
|
||||||
setI18nLanguage(i18n, options.locale);
|
setI18nLanguage(options.locale);
|
||||||
return i18n;
|
return i18n;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setI18nLanguage(i18n: any, locale: string) {
|
export function setI18nLanguage(locale: string) {
|
||||||
if (i18n.mode === "legacy") {
|
if (i18n.mode === "legacy") {
|
||||||
i18n.global.locale = locale;
|
i18n.global.locale = locale;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -26,7 +27,7 @@ export function setI18nLanguage(i18n: any, locale: string) {
|
||||||
document.querySelector("html")?.setAttribute("lang", locale);
|
document.querySelector("html")?.setAttribute("lang", locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadLocaleMessages(i18n: any, locale: any) {
|
export async function loadLocaleMessages(locale: any) {
|
||||||
// load locale messages with dynamic import
|
// load locale messages with dynamic import
|
||||||
const messages = await import(
|
const messages = await import(
|
||||||
/* webpackChunkName: "locale-[request]" */ `./locales/${locale}.json`
|
/* webpackChunkName: "locale-[request]" */ `./locales/${locale}.json`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"nextStep": "Prochaine étape",
|
||||||
|
"start": "Commencer",
|
||||||
|
"backToLearningPath": "zurück zum Lernpfad",
|
||||||
|
"backToCircle": "zurück zum Circle",
|
||||||
|
"next": "Weiter",
|
||||||
|
"back": "zurück",
|
||||||
|
"backCapitalized": "@.capitalize:general.back",
|
||||||
|
"save": "Speichern",
|
||||||
|
"learningUnit": "Lerneinheit",
|
||||||
|
"learningPath": "Lernpfad",
|
||||||
|
"learningSequence": "Lernsequenz",
|
||||||
|
"show": "Anschauen",
|
||||||
|
"circles": "Circles",
|
||||||
|
"transferTask": "Transferauftrag | Transferaufträge",
|
||||||
|
"feedback": "Feedback | Feedbacks",
|
||||||
|
"exam": "Prüfung | Prüfungen",
|
||||||
|
"examResult": "Prüfungsresultat | Prüfungsresultate",
|
||||||
|
"certificate": "Zertifikat | Zertifikate",
|
||||||
|
"notification": "Benachrichtigung | Benachrichtigungen",
|
||||||
|
"profileLink": "Profil anzeigen",
|
||||||
|
"shop": "Shop",
|
||||||
|
"yes": "Ja",
|
||||||
|
"no": "Nein"
|
||||||
|
},
|
||||||
|
"mainNavigation": {
|
||||||
|
"logout": "Abmelden",
|
||||||
|
"settings": "Kontoeinstellungen"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"welcome": "Willkommen, {name}"
|
||||||
|
},
|
||||||
|
"learningPathPage": {
|
||||||
|
"welcomeBack": "Willkommen zurück, {name}",
|
||||||
|
"showListView": "Listenansicht anzeigen",
|
||||||
|
"nextStep": "Nächster Schritt"
|
||||||
|
},
|
||||||
|
"circlePage": {
|
||||||
|
"duration": "Dauer",
|
||||||
|
"circleContentBoxTitle": "Das lernst du in diesem Circle.",
|
||||||
|
"gotQuestions": "Hast du Fragen?",
|
||||||
|
"contactExpertButton": "Fachexpertin kontaktieren",
|
||||||
|
"contactExpertDescription": "Tausche dich mit der Fachexpertin für den Circle {circleName} aus.",
|
||||||
|
"learnMore": "Erfahre mehr dazu",
|
||||||
|
"documents": {
|
||||||
|
"title": "Unterlagen",
|
||||||
|
"expertDescription": "Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.",
|
||||||
|
"userDescription": "Hier findest du die Unterlagen, die dir die Fachexpertin zur Verfügung gestellt hat.",
|
||||||
|
"action": "Unterlagen hochladen",
|
||||||
|
"modalAction": "Datei auswählen",
|
||||||
|
"fileLabel": "Datei",
|
||||||
|
"modalFileName": "Name",
|
||||||
|
"modalNameInformation": "Max. 70 Zeichen",
|
||||||
|
"chooseSequence": "Wähle eine Lernsequenz aus",
|
||||||
|
"selectFile": "Bitte wähle eine Datei aus",
|
||||||
|
"chooseName": "Bitte wähle einen Namen",
|
||||||
|
"chooseLearningSequence": "Bitte wähle eine Lernsequenz aus",
|
||||||
|
"uploadErrorMessage": "Beim Hochladen ist ein Fehler aufgetreten. Bitte versuche es erneut.",
|
||||||
|
"maxFileSize": "Maximale Dateigrösse: 20 MB"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"learningContent": {
|
||||||
|
"completeAndContinue": "Als erledigt markieren"
|
||||||
|
},
|
||||||
|
"selfEvaluation": {
|
||||||
|
"selfEvaluation": "Selbsteinschätzung",
|
||||||
|
"title": "@:selfEvaluation.selfEvaluation {title}",
|
||||||
|
"steps": "Schritt {current} von {max}",
|
||||||
|
"instruction": [
|
||||||
|
"Überprüfe, ob du in der Lernheinheit",
|
||||||
|
"alles verstanden hast.",
|
||||||
|
"Lies die folgende Aussage und bewerte sie:"
|
||||||
|
],
|
||||||
|
"yes": "Ja, ich kann das",
|
||||||
|
"no": "Das muss ich nochmals anschauen",
|
||||||
|
"progressText": "Schau dein Fortschritt in deinem KompetenzNavi:",
|
||||||
|
"progressLink": "KompetenzNavi öffnen",
|
||||||
|
"selfEvaluationYes": "@:selfEvaluation: Ich kann das.",
|
||||||
|
"selfEvaluationNo": "@:selfEvaluation: Muss ich nochmals anschauen."
|
||||||
|
},
|
||||||
|
"competences": {
|
||||||
|
"competences": "Kompetenzen",
|
||||||
|
"title": "KompetenzNavi",
|
||||||
|
"lastImprovements": "Letzte verbesserte Kompetenzen",
|
||||||
|
"showAll": "Alle anschauen",
|
||||||
|
"assessment": "Einschätzungen",
|
||||||
|
"notAssessed": "Nicht eingeschätzt",
|
||||||
|
"assessAgain": "Sich nochmals einschätzen"
|
||||||
|
},
|
||||||
|
"mediaLibrary": {
|
||||||
|
"title": "Mediathek",
|
||||||
|
"learningMedia": {
|
||||||
|
"titel": "Lernmedien",
|
||||||
|
"description": "Finde eine vollständige Liste der Bücher und anderen Medien, auf die im Kurs verwiesen wird."
|
||||||
|
},
|
||||||
|
"handlungsfelder": {
|
||||||
|
"title": "Handlungsfeld | Handlungsfelder",
|
||||||
|
"description": "Finde alle Ressourcen der Handlungsfelder wie Lernmedien, Links und andere nützliche Informationen."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"dataProtection": "Datenschutzbestimmungen",
|
||||||
|
"imprint": "Impressum",
|
||||||
|
"contact": "Kontakt",
|
||||||
|
"faq": "FAQ"
|
||||||
|
},
|
||||||
|
"cockpit": {
|
||||||
|
"title": "Cockpit",
|
||||||
|
"tasksDone": "Erledigte Transferaufträge von Teilnehmer.",
|
||||||
|
"feedbacksDone": "Abgeschickte Feedbacks von Teilnehmer.",
|
||||||
|
"examsDone": "Abgelegte Prüfungen von Teilnehmer.",
|
||||||
|
"progress": "Fortschritt",
|
||||||
|
"profileLink": "Profil anzeigen"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"sendMessage": "Nachricht schreiben"
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"intro": "{name}, dein/e Trainer/-in, bittet dich, ihm/ihr Feedback zu geben. Das ist freiwillig, würde aber ihm/ihr helfen, deine Lernerlebniss zu verbessern.",
|
||||||
|
"areYouSatisfied": "Wie zufrieden bist du?",
|
||||||
|
"recommendLabel": "Würden Sie den Kurs weiterempfehlen?",
|
||||||
|
"satisfactionLabel": "Zufriedenheit insgesamt",
|
||||||
|
"goalAttainmentLabel": "Zielerreichung insgesamt",
|
||||||
|
"proficiencyLabel": "Wie beurteilen Sie Ihre Sicherheit bezüglichen den Themen nach dem Kurs?",
|
||||||
|
"receivedMaterialsLabel": "Haben Sie Vorbereitungsunterlagen (z.B. eLearning) erhalten?",
|
||||||
|
"materialsRatingLabel": "Falls ja: Wie beurteilen Sie die Vorbereitungsunterlagen (z.B. eLearning)?",
|
||||||
|
"instructorCompetenceLabel": "Der Kursleiter war themenstark, fachkompetent.",
|
||||||
|
"instructorRespectLabel": "Fragen und Anregungen der Kursteilnehmenden wurden ernst genommen und aufgegriffen.",
|
||||||
|
"instructorOpenFeedbackLabel": "Was ich dem Kursleiter sonst noch sagen wollte:",
|
||||||
|
"courseNegativeFeedbackLabel": "Wo sehen Sie Verbesserungspotenzial?",
|
||||||
|
"coursePositiveFeedbackLabel": "Was hat Ihnen besonders gut gefallen?",
|
||||||
|
"completionTitle": "Schicke dein Feedback an {name}",
|
||||||
|
"completionDescription": "Dein Feedback ist anonym. Dein Vor- und Nachname werden bei deiner Trainer/-in nicht angezeigt.",
|
||||||
|
"sendFeedback": "Feedback abschicken",
|
||||||
|
"feedbackSent": "Dein Feedback wurde abgeschickt",
|
||||||
|
"circleFeedback": "Feedback zum Circle",
|
||||||
|
"showDetails": "Details anzeigen",
|
||||||
|
"sentByUsers": "Von {count} Teilnehmern ausgefüllt",
|
||||||
|
"feedbackPageTitle": "Feedback zum Lehrgang",
|
||||||
|
"feedbackPageInfo": "Teilnehmer haben das Feedback ausgefüllt",
|
||||||
|
"questionTitle": "Frage",
|
||||||
|
"veryUnhappy": "Sehr unzufrieden",
|
||||||
|
"unhappy": "Unzufrieden",
|
||||||
|
"happy": "Zufrieden",
|
||||||
|
"veryHappy": "Sehr zufrieden",
|
||||||
|
"average": "Durchschnitt",
|
||||||
|
"answers": "Antworten",
|
||||||
|
"noFeedbacks": "Es wurden noch keine Feedbacks abgegeben"
|
||||||
|
},
|
||||||
|
"constants": {
|
||||||
|
"yes": "Ja",
|
||||||
|
"no": "Nein",
|
||||||
|
"verySatisfied": "sehr zufrieden",
|
||||||
|
"satisfied": "zufrieden",
|
||||||
|
"unsatisfied": "unzufrieden",
|
||||||
|
"veryUnsatisfied": "sehr unzufrieden"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ Sentry.init({
|
||||||
});
|
});
|
||||||
|
|
||||||
// todo: define lang setup
|
// todo: define lang setup
|
||||||
loadLocaleMessages(i18n, "de").then(() => {
|
loadLocaleMessages("de").then(() => {
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { useCompetenceStore } from "@/stores/competence";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import groupBy from "lodash/groupBy";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
|
@ -25,10 +26,14 @@ const competenceStore = useCompetenceStore();
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
const courseSessionStore = useCourseSessionsStore();
|
const courseSessionStore = useCourseSessionsStore();
|
||||||
|
|
||||||
function userCountStatus(userId: number) {
|
function userCountStatusForCircle(userId: number, translationKey: string) {
|
||||||
return competenceStore.calcStatusCount(
|
const criteria = competenceStore.flatPerformanceCriteria(
|
||||||
competenceStore.flatPerformanceCriteria(userId, cockpitStore.selectedCircles)
|
userId,
|
||||||
|
cockpitStore.selectedCircles
|
||||||
);
|
);
|
||||||
|
const grouped = groupBy(criteria, "circle.translation_key");
|
||||||
|
|
||||||
|
return competenceStore.calcStatusCount(grouped[translationKey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const circles = computed(() => {
|
const circles = computed(() => {
|
||||||
|
|
@ -141,9 +146,15 @@ function setActiveClasses(translationKey: string) {
|
||||||
:avatar-url="csu.avatar_url"
|
:avatar-url="csu.avatar_url"
|
||||||
>
|
>
|
||||||
<template #center>
|
<template #center>
|
||||||
<div class="flex flex-row items-center justify-between">
|
<div
|
||||||
<div class="mr-6 flex flex-row items-center gap-4">
|
class="mt-2 flex w-full flex-row items-center justify-between lg:mt-0"
|
||||||
<div class="h-12">
|
>
|
||||||
|
<ul class="w-full">
|
||||||
|
<li
|
||||||
|
class="flex h-12 items-center justify-between"
|
||||||
|
v-for="(circle, i) of cockpitStore.selectedCircles"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
<LearningPathDiagram
|
<LearningPathDiagram
|
||||||
v-if="
|
v-if="
|
||||||
learningPathStore.learningPathForUser(
|
learningPathStore.learningPathForUser(
|
||||||
|
|
@ -157,44 +168,44 @@ function setActiveClasses(translationKey: string) {
|
||||||
csu.user_id
|
csu.user_id
|
||||||
) as LearningPath
|
) as LearningPath
|
||||||
"
|
"
|
||||||
:postfix="`cockpit-${csu.user_id}`"
|
:postfix="`cockpit-${csu.user_id}-${i}`"
|
||||||
:profile-user-id="`${csu.user_id}`"
|
:profile-user-id="`${csu.user_id}`"
|
||||||
:show-circle-translation-keys="cockpitStore.selectedCircles"
|
:show-circle-translation-keys="[circle]"
|
||||||
diagram-type="horizontalSmall"
|
:pull-up="false"
|
||||||
|
diagram-type="singleSmall"
|
||||||
|
class="mr-4"
|
||||||
></LearningPathDiagram>
|
></LearningPathDiagram>
|
||||||
</div>
|
<p class="lg:min-w-[150px]">
|
||||||
<div>
|
{{ selectedCirclesTitles[i] }}
|
||||||
<span v-for="title in selectedCirclesTitles" :key="title">
|
|
||||||
{{ title }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4 flex flex-row items-center">
|
|
||||||
<div class="mr-6 flex flex-row items-center">
|
|
||||||
<it-icon-smiley-thinking
|
|
||||||
class="mr-2 inline-block h-8 w-8"
|
|
||||||
></it-icon-smiley-thinking>
|
|
||||||
<p class="text-bold inline-block">
|
|
||||||
{{ userCountStatus(csu.user_id).fail }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<li class="mr-6 flex flex-row items-center">
|
|
||||||
<it-icon-smiley-happy
|
|
||||||
class="mr-2 inline-block h-8 w-8"
|
|
||||||
></it-icon-smiley-happy>
|
|
||||||
<p class="text-bold inline-block">
|
|
||||||
{{ userCountStatus(csu.user_id).success }}
|
|
||||||
</p>
|
</p>
|
||||||
|
<div class="ml-4 flex flex-row items-center">
|
||||||
|
<div class="mr-6 flex flex-row items-center">
|
||||||
|
<it-icon-smiley-thinking
|
||||||
|
class="mr-2 inline-block h-8 w-8"
|
||||||
|
></it-icon-smiley-thinking>
|
||||||
|
<p class="text-bold inline-block">
|
||||||
|
{{ userCountStatusForCircle(csu.user_id, circle).fail }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<li class="mr-6 flex flex-row items-center">
|
||||||
|
<it-icon-smiley-happy
|
||||||
|
class="mr-2 inline-block h-8 w-8"
|
||||||
|
></it-icon-smiley-happy>
|
||||||
|
<p class="text-bold inline-block">
|
||||||
|
{{ userCountStatusForCircle(csu.user_id, circle).success }}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-row items-center">
|
||||||
|
<it-icon-smiley-neutral
|
||||||
|
class="mr-2 inline-block h-8 w-8"
|
||||||
|
></it-icon-smiley-neutral>
|
||||||
|
<p class="text-bold inline-block">
|
||||||
|
{{ userCountStatusForCircle(csu.user_id, circle).unknown }}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex flex-row items-center">
|
</ul>
|
||||||
<it-icon-smiley-neutral
|
|
||||||
class="mr-2 inline-block h-8 w-8"
|
|
||||||
></it-icon-smiley-neutral>
|
|
||||||
<p class="text-bold inline-block">
|
|
||||||
{{ userCountStatus(csu.user_id).unknown }}
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #link>
|
<template #link>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ import PerformanceCriteriaRow from "@/components/competences/PerformanceCriteria
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
import { useCompetenceStore } from "@/stores/competence";
|
||||||
import _ from "lodash";
|
import maxBy from "lodash/maxBy";
|
||||||
|
import orderBy from "lodash/orderBy";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ const failedCriteria = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const lastUpdatedCompetences = computed(() => {
|
const lastUpdatedCompetences = computed(() => {
|
||||||
return _.orderBy(
|
return orderBy(
|
||||||
competenceStore.competences(),
|
competenceStore.competences(),
|
||||||
[
|
[
|
||||||
(competence) => {
|
(competence) => {
|
||||||
|
|
@ -38,7 +39,7 @@ const lastUpdatedCompetences = computed(() => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
_.maxBy(criteria, "completion_status_updated_at")
|
maxBy(criteria, "completion_status_updated_at")
|
||||||
?.completion_status_updated_at || ""
|
?.completion_status_updated_at || ""
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { useCircleStore } from "@/stores/circle";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type { CourseSessionUser, DocumentUploadData } from "@/types";
|
import type { CourseSessionUser, DocumentUploadData } from "@/types";
|
||||||
import { humanizeDuration } from "@/utils/humanizeDuration";
|
import { humanizeDuration } from "@/utils/humanizeDuration";
|
||||||
import _ from "lodash";
|
import sumBy from "lodash/sumBy";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
@ -44,7 +44,7 @@ const circleStore = useCircleStore();
|
||||||
|
|
||||||
const duration = computed(() => {
|
const duration = computed(() => {
|
||||||
if (circleStore.circle) {
|
if (circleStore.circle) {
|
||||||
const minutes = _.sumBy(circleStore.circle.learningSequences, "minutes");
|
const minutes = sumBy(circleStore.circle.learningSequences, "minutes");
|
||||||
return humanizeDuration(minutes);
|
return humanizeDuration(minutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ import type {
|
||||||
LearningUnitPerformanceCriteria,
|
LearningUnitPerformanceCriteria,
|
||||||
WagtailCircle,
|
WagtailCircle,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import _ from "lodash";
|
import groupBy from "lodash/groupBy";
|
||||||
|
import partition from "lodash/partition";
|
||||||
|
import values from "lodash/values";
|
||||||
|
|
||||||
export function parseLearningSequences(
|
export function parseLearningSequences(
|
||||||
circle: Circle,
|
circle: Circle,
|
||||||
|
|
@ -192,7 +194,7 @@ export class Circle implements WagtailCircle {
|
||||||
|
|
||||||
public allFinishedInLearningSequence(translationKey: string): boolean {
|
public allFinishedInLearningSequence(translationKey: string): boolean {
|
||||||
if (translationKey) {
|
if (translationKey) {
|
||||||
const [performanceCriteria, learningContents] = _.partition(
|
const [performanceCriteria, learningContents] = partition(
|
||||||
this.flatChildren.filter(
|
this.flatChildren.filter(
|
||||||
(lc) => lc.parentLearningSequence?.translation_key === translationKey
|
(lc) => lc.parentLearningSequence?.translation_key === translationKey
|
||||||
),
|
),
|
||||||
|
|
@ -201,8 +203,8 @@ export class Circle implements WagtailCircle {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupedPerformanceCriteria = _.values(
|
const groupedPerformanceCriteria = values(
|
||||||
_.groupBy(performanceCriteria, (pc) => pc.parentLearningUnit?.id)
|
groupBy(performanceCriteria, (pc) => pc.parentLearningUnit?.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import * as _ from "lodash";
|
import orderBy from "lodash/orderBy";
|
||||||
|
|
||||||
import { Circle } from "@/services/circle";
|
import { Circle } from "@/services/circle";
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -11,15 +11,13 @@ import type {
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
|
|
||||||
function getLastCompleted(courseId: number, completionData: CourseCompletion[]) {
|
function getLastCompleted(courseId: number, completionData: CourseCompletion[]) {
|
||||||
return _.orderBy(completionData, ["updated_at"], "desc").find(
|
return orderBy(completionData, ["updated_at"], "desc").find((c: CourseCompletion) => {
|
||||||
(c: CourseCompletion) => {
|
return (
|
||||||
return (
|
c.completion_status === "success" &&
|
||||||
c.completion_status === "success" &&
|
c.course === courseId &&
|
||||||
c.course === courseId &&
|
c.page_type === "learnpath.LearningContent"
|
||||||
c.page_type === "learnpath.LearningContent"
|
);
|
||||||
);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LearningPath implements WagtailLearningPath {
|
export class LearningPath implements WagtailLearningPath {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ import type {
|
||||||
CompetenceProfilePage,
|
CompetenceProfilePage,
|
||||||
PerformanceCriteria,
|
PerformanceCriteria,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import _ from "lodash";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
|
import groupBy from "lodash/groupBy";
|
||||||
|
import orderBy from "lodash/orderBy";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
export type CompetenceStoreState = {
|
export type CompetenceStoreState = {
|
||||||
|
|
@ -30,7 +32,7 @@ export const useCompetenceStore = defineStore({
|
||||||
actions: {
|
actions: {
|
||||||
calcStatusCount(criteria: PerformanceCriteria[]) {
|
calcStatusCount(criteria: PerformanceCriteria[]) {
|
||||||
if (criteria) {
|
if (criteria) {
|
||||||
const grouped = _.groupBy(criteria, "completion_status");
|
const grouped = groupBy(criteria, "completion_status");
|
||||||
return {
|
return {
|
||||||
fail: grouped?.fail?.length || 0,
|
fail: grouped?.fail?.length || 0,
|
||||||
success: grouped?.success?.length || 0,
|
success: grouped?.success?.length || 0,
|
||||||
|
|
@ -73,7 +75,7 @@ export const useCompetenceStore = defineStore({
|
||||||
if (this.competenceProfilePages.get(userId)) {
|
if (this.competenceProfilePages.get(userId)) {
|
||||||
const competenceProfilePage = this.competenceProfilePages.get(userId);
|
const competenceProfilePage = this.competenceProfilePages.get(userId);
|
||||||
if (competenceProfilePage) {
|
if (competenceProfilePage) {
|
||||||
let criteria = _.orderBy(
|
let criteria = orderBy(
|
||||||
competenceProfilePage.children.flatMap((competence) => {
|
competenceProfilePage.children.flatMap((competence) => {
|
||||||
return competence.children;
|
return competence.children;
|
||||||
}),
|
}),
|
||||||
|
|
@ -147,7 +149,7 @@ export const useCompetenceStore = defineStore({
|
||||||
throw `No competenceProfilePageData found with: ${slug}`;
|
throw `No competenceProfilePageData found with: ${slug}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.competenceProfilePages.set(userId, _.cloneDeep(competenceProfilePage));
|
this.competenceProfilePages.set(userId, cloneDeep(competenceProfilePage));
|
||||||
|
|
||||||
const circles = competenceProfilePage.circles.map((c: CircleLight) => {
|
const circles = competenceProfilePage.circles.map((c: CircleLight) => {
|
||||||
return { id: c.translation_key, name: `Circle: ${c.title}` };
|
return { id: c.translation_key, name: `Circle: ${c.title}` };
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import type {
|
||||||
CourseSessionUser,
|
CourseSessionUser,
|
||||||
ExpertSessionUser,
|
ExpertSessionUser,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import _ from "lodash";
|
import uniqBy from "lodash/uniqBy";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
|
|
@ -72,7 +72,7 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
// these will become getters
|
// these will become getters
|
||||||
const coursesFromCourseSessions = computed(() =>
|
const coursesFromCourseSessions = computed(() =>
|
||||||
// TODO: refactor after implementing of Klassenkonzept
|
// TODO: refactor after implementing of Klassenkonzept
|
||||||
_.uniqBy(courseSessions.value, "course.id")
|
uniqBy(courseSessions.value, "course.id")
|
||||||
);
|
);
|
||||||
|
|
||||||
const courseSessionForRoute = computed(() => {
|
const courseSessionForRoute = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { itGetCached } from "@/fetchHelpers";
|
||||||
import { LearningPath } from "@/services/learningPath";
|
import { LearningPath } from "@/services/learningPath";
|
||||||
import { useCompletionStore } from "@/stores/completion";
|
import { useCompletionStore } from "@/stores/completion";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import _ from "lodash";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
export type LearningPathStoreState = {
|
export type LearningPathStoreState = {
|
||||||
|
|
@ -62,7 +62,7 @@ export const useLearningPathStore = defineStore({
|
||||||
);
|
);
|
||||||
|
|
||||||
const learningPath = LearningPath.fromJson(
|
const learningPath = LearningPath.fromJson(
|
||||||
_.cloneDeep(learningPathData),
|
cloneDeep(learningPathData),
|
||||||
completionData
|
completionData
|
||||||
);
|
);
|
||||||
this.learningPaths.set(key, learningPath);
|
this.learningPaths.set(key, learningPath);
|
||||||
|
|
|
||||||
|
|
@ -112,9 +112,6 @@ def create_vv_new_learning_path(
|
||||||
lu_title="Selbsständigerwerbende versichern",
|
lu_title="Selbsständigerwerbende versichern",
|
||||||
)
|
)
|
||||||
|
|
||||||
TopicFactory(title="Vertiefen und Festigen", parent=lp)
|
|
||||||
create_circle_vernetzen(lp)
|
|
||||||
|
|
||||||
TopicFactory(title="Prüfung", parent=lp)
|
TopicFactory(title="Prüfung", parent=lp)
|
||||||
create_circle_pruefungsvorbereitung(lp)
|
create_circle_pruefungsvorbereitung(lp)
|
||||||
create_circle_pruefung(lp)
|
create_circle_pruefung(lp)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue