Add backend data to competence profile

This commit is contained in:
Daniel Egger 2022-10-07 11:35:43 +02:00
parent 22e3fce59e
commit dc3b1a4ca6
11 changed files with 165 additions and 69 deletions

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import ComptenceProgress from "@/components/competences/CompetenceProgress.vue"; import ComptenceProgress from "@/components/competences/CompetenceProgress.vue";
import LeistungskriteriumRow from "@/components/competences/LeistungskriteriumRow.vue"; import LeistungskriteriumRow from "@/components/competences/PerformanceCriteriaRow.vue";
import { ref } from "vue"; import { ref } from "vue";
const props = defineProps<{ const props = defineProps<{
@ -25,9 +25,9 @@ const userStateForCriteria = (id: string) =>
<h2 class="text-large">{{ competence.description }}</h2> <h2 class="text-large">{{ competence.description }}</h2>
<button class="transition-transform" :class="{ 'rotate-180': isOpen }"> <button class="transition-transform" :class="{ 'rotate-180': isOpen }">
<it-icon-arrow-down <it-icon-arrow-down
@click="togglePerformanceCriteria()"
class="h-10 w-10" class="h-10 w-10"
aria-hidden="true" aria-hidden="true"
@click="togglePerformanceCriteria()"
/> />
</button> </button>
</div> </div>
@ -41,7 +41,7 @@ const userStateForCriteria = (id: string) =>
> >
<LeistungskriteriumRow <LeistungskriteriumRow
:state="userStateForCriteria(`${performanceCriteria.id}`)" :state="userStateForCriteria(`${performanceCriteria.id}`)"
:showState="true" :show-state="true"
:unit-url="performanceCriteria.learning_unit.slug" :unit-url="performanceCriteria.learning_unit.slug"
:unit="performanceCriteria.pc_id" :unit="performanceCriteria.pc_id"
:title="performanceCriteria.title" :title="performanceCriteria.title"

View File

@ -1,42 +0,0 @@
<script setup lang="ts">
interface Props {
title: string;
unit: string;
unitUrl: string;
unitId: number;
state?: string; // maybe enum
showState?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
title: "",
unit: "",
unitUrl: "",
unitId: -1,
state: "open",
showState: false,
});
</script>
<template>
<div class="flex flex-col lg:flex-row lg:items-center justify-between">
<div class="flex flex-row items-center">
<div v-if="showState" class="h-8 w-8 mr-4">
<it-icon-smiley-happy v-if="state === 'done'"></it-icon-smiley-happy>
<it-icon-smiley-thinking
v-else-if="state === 'notDone'"
></it-icon-smiley-thinking>
<it-icon-smiley-neutral v-else></it-icon-smiley-neutral>
</div>
<div class="pr-5 lg:mr-10 mb-4 lg:mb-0">
<h4 class="text-bold mb-2">{{ title }}</h4>
<p>
Lerneinheit: <a :href="unitUrl">{{ unit }}</a>
</p>
</div>
</div>
<span class="whitespace-nowrap">Sich nochmals einschätzen</span>
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,45 @@
<script setup lang="ts">
import type { PerformanceCriteria } from "@/types";
interface Props {
criteria: PerformanceCriteria;
showState: boolean;
}
const props = withDefaults(defineProps<Props>(), {
criteria: undefined,
showState: false,
});
</script>
<template>
<div class="flex flex-col lg:flex-row lg:items-center justify-between">
<div class="flex flex-row items-center">
<div v-if="showState" class="h-8 w-8 mr-4">
<it-icon-smiley-happy
v-if="criteria.completion_status === 'success'"
></it-icon-smiley-happy>
<it-icon-smiley-thinking
v-else-if="criteria.completion_status === 'fail'"
></it-icon-smiley-thinking>
<it-icon-smiley-neutral v-else></it-icon-smiley-neutral>
</div>
<div class="pr-5 lg:mr-10 mb-4 lg:mb-0">
<h4 class="text-bold mb-2">{{ criteria.title }}</h4>
<p>
Lerneinheit:
<a class="link" :href="criteria.learning_unit.frontend_url">
{{ criteria.learning_unit.title }}
</a>
</p>
</div>
</div>
<span class="whitespace-nowrap">
<a class="link" :href="criteria.learning_unit.frontend_url">
Sich nochmals einschätzen
</a>
</span>
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import LeistungskriteriumRow from "@/components/competences/LeistungskriteriumRow.vue"; import { default as PerformanceCriteriaRow } from "@/components/competences/PerformanceCriteriaRow.vue";
import { useCompetenceStore } from "@/stores/competence";
import * as log from "loglevel"; import * as log from "loglevel";
import { computed, Ref, ref } from "vue"; import { computed, Ref, ref } from "vue";
@ -11,6 +12,8 @@ enum UserCriteriaState {
Nok = "notDone", Nok = "notDone",
} }
const competenceStore = useCompetenceStore();
const data = { const data = {
id: 340, id: 340,
title: "Kompetenzprofil", title: "Kompetenzprofil",
@ -86,17 +89,16 @@ const userProgress = {
const activeState: Ref<UserCriteriaState> = ref(UserCriteriaState.Nok); const activeState: Ref<UserCriteriaState> = ref(UserCriteriaState.Nok);
const shownCriteria = computed(() => { const shownCriteria = computed(() => {
return data.children.filter( return competenceStore.flatPerformanceCriteria;
(criteria) => userProgress[`${criteria.id}`] === activeState.value
);
}); });
</script> </script>
<template> <template>
<div class="mx-auto max-w-5xl"> <div class="container-large">
<nav> <nav>
<a class="block mb-8 cursor-pointer flex items-center" @click="router.go(-1)" <a class="block mb-8 cursor-pointer flex items-center" @click="router.go(-1)">
><it-icon-arrow-left /><span>zurück</span></a <it-icon-arrow-left />
<span>zurück</span></a
> >
</nav> </nav>
<div class="flex flex-col lg:flex-row items-center justify-between mb-10"> <div class="flex flex-col lg:flex-row items-center justify-between mb-10">
@ -110,25 +112,25 @@ const shownCriteria = computed(() => {
class="border-b border-gray-500 flex flex-col lg:flex-row lg:items-center pb-4 mb-4" class="border-b border-gray-500 flex flex-col lg:flex-row lg:items-center pb-4 mb-4"
> >
<button <button
@click="activeState = UserCriteriaState.Nok"
:class="{ 'bg-gray-200': activeState === UserCriteriaState.Nok }" :class="{ 'bg-gray-200': activeState === UserCriteriaState.Nok }"
class="flex flex-row items-center py-4 px-2 mr-6" class="flex flex-row items-center py-4 px-2 mr-6"
@click="activeState = UserCriteriaState.Nok"
> >
<span class="inline-block mr-2">«Das muss ich nochmals anschauen»</span> <span class="inline-block mr-2">«Das muss ich nochmals anschauen»</span>
<span><it-icon-smiley-thinking></it-icon-smiley-thinking></span> <span><it-icon-smiley-thinking></it-icon-smiley-thinking></span>
</button> </button>
<button <button
@click="activeState = UserCriteriaState.Done"
:class="{ 'bg-gray-200': activeState === UserCriteriaState.Done }" :class="{ 'bg-gray-200': activeState === UserCriteriaState.Done }"
class="flex flex-row items-center py-4 px-2 mr-6" class="flex flex-row items-center py-4 px-2 mr-6"
@click="activeState = UserCriteriaState.Done"
> >
<span class="inline-block mr-2">«Ja, ich kann das»</span> <span class="inline-block mr-2">«Ja, ich kann das»</span>
<span><it-icon-smiley-happy></it-icon-smiley-happy></span> <span><it-icon-smiley-happy></it-icon-smiley-happy></span>
</button> </button>
<button <button
@click="activeState = UserCriteriaState.Open"
:class="{ 'bg-gray-200': activeState === UserCriteriaState.Open }" :class="{ 'bg-gray-200': activeState === UserCriteriaState.Open }"
class="flex flex-row items-center py-4 px-2" class="flex flex-row items-center py-4 px-2"
@click="activeState = UserCriteriaState.Open"
> >
<span class="inline-block mr-2">Nicht eingeschätzt</span> <span class="inline-block mr-2">Nicht eingeschätzt</span>
<span><it-icon-smiley-neutral></it-icon-smiley-neutral></span> <span><it-icon-smiley-neutral></it-icon-smiley-neutral></span>
@ -140,12 +142,7 @@ const shownCriteria = computed(() => {
:key="criteria.title" :key="criteria.title"
class="mb-4 pb-4 border-b border-gray-500" class="mb-4 pb-4 border-b border-gray-500"
> >
<LeistungskriteriumRow <PerformanceCriteriaRow :criteria="criteria"> </PerformanceCriteriaRow>
:unit-url="criteria.learning_unit.slug"
:unit="criteria.pc_id"
:title="criteria.title"
:unit-id="criteria.learning_unit.id"
></LeistungskriteriumRow>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import ComptenceProgress from "@/components/competences/CompetenceProgress.vue"; import ComptenceProgress from "@/components/competences/CompetenceProgress.vue";
import LeistungskriteriumRow from "@/components/competences/LeistungskriteriumRow.vue"; import LeistungskriteriumRow from "@/components/competences/PerformanceCriteriaRow.vue";
import * as log from "loglevel"; import * as log from "loglevel";
log.debug("CompetencesMainView created"); log.debug("CompetencesMainView created");

View File

@ -1,21 +1,30 @@
<script setup lang="ts"> <script setup lang="ts">
import { useCompetenceStore } from "@/stores/competence";
import * as log from "loglevel"; import * as log from "loglevel";
import { onMounted } from "vue"; import { onMounted } from "vue";
log.debug("CometencesView created"); log.debug("CometencesView created");
const props = defineProps<{ const props = defineProps<{
competencesPageSlug: string; competenceProfilePageSlug: string;
}>(); }>();
const competenceStore = useCompetenceStore();
onMounted(async () => { onMounted(async () => {
log.debug("CompetencesView mounted", props.competencesPageSlug); log.debug("CompetencesView mounted", props.competenceProfilePageSlug);
try {
await competenceStore.loadCompetenceProfilePage(props.competenceProfilePageSlug);
} catch (error) {
log.error(error);
}
}); });
</script> </script>
<template> <template>
<div class="bg-gray-200"> <div class="bg-gray-200">
<main class="px-8 py-8"> <main>
<router-view></router-view> <router-view></router-view>
</main> </main>
</div> </div>

View File

@ -53,7 +53,7 @@ const router = createRouter({
], ],
}, },
{ {
path: "/competences/:competencesPageSlug", path: "/competence/:competenceProfilePageSlug",
props: true, props: true,
component: () => import("@/pages/ComptencesView.vue"), component: () => import("@/pages/ComptencesView.vue"),
children: [ children: [

View File

@ -0,0 +1,53 @@
import { itGet } from "@/fetchHelpers";
import type { CompetenceProfilePage } from "@/types";
import _ from "lodash";
import { defineStore } from "pinia";
export type CompetenceStoreState = {
competenceProfilePage: CompetenceProfilePage | undefined;
};
export const useCompetenceStore = defineStore({
id: "competence",
state: () => {
return {
competenceProfilePage: undefined,
} as CompetenceStoreState;
},
getters: {
flatPerformanceCriteria: (state, circleTitle = "") => {
if (!state.competenceProfilePage) {
return [];
}
let criteria = _.orderBy(
state.competenceProfilePage.children.flatMap((competence) => {
return competence.children;
}),
["competence_id"],
["asc"]
);
if (circleTitle) {
criteria = criteria.filter((c) => c.circle === circleTitle);
}
return criteria;
},
},
actions: {
async loadCompetenceProfilePage(slug: string, reload = false) {
if (this.competenceProfilePage && !reload) {
return this.competenceProfilePage;
}
const competenceProfilePageData = await itGet(`/api/course/page/${slug}/`);
if (!competenceProfilePageData) {
throw `No competenceProfilePageData found with: ${slug}`;
}
this.competenceProfilePage = competenceProfilePageData;
return this.competenceProfilePage;
},
},
});

View File

@ -290,3 +290,23 @@ export interface MediaLibraryPage extends CourseWagtailPage {
course: Course; course: Course;
children: MediaCategoryPage[]; children: MediaCategoryPage[];
} }
export interface PerformanceCriteria extends CourseWagtailPage {
type: "competence.PerformanceCriteria";
competence_id: string;
circle: string;
course_category: CourseCategory;
learning_unit: CourseWagtailPage;
}
export interface CompetencePage extends CourseWagtailPage {
type: "competence.CompetencePage";
competence_id: string;
children: PerformanceCriteria[];
}
export interface CompetenceProfilePage extends CourseWagtailPage {
type: "competence.CompetenceProfilePage";
course: Course;
children: CompetencePage[];
}

View File

@ -1,14 +1,27 @@
{ {
"extends": "@vue/tsconfig/tsconfig.web.json", "extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "include": [
"exclude": ["src/**/__tests__/*"], "env.d.ts",
"src/**/*",
"src/**/*.vue"
],
"exclude": [
"src/**/__tests__/*"
],
"compilerOptions": { "compilerOptions": {
"lib": [
"ES2021",
"DOM",
"DOM.Iterable"
],
"composite": true, "composite": true,
"strict": true, "strict": true,
"allowJs": true, "allowJs": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": [
"./src/*"
]
} }
} }
} }

View File

@ -64,6 +64,7 @@ class CompetencePage(Page):
return get_it_serializer_class( return get_it_serializer_class(
cls, cls,
[ [
"competence_id",
"children", "children",
], ],
) )