Add single criteria view

This commit is contained in:
Christian Cueni 2022-10-20 08:04:35 +02:00
parent e653f915fe
commit bbc67a8526
11 changed files with 296 additions and 148 deletions

View File

@ -8,9 +8,17 @@ import ItDropdown from "@/components/ui/ItDropdown.vue";
import { useAppStore } from "@/stores/app"; import { useAppStore } from "@/stores/app";
import { useLearningPathStore } from "@/stores/learningPath"; import { useLearningPathStore } from "@/stores/learningPath";
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import type { DropdownListItem } from "@/types";
import type { Component } from "vue";
import { onMounted, reactive } from "vue"; import { onMounted, reactive } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
type DropdownActions = "logout" | "settings";
interface DropdownData {
action: DropdownActions;
}
log.debug("MainNavigationBar created"); log.debug("MainNavigationBar created");
const route = useRoute(); const route = useRoute();
@ -46,7 +54,7 @@ function learninPathSlug(): string {
return getLearningPathStringProp("slug"); return getLearningPathStringProp("slug");
} }
function handleDropdownSelect(data) { function handleDropdownSelect(data: DropdownData) {
log.debug("Selected action:", data.action); log.debug("Selected action:", data.action);
switch (data.action) { switch (data.action) {
case "settings": case "settings":
@ -56,7 +64,7 @@ function handleDropdownSelect(data) {
userStore.handleLogout(); userStore.handleLogout();
break; break;
default: default:
console.log("no action"); console.log("No action");
} }
} }
@ -68,11 +76,11 @@ onMounted(() => {
log.debug("MainNavigationBar mounted"); log.debug("MainNavigationBar mounted");
}); });
const profileDropdownData = [ const profileDropdownData: DropdownListItem[][] = [
[ [
{ {
title: "Kontoeinstellungen", title: "Kontoeinstellungen",
icon: IconSettings, icon: IconSettings as Component,
data: { data: {
action: "settings", action: "settings",
}, },
@ -81,7 +89,7 @@ const profileDropdownData = [
[ [
{ {
title: "Abmelden", title: "Abmelden",
icon: IconLogout, icon: IconLogout as Component,
data: { data: {
action: "logout", action: "logout",
}, },

View File

@ -21,17 +21,18 @@ const togglePerformanceCriteria = () => {
<template> <template>
<div> <div>
<div :class="{ 'pb-8 border-b mb-4': isOpen }" class="-mx-8 px-8"> <div :class="{ 'pb-8 border-b mb-4': isOpen }" class="-mx-8 px-8">
<div class="mb-4 flex flex-row justify-between items-center"> <div
class="mb-4 flex flex-row justify-between items-center"
role="button"
aria-pressed="false"
@click="togglePerformanceCriteria()"
>
<h2 class="text-large"> <h2 class="text-large">
{{ competence.competence_id }} {{ competence.title }} {{ competence.competence_id }} {{ competence.title }}
</h2> </h2>
<button class="transition-transform" :class="{ 'rotate-180': isOpen }"> <div class="transition-transform" :class="{ 'rotate-180': isOpen }">
<it-icon-arrow-down <it-icon-arrow-down class="h-10 w-10" aria-hidden="true" />
class="h-10 w-10" </div>
aria-hidden="true"
@click="togglePerformanceCriteria()"
/>
</button>
</div> </div>
<ComptenceProgress <ComptenceProgress
:status-count=" :status-count="

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PerformanceCriteria } from "@/types"; import type { PerformanceCriteria } from "@/types";
import { useRoute } from "vue-router";
interface Props { interface Props {
criteria: PerformanceCriteria; criteria: PerformanceCriteria;
@ -10,6 +11,9 @@ const props = withDefaults(defineProps<Props>(), {
criteria: undefined, criteria: undefined,
showState: false, showState: false,
}); });
const route = useRoute();
const profilePageSlug = route.params["competenceProfilePageSlug"];
</script> </script>
<template> <template>
@ -37,7 +41,10 @@ const props = withDefaults(defineProps<Props>(), {
</div> </div>
</div> </div>
<span class="whitespace-nowrap"> <span class="whitespace-nowrap">
<router-link class="link" :to="criteria.learning_unit.evaluate_url"> <router-link
class="link"
:to="`/competence/${profilePageSlug}/criteria/${criteria.slug}`"
>
Sich nochmals einschätzen Sich nochmals einschätzen
</router-link> </router-link>
</span> </span>

View File

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import LearningContentContainer from "@/components/learningPath/LearningContentContainer.vue";
import { useCircleStore } from "@/stores/circle"; import { useCircleStore } from "@/stores/circle";
import type { LearningContent } from "@/types"; import type { LearningContent } from "@/types";
import * as log from "loglevel"; import * as log from "loglevel";
@ -26,75 +27,58 @@ const block = computed(() => {
<template> <template>
<div v-if="block"> <div v-if="block">
<nav class="px-4 lg:px-8 py-4 flex justify-between items-center border-b"> <LearningContentContainer
<button :title="learningContent.title"
type="button" next-button-text="Abschliessen und weiter"
class="btn-text inline-flex items-center px-3 py-2" back-text="zurück zum Circle"
data-cy="close-learning-content" @back="circleStore.closeLearningContent(props.learningContent)"
@click="circleStore.closeLearningContent(props.learningContent)" @next="circleStore.continueFromLearningContent(props.learningContent)"
> >
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left> <div v-if="block.type === 'exercise' || block.type === 'test'" class="h-screen">
<span class="hidden lg:inline">zurück zum Circle</span> <iframe width="100%" height="100%" scrolling="no" :src="block.value.url" />
</button> </div>
<h1 class="text-large hidden lg:block" data-cy="ln-title"> <div v-else class="container-medium">
{{ learningContent.title }} <div v-if="block.type === 'video'">
</h1> <iframe
class="mt-8 w-full aspect-video"
:src="block.value.url"
:title="learningContent.title"
frameborder="0"
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
>
</iframe>
</div>
<button <div v-else-if="block.type === 'media_library'" class="mt-4 lg:mt-12">
type="button" <h1>{{ learningContent.title }}</h1>
class="btn-blue"
data-cy="complete-and-continue"
@click="circleStore.continueFromLearningContent(props.learningContent)"
>
Abschliessen und weiter
</button>
</nav>
<div v-if="block.type === 'exercise' || block.type === 'test'" class="h-screen"> <p class="text-large my-4 lg:my-8">{{ block.value.description }}</p>
<iframe width="100%" height="100%" scrolling="no" :src="block.value.url" /> <router-link
</div> :to="`${block.value.url}?back=${route.path}`"
class="button btn-primary"
>
Mediathek öffnen
</router-link>
</div>
<div v-else class="container-medium"> <div
<div v-if="block.type === 'video'"> v-else-if="block.type === 'resource' || block.type === 'assignment'"
<iframe class="mt-4 lg:mt-12"
class="mt-8 w-full aspect-video"
:src="block.value.url"
:title="learningContent.title"
frameborder="0"
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
> >
</iframe> <p class="text-large my-4">{{ block.value.description }}</p>
<div class="resource-text" v-html="block.value.text"></div>
</div>
<div v-else-if="block.type === 'placeholder'" class="mt-4 lg:mt-12">
<p class="text-large my-4">{{ block.value.description }}</p>
<h1>{{ learningContent.title }}</h1>
</div>
<div v-else class="text-large my-4">{{ block.value.description }}</div>
</div> </div>
</LearningContentContainer>
<div v-else-if="block.type === 'media_library'" class="mt-4 lg:mt-12">
<h1>{{ learningContent.title }}</h1>
<p class="text-large my-4 lg:my-8">{{ block.value.description }}</p>
<router-link
:to="`${block.value.url}?back=${route.path}`"
class="button btn-primary"
>
Mediathek öffnen
</router-link>
</div>
<div
v-else-if="block.type === 'resource' || block.type === 'assignment'"
class="mt-4 lg:mt-12"
>
<p class="text-large my-4">{{ block.value.description }}</p>
<div class="resource-text" v-html="block.value.text"></div>
</div>
<div v-else-if="block.type === 'placeholder'" class="mt-4 lg:mt-12">
<p class="text-large my-4">{{ block.value.description }}</p>
<h1>{{ learningContent.title }}</h1>
</div>
<div v-else class="text-large my-4">{{ block.value.description }}</div>
</div>
</div> </div>
</template> </template>

View File

@ -0,0 +1,44 @@
<script setup lang="ts">
import * as log from "loglevel";
log.debug("LeariningContentContainer.vue setup");
const props = defineProps<{
backText: string;
title: string;
nextButtonText: string;
}>();
const emit = defineEmits(["back", "next"]);
</script>
<template>
<div>
<nav class="px-4 lg:px-8 py-4 flex justify-between items-center border-b">
<button
type="button"
class="btn-text inline-flex items-center px-3 py-2"
@click="$emit('back')"
>
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
<span class="hidden lg:inline">{{ backText }}</span>
</button>
<h1 class="text-large hidden lg:block" data-cy="ln-title">
{{ title }}
</h1>
<button
type="button"
class="btn-blue"
data-cy="complete-and-continue"
@click="$emit('next')"
>
{{ nextButtonText }}
</button>
</nav>
<slot></slot>
</div>
</template>
<style scoped></style>

View File

@ -2,6 +2,8 @@
import { useCircleStore } from "@/stores/circle"; import { useCircleStore } from "@/stores/circle";
import type { LearningUnit } from "@/types"; import type { LearningUnit } from "@/types";
import * as log from "loglevel"; import * as log from "loglevel";
import LearningContentContainer from "@/components/learningPath/LearningContentContainer.vue";
import { computed, reactive } from "vue"; import { computed, reactive } from "vue";
log.debug("LearningContent.vue setup"); log.debug("LearningContent.vue setup");
@ -33,79 +35,63 @@ function handleContinue() {
<template> <template>
<div v-if="learningUnit"> <div v-if="learningUnit">
<nav class="px-4 lg:px-8 py-4 flex justify-between items-center border-b"> <LearningContentContainer
<button :title="`Selbsteinschätzung ${learningUnit.title}`"
type="button" :back-text="'zurück zum Circle'"
class="btn-text inline-flex items-center px-3 py-2" :next-button-text="'Weiter'"
@click="circleStore.closeSelfEvaluation(props.learningUnit)" @back="circleStore.closeSelfEvaluation(props.learningUnit)"
> @next="handleContinue()"
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left> >
<span class="hidden lg:inline">zurück zum Circle</span> <div class="container-medium">
</button> <div class="mt-2 lg:mt-8 text-gray-700">
Schritt {{ state.questionIndex + 1 }} von {{ questions.length }}
<h1 class="text-large hidden lg:block" data-cy="ln-title">
Selbsteinschätzung {{ learningUnit.title }}
</h1>
<button
type="button"
class="btn-blue"
data-cy="complete-and-continue"
@click="handleContinue()"
>
Weiter
</button>
</nav>
<div class="container-medium">
<div class="mt-2 lg:mt-8 text-gray-700">
Schritt {{ state.questionIndex + 1 }} von {{ questions.length }}
</div>
<p class="text-large mt-4">
Überprüfe, ob du in der Lernheinheit
<span class="font-bold">«{{ learningUnit.title }}»</span> alles verstanden
hast.<br />
Lies die folgende Aussage und bewerte sie:
</p>
<div class="mt-4 lg:mt-8 p-6 lg:p-12 border">
<h2 class="heading-2">
{{ currentQuestion.competence_id }} {{ currentQuestion.title }}
</h2>
<div class="mt-4 lg:mt-8 flex flex-col lg:flex-row justify-between gap-6">
<button
class="flex-1 inline-flex items-center text-left p-4 border"
:class="{
'border-green-500': currentQuestion.completion_status === 'success',
'border-2': currentQuestion.completion_status === 'success',
}"
data-cy="success"
@click="circleStore.markCompletion(currentQuestion, 'success')"
>
<it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy>
<span class="font-bold text-large"> Ja, ich kann das. </span>
</button>
<button
class="flex-1 inline-flex items-center text-left p-4 border"
:class="{
'border-orange-500': currentQuestion.completion_status === 'fail',
'border-2': currentQuestion.completion_status === 'fail',
}"
data-cy="fail"
@click="circleStore.markCompletion(currentQuestion, 'fail')"
>
<it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking>
<span class="font-bold text-xl"> Das muss ich nochmals anschauen. </span>
</button>
</div> </div>
<div class="mt-6 lg:mt-12"> <p class="text-large mt-4">
Schau dein Fortschritt in deinem KompetenzNavi: KompetenzNavi öffnen Überprüfe, ob du in der Lernheinheit
<span class="font-bold">«{{ learningUnit.title }}»</span> alles verstanden
hast.<br />
Lies die folgende Aussage und bewerte sie:
</p>
<div class="mt-4 lg:mt-8 p-6 lg:p-12 border">
<h2 class="heading-2">
{{ currentQuestion.competence_id }} {{ currentQuestion.title }}
</h2>
<div class="mt-4 lg:mt-8 flex flex-col lg:flex-row justify-between gap-6">
<button
class="flex-1 inline-flex items-center text-left p-4 border"
:class="{
'border-green-500': currentQuestion.completion_status === 'success',
'border-2': currentQuestion.completion_status === 'success',
}"
data-cy="success"
@click="circleStore.markCompletion(currentQuestion, 'success')"
>
<it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy>
<span class="font-bold text-large"> Ja, ich kann das. </span>
</button>
<button
class="flex-1 inline-flex items-center text-left p-4 border"
:class="{
'border-orange-500': currentQuestion.completion_status === 'fail',
'border-2': currentQuestion.completion_status === 'fail',
}"
data-cy="fail"
@click="circleStore.markCompletion(currentQuestion, 'fail')"
>
<it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking>
<span class="font-bold text-xl"> Das muss ich nochmals anschauen. </span>
</button>
</div>
<div class="mt-6 lg:mt-12">
Schau dein Fortschritt in deinem KompetenzNavi: KompetenzNavi öffnen
</div>
</div> </div>
</div> </div>
</div> </LearningContentContainer>
</div> </div>
</template> </template>

View File

@ -1,9 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownListItem } from "@/types";
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue"; import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue";
const props = defineProps<{ const props = defineProps<{
buttonClasses: [string]; buttonClasses: [string];
listItems: [[object]]; listItems: [[DropdownListItem]];
align: "left" | "right"; align: "left" | "right";
}>(); }>();

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from "@headlessui/vue"; import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from "@headlessui/vue";
import { computed, defineEmits } from "vue"; import { computed } from "vue";
interface DropdownSelectable { interface DropdownSelectable {
id: number | string; id: number | string;

View File

@ -0,0 +1,104 @@
<script setup lang="ts">
import { useCircleStore } from "@/stores/circle";
import { useCompetenceStore } from "@/stores/competence";
import type { CompetencePage, LearningUnit, PerformanceCriteria } from "@/types";
import * as log from "loglevel";
import LearningContentContainer from "@/components/learningPath/LearningContentContainer.vue";
import { useRoute, useRouter } from "vue-router";
log.debug("SinglePerformanceCriteriaPage.vue setup");
const competenceStore = useCompetenceStore();
const circleStore = useCircleStore();
const route = useRoute();
const router = useRouter();
let currentQuestion: PerformanceCriteria | undefined;
let competencePage: CompetencePage | undefined;
const findCriteria = () => {
for (const page of competenceStore.competenceProfilePage
?.children as CompetencePage[]) {
for (let criteria of page.children) {
if (criteria.slug === route.params["criteriaSlug"]) {
currentQuestion = criteria;
competencePage = page;
break;
}
}
if (competencePage) {
break;
}
}
};
findCriteria();
// onMounted(() => {
// console.log(route.params)
// });
// const questions = computed(() => props.learningUnit?.children);
// const currentQuestion = computed(() => questions.value[state.questionIndex]);
//
// function handleContinue() {
// log.debug("handleContinue");
// if (state.questionIndex + 1 < questions.value.length) {
// log.debug("increment questionIndex", state.questionIndex);
// state.questionIndex += 1;
// } else {
// log.debug("continue to next learning content");
// circleStore.continueFromSelfEvaluation(props.learningUnit);
// }
// }
</script>
<template>
<div v-if="competencePage" class="absolute top-0 w-full bottom-0 bg-white">
<LearningContentContainer
:title="''"
:back-text="'zurück'"
:next-button-text="'Speichern'"
@back="router.back()"
@next="router.back()"
>
<div class="container-medium">
<div class="mt-4 lg:mt-8 p-6 lg:p-12 border">
<h2 class="heading-2">
{{ currentQuestion.competence_id }} {{ currentQuestion.title }}
</h2>
<div class="mt-4 lg:mt-8 flex flex-col lg:flex-row justify-between gap-6">
<button
class="flex-1 inline-flex items-center text-left p-4 border"
:class="{
'border-green-500': currentQuestion.completion_status === 'success',
'border-2': currentQuestion.completion_status === 'success',
}"
data-cy="success"
@click="circleStore.markCompletion(currentQuestion, 'success')"
>
<it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy>
<span class="font-bold text-large"> Ja, ich kann das. </span>
</button>
<button
class="flex-1 inline-flex items-center text-left p-4 border"
:class="{
'border-orange-500': currentQuestion.completion_status === 'fail',
'border-2': currentQuestion.completion_status === 'fail',
}"
data-cy="fail"
@click="circleStore.markCompletion(currentQuestion, 'fail')"
>
<it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking>
<span class="font-bold text-xl"> Das muss ich nochmals anschauen. </span>
</button>
</div>
</div>
</div>
</LearningContentContainer>
</div>
</template>
<style scoped></style>

View File

@ -69,6 +69,11 @@ const router = createRouter({
path: "criteria", path: "criteria",
component: () => import("@/pages/competence/PerformanceCriteriaPage.vue"), component: () => import("@/pages/competence/PerformanceCriteriaPage.vue"),
}, },
{
path: "criteria/:criteriaSlug",
component: () =>
import("@/pages/competence/SinglePerformanceCriteriaPage.vue"),
},
], ],
}, },
{ {

View File

@ -1,4 +1,5 @@
import type { Circle } from "@/services/circle"; import type { Circle } from "@/services/circle";
import type { Component } from "vue";
export type CourseCompletionStatus = "unknown" | "fail" | "success"; export type CourseCompletionStatus = "unknown" | "fail" | "success";
@ -320,3 +321,10 @@ export interface CompetenceProfilePage extends CourseWagtailPage {
circles: CircleLight[]; circles: CircleLight[];
children: CompetencePage[]; children: CompetencePage[];
} }
// dropdown
export interface DropdownListItem {
title: string;
icon: Component;
data: object;
}