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

View File

@ -21,17 +21,18 @@ const togglePerformanceCriteria = () => {
<template>
<div>
<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">
{{ competence.competence_id }} {{ competence.title }}
</h2>
<button class="transition-transform" :class="{ 'rotate-180': isOpen }">
<it-icon-arrow-down
class="h-10 w-10"
aria-hidden="true"
@click="togglePerformanceCriteria()"
/>
</button>
<div class="transition-transform" :class="{ 'rotate-180': isOpen }">
<it-icon-arrow-down class="h-10 w-10" aria-hidden="true" />
</div>
</div>
<ComptenceProgress
:status-count="

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import type { PerformanceCriteria } from "@/types";
import { useRoute } from "vue-router";
interface Props {
criteria: PerformanceCriteria;
@ -10,6 +11,9 @@ const props = withDefaults(defineProps<Props>(), {
criteria: undefined,
showState: false,
});
const route = useRoute();
const profilePageSlug = route.params["competenceProfilePageSlug"];
</script>
<template>
@ -37,7 +41,10 @@ const props = withDefaults(defineProps<Props>(), {
</div>
</div>
<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
</router-link>
</span>

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import LearningContentContainer from "@/components/learningPath/LearningContentContainer.vue";
import { useCircleStore } from "@/stores/circle";
import type { LearningContent } from "@/types";
import * as log from "loglevel";
@ -26,75 +27,58 @@ const block = computed(() => {
<template>
<div v-if="block">
<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"
data-cy="close-learning-content"
@click="circleStore.closeLearningContent(props.learningContent)"
>
<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>
</button>
<LearningContentContainer
:title="learningContent.title"
next-button-text="Abschliessen und weiter"
back-text="zurück zum Circle"
@back="circleStore.closeLearningContent(props.learningContent)"
@next="circleStore.continueFromLearningContent(props.learningContent)"
>
<div v-if="block.type === 'exercise' || block.type === 'test'" class="h-screen">
<iframe width="100%" height="100%" scrolling="no" :src="block.value.url" />
</div>
<h1 class="text-large hidden lg:block" data-cy="ln-title">
{{ learningContent.title }}
</h1>
<div v-else class="container-medium">
<div v-if="block.type === 'video'">
<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
type="button"
class="btn-blue"
data-cy="complete-and-continue"
@click="circleStore.continueFromLearningContent(props.learningContent)"
>
Abschliessen und weiter
</button>
</nav>
<div v-else-if="block.type === 'media_library'" class="mt-4 lg:mt-12">
<h1>{{ learningContent.title }}</h1>
<div v-if="block.type === 'exercise' || block.type === 'test'" class="h-screen">
<iframe width="100%" height="100%" scrolling="no" :src="block.value.url" />
</div>
<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 class="container-medium">
<div v-if="block.type === 'video'">
<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
<div
v-else-if="block.type === 'resource' || block.type === 'assignment'"
class="mt-4 lg:mt-12"
>
</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 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>
</LearningContentContainer>
</div>
</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 type { LearningUnit } from "@/types";
import * as log from "loglevel";
import LearningContentContainer from "@/components/learningPath/LearningContentContainer.vue";
import { computed, reactive } from "vue";
log.debug("LearningContent.vue setup");
@ -33,79 +35,63 @@ function handleContinue() {
<template>
<div v-if="learningUnit">
<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="circleStore.closeSelfEvaluation(props.learningUnit)"
>
<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>
</button>
<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>
<LearningContentContainer
:title="`Selbsteinschätzung ${learningUnit.title}`"
:back-text="'zurück zum Circle'"
:next-button-text="'Weiter'"
@back="circleStore.closeSelfEvaluation(props.learningUnit)"
@next="handleContinue()"
>
<div class="container-medium">
<div class="mt-2 lg:mt-8 text-gray-700">
Schritt {{ state.questionIndex + 1 }} von {{ questions.length }}
</div>
<div class="mt-6 lg:mt-12">
Schau dein Fortschritt in deinem KompetenzNavi: KompetenzNavi öffnen
<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 class="mt-6 lg:mt-12">
Schau dein Fortschritt in deinem KompetenzNavi: KompetenzNavi öffnen
</div>
</div>
</div>
</div>
</LearningContentContainer>
</div>
</template>

View File

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

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from "@headlessui/vue";
import { computed, defineEmits } from "vue";
import { computed } from "vue";
interface DropdownSelectable {
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",
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 { Component } from "vue";
export type CourseCompletionStatus = "unknown" | "fail" | "success";
@ -320,3 +321,10 @@ export interface CompetenceProfilePage extends CourseWagtailPage {
circles: CircleLight[];
children: CompetencePage[];
}
// dropdown
export interface DropdownListItem {
title: string;
icon: Component;
data: object;
}