Add competence detail page

This commit is contained in:
Daniel Egger 2023-09-07 14:40:36 +02:00
parent 93bec05abc
commit 24511df01e
9 changed files with 204 additions and 118 deletions

View File

@ -4,9 +4,14 @@ import * as log from "loglevel";
log.debug("CompetenceAssignmentRow setup");
const props = defineProps<{
export interface Props {
assignment: CompetenceCertificateAssignment;
}>();
addBorderBottom?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
addBorderBottom: false,
});
const getIconName = () => {
if (props.assignment.assignment_type === "EDONIQ_TEST") {
@ -17,7 +22,7 @@ const getIconName = () => {
</script>
<template>
<div class="flex items-center border-b py-8">
<div class="flex items-center py-8" :class="{ 'border-b': props.addBorderBottom }">
<component :is="getIconName()" class="mr-4 h-9 w-9"></component>
<div class="flex w-[420px] flex-col">
<h3 class="text-bold flex items-center gap-2">{{ assignment.title }}</h3>

View File

@ -5,7 +5,11 @@ import CompetenceAssignmentRow from "@/pages/competence/CompetenceAssignmentRow.
import { computed } from "vue";
import type { StatusCount } from "@/components/ui/ItProgress.vue";
import ItProgress from "@/components/ui/ItProgress.vue";
import _ from "lodash";
import {
assignmentsMaxEvaluationPoints,
assignmentsUserPoints,
competenceCertificateProgressStatusCount,
} from "@/pages/competence/utils";
log.debug("CompetenceCertificateComponent setup");
@ -15,19 +19,11 @@ const props = defineProps<{
}>();
const totalPointsEvaluatedAssignments = computed(() => {
return _.sum(
props.competenceCertificate.assignments
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
.map((a) => a.max_points)
);
return assignmentsMaxEvaluationPoints(props.competenceCertificate.assignments);
});
const userPointsEvaluatedAssignments = computed(() => {
return _.sum(
props.competenceCertificate.assignments
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
.map((a) => a.completion?.evaluation_points ?? 0)
);
return assignmentsUserPoints(props.competenceCertificate.assignments);
});
const numAssignmentsEvaluated = computed(() => {
@ -41,11 +37,9 @@ const numAssignmentsTotal = computed(() => {
});
const progressStatusCount = computed(() => {
return {
SUCCESS: numAssignmentsEvaluated.value,
UNKNOWN: numAssignmentsTotal.value - numAssignmentsEvaluated.value,
FAIL: 0,
} as StatusCount;
return competenceCertificateProgressStatusCount(
props.competenceCertificate.assignments
);
});
</script>
@ -92,11 +86,14 @@ const progressStatusCount = computed(() => {
<div v-if="props.detailView">
<div
v-for="assignment in props.competenceCertificate.assignments"
v-for="(assignment, index) in competenceCertificate.assignments"
:key="assignment.id"
class="bg-white px-8"
>
<CompetenceAssignmentRow :assignment="assignment"></CompetenceAssignmentRow>
<CompetenceAssignmentRow
:assignment="assignment"
:add-border-bottom="index < competenceCertificate.assignments.length - 1"
></CompetenceAssignmentRow>
</div>
</div>
</div>

View File

@ -12,7 +12,7 @@ const props = defineProps<{
certificateSlug: string;
}>();
log.debug("CompetenceCertificateDetailPage created", props);
log.debug("CompetenceCertificateDetailPage setup", props);
const courseSession = useCurrentCourseSession();

View File

@ -6,13 +6,16 @@ import { computed, onMounted } from "vue";
import type { CompetenceCertificate } from "@/types";
import { useCurrentCourseSession } from "@/composables";
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
import _ from "lodash";
import {
assignmentsMaxEvaluationPoints,
assignmentsUserPoints,
} from "@/pages/competence/utils";
const props = defineProps<{
courseSlug: string;
}>();
log.debug("CompetenceOverviewPage created", props);
log.debug("CompetenceCertificateListPage setup", props);
const courseSession = useCurrentCourseSession();
@ -36,19 +39,11 @@ const assignments = computed(() => {
});
const totalPointsEvaluatedAssignments = computed(() => {
return _.sum(
assignments.value
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
.map((a) => a.max_points)
);
return assignmentsMaxEvaluationPoints(assignments.value);
});
const userPointsEvaluatedAssignments = computed(() => {
return _.sum(
assignments.value
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
.map((a) => a.completion?.evaluation_points ?? 0)
);
return assignmentsUserPoints(assignments.value);
});
const numAssignmentsEvaluated = computed(() => {

View File

@ -1,82 +0,0 @@
<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 { useCompetenceStore } from "@/stores/competence";
import type { CompetencePage } from "@/types";
import log from "loglevel";
import { ref } from "vue";
const competenceStore = useCompetenceStore();
interface Props {
competence: CompetencePage;
courseSlug: string;
showAssessAgain?: boolean;
isInline?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
showAssessAgain: true,
isInline: false,
});
log.debug("PerformanceCriteriaRow created", props);
const isOpen = ref(false);
const togglePerformanceCriteria = () => {
isOpen.value = !isOpen.value;
};
</script>
<template>
<div>
<div :class="{ 'mb-4 border-b pb-8': isOpen }" class="-mx-8 px-8">
<div
class="flex flex-row items-center justify-between"
:class="props.isInline ? '' : 'mb-4'"
role="button"
aria-pressed="false"
@click="togglePerformanceCriteria()"
>
<h2 :class="props.isInline ? ['text-bold', 'w-2/5'] : 'text-large'">
{{ competence.competence_id }} {{ competence.title }}
</h2>
<ItProgress
v-if="isInline"
class="w-[330px]"
:status-count="
competenceStore.calcStatusCount(
competenceStore.criteriaByCompetence(competence)
)
"
></ItProgress>
<ItToggleArrow :is-open="isOpen" :small="isInline"></ItToggleArrow>
</div>
<ItProgress
v-if="!isInline"
:status-count="
competenceStore.calcStatusCount(
competenceStore.criteriaByCompetence(competence)
)
"
></ItProgress>
</div>
<ul v-if="isOpen">
<li
v-for="performanceCriteria in competenceStore.criteriaByCompetence(competence)"
:key="performanceCriteria.id"
class="mb-4 border-b pb-4 last:border-0"
>
<PerformanceCriteriaRow
:criteria="performanceCriteria"
:show-state="true"
:course-slug="props.courseSlug"
:show-assess-again="props.showAssessAgain"
></PerformanceCriteriaRow>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,120 @@
<script setup lang="ts">
import log from "loglevel";
import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
import { useQuery } from "@urql/vue";
import { computed } from "vue";
import type { CompetenceCertificate } from "@/types";
import { useCurrentCourseSession } from "@/composables";
import {
assignmentsMaxEvaluationPoints,
assignmentsUserPoints,
competenceCertificateProgressStatusCount,
} from "@/pages/competence/utils";
import ItProgress from "@/components/ui/ItProgress.vue";
const props = defineProps<{
courseSlug: string;
}>();
log.debug("CompetenceIndexPage setup", props);
const courseSession = useCurrentCourseSession();
const certificatesQuery = useQuery({
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
variables: {
courseSlug: props.courseSlug,
courseSessionId: courseSession.value.id.toString(),
},
});
const competenceCertificates = computed(() => {
return (
(certificatesQuery.data.value?.competence_certificate_list
?.competence_certificates as unknown as CompetenceCertificate[]) ?? []
);
});
const allAssignments = computed(() => {
return competenceCertificates.value.flatMap((cc) => cc.assignments);
});
const totalPointsEvaluatedAssignments = computed(() => {
return assignmentsMaxEvaluationPoints(allAssignments.value);
});
const userPointsEvaluatedAssignments = computed(() => {
return assignmentsUserPoints(allAssignments.value);
});
</script>
<template>
<div class="container-large lg:mt-4">
<h1 class="mb-8">{{ $t("a.KompetenzNavi") }}</h1>
<div class="mb-4 bg-white p-8">
<div class="flex items-center">
<h3>{{ $t("a.Kompetenznachweise") }}</h3>
</div>
<div class="mt-4">
{{ $t("a.Zwischenstand") }} {{ $t("a.Gesamtpunktzahl") }}:
<span class="font-bold">
{{ userPointsEvaluatedAssignments }}
</span>
von {{ totalPointsEvaluatedAssignments }} Punkten
</div>
<div>
<div class="mt-4">
<div
v-for="certificate in competenceCertificates"
:key="certificate.id"
class="flex items-center justify-between border-b py-4 first:border-t"
>
<div class="text-bold text-xl">
{{ certificate.title }}
</div>
<div>
<span class="text-bold">
{{ assignmentsMaxEvaluationPoints(certificate.assignments) }}
</span>
von
{{ assignmentsUserPoints(certificate.assignments) }}
Punkten
</div>
<div class="flex">
<div>
{{
certificate.assignments.filter(
(a) => a.completion?.completion_status === "EVALUATION_SUBMITTED"
).length
}}
von
{{ certificate.assignments.length }}
Arbeiten abgeschlossen
</div>
<div class="ml-2 w-40">
<ItProgress
:status-count="
competenceCertificateProgressStatusCount(certificate.assignments)
"
/>
</div>
</div>
</div>
<div>
<router-link
:to="`/course/${props.courseSlug}/competence/certificates`"
class="btn-text mt-4 inline-flex items-center py-2 pl-0"
>
<span>{{ $t("a.Details anschauen") }}</span>
<it-icon-arrow-right></it-icon-arrow-right>
</router-link>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped></style>

View File

@ -2,6 +2,7 @@
import { useCompetenceStore } from "@/stores/competence";
import * as log from "loglevel";
import { onMounted } from "vue";
import { useRoute } from "vue-router";
log.debug("CompetenceParentPage created");
@ -11,6 +12,16 @@ const props = defineProps<{
const competenceStore = useCompetenceStore();
const route = useRoute();
function routeInOverview() {
return route.path.endsWith("/competence");
}
function routeInCompetenceCertificate() {
return route.path.includes("/certificate");
}
onMounted(async () => {
log.debug("CompetenceParentPage mounted", props.courseSlug);
@ -29,7 +40,7 @@ onMounted(async () => {
<ul class="scrollbar overflow-auto whitespace-nowrap">
<li
class="inline-block border-t-2 border-t-transparent py-3"
:class="{ 'border-b-2 border-b-blue-900': true }"
:class="{ 'border-b-2 border-b-blue-900': routeInOverview() }"
>
<router-link :to="`/course/${courseSlug}/competence`">
{{ $t("mediaLibrary.overview") }}
@ -37,7 +48,7 @@ onMounted(async () => {
</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': true }"
:class="{ 'border-b-2 border-b-blue-900': routeInCompetenceCertificate() }"
>
<router-link :to="`/course/${courseSlug}/competence/certificates`">
{{ $t("a.Kompetenznachweise") }}

View File

@ -0,0 +1,34 @@
import type { StatusCount } from "@/components/ui/ItProgress.vue";
import type { CompetenceCertificateAssignment } from "@/types";
import _ from "lodash";
export function assignmentsMaxEvaluationPoints(
assignments: CompetenceCertificateAssignment[]
): number {
return _.sum(
assignments
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
.map((a) => a.max_points)
);
}
export function assignmentsUserPoints(assignments: CompetenceCertificateAssignment[]) {
return _.sum(
assignments
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
.map((a) => a.completion?.evaluation_points ?? 0)
);
}
export function competenceCertificateProgressStatusCount(
assignments: CompetenceCertificateAssignment[]
) {
const numAssignmentsEvaluated = assignments.filter(
(a) => a.completion?.completion_status === "EVALUATION_SUBMITTED"
).length;
return {
SUCCESS: numAssignmentsEvaluated,
UNKNOWN: assignments.length - numAssignmentsEvaluated,
FAIL: 0,
} as StatusCount;
}

View File

@ -66,6 +66,12 @@ const router = createRouter({
props: true,
component: () => import("@/pages/competence/CompetenceParentPage.vue"),
children: [
{
path: "",
props: true,
component: () => import("@/pages/competence/CompetenceIndexPage.vue"),
},
{
path: "certificates",
props: true,