Add PerformanceCriteria page
This commit is contained in:
parent
e5d6dd60f6
commit
5dfdd470ae
|
|
@ -2,7 +2,7 @@
|
|||
<li
|
||||
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>
|
||||
</div>
|
||||
<div class="flex flex-1 items-center">
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ function setActiveClasses(translationKey: string) {
|
|||
<it-icon-smiley-thinking
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-thinking>
|
||||
<p class="text-bold inline-block">
|
||||
<p class="text-bold inline-block w-6">
|
||||
{{ userCountStatusForCircle(csu.user_id, circle).FAIL }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -202,7 +202,7 @@ function setActiveClasses(translationKey: string) {
|
|||
<it-icon-smiley-happy
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-happy>
|
||||
<p class="text-bold inline-block">
|
||||
<p class="text-bold inline-block w-6">
|
||||
{{ userCountStatusForCircle(csu.user_id, circle).SUCCESS }}
|
||||
</p>
|
||||
</li>
|
||||
|
|
@ -210,7 +210,7 @@ function setActiveClasses(translationKey: string) {
|
|||
<it-icon-smiley-neutral
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-neutral>
|
||||
<p class="text-bold inline-block">
|
||||
<p class="text-bold inline-block w-6">
|
||||
{{ userCountStatusForCircle(csu.user_id, circle).UNKNOWN }}
|
||||
</p>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-large lg:mt-4">
|
||||
<nav class="lg:pb-4">
|
||||
<div class="container-large">
|
||||
<nav class="py-4">
|
||||
<router-link
|
||||
class="btn-text inline-flex items-center pl-0"
|
||||
:to="`/course/${props.courseSlug}/competence/certificates`"
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-large lg:mt-4">
|
||||
<h2 class="mb-4">{{ $t("a.Kompetenznachweise") }}</h2>
|
||||
<div class="container-large">
|
||||
<h2 class="mb-4 lg:py-4">{{ $t("a.Kompetenznachweise") }}</h2>
|
||||
|
||||
<div class="mb-4 bg-white p-8">
|
||||
<div class="flex items-center">
|
||||
|
|
|
|||
|
|
@ -168,13 +168,16 @@ const performanceCriteriaStatusCount = computed(() => {
|
|||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- <router-link-->
|
||||
<!-- :to="`${competenceStore.competenceProfilePage()?.frontend_url}-old/criteria`"-->
|
||||
<!-- class="btn-text inline-flex items-center py-2 pl-0"-->
|
||||
<!-- >-->
|
||||
<!-- <span>{{ $t("general.showAll") }}</span>-->
|
||||
<!-- <it-icon-arrow-right></it-icon-arrow-right>-->
|
||||
<!-- </router-link>-->
|
||||
|
||||
<div>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/competence/criteria`"
|
||||
class="btn-text mt-4 inline-flex items-center py-2 pl-0"
|
||||
>
|
||||
<span>{{ $t("general.showAll") }}</span>
|
||||
<it-icon-arrow-right></it-icon-arrow-right>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ function routeInCompetenceCertificate() {
|
|||
return route.path.includes("/certificate");
|
||||
}
|
||||
|
||||
function routeInPerformanceCriteria() {
|
||||
return route.path.endsWith("/criteria");
|
||||
}
|
||||
|
||||
function routeInActionCompetences() {
|
||||
return route.path.endsWith("/competences");
|
||||
}
|
||||
|
|
@ -58,6 +62,15 @@ onMounted(async () => {
|
|||
{{ $t("a.Kompetenznachweise") }}
|
||||
</router-link>
|
||||
</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
|
||||
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() }"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
updateLoggedIn,
|
||||
} from "@/router/guards";
|
||||
import { addToHistory } from "@/router/history";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
|
||||
const router = createRouter({
|
||||
|
|
@ -84,6 +85,11 @@ const router = createRouter({
|
|||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "criteria",
|
||||
props: true,
|
||||
component: () => import("@/pages/competence/PerformanceCriteriaPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "competences",
|
||||
props: true,
|
||||
|
|
@ -226,4 +232,6 @@ router.beforeEach(redirectToLoginIfRequired);
|
|||
// register after login hooks
|
||||
router.beforeEach(handleCourseSessions);
|
||||
|
||||
router.beforeEach(addToHistory);
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import * as log from "loglevel";
|
||||
|
||||
import { routerBackOrFallback } from "@/router/history";
|
||||
import type { Circle } from "@/services/circle";
|
||||
import { useCompletionStore } from "@/stores/completion";
|
||||
import { useLearningPathStore } from "@/stores/learningPath";
|
||||
|
|
@ -11,6 +10,7 @@ import type {
|
|||
LearningUnitPerformanceCriteria,
|
||||
PerformanceCriteria,
|
||||
} from "@/types";
|
||||
import * as log from "loglevel";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export type CircleStoreState = {
|
||||
|
|
@ -128,7 +128,7 @@ export const useCircleStore = defineStore({
|
|||
});
|
||||
},
|
||||
closeLearningContent(learningContent: LearningContentInterface) {
|
||||
this.router.push({
|
||||
routerBackOrFallback(this.router, {
|
||||
path: `${this.circle?.frontend_url}`,
|
||||
hash: createLearningUnitHash(learningContent.parentLearningUnit),
|
||||
});
|
||||
|
|
@ -139,7 +139,7 @@ export const useCircleStore = defineStore({
|
|||
});
|
||||
},
|
||||
closeSelfEvaluation(learningUnit: LearningUnit) {
|
||||
this.router.push({
|
||||
routerBackOrFallback(this.router, {
|
||||
path: `${this.circle?.frontend_url}`,
|
||||
hash: createLearningUnitHash(learningUnit),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export type CompetenceStoreState = {
|
|||
|
||||
selectedCircle: { id: string; name: string };
|
||||
availableCircles: { id: string; name: string }[];
|
||||
circles: CircleLight[];
|
||||
};
|
||||
|
||||
export const useCompetenceStore = defineStore({
|
||||
|
|
@ -28,6 +29,7 @@ export const useCompetenceStore = defineStore({
|
|||
competenceProfilePages: new Map<string, CompetenceProfilePage>(),
|
||||
selectedCircle: { id: "all", name: `Circle: ${i18next.t("Alle")}` },
|
||||
availableCircles: [],
|
||||
circles: [],
|
||||
} as CompetenceStoreState;
|
||||
},
|
||||
getters: {},
|
||||
|
|
@ -153,6 +155,7 @@ export const useCompetenceStore = defineStore({
|
|||
|
||||
this.competenceProfilePages.set(userId, cloneDeep(competenceProfilePage));
|
||||
|
||||
this.circles = competenceProfilePage.circles;
|
||||
const circles = competenceProfilePage.circles.map((c: CircleLight) => {
|
||||
return { id: c.translation_key, name: `Circle: ${c.title}` };
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue