Handle closing of self evaluation in KompetenzNavi
This commit is contained in:
parent
5dfdd470ae
commit
dfa03baa25
|
|
@ -1,179 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import PerformanceCriteriaRow from "@/pages/competence-old/PerformanceCriteriaRow.vue";
|
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
|
||||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
|
||||||
import maxBy from "lodash/maxBy";
|
|
||||||
import orderBy from "lodash/orderBy";
|
|
||||||
import * as log from "loglevel";
|
|
||||||
import { computed } from "vue";
|
|
||||||
|
|
||||||
log.debug("CompetenceIndexPage created");
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
courseSlug: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
log.debug("CompetenceIndexPage created", props);
|
|
||||||
|
|
||||||
const competenceStore = useCompetenceStore();
|
|
||||||
|
|
||||||
const failedCriteria = computed(() => {
|
|
||||||
return competenceStore
|
|
||||||
.flatPerformanceCriteria()
|
|
||||||
.filter((criteria) => {
|
|
||||||
return criteria.completion_status === "FAIL";
|
|
||||||
})
|
|
||||||
.slice(0, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
const lastUpdatedCompetences = computed(() => {
|
|
||||||
return orderBy(
|
|
||||||
competenceStore.competences(),
|
|
||||||
[
|
|
||||||
(competence) => {
|
|
||||||
let criteria = competence.children;
|
|
||||||
if (competenceStore.selectedCircle.id != "all") {
|
|
||||||
criteria = criteria.filter((criteria) => {
|
|
||||||
return (
|
|
||||||
criteria.circle.translation_key === competenceStore.selectedCircle.id
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
maxBy(criteria, "completion_status_updated_at")
|
|
||||||
?.completion_status_updated_at || ""
|
|
||||||
);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
["desc"]
|
|
||||||
).slice(0, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
const countStatus = computed(() => {
|
|
||||||
return competenceStore.calcStatusCount(competenceStore.flatPerformanceCriteria());
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="container-large lg:mt-4">
|
|
||||||
<div
|
|
||||||
v-if="competenceStore.competenceProfilePage()"
|
|
||||||
class="mb-10 flex flex-col justify-between lg:flex-row lg:items-center"
|
|
||||||
>
|
|
||||||
<h1>{{ $t("competences.title") }}</h1>
|
|
||||||
<ItDropdownSelect
|
|
||||||
v-model="competenceStore.selectedCircle"
|
|
||||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
|
||||||
:items="competenceStore.availableCircles"
|
|
||||||
></ItDropdownSelect>
|
|
||||||
</div>
|
|
||||||
<div class="l mb-4 bg-white px-8 py-4 lg:mb-8 lg:py-8">
|
|
||||||
<div>
|
|
||||||
<h3 class="border-b pb-4">{{ $t("competences.lastImprovements") }}</h3>
|
|
||||||
<ul>
|
|
||||||
<li
|
|
||||||
v-for="competence in lastUpdatedCompetences"
|
|
||||||
:key="competence.id"
|
|
||||||
class="flex flex-col border-b py-4 last:mb-8 lg:flex-row lg:items-center"
|
|
||||||
>
|
|
||||||
<p class="mb-4 inline-block lg:mb-0 lg:mr-5 lg:w-1/4">
|
|
||||||
{{ competence.competence_id }} {{ competence.title }}
|
|
||||||
</p>
|
|
||||||
<ItProgress
|
|
||||||
:status-count="
|
|
||||||
competenceStore.calcStatusCount(
|
|
||||||
competenceStore.criteriaByCompetence(competence)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
></ItProgress>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<router-link
|
|
||||||
:to="`${
|
|
||||||
competenceStore.competenceProfilePage()?.frontend_url
|
|
||||||
}-old/competences`"
|
|
||||||
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>
|
|
||||||
</div>
|
|
||||||
<div class="l mb-4 bg-white px-8 py-4 lg:mb-8 lg:py-8">
|
|
||||||
<h3 class="mb-4 border-b pb-4 lg:border-0 lg:pb-0">
|
|
||||||
{{ $t("competences.assessment") }}
|
|
||||||
</h3>
|
|
||||||
<ul
|
|
||||||
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
class="mb-4 inline-block flex-1 border-b pb-4 lg:mb-0 lg:border-b-0 lg:border-r lg:pb-0"
|
|
||||||
>
|
|
||||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.no") }}»</h5>
|
|
||||||
<div class="flex flex-row items-center">
|
|
||||||
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
|
||||||
<p class="ml-4 inline-block text-7xl font-bold">{{ countStatus.FAIL }}</p>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
class="mb-4 inline-block flex-1 border-b pb-4 lg:mb-0 lg:border-b-0 lg:border-r lg:pb-0"
|
|
||||||
>
|
|
||||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.yes") }}»</h5>
|
|
||||||
<div class="flex flex-row items-center">
|
|
||||||
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
|
||||||
<p class="ml-4 inline-block text-7xl font-bold">
|
|
||||||
{{ countStatus.SUCCESS }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="lg:mb-0flex-1 border-b pb-4 lg:border-b-0 lg:pb-0">
|
|
||||||
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
|
||||||
<div class="flex flex-row items-center">
|
|
||||||
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
|
||||||
<p class="ml-4 inline-block text-7xl font-bold">
|
|
||||||
{{ countStatus.UNKNOWN }}
|
|
||||||
</p>
|
|
||||||
</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>
|
|
||||||
<div
|
|
||||||
v-if="failedCriteria.length > 0"
|
|
||||||
class="l mb-4 bg-white px-8 py-4 lg:mb-8 lg:py-8"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex flex-row items-center border-b pb-4">
|
|
||||||
<it-icon-smiley-thinking class="mr-5 h-11 w-11"></it-icon-smiley-thinking>
|
|
||||||
<h3>«{{ $t("selfEvaluation.no") }}»</h3>
|
|
||||||
</div>
|
|
||||||
<ul class="mb-6">
|
|
||||||
<li
|
|
||||||
v-for="criteria in failedCriteria"
|
|
||||||
:key="criteria.id"
|
|
||||||
class="mb-4 border-b pb-4"
|
|
||||||
>
|
|
||||||
<PerformanceCriteriaRow
|
|
||||||
:criteria="criteria"
|
|
||||||
:course-slug="props.courseSlug"
|
|
||||||
></PerformanceCriteriaRow>
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
|
||||||
import * as log from "loglevel";
|
|
||||||
import { onMounted } from "vue";
|
|
||||||
|
|
||||||
log.debug("CompetenceParentPage created");
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
courseSlug: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const competenceStore = useCompetenceStore();
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
log.debug("CompetenceParentPage mounted", props.courseSlug);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const competencePageSlug = props.courseSlug + "-competence";
|
|
||||||
await competenceStore.loadCompetenceProfilePage(competencePageSlug);
|
|
||||||
} catch (error) {
|
|
||||||
log.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="bg-gray-200">
|
|
||||||
<main>
|
|
||||||
<router-view></router-view>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { default as PerformanceCriteriaRow } from "@/pages/competence-old/PerformanceCriteriaRow.vue";
|
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
|
||||||
import type { CourseCompletionStatus } from "@/types";
|
|
||||||
import * as log from "loglevel";
|
|
||||||
import type { Ref } from "vue";
|
|
||||||
import { computed, ref } from "vue";
|
|
||||||
import { useTranslation } from "i18next-vue";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
courseSlug: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
interface MenuItem {
|
|
||||||
id: CourseCompletionStatus;
|
|
||||||
name: string;
|
|
||||||
iconName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("PerformanceCriteriaPage created", props);
|
|
||||||
|
|
||||||
const competenceStore = useCompetenceStore();
|
|
||||||
|
|
||||||
const shownCriteria = computed(() => {
|
|
||||||
return competenceStore.flatPerformanceCriteria().filter((criteria) => {
|
|
||||||
return criteria.completion_status === activeMenuItem.value.id;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const mobileMenuItems: MenuItem[] = [
|
|
||||||
{
|
|
||||||
id: "FAIL",
|
|
||||||
name: `«${t("selfEvaluation.no")}»`,
|
|
||||||
iconName: "it-icon-smiley-thinking",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "SUCCESS",
|
|
||||||
name: `«${t("selfEvaluation.yes")}»`,
|
|
||||||
iconName: "it-icon-smiley-happy",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "UNKNOWN",
|
|
||||||
name: t("competences.notAssessed"),
|
|
||||||
iconName: "it-icon-smiley-neutral",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const activeMenuItem: Ref<MenuItem> = ref(mobileMenuItems[0]);
|
|
||||||
|
|
||||||
function updateActiveState(status: CourseCompletionStatus) {
|
|
||||||
activeMenuItem.value =
|
|
||||||
mobileMenuItems.find((item) => status === item.id) || mobileMenuItems[0];
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="container-large">
|
|
||||||
<nav class="py-4 lg:pb-8">
|
|
||||||
<router-link
|
|
||||||
class="btn-text inline-flex items-center pl-0"
|
|
||||||
:to="`${competenceStore.competenceProfilePage()?.frontend_url}-old`"
|
|
||||||
>
|
|
||||||
<it-icon-arrow-left />
|
|
||||||
<span>{{ $t("general.back") }}</span>
|
|
||||||
</router-link>
|
|
||||||
</nav>
|
|
||||||
<div class="mb-4 flex flex-col items-center justify-between lg:mb-10 lg:flex-row">
|
|
||||||
<h1>Einschätzungen</h1>
|
|
||||||
<ItDropdownSelect
|
|
||||||
v-model="competenceStore.selectedCircle"
|
|
||||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
|
||||||
:items="competenceStore.availableCircles"
|
|
||||||
></ItDropdownSelect>
|
|
||||||
<div class="w-full lg:hidden">
|
|
||||||
<ItDropdownSelect
|
|
||||||
v-model="activeMenuItem"
|
|
||||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
|
||||||
:items="mobileMenuItems"
|
|
||||||
></ItDropdownSelect>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white p-8">
|
|
||||||
<div
|
|
||||||
class="mb-4 flex hidden flex-col justify-between border-b pb-4 lg:block lg:flex-row lg:items-center"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-for="item in mobileMenuItems"
|
|
||||||
:key="item.id"
|
|
||||||
:class="{
|
|
||||||
'bg-gray-200': activeMenuItem.id === item.id,
|
|
||||||
'mr-6': item.id !== 'UNKNOWN',
|
|
||||||
}"
|
|
||||||
class="mr-6 inline-block px-2 py-4"
|
|
||||||
@click="updateActiveState(item.id)"
|
|
||||||
>
|
|
||||||
<div class="flex flex-row items-center">
|
|
||||||
<span class="mr-2 inline-block">{{ item.name }}</span>
|
|
||||||
<component :is="item.iconName"></component>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<ul class="mb-6">
|
|
||||||
<li
|
|
||||||
v-for="criteria in shownCriteria"
|
|
||||||
:key="criteria.id"
|
|
||||||
class="mb-4 border-b pb-4"
|
|
||||||
>
|
|
||||||
<PerformanceCriteriaRow
|
|
||||||
:criteria="criteria"
|
|
||||||
:show-state="true"
|
|
||||||
:course-slug="props.courseSlug"
|
|
||||||
></PerformanceCriteriaRow>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useCircleStore } from "@/stores/circle";
|
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
|
||||||
import * as log from "loglevel";
|
|
||||||
|
|
||||||
import LearningContentContainer from "@/pages/learningPath/learningContentPage/LearningContentContainer.vue";
|
|
||||||
import { computed } from "vue";
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
courseSlug: string;
|
|
||||||
criteriaSlug: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
log.debug("SinglePerformanceCriteriaPage.vue setup");
|
|
||||||
|
|
||||||
const competenceStore = useCompetenceStore();
|
|
||||||
const circleStore = useCircleStore();
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const singleCriteria = computed(() => {
|
|
||||||
return competenceStore.flatPerformanceCriteria().find((criteria) => {
|
|
||||||
return criteria.slug === props.criteriaSlug;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="singleCriteria" class="absolute bottom-0 top-0 w-full bg-white">
|
|
||||||
<LearningContentContainer @exit="router.back()">
|
|
||||||
<div v-if="singleCriteria" class="container-medium">
|
|
||||||
<div class="mt-4 border p-6 lg:mt-8 lg:p-12">
|
|
||||||
<h2 class="heading-2">
|
|
||||||
{{ singleCriteria.competence_id }} {{ singleCriteria.title }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="mt-4 flex flex-col justify-between gap-6 lg:mt-8 lg:flex-row">
|
|
||||||
<button
|
|
||||||
class="inline-flex flex-1 items-center border p-4 text-left"
|
|
||||||
:class="{
|
|
||||||
'border-green-500': singleCriteria.completion_status === 'SUCCESS',
|
|
||||||
'border-2': singleCriteria.completion_status === 'SUCCESS',
|
|
||||||
}"
|
|
||||||
data-cy="success"
|
|
||||||
@click="circleStore.markCompletion(singleCriteria, 'SUCCESS')"
|
|
||||||
>
|
|
||||||
<it-icon-smiley-happy class="mr-4 h-16 w-16"></it-icon-smiley-happy>
|
|
||||||
<span class="text-large font-bold">{{ $t("selfEvaluation.yes") }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="inline-flex flex-1 items-center border p-4 text-left"
|
|
||||||
:class="{
|
|
||||||
'border-orange-500': singleCriteria.completion_status === 'FAIL',
|
|
||||||
'border-2': singleCriteria.completion_status === 'FAIL',
|
|
||||||
}"
|
|
||||||
data-cy="fail"
|
|
||||||
@click="circleStore.markCompletion(singleCriteria, 'FAIL')"
|
|
||||||
>
|
|
||||||
<it-icon-smiley-thinking class="mr-4 h-16 w-16"></it-icon-smiley-thinking>
|
|
||||||
<span class="text-xl font-bold">{{ $t("selfEvaluation.no") }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</LearningContentContainer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PerformanceCriteriaRow from "@/pages/competence-old/PerformanceCriteriaRow.vue";
|
|
||||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
|
||||||
import ItToggleArrow from "@/components/ui/ItToggleArrow.vue";
|
import ItToggleArrow from "@/components/ui/ItToggleArrow.vue";
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
import { useCompetenceStore } from "@/stores/competence";
|
||||||
import type { CompetencePage } from "@/types";
|
import type { CompetencePage } from "@/types";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { ref } from "vue";
|
import { ref, watch } from "vue";
|
||||||
|
import SinglePerformanceCriteriaRow from "@/pages/competence/SinglePerformanceCriteriaRow.vue";
|
||||||
|
|
||||||
const competenceStore = useCompetenceStore();
|
const competenceStore = useCompetenceStore();
|
||||||
|
|
||||||
|
|
@ -14,17 +13,26 @@ interface Props {
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
showAssessAgain?: boolean;
|
showAssessAgain?: boolean;
|
||||||
isInline?: boolean;
|
isInline?: boolean;
|
||||||
|
allOpen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
showAssessAgain: true,
|
showAssessAgain: true,
|
||||||
isInline: false,
|
isInline: false,
|
||||||
|
allOpen: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
log.debug("PerformanceCriteriaRow created", props);
|
log.debug("PerformanceCriteriaRow created", props);
|
||||||
|
|
||||||
const isOpen = ref(false);
|
const isOpen = ref(false);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.allOpen,
|
||||||
|
(newValue) => {
|
||||||
|
isOpen.value = newValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const togglePerformanceCriteria = () => {
|
const togglePerformanceCriteria = () => {
|
||||||
isOpen.value = !isOpen.value;
|
isOpen.value = !isOpen.value;
|
||||||
};
|
};
|
||||||
|
|
@ -32,7 +40,7 @@ const togglePerformanceCriteria = () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div :class="{ 'mb-4 border-b pb-8': isOpen }" class="-mx-8 px-8">
|
<div class="-mx-8 border-b px-8">
|
||||||
<div
|
<div
|
||||||
class="flex flex-row items-center justify-between"
|
class="flex flex-row items-center justify-between"
|
||||||
:class="props.isInline ? '' : 'mb-4'"
|
:class="props.isInline ? '' : 'mb-4'"
|
||||||
|
|
@ -41,40 +49,27 @@ const togglePerformanceCriteria = () => {
|
||||||
@click="togglePerformanceCriteria()"
|
@click="togglePerformanceCriteria()"
|
||||||
>
|
>
|
||||||
<h2 :class="props.isInline ? ['text-bold', 'w-2/5'] : 'text-large'">
|
<h2 :class="props.isInline ? ['text-bold', 'w-2/5'] : 'text-large'">
|
||||||
{{ competence.competence_id }} {{ competence.title }}
|
{{ competence.title }} ({{ competence.competence_id }})
|
||||||
</h2>
|
</h2>
|
||||||
<ItProgress
|
|
||||||
v-if="isInline"
|
|
||||||
class="w-[330px]"
|
|
||||||
:status-count="
|
|
||||||
competenceStore.calcStatusCount(
|
|
||||||
competenceStore.criteriaByCompetence(competence)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
></ItProgress>
|
|
||||||
<ItToggleArrow :is-open="isOpen" :small="isInline"></ItToggleArrow>
|
<ItToggleArrow :is-open="isOpen" :small="isInline"></ItToggleArrow>
|
||||||
</div>
|
</div>
|
||||||
<ItProgress
|
|
||||||
v-if="!isInline"
|
|
||||||
:status-count="
|
|
||||||
competenceStore.calcStatusCount(
|
|
||||||
competenceStore.criteriaByCompetence(competence)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
></ItProgress>
|
|
||||||
</div>
|
</div>
|
||||||
<ul v-if="isOpen">
|
<ul v-if="isOpen">
|
||||||
|
<li class="my-4 border-b pb-4 font-bold">
|
||||||
|
{{ $t("a.Leistungsziele") }}
|
||||||
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
v-for="performanceCriteria in competenceStore.criteriaByCompetence(competence)"
|
v-for="performanceCriteria in competenceStore.criteriaByCompetence(competence)"
|
||||||
:key="performanceCriteria.id"
|
:key="performanceCriteria.id"
|
||||||
class="mb-4 border-b pb-4 last:border-0"
|
class="my-4 border-b pb-4 last:border-0"
|
||||||
>
|
>
|
||||||
<PerformanceCriteriaRow
|
<SinglePerformanceCriteriaRow
|
||||||
:criteria="performanceCriteria"
|
:criteria="performanceCriteria"
|
||||||
:show-state="true"
|
:show-state="false"
|
||||||
:course-slug="props.courseSlug"
|
:course-slug="props.courseSlug"
|
||||||
:show-assess-again="props.showAssessAgain"
|
:show-assess-again="props.showAssessAgain"
|
||||||
></PerformanceCriteriaRow>
|
></SinglePerformanceCriteriaRow>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import CompetenceDetail from "@/pages/competence/ActionCompetenceDetail.vue";
|
import CompetenceDetail from "@/pages/competence/ActionCompetenceDetail.vue";
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
import { useCompetenceStore } from "@/stores/competence";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
log.debug("CompetenceListPage created");
|
log.debug("CompetenceListPage created");
|
||||||
|
|
||||||
|
|
@ -10,21 +11,45 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const competenceStore = useCompetenceStore();
|
const competenceStore = useCompetenceStore();
|
||||||
|
|
||||||
|
const isOpenAll = ref(false);
|
||||||
|
|
||||||
|
function toggleOpen() {
|
||||||
|
log.debug("toggleOpen");
|
||||||
|
isOpenAll.value = !isOpenAll.value;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container-large">
|
<div class="container-large">
|
||||||
<h2 class="mb-4 lg:py-4">{{ $t("a.Handlungskompetenzen") }}</h2>
|
<h2 class="py-4">{{ $t("a.Handlungskompetenzen") }}</h2>
|
||||||
|
|
||||||
|
<div class="flex justify-between py-8">
|
||||||
|
<div class="w-3/4 text-xl">
|
||||||
|
{{ $t("competence.listPageDescription") }}
|
||||||
|
</div>
|
||||||
|
<div class="whitespace-nowrap">
|
||||||
|
<button class="link" @click="toggleOpen()">
|
||||||
|
<span v-if="isOpenAll">
|
||||||
|
{{ $t("a.Alle zuklappen") }}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ $t("a.Alle aufklappen") }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul v-if="competenceStore.competenceProfilePage()">
|
<ul v-if="competenceStore.competenceProfilePage()">
|
||||||
<li
|
<li
|
||||||
v-for="competence in competenceStore.competences()"
|
v-for="competence in competenceStore.competences()"
|
||||||
:key="competence.id"
|
:key="competence.id"
|
||||||
class="mb-8 bg-white p-8"
|
class="mb-8 bg-white px-8 pt-6"
|
||||||
>
|
>
|
||||||
<CompetenceDetail
|
<CompetenceDetail
|
||||||
:competence="competence"
|
:competence="competence"
|
||||||
:course-slug="props.courseSlug"
|
:course-slug="props.courseSlug"
|
||||||
|
:all-open="isOpenAll"
|
||||||
></CompetenceDetail>
|
></CompetenceDetail>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -26,31 +26,17 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
></it-icon-smiley-thinking>
|
></it-icon-smiley-thinking>
|
||||||
<it-icon-smiley-neutral v-else></it-icon-smiley-neutral>
|
<it-icon-smiley-neutral v-else></it-icon-smiley-neutral>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4 pr-5 lg:mb-0 lg:mr-10">
|
<div class="mb-4 pr-4 lg:mb-0 lg:mr-8">
|
||||||
<h4 class="text-bold mb-2">
|
{{ criteria.title }}
|
||||||
{{ criteria.competence_id }} {{ criteria.title }}
|
|
||||||
</h4>
|
|
||||||
<p class="hidden lg:block">
|
|
||||||
{{ $t("general.learningUnit") }}:
|
|
||||||
<router-link class="link" :to="criteria.learning_unit.frontend_url">
|
|
||||||
{{ criteria.learning_unit.title }}
|
|
||||||
</router-link>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-2 lg:hidden">
|
|
||||||
{{ $t("general.learningUnit") }}:
|
|
||||||
<router-link class="link" :to="criteria.learning_unit.frontend_url">
|
|
||||||
{{ criteria.learning_unit.title }}
|
|
||||||
</router-link>
|
|
||||||
</p>
|
|
||||||
<span class="whitespace-nowrap">
|
<span class="whitespace-nowrap">
|
||||||
<router-link
|
<router-link
|
||||||
v-if="props.showAssessAgain"
|
v-if="props.showAssessAgain"
|
||||||
class="link"
|
class="link"
|
||||||
:to="`/course/${props.courseSlug}/competence-old/criteria/${criteria.slug}`"
|
:to="criteria.learning_unit.evaluate_url"
|
||||||
>
|
>
|
||||||
{{ $t("competences.assessAgain") }}
|
{{ $t("competences.assessAgain", { x: criteria.circle.title }) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -16,6 +16,7 @@ import MediaLibraryBlock from "./blocks/MediaLibraryBlock.vue";
|
||||||
import PlaceholderBlock from "./blocks/PlaceholderBlock.vue";
|
import PlaceholderBlock from "./blocks/PlaceholderBlock.vue";
|
||||||
import RichTextBlock from "./blocks/RichTextBlock.vue";
|
import RichTextBlock from "./blocks/RichTextBlock.vue";
|
||||||
import VideoBlock from "./blocks/VideoBlock.vue";
|
import VideoBlock from "./blocks/VideoBlock.vue";
|
||||||
|
import { getPreviousRoute } from "@/router/history";
|
||||||
|
|
||||||
const circleStore = useCircleStore();
|
const circleStore = useCircleStore();
|
||||||
|
|
||||||
|
|
@ -25,6 +26,8 @@ const props = defineProps<{
|
||||||
|
|
||||||
log.debug("LearningContentParent setup", props.learningContent);
|
log.debug("LearningContentParent setup", props.learningContent);
|
||||||
|
|
||||||
|
const previousRoute = getPreviousRoute();
|
||||||
|
|
||||||
// can't use the type as component name, as some are reserved HTML components, e.g. video
|
// can't use the type as component name, as some are reserved HTML components, e.g. video
|
||||||
const COMPONENTS: Record<LearningContentType, Component> = {
|
const COMPONENTS: Record<LearningContentType, Component> = {
|
||||||
"learnpath.LearningContentAssignment": AssignmentBlock,
|
"learnpath.LearningContentAssignment": AssignmentBlock,
|
||||||
|
|
@ -45,7 +48,7 @@ const component = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleFinishedLearningContent() {
|
function handleFinishedLearningContent() {
|
||||||
circleStore.continueFromLearningContent(props.learningContent);
|
circleStore.continueFromLearningContent(props.learningContent, previousRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
eventBus.on("finishedLearningContent", handleFinishedLearningContent);
|
eventBus.on("finishedLearningContent", handleFinishedLearningContent);
|
||||||
|
|
@ -57,7 +60,7 @@ onUnmounted(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LearningContentContainer
|
<LearningContentContainer
|
||||||
@exit="circleStore.closeLearningContent(props.learningContent)"
|
@exit="circleStore.closeLearningContent(props.learningContent, previousRoute)"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<component :is="component" :content="learningContent"></component>
|
<component :is="component" :content="learningContent"></component>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage
|
||||||
import eventBus from "@/utils/eventBus";
|
import eventBus from "@/utils/eventBus";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import { computed, onUnmounted } from "vue";
|
import { computed, onUnmounted } from "vue";
|
||||||
|
import { getPreviousRoute } from "@/router/history";
|
||||||
|
|
||||||
log.debug("LearningContent.vue setup");
|
log.debug("LearningContent.vue setup");
|
||||||
|
|
||||||
|
|
@ -18,6 +19,8 @@ const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
const questionIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" });
|
const questionIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" });
|
||||||
|
|
||||||
|
const previousRoute = getPreviousRoute();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
learningUnit: LearningUnit;
|
learningUnit: LearningUnit;
|
||||||
}>();
|
}>();
|
||||||
|
|
@ -52,7 +55,7 @@ function handleBack() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFinishedLearningContent() {
|
function handleFinishedLearningContent() {
|
||||||
circleStore.closeSelfEvaluation(props.learningUnit);
|
circleStore.closeSelfEvaluation(props.learningUnit, previousRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
eventBus.on("finishedLearningContent", handleFinishedLearningContent);
|
eventBus.on("finishedLearningContent", handleFinishedLearningContent);
|
||||||
|
|
@ -65,7 +68,7 @@ onUnmounted(() => {
|
||||||
<template>
|
<template>
|
||||||
<div v-if="learningUnit">
|
<div v-if="learningUnit">
|
||||||
<LearningContentContainer
|
<LearningContentContainer
|
||||||
@exit="circleStore.closeSelfEvaluation(props.learningUnit)"
|
@exit="circleStore.closeSelfEvaluation(props.learningUnit, previousRoute)"
|
||||||
>
|
>
|
||||||
<LearningContentMultiLayout
|
<LearningContentMultiLayout
|
||||||
:current-step="questionIndex"
|
:current-step="questionIndex"
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,16 @@ export const addToHistory: NavigationGuard = (to, from, next) => {
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getPreviousRoute() {
|
||||||
|
if (routeHistory.length > 0) {
|
||||||
|
return routeHistory[routeHistory.length - 1];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function routerBackOrFallback(router: Router, fallbackRoute: RouteLocationRaw) {
|
export function routerBackOrFallback(router: Router, fallbackRoute: RouteLocationRaw) {
|
||||||
// Check the latest route in history
|
// Check the latest route in history
|
||||||
const previousRoute = routeHistory[routeHistory.length - 1];
|
const previousRoute = getPreviousRoute();
|
||||||
if (previousRoute) {
|
if (previousRoute) {
|
||||||
router.back();
|
router.back();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -97,35 +97,6 @@ const router = createRouter({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
path: "/course/:courseSlug/competence-old",
|
|
||||||
props: true,
|
|
||||||
component: () => import("@/pages/competence-old/CompetenceParentPage.vue"),
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "",
|
|
||||||
props: true,
|
|
||||||
component: () => import("@/pages/competence-old/CompetenceIndexPage.vue"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "competences",
|
|
||||||
props: true,
|
|
||||||
component: () => import("@/pages/competence/ActionCompetenceListPage.vue"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "criteria",
|
|
||||||
props: true,
|
|
||||||
component: () => import("@/pages/competence-old/PerformanceCriteriaPage.vue"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "criteria/:criteriaSlug",
|
|
||||||
props: true,
|
|
||||||
component: () =>
|
|
||||||
import("@/pages/competence-old/SinglePerformanceCriteriaPage.vue"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/course/:courseSlug/learn",
|
path: "/course/:courseSlug/learn",
|
||||||
component: () =>
|
component: () =>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
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";
|
||||||
|
|
@ -12,6 +11,7 @@ import type {
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
import type { RouteLocationNormalized } from "vue-router";
|
||||||
|
|
||||||
export type CircleStoreState = {
|
export type CircleStoreState = {
|
||||||
circle: Circle | undefined;
|
circle: Circle | undefined;
|
||||||
|
|
@ -127,22 +127,36 @@ export const useCircleStore = defineStore({
|
||||||
path: learningContent.frontend_url,
|
path: learningContent.frontend_url,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
closeLearningContent(learningContent: LearningContentInterface) {
|
closeLearningContent(
|
||||||
routerBackOrFallback(this.router, {
|
learningContent: LearningContentInterface,
|
||||||
path: `${this.circle?.frontend_url}`,
|
returnRoute?: RouteLocationNormalized
|
||||||
hash: createLearningUnitHash(learningContent.parentLearningUnit),
|
) {
|
||||||
});
|
if (returnRoute) {
|
||||||
|
this.router.push(returnRoute);
|
||||||
|
} else {
|
||||||
|
this.router.push({
|
||||||
|
path: `${this.circle?.frontend_url}`,
|
||||||
|
hash: createLearningUnitHash(learningContent.parentLearningUnit),
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
openSelfEvaluation(learningUnit: LearningUnit) {
|
openSelfEvaluation(learningUnit: LearningUnit) {
|
||||||
this.router.push({
|
this.router.push({
|
||||||
path: learningUnit.evaluate_url,
|
path: learningUnit.evaluate_url,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
closeSelfEvaluation(learningUnit: LearningUnit) {
|
closeSelfEvaluation(
|
||||||
routerBackOrFallback(this.router, {
|
learningUnit: LearningUnit,
|
||||||
path: `${this.circle?.frontend_url}`,
|
returnRoute?: RouteLocationNormalized
|
||||||
hash: createLearningUnitHash(learningUnit),
|
) {
|
||||||
});
|
if (returnRoute) {
|
||||||
|
this.router.push(returnRoute);
|
||||||
|
} else {
|
||||||
|
this.router.push({
|
||||||
|
path: `${this.circle?.frontend_url}`,
|
||||||
|
hash: createLearningUnitHash(learningUnit),
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
calcSelfEvaluationStatus(learningUnit: LearningUnit): CourseCompletionStatus {
|
calcSelfEvaluationStatus(learningUnit: LearningUnit): CourseCompletionStatus {
|
||||||
if (learningUnit.children.length > 0) {
|
if (learningUnit.children.length > 0) {
|
||||||
|
|
@ -159,7 +173,10 @@ export const useCircleStore = defineStore({
|
||||||
}
|
}
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
},
|
},
|
||||||
continueFromLearningContent(currentLearningContent: LearningContentInterface) {
|
continueFromLearningContent(
|
||||||
|
currentLearningContent: LearningContentInterface,
|
||||||
|
returnRoute?: RouteLocationNormalized
|
||||||
|
) {
|
||||||
if (currentLearningContent) {
|
if (currentLearningContent) {
|
||||||
if (currentLearningContent.can_user_self_toggle_course_completion) {
|
if (currentLearningContent.can_user_self_toggle_course_completion) {
|
||||||
this.markCompletion(currentLearningContent, "SUCCESS");
|
this.markCompletion(currentLearningContent, "SUCCESS");
|
||||||
|
|
@ -167,7 +184,7 @@ export const useCircleStore = defineStore({
|
||||||
// reload completion data anyway
|
// reload completion data anyway
|
||||||
currentLearningContent.parentCircle?.parentLearningPath?.reloadCompletionData();
|
currentLearningContent.parentCircle?.parentLearningPath?.reloadCompletionData();
|
||||||
}
|
}
|
||||||
this.closeLearningContent(currentLearningContent);
|
this.closeLearningContent(currentLearningContent, returnRoute);
|
||||||
} else {
|
} else {
|
||||||
log.error("currentLearningContent is undefined");
|
log.error("currentLearningContent is undefined");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue