Add competence detail page
This commit is contained in:
parent
93bec05abc
commit
24511df01e
|
|
@ -4,9 +4,14 @@ import * as log from "loglevel";
|
||||||
|
|
||||||
log.debug("CompetenceAssignmentRow setup");
|
log.debug("CompetenceAssignmentRow setup");
|
||||||
|
|
||||||
const props = defineProps<{
|
export interface Props {
|
||||||
assignment: CompetenceCertificateAssignment;
|
assignment: CompetenceCertificateAssignment;
|
||||||
}>();
|
addBorderBottom?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
addBorderBottom: false,
|
||||||
|
});
|
||||||
|
|
||||||
const getIconName = () => {
|
const getIconName = () => {
|
||||||
if (props.assignment.assignment_type === "EDONIQ_TEST") {
|
if (props.assignment.assignment_type === "EDONIQ_TEST") {
|
||||||
|
|
@ -17,7 +22,7 @@ const getIconName = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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>
|
<component :is="getIconName()" class="mr-4 h-9 w-9"></component>
|
||||||
<div class="flex w-[420px] flex-col">
|
<div class="flex w-[420px] flex-col">
|
||||||
<h3 class="text-bold flex items-center gap-2">{{ assignment.title }}</h3>
|
<h3 class="text-bold flex items-center gap-2">{{ assignment.title }}</h3>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,11 @@ import CompetenceAssignmentRow from "@/pages/competence/CompetenceAssignmentRow.
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import type { StatusCount } from "@/components/ui/ItProgress.vue";
|
import type { StatusCount } from "@/components/ui/ItProgress.vue";
|
||||||
import ItProgress 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");
|
log.debug("CompetenceCertificateComponent setup");
|
||||||
|
|
||||||
|
|
@ -15,19 +19,11 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const totalPointsEvaluatedAssignments = computed(() => {
|
const totalPointsEvaluatedAssignments = computed(() => {
|
||||||
return _.sum(
|
return assignmentsMaxEvaluationPoints(props.competenceCertificate.assignments);
|
||||||
props.competenceCertificate.assignments
|
|
||||||
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
|
|
||||||
.map((a) => a.max_points)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const userPointsEvaluatedAssignments = computed(() => {
|
const userPointsEvaluatedAssignments = computed(() => {
|
||||||
return _.sum(
|
return assignmentsUserPoints(props.competenceCertificate.assignments);
|
||||||
props.competenceCertificate.assignments
|
|
||||||
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
|
|
||||||
.map((a) => a.completion?.evaluation_points ?? 0)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const numAssignmentsEvaluated = computed(() => {
|
const numAssignmentsEvaluated = computed(() => {
|
||||||
|
|
@ -41,11 +37,9 @@ const numAssignmentsTotal = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const progressStatusCount = computed(() => {
|
const progressStatusCount = computed(() => {
|
||||||
return {
|
return competenceCertificateProgressStatusCount(
|
||||||
SUCCESS: numAssignmentsEvaluated.value,
|
props.competenceCertificate.assignments
|
||||||
UNKNOWN: numAssignmentsTotal.value - numAssignmentsEvaluated.value,
|
);
|
||||||
FAIL: 0,
|
|
||||||
} as StatusCount;
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -92,11 +86,14 @@ const progressStatusCount = computed(() => {
|
||||||
|
|
||||||
<div v-if="props.detailView">
|
<div v-if="props.detailView">
|
||||||
<div
|
<div
|
||||||
v-for="assignment in props.competenceCertificate.assignments"
|
v-for="(assignment, index) in competenceCertificate.assignments"
|
||||||
:key="assignment.id"
|
:key="assignment.id"
|
||||||
class="bg-white px-8"
|
class="bg-white px-8"
|
||||||
>
|
>
|
||||||
<CompetenceAssignmentRow :assignment="assignment"></CompetenceAssignmentRow>
|
<CompetenceAssignmentRow
|
||||||
|
:assignment="assignment"
|
||||||
|
:add-border-bottom="index < competenceCertificate.assignments.length - 1"
|
||||||
|
></CompetenceAssignmentRow>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const props = defineProps<{
|
||||||
certificateSlug: string;
|
certificateSlug: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
log.debug("CompetenceCertificateDetailPage created", props);
|
log.debug("CompetenceCertificateDetailPage setup", props);
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@ import { computed, onMounted } from "vue";
|
||||||
import type { CompetenceCertificate } from "@/types";
|
import type { CompetenceCertificate } from "@/types";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
|
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
|
||||||
import _ from "lodash";
|
import {
|
||||||
|
assignmentsMaxEvaluationPoints,
|
||||||
|
assignmentsUserPoints,
|
||||||
|
} from "@/pages/competence/utils";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
log.debug("CompetenceOverviewPage created", props);
|
log.debug("CompetenceCertificateListPage setup", props);
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
|
|
@ -36,19 +39,11 @@ const assignments = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalPointsEvaluatedAssignments = computed(() => {
|
const totalPointsEvaluatedAssignments = computed(() => {
|
||||||
return _.sum(
|
return assignmentsMaxEvaluationPoints(assignments.value);
|
||||||
assignments.value
|
|
||||||
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
|
|
||||||
.map((a) => a.max_points)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const userPointsEvaluatedAssignments = computed(() => {
|
const userPointsEvaluatedAssignments = computed(() => {
|
||||||
return _.sum(
|
return assignmentsUserPoints(assignments.value);
|
||||||
assignments.value
|
|
||||||
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
|
|
||||||
.map((a) => a.completion?.evaluation_points ?? 0)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const numAssignmentsEvaluated = computed(() => {
|
const numAssignmentsEvaluated = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
import { useCompetenceStore } from "@/stores/competence";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
import { onMounted } from "vue";
|
import { onMounted } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
log.debug("CompetenceParentPage created");
|
log.debug("CompetenceParentPage created");
|
||||||
|
|
||||||
|
|
@ -11,6 +12,16 @@ const props = defineProps<{
|
||||||
|
|
||||||
const competenceStore = useCompetenceStore();
|
const competenceStore = useCompetenceStore();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
function routeInOverview() {
|
||||||
|
return route.path.endsWith("/competence");
|
||||||
|
}
|
||||||
|
|
||||||
|
function routeInCompetenceCertificate() {
|
||||||
|
return route.path.includes("/certificate");
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
log.debug("CompetenceParentPage mounted", props.courseSlug);
|
log.debug("CompetenceParentPage mounted", props.courseSlug);
|
||||||
|
|
||||||
|
|
@ -29,7 +40,7 @@ onMounted(async () => {
|
||||||
<ul class="scrollbar overflow-auto whitespace-nowrap">
|
<ul class="scrollbar overflow-auto whitespace-nowrap">
|
||||||
<li
|
<li
|
||||||
class="inline-block border-t-2 border-t-transparent py-3"
|
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`">
|
<router-link :to="`/course/${courseSlug}/competence`">
|
||||||
{{ $t("mediaLibrary.overview") }}
|
{{ $t("mediaLibrary.overview") }}
|
||||||
|
|
@ -37,7 +48,7 @@ onMounted(async () => {
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="ml-6 inline-block border-t-2 border-t-transparent py-3 lg:ml-12"
|
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`">
|
<router-link :to="`/course/${courseSlug}/competence/certificates`">
|
||||||
{{ $t("a.Kompetenznachweise") }}
|
{{ $t("a.Kompetenznachweise") }}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -66,6 +66,12 @@ const router = createRouter({
|
||||||
props: true,
|
props: true,
|
||||||
component: () => import("@/pages/competence/CompetenceParentPage.vue"),
|
component: () => import("@/pages/competence/CompetenceParentPage.vue"),
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
props: true,
|
||||||
|
component: () => import("@/pages/competence/CompetenceIndexPage.vue"),
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "certificates",
|
path: "certificates",
|
||||||
props: true,
|
props: true,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue