Add PerformanceCriteria page
This commit is contained in:
parent
e5d6dd60f6
commit
5dfdd470ae
|
|
@ -2,7 +2,7 @@
|
||||||
<li
|
<li
|
||||||
class="flex flex-col justify-between border-t border-gray-500 py-4 leading-[45px] lg:flex-row"
|
class="flex flex-col justify-between border-t border-gray-500 py-4 leading-[45px] lg:flex-row"
|
||||||
>
|
>
|
||||||
<div class="flex flex-row items-center md:w-1/4">
|
<div class="flex flex-row items-center lg:w-1/3">
|
||||||
<slot name="firstRow"></slot>
|
<slot name="firstRow"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 items-center">
|
<div class="flex flex-1 items-center">
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ function setActiveClasses(translationKey: string) {
|
||||||
<it-icon-smiley-thinking
|
<it-icon-smiley-thinking
|
||||||
class="mr-2 inline-block h-8 w-8"
|
class="mr-2 inline-block h-8 w-8"
|
||||||
></it-icon-smiley-thinking>
|
></it-icon-smiley-thinking>
|
||||||
<p class="text-bold inline-block">
|
<p class="text-bold inline-block w-6">
|
||||||
{{ userCountStatusForCircle(csu.user_id, circle).FAIL }}
|
{{ userCountStatusForCircle(csu.user_id, circle).FAIL }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -202,7 +202,7 @@ function setActiveClasses(translationKey: string) {
|
||||||
<it-icon-smiley-happy
|
<it-icon-smiley-happy
|
||||||
class="mr-2 inline-block h-8 w-8"
|
class="mr-2 inline-block h-8 w-8"
|
||||||
></it-icon-smiley-happy>
|
></it-icon-smiley-happy>
|
||||||
<p class="text-bold inline-block">
|
<p class="text-bold inline-block w-6">
|
||||||
{{ userCountStatusForCircle(csu.user_id, circle).SUCCESS }}
|
{{ userCountStatusForCircle(csu.user_id, circle).SUCCESS }}
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -210,7 +210,7 @@ function setActiveClasses(translationKey: string) {
|
||||||
<it-icon-smiley-neutral
|
<it-icon-smiley-neutral
|
||||||
class="mr-2 inline-block h-8 w-8"
|
class="mr-2 inline-block h-8 w-8"
|
||||||
></it-icon-smiley-neutral>
|
></it-icon-smiley-neutral>
|
||||||
<p class="text-bold inline-block">
|
<p class="text-bold inline-block w-6">
|
||||||
{{ userCountStatusForCircle(csu.user_id, circle).UNKNOWN }}
|
{{ userCountStatusForCircle(csu.user_id, circle).UNKNOWN }}
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ onMounted(async () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container-large lg:mt-4">
|
<div class="container-large">
|
||||||
<nav class="lg:pb-4">
|
<nav class="py-4">
|
||||||
<router-link
|
<router-link
|
||||||
class="btn-text inline-flex items-center pl-0"
|
class="btn-text inline-flex items-center pl-0"
|
||||||
:to="`/course/${props.courseSlug}/competence/certificates`"
|
:to="`/course/${props.courseSlug}/competence/certificates`"
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,8 @@ onMounted(async () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container-large lg:mt-4">
|
<div class="container-large">
|
||||||
<h2 class="mb-4">{{ $t("a.Kompetenznachweise") }}</h2>
|
<h2 class="mb-4 lg:py-4">{{ $t("a.Kompetenznachweise") }}</h2>
|
||||||
|
|
||||||
<div class="mb-4 bg-white p-8">
|
<div class="mb-4 bg-white p-8">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
|
|
||||||
|
|
@ -168,13 +168,16 @@ const performanceCriteriaStatusCount = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- <router-link-->
|
|
||||||
<!-- :to="`${competenceStore.competenceProfilePage()?.frontend_url}-old/criteria`"-->
|
<div>
|
||||||
<!-- class="btn-text inline-flex items-center py-2 pl-0"-->
|
<router-link
|
||||||
<!-- >-->
|
:to="`/course/${props.courseSlug}/competence/criteria`"
|
||||||
<!-- <span>{{ $t("general.showAll") }}</span>-->
|
class="btn-text mt-4 inline-flex items-center py-2 pl-0"
|
||||||
<!-- <it-icon-arrow-right></it-icon-arrow-right>-->
|
>
|
||||||
<!-- </router-link>-->
|
<span>{{ $t("general.showAll") }}</span>
|
||||||
|
<it-icon-arrow-right></it-icon-arrow-right>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ function routeInCompetenceCertificate() {
|
||||||
return route.path.includes("/certificate");
|
return route.path.includes("/certificate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function routeInPerformanceCriteria() {
|
||||||
|
return route.path.endsWith("/criteria");
|
||||||
|
}
|
||||||
|
|
||||||
function routeInActionCompetences() {
|
function routeInActionCompetences() {
|
||||||
return route.path.endsWith("/competences");
|
return route.path.endsWith("/competences");
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +62,15 @@ onMounted(async () => {
|
||||||
{{ $t("a.Kompetenznachweise") }}
|
{{ $t("a.Kompetenznachweise") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
<li
|
||||||
|
class="ml-6 inline-block border-t-2 border-t-transparent py-3 lg:ml-12"
|
||||||
|
:class="{ 'border-b-2 border-b-blue-900': routeInPerformanceCriteria() }"
|
||||||
|
>
|
||||||
|
<router-link :to="`/course/${courseSlug}/competence/criteria`">
|
||||||
|
{{ $t("a.Selbsteinschätzungen") }}
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
class="ml-6 inline-block border-t-2 border-t-transparent py-3 lg:ml-12"
|
class="ml-6 inline-block border-t-2 border-t-transparent py-3 lg:ml-12"
|
||||||
:class="{ 'border-b-2 border-b-blue-900': routeInActionCompetences() }"
|
:class="{ 'border-b-2 border-b-blue-900': routeInActionCompetences() }"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useCompetenceStore } from "@/stores/competence";
|
||||||
|
import * as log from "loglevel";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
courseSlug: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("PerformanceCriteriaPage created", props);
|
||||||
|
|
||||||
|
const competenceStore = useCompetenceStore();
|
||||||
|
|
||||||
|
const uniqueLearningUnits = computed(() => {
|
||||||
|
// FIXME: this complex calculation can go away,
|
||||||
|
// once the criteria are in its own learning content
|
||||||
|
// get the learningUnits sorted by circle order in the course
|
||||||
|
const circles = competenceStore.circles.map((c, index) => {
|
||||||
|
return { ...c, sortKey: index };
|
||||||
|
});
|
||||||
|
return _.orderBy(
|
||||||
|
_.uniqBy(
|
||||||
|
competenceStore.flatPerformanceCriteria().map((pc) => {
|
||||||
|
return {
|
||||||
|
luId: pc.learning_unit.id,
|
||||||
|
luTitle: pc.learning_unit.title,
|
||||||
|
circleId: pc.circle.id,
|
||||||
|
circleTitle: pc.circle.title,
|
||||||
|
url: pc.learning_unit.evaluate_url,
|
||||||
|
sortKey: circles.find((c) => c.id === pc.circle.id)?.sortKey,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
"luId"
|
||||||
|
),
|
||||||
|
"sortKey"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const criteriaByLearningUnit = computed(() => {
|
||||||
|
return uniqueLearningUnits.value.map((lu) => {
|
||||||
|
const criteria = competenceStore
|
||||||
|
.flatPerformanceCriteria()
|
||||||
|
.filter((pc) => pc.learning_unit.id === lu.luId);
|
||||||
|
return {
|
||||||
|
...lu,
|
||||||
|
countSuccess: criteria.filter((c) => c.completion_status === "SUCCESS").length,
|
||||||
|
countFail: criteria.filter((c) => c.completion_status === "FAIL").length,
|
||||||
|
countUnknown: criteria.filter((c) => c.completion_status === "UNKNOWN").length,
|
||||||
|
criteria: criteria,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container-large">
|
||||||
|
<h2 class="mb-4 lg:py-4">{{ $t("a.Selbsteinschätzungen") }}</h2>
|
||||||
|
<section class="mb-4 bg-white px-4 py-2">
|
||||||
|
<div
|
||||||
|
v-for="selfEvaluation in criteriaByLearningUnit"
|
||||||
|
:key="selfEvaluation.luId"
|
||||||
|
class="flex items-center justify-between border-b py-4 last:border-b-0"
|
||||||
|
>
|
||||||
|
<div class="w-1/3">
|
||||||
|
{{ $t("a.Circle") }}
|
||||||
|
{{ selfEvaluation.circleTitle }}:
|
||||||
|
{{ selfEvaluation.luTitle }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-4 flex w-1/3 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>
|
||||||
|
<div class="w-6">
|
||||||
|
{{ selfEvaluation.countFail }}
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
<div class="w-6">
|
||||||
|
{{ selfEvaluation.countSuccess }}
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
<div class="w-6">
|
||||||
|
{{ selfEvaluation.countUnknown }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<router-link :to="selfEvaluation.url" class="link">
|
||||||
|
{{ $t("a.Selbsteinschätzung anschauen") }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
// handle route history
|
||||||
|
import type {
|
||||||
|
NavigationGuard,
|
||||||
|
RouteLocationNormalized,
|
||||||
|
RouteLocationRaw,
|
||||||
|
Router,
|
||||||
|
} from "vue-router";
|
||||||
|
|
||||||
|
const routeHistory: RouteLocationNormalized[] = [];
|
||||||
|
const MAX_HISTORY = 10; // for example, store the last 10 visited routes
|
||||||
|
let isFirstNavigation = true;
|
||||||
|
|
||||||
|
export const addToHistory: NavigationGuard = (to, from, next) => {
|
||||||
|
// Add the current route to the history, and ensure it doesn't exceed the maximum length
|
||||||
|
if (isFirstNavigation) {
|
||||||
|
isFirstNavigation = false;
|
||||||
|
} else {
|
||||||
|
routeHistory.push(from);
|
||||||
|
}
|
||||||
|
if (routeHistory.length > MAX_HISTORY) {
|
||||||
|
routeHistory.shift();
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export function routerBackOrFallback(router: Router, fallbackRoute: RouteLocationRaw) {
|
||||||
|
// Check the latest route in history
|
||||||
|
const previousRoute = routeHistory[routeHistory.length - 1];
|
||||||
|
if (previousRoute) {
|
||||||
|
router.back();
|
||||||
|
} else {
|
||||||
|
router.push(fallbackRoute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
redirectToLoginIfRequired,
|
redirectToLoginIfRequired,
|
||||||
updateLoggedIn,
|
updateLoggedIn,
|
||||||
} from "@/router/guards";
|
} from "@/router/guards";
|
||||||
|
import { addToHistory } from "@/router/history";
|
||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|
@ -84,6 +85,11 @@ const router = createRouter({
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "criteria",
|
||||||
|
props: true,
|
||||||
|
component: () => import("@/pages/competence/PerformanceCriteriaPage.vue"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "competences",
|
path: "competences",
|
||||||
props: true,
|
props: true,
|
||||||
|
|
@ -226,4 +232,6 @@ router.beforeEach(redirectToLoginIfRequired);
|
||||||
// register after login hooks
|
// register after login hooks
|
||||||
router.beforeEach(handleCourseSessions);
|
router.beforeEach(handleCourseSessions);
|
||||||
|
|
||||||
|
router.beforeEach(addToHistory);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import * as log from "loglevel";
|
import { routerBackOrFallback } from "@/router/history";
|
||||||
|
|
||||||
import type { Circle } from "@/services/circle";
|
import type { Circle } from "@/services/circle";
|
||||||
import { useCompletionStore } from "@/stores/completion";
|
import { useCompletionStore } from "@/stores/completion";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
|
|
@ -11,6 +10,7 @@ import type {
|
||||||
LearningUnitPerformanceCriteria,
|
LearningUnitPerformanceCriteria,
|
||||||
PerformanceCriteria,
|
PerformanceCriteria,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
|
import * as log from "loglevel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
export type CircleStoreState = {
|
export type CircleStoreState = {
|
||||||
|
|
@ -128,7 +128,7 @@ export const useCircleStore = defineStore({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
closeLearningContent(learningContent: LearningContentInterface) {
|
closeLearningContent(learningContent: LearningContentInterface) {
|
||||||
this.router.push({
|
routerBackOrFallback(this.router, {
|
||||||
path: `${this.circle?.frontend_url}`,
|
path: `${this.circle?.frontend_url}`,
|
||||||
hash: createLearningUnitHash(learningContent.parentLearningUnit),
|
hash: createLearningUnitHash(learningContent.parentLearningUnit),
|
||||||
});
|
});
|
||||||
|
|
@ -139,7 +139,7 @@ export const useCircleStore = defineStore({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
closeSelfEvaluation(learningUnit: LearningUnit) {
|
closeSelfEvaluation(learningUnit: LearningUnit) {
|
||||||
this.router.push({
|
routerBackOrFallback(this.router, {
|
||||||
path: `${this.circle?.frontend_url}`,
|
path: `${this.circle?.frontend_url}`,
|
||||||
hash: createLearningUnitHash(learningUnit),
|
hash: createLearningUnitHash(learningUnit),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export type CompetenceStoreState = {
|
||||||
|
|
||||||
selectedCircle: { id: string; name: string };
|
selectedCircle: { id: string; name: string };
|
||||||
availableCircles: { id: string; name: string }[];
|
availableCircles: { id: string; name: string }[];
|
||||||
|
circles: CircleLight[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCompetenceStore = defineStore({
|
export const useCompetenceStore = defineStore({
|
||||||
|
|
@ -28,6 +29,7 @@ export const useCompetenceStore = defineStore({
|
||||||
competenceProfilePages: new Map<string, CompetenceProfilePage>(),
|
competenceProfilePages: new Map<string, CompetenceProfilePage>(),
|
||||||
selectedCircle: { id: "all", name: `Circle: ${i18next.t("Alle")}` },
|
selectedCircle: { id: "all", name: `Circle: ${i18next.t("Alle")}` },
|
||||||
availableCircles: [],
|
availableCircles: [],
|
||||||
|
circles: [],
|
||||||
} as CompetenceStoreState;
|
} as CompetenceStoreState;
|
||||||
},
|
},
|
||||||
getters: {},
|
getters: {},
|
||||||
|
|
@ -153,6 +155,7 @@ export const useCompetenceStore = defineStore({
|
||||||
|
|
||||||
this.competenceProfilePages.set(userId, cloneDeep(competenceProfilePage));
|
this.competenceProfilePages.set(userId, cloneDeep(competenceProfilePage));
|
||||||
|
|
||||||
|
this.circles = competenceProfilePage.circles;
|
||||||
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}` };
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue