Add competence detail page

This commit is contained in:
Daniel Egger 2023-09-07 09:55:48 +02:00
parent 7597311220
commit 93bec05abc
9 changed files with 186 additions and 196 deletions

View File

@ -43,7 +43,7 @@ const getIconName = () => {
> >
<it-icon-check class="h-4/5 w-4/5"></it-icon-check> <it-icon-check class="h-4/5 w-4/5"></it-icon-check>
</div> </div>
<div class="ml-2">Bewertung freigegeben</div> <div class="ml-2">{{ $t("a.Bewertung freigegeben") }}</div>
</div> </div>
<div <div
v-else-if=" v-else-if="
@ -58,7 +58,7 @@ const getIconName = () => {
> >
<it-icon-check class="h-6 w-6"></it-icon-check> <it-icon-check class="h-6 w-6"></it-icon-check>
</div> </div>
<div class="ml-2">Ergebnisse abgegeben</div> <div class="ml-2">{{ $t("a.Ergebnisse abgegeben") }}</div>
</div> </div>
</div> </div>
<div> <div>
@ -74,8 +74,8 @@ const getIconName = () => {
</div> </div>
</div> </div>
<div v-else class="flex flex-col items-center"> <div v-else class="flex flex-col items-center">
<div>Höchstpunktzahl</div> <div>{{ $t("a.Höchstpunktzahl") }}</div>
<div>{{ assignment.max_points }} Punkte</div> <div>{{ assignment.max_points }} {{ $t("a.Punkte") }}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,29 +5,29 @@ 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";
log.debug("CompetenceCertificateComponent setup"); log.debug("CompetenceCertificateComponent setup");
const props = defineProps<{ const props = defineProps<{
competenceCertificate: CompetenceCertificate; competenceCertificate: CompetenceCertificate;
detailView: boolean;
}>(); }>();
const totalPointsEvaluatedAssignments = computed(() => { const totalPointsEvaluatedAssignments = computed(() => {
return props.competenceCertificate.assignments.reduce((acc, assignment) => { return _.sum(
if (assignment.completion?.completion_status === "EVALUATION_SUBMITTED") { props.competenceCertificate.assignments
return acc + assignment.max_points; .filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
} .map((a) => a.max_points)
return acc; );
}, 0);
}); });
const userPointsEvaluatedAssignments = computed(() => { const userPointsEvaluatedAssignments = computed(() => {
return props.competenceCertificate.assignments.reduce((acc, assignment) => { return _.sum(
if (assignment.completion?.completion_status === "EVALUATION_SUBMITTED") { props.competenceCertificate.assignments
return acc + (assignment.completion?.evaluation_points ?? 0); .filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
} .map((a) => a.completion?.evaluation_points ?? 0)
return acc; );
}, 0);
}); });
const numAssignmentsEvaluated = computed(() => { const numAssignmentsEvaluated = computed(() => {
@ -52,12 +52,23 @@ const progressStatusCount = computed(() => {
<template> <template>
<div> <div>
<div class="mb-4 bg-white p-8"> <div class="mb-4 bg-white p-8">
<h2> <div class="flex items-center">
{{ props.competenceCertificate.title }} <h3 :class="{ 'heading-2': props.detailView }">
</h2> {{ props.competenceCertificate.title }}
</h3>
<div
v-if="numAssignmentsEvaluated < numAssignmentsTotal"
class="ml-2 rounded-full bg-gray-200 px-2.5 py-0.5 text-sm"
>
{{ $t("a.Zwischenstand") }}
</div>
</div>
<div class="flex items-center"> <div class="flex items-center">
<div class="heading-1 py-4"> <div
class="py-4"
:class="{ 'heading-1': props.detailView, 'heading-2': !props.detailView }"
>
{{ userPointsEvaluatedAssignments }} {{ userPointsEvaluatedAssignments }}
</div> </div>
<div class="pl-2">von {{ totalPointsEvaluatedAssignments }} Punkten</div> <div class="pl-2">von {{ totalPointsEvaluatedAssignments }} Punkten</div>
@ -67,14 +78,26 @@ const progressStatusCount = computed(() => {
{{ numAssignmentsEvaluated }} von {{ numAssignmentsTotal }} Arbeiten {{ numAssignmentsEvaluated }} von {{ numAssignmentsTotal }} Arbeiten
abgeschlossen abgeschlossen
</div> </div>
<div v-if="!props.detailView">
<router-link
:to="competenceCertificate.frontend_url"
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 v-if="props.detailView">
v-for="assignment in props.competenceCertificate.assignments" <div
:key="assignment.id" v-for="assignment in props.competenceCertificate.assignments"
class="bg-white px-8" :key="assignment.id"
> class="bg-white px-8"
<CompetenceAssignmentRow :assignment="assignment"></CompetenceAssignmentRow> >
<CompetenceAssignmentRow :assignment="assignment"></CompetenceAssignmentRow>
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -9,13 +9,14 @@ import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertifi
const props = defineProps<{ const props = defineProps<{
courseSlug: string; courseSlug: string;
certificateSlug: string;
}>(); }>();
log.debug("CompetenceOverviewPage created", props); log.debug("CompetenceCertificateDetailPage created", props);
const courseSession = useCurrentCourseSession(); const courseSession = useCurrentCourseSession();
const queryResult = useQuery({ const certificatesQuery = useQuery({
query: COMPETENCE_NAVI_CERTIFICATE_QUERY, query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
variables: { variables: {
courseSlug: props.courseSlug, courseSlug: props.courseSlug,
@ -23,11 +24,11 @@ const queryResult = useQuery({
}, },
}); });
const competenceCertificates = computed(() => { const certificate = computed(() => {
return ( return (
(queryResult.data.value?.competence_certificate_list (certificatesQuery.data.value?.competence_certificate_list
?.competence_certificates as unknown as CompetenceCertificate[]) ?? [] ?.competence_certificates as unknown as CompetenceCertificate[]) ?? []
); ).find((cc) => cc.slug.endsWith(props.certificateSlug));
}); });
onMounted(async () => { onMounted(async () => {
@ -37,14 +38,19 @@ onMounted(async () => {
<template> <template>
<div class="container-large lg:mt-4"> <div class="container-large lg:mt-4">
<h1>{{ $t("competences.title") }}</h1> <nav class="lg:pb-4">
<router-link
<div class="btn-text inline-flex items-center pl-0"
v-for="competenceCertificate in competenceCertificates" :to="`/course/${props.courseSlug}/competence/certificates`"
:key="competenceCertificate.id" >
> <it-icon-arrow-left />
<span>{{ $t("general.back") }}</span>
</router-link>
</nav>
<div v-if="certificate">
<CompetenceCertificateComponent <CompetenceCertificateComponent
:competence-certificate="competenceCertificate" :competence-certificate="certificate"
:detail-view="true"
></CompetenceCertificateComponent> ></CompetenceCertificateComponent>
</div> </div>
</div> </div>

View File

@ -0,0 +1,100 @@
<script setup lang="ts">
import log from "loglevel";
import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
import { useQuery } from "@urql/vue";
import { computed, onMounted } from "vue";
import type { CompetenceCertificate } from "@/types";
import { useCurrentCourseSession } from "@/composables";
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
import _ from "lodash";
const props = defineProps<{
courseSlug: string;
}>();
log.debug("CompetenceOverviewPage created", 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 assignments = computed(() => {
return competenceCertificates.value.flatMap((cc) => cc.assignments);
});
const totalPointsEvaluatedAssignments = computed(() => {
return _.sum(
assignments.value
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
.map((a) => a.max_points)
);
});
const userPointsEvaluatedAssignments = computed(() => {
return _.sum(
assignments.value
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
.map((a) => a.completion?.evaluation_points ?? 0)
);
});
const numAssignmentsEvaluated = computed(() => {
return assignments.value.filter((a) => {
return a.completion?.completion_status === "EVALUATION_SUBMITTED";
}).length;
});
onMounted(async () => {
// log.debug("AssignmentView mounted", props.assignmentId, props.userId);
});
</script>
<template>
<div class="container-large lg:mt-4">
<h2 class="mb-4">{{ $t("a.Kompetenznachweise") }}</h2>
<div class="mb-4 bg-white p-8">
<div class="flex items-center">
<h3>{{ $t("a.Gesamtpunktzahl") }}</h3>
<div
v-if="numAssignmentsEvaluated < assignments.length"
class="ml-2 rounded-full bg-gray-200 px-2.5 py-0.5 text-sm"
>
{{ $t("a.Zwischenstand") }}
</div>
</div>
<div class="flex items-center">
<div class="heading-1 py-4">
{{ userPointsEvaluatedAssignments }}
</div>
<div class="pl-2">von {{ totalPointsEvaluatedAssignments }} Punkten</div>
</div>
</div>
<div
v-for="competenceCertificate in competenceCertificates"
:key="competenceCertificate.id"
>
<CompetenceCertificateComponent
:competence-certificate="competenceCertificate"
:detail-view="false"
></CompetenceCertificateComponent>
</div>
</div>
</template>
<style scoped></style>

View File

@ -1,51 +0,0 @@
<script setup lang="ts">
import CompetenceDetail from "@/pages/competence-old/CompetenceDetail.vue";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { useCompetenceStore } from "@/stores/competence";
import * as log from "loglevel";
log.debug("CompetenceListPage created");
const props = defineProps<{
courseSlug: string;
}>();
const competenceStore = useCompetenceStore();
</script>
<template>
<div class="container-large">
<nav class="py-4 lg:pb-8">
<router-link
v-if="competenceStore.competenceProfilePage()"
class="btn-text inline-flex items-center pl-0"
:to="`${competenceStore.competenceProfilePage()?.frontend_url}-old` as string"
>
<it-icon-arrow-left />
<span>{{ $t("general.back") }}</span>
</router-link>
</nav>
<div class="mb-10 flex flex-col items-center justify-between lg:flex-row">
<h1>Kompetenzen</h1>
<ItDropdownSelect
v-model="competenceStore.selectedCircle"
class="mt-4 w-full lg:mt-0 lg:w-96"
:items="competenceStore.availableCircles"
></ItDropdownSelect>
</div>
<ul v-if="competenceStore.competenceProfilePage()">
<li
v-for="competence in competenceStore.competences()"
:key="competence.id"
class="mb-8 bg-white p-8"
>
<CompetenceDetail
:competence="competence"
:course-slug="props.courseSlug"
></CompetenceDetail>
</li>
</ul>
</div>
</template>
<style scoped></style>

View File

@ -31,10 +31,19 @@ onMounted(async () => {
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': true }"
> >
<router-link :to="`/`"> <router-link :to="`/course/${courseSlug}/competence`">
{{ $t("mediaLibrary.overview") }} {{ $t("mediaLibrary.overview") }}
</router-link> </router-link>
</li> </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 }"
>
<router-link :to="`/course/${courseSlug}/competence/certificates`">
{{ $t("a.Kompetenznachweise") }}
</router-link>
</li>
<!-- Add similar logic for other `li` items as you expand the list --> <!-- Add similar logic for other `li` items as you expand the list -->
<li class="ml-6 inline-block lg:ml-12"></li> <li class="ml-6 inline-block lg:ml-12"></li>
</ul> </ul>

View File

@ -67,9 +67,16 @@ const router = createRouter({
component: () => import("@/pages/competence/CompetenceParentPage.vue"), component: () => import("@/pages/competence/CompetenceParentPage.vue"),
children: [ children: [
{ {
path: "", path: "certificates",
props: true, props: true,
component: () => import("@/pages/competence/CompetenceOverviewPage.vue"), component: () =>
import("@/pages/competence/CompetenceCertificateListPage.vue"),
},
{
path: "certificates/:certificateSlug",
props: true,
component: () =>
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
}, },
], ],
}, },

View File

@ -1,96 +0,0 @@
{% load i18n %}
{% load l10n %}
{% load wagtailadmin_tags %}
<table class="listing {% if full_width %}full-width{% endif %} {% block table_classname %}{% endblock %}">
{% if show_ordering_column or show_bulk_actions %}
<col width="10px"/>
{% endif %}
<col/>
{% if show_parent %}
<col/>
{% endif %}
<col width="12%"/>
<col width="12%"/>
<col width="12%"/>
<col width="10%"/>
<thead>
{% block pre_parent_page_headers %}
{% endblock %}
{% if parent_page %}
{% page_permissions parent_page as parent_page_perms %}
<tr class="index {% if not parent_page.live %} unpublished{% endif %}
{% block parent_page_row_classname %}{% endblock %}">
<td class="title"{% if show_ordering_column or show_bulk_actions %} colspan="2"{% endif %}>
{% block parent_page_title %}
{% endblock %}
</td>
<td class="updated" valign="bottom">{% if parent_page.latest_revision_created_at %}
<div class="human-readable-date" title="{{ parent_page.latest_revision_created_at|date:"DATETIME_FORMAT" }}">
{% blocktrans trimmed with time_period=parent_page.latest_revision_created_at|timesince %}{{ time_period }}
ago{% endblocktrans %}</div>{% endif %}</td>
<td class="type" valign="bottom">
{% if not parent_page.is_root %}
{{ parent_page.content_type.model_class.get_verbose_name }}
{% endif %}
</td>
<td class="status" valign="bottom">
{% if not parent_page.is_root %}
{% include "wagtailadmin/shared/page_status_tag.html" with page=parent_page %}
{% endif %}
</td>
<td></td>
</tr>
{% endif %}
{% block post_parent_page_headers %}
{% endblock %}
</thead>
<tbody>
{% if pages %}
{% trans "Select page" as checkbox_aria_label %}
{% for page in pages %}
{% page_permissions page as page_perms %}
<tr {% if ordering == "ord" %}id="page_{{ page.id|unlocalize }}"
data-page-title="{{ page.get_admin_display_title }}"{% endif %}
class="{% if not page.live %}unpublished{% endif %} {% block page_row_classname %}{% endblock %}">
{% if show_ordering_column %}
<td class="ord">{% if orderable and ordering == "ord" %}
<div class="handle icon icon-grip text-replace">{% trans 'Drag' %}</div>{% endif %}</td>
{% elif show_bulk_actions %}
{% include "wagtailadmin/bulk_actions/listing_checkbox_cell.html" with obj_type="page" obj=page aria_labelledby_prefix="page_" aria_labelledby=page.pk|unlocalize aria_labelledby_suffix="_title" %}
{% endif %}
<td id="page_{{ page.pk|unlocalize }}_title" class="title title_type_{{ page.content_type.model }}" valign="top" data-listing-page-title>
{{ page.type }}
{% block page_title %}
{% endblock %}
</td>
{% if show_parent %}
<td class="parent" valign="top">
{% block page_parent_page_title %}
{% with page.get_parent as parent %}
{% if parent %}
<a
href="{% url 'wagtailadmin_explore' parent.id %}">{{ parent.specific_deferred.get_admin_display_title }}</a>
{% endif %}
{% endwith %}
{% endblock %}
</td>
{% endif %}
<td class="updated" valign="top">{% if page.latest_revision_created_at %}
<div class="human-readable-date" title="{{ page.latest_revision_created_at|date:"DATETIME_FORMAT" }}">
{% blocktrans trimmed with time_period=page.latest_revision_created_at|timesince %}{{ time_period }}
ago{% endblocktrans %}</div>{% endif %}</td>
<td class="type" valign="top">{{ page.content_type.model_class.get_verbose_name }}</td>
<td class="status" valign="top">
{% include "wagtailadmin/shared/page_status_tag.html" with page=page %}
</td>
{% block page_navigation %}
{% endblock %}
</tr>
{% endfor %}
{% else %}
{% block no_results %}{% endblock %}
{% endif %}
</tbody>
</table>

View File

@ -1,8 +0,0 @@
{% extends "wagtailadmin/pages/listing/_list_explore.html" %}
{% load i18n wagtailadmin_tags %}
{% block page_title %}
{% include "wagtailadmin/pages/listing/_page_title_explore.html" %}
{% endblock %}