feat: kompetenznavi overview
This commit is contained in:
parent
0d402d7912
commit
964c1f7276
|
|
@ -8,7 +8,7 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const hasFeedbackReceived = computed(() => {
|
const hasFeedbackReceived = computed(() => {
|
||||||
return props.summary.feedback_assessment?.provider_user !== undefined;
|
return props.summary.feedback_assessment?.submitted_by_provider ?? false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const feedbackProviderAvatar = computed(() => {
|
const feedbackProviderAvatar = computed(() => {
|
||||||
|
|
@ -46,19 +46,19 @@ const feedbackProviderName = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
<SmileyCell
|
<SmileyCell
|
||||||
:count="props.summary.self_assessment.counts.pass + 1"
|
:count="props.summary.self_assessment.counts.pass"
|
||||||
smiley="it-icon-smiley-happy"
|
smiley="it-icon-smiley-happy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
<SmileyCell
|
<SmileyCell
|
||||||
:count="props.summary.self_assessment.counts.fail + 1"
|
:count="props.summary.self_assessment.counts.fail"
|
||||||
smiley="it-icon-smiley-thinking"
|
smiley="it-icon-smiley-thinking"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
<SmileyCell
|
<SmileyCell
|
||||||
:count="props.summary.self_assessment.counts.unknown + 1"
|
:count="props.summary.self_assessment.counts.unknown"
|
||||||
smiley="it-icon-smiley-neutral"
|
smiley="it-icon-smiley-neutral"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
|
||||||
import { useQuery } from "@urql/vue";
|
import { useQuery } from "@urql/vue";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import type { CompetenceCertificate } from "@/types";
|
import type { CompetenceCertificate } from "@/types";
|
||||||
import { useCurrentCourseSession, useCourseDataWithCompletion } from "@/composables";
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
import {
|
import {
|
||||||
assignmentsMaxEvaluationPoints,
|
assignmentsMaxEvaluationPoints,
|
||||||
assignmentsUserPoints,
|
assignmentsUserPoints,
|
||||||
competenceCertificateProgressStatusCount,
|
competenceCertificateProgressStatusCount,
|
||||||
} from "@/pages/competence/utils";
|
} from "@/pages/competence/utils";
|
||||||
|
import { useSelfEvaluationFeedbackSummaries } from "@/services/selfEvaluationFeedback";
|
||||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||||
import { calcPerformanceCriteriaStatusCount } from "@/services/competence";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -20,7 +20,6 @@ const props = defineProps<{
|
||||||
log.debug("CompetenceIndexPage setup", props);
|
log.debug("CompetenceIndexPage setup", props);
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const courseData = useCourseDataWithCompletion(props.courseSlug);
|
|
||||||
|
|
||||||
const certificatesQuery = useQuery({
|
const certificatesQuery = useQuery({
|
||||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||||
|
|
@ -49,9 +48,23 @@ const userPointsEvaluatedAssignments = computed(() => {
|
||||||
return assignmentsUserPoints(allAssignments.value);
|
return assignmentsUserPoints(allAssignments.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const performanceCriteriaStatusCount = computed(() => {
|
const selfEvaluationFeedbackSummaries = useSelfEvaluationFeedbackSummaries(
|
||||||
return calcPerformanceCriteriaStatusCount(courseData.flatPerformanceCriteria.value);
|
useCurrentCourseSession().value.id
|
||||||
});
|
);
|
||||||
|
|
||||||
|
const selfAssessmentCounts = computed(
|
||||||
|
() => selfEvaluationFeedbackSummaries.aggregates.value?.self_assessment
|
||||||
|
);
|
||||||
|
|
||||||
|
const feedbackEvaluationCounts = computed(
|
||||||
|
() => selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFeedbackEvaluationVisible = computed(
|
||||||
|
() =>
|
||||||
|
selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment_visible ??
|
||||||
|
false
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -80,7 +93,7 @@ const performanceCriteriaStatusCount = computed(() => {
|
||||||
<div
|
<div
|
||||||
v-for="certificate in competenceCertificates"
|
v-for="certificate in competenceCertificates"
|
||||||
:key="certificate.id"
|
:key="certificate.id"
|
||||||
class="flex flex-col justify-between border-b py-4 first:border-t lg:flex-row lg:items-center"
|
class="flex flex-col justify-between py-4 lg:flex-row lg:items-center"
|
||||||
:data-cy="`certificate-${certificate.slug}`"
|
:data-cy="`certificate-${certificate.slug}`"
|
||||||
>
|
>
|
||||||
<div class="text-bold text-xl">
|
<div class="text-bold text-xl">
|
||||||
|
|
@ -130,16 +143,15 @@ const performanceCriteriaStatusCount = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Self-evaluation -->
|
||||||
<section class="mb-4 bg-white px-8 py-4 lg:mb-8 lg:py-8">
|
<section class="mb-4 bg-white px-8 py-4 lg:mb-8 lg:py-8">
|
||||||
<h3 class="mb-4 border-b pb-4 lg:border-0 lg:pb-0">
|
<h3 class="mb-4 pb-4 lg:pb-0">
|
||||||
{{ $t("a.Selbsteinschätzungen") }}
|
{{ $t("a.Selbsteinschätzungen") }}
|
||||||
</h3>
|
</h3>
|
||||||
<ul
|
<ul
|
||||||
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
||||||
>
|
>
|
||||||
<li
|
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||||
class="mb-4 inline-block flex-1 border-b pb-4 lg:mb-0 lg:w-1/3 lg:border-b-0 lg:border-r lg:pb-0"
|
|
||||||
>
|
|
||||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.no") }}»</h5>
|
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.no") }}»</h5>
|
||||||
<div class="flex flex-row items-center">
|
<div class="flex flex-row items-center">
|
||||||
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
||||||
|
|
@ -147,13 +159,11 @@ const performanceCriteriaStatusCount = computed(() => {
|
||||||
class="ml-4 inline-block text-7xl font-bold"
|
class="ml-4 inline-block text-7xl font-bold"
|
||||||
data-cy="self-evaluation-fail"
|
data-cy="self-evaluation-fail"
|
||||||
>
|
>
|
||||||
{{ performanceCriteriaStatusCount.FAIL }}
|
{{ selfAssessmentCounts?.fail }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||||
class="mb-4 inline-block flex-1 border-b pb-4 lg:mb-0 lg:w-1/3 lg:border-b-0 lg:border-r lg:pb-0"
|
|
||||||
>
|
|
||||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.yes") }}»</h5>
|
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.yes") }}»</h5>
|
||||||
<div class="flex flex-row items-center">
|
<div class="flex flex-row items-center">
|
||||||
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
||||||
|
|
@ -161,11 +171,11 @@ const performanceCriteriaStatusCount = computed(() => {
|
||||||
class="ml-4 inline-block text-7xl font-bold"
|
class="ml-4 inline-block text-7xl font-bold"
|
||||||
data-cy="self-evaluation-success"
|
data-cy="self-evaluation-success"
|
||||||
>
|
>
|
||||||
{{ performanceCriteriaStatusCount.SUCCESS }}
|
{{ selfAssessmentCounts?.pass }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex-1 border-b pb-4 lg:mb-0 lg:w-1/3 lg:border-b-0 lg:pb-0">
|
<li class="flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||||
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
||||||
<div class="flex flex-row items-center">
|
<div class="flex flex-row items-center">
|
||||||
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
||||||
|
|
@ -173,12 +183,59 @@ const performanceCriteriaStatusCount = computed(() => {
|
||||||
class="ml-4 inline-block text-7xl font-bold"
|
class="ml-4 inline-block text-7xl font-bold"
|
||||||
data-cy="self-evaluation-unknown"
|
data-cy="self-evaluation-unknown"
|
||||||
>
|
>
|
||||||
{{ performanceCriteriaStatusCount.UNKNOWN }}
|
{{ selfAssessmentCounts?.unknown }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<!-- Feedback evaluation -->
|
||||||
|
<template v-if="isFeedbackEvaluationVisible">
|
||||||
|
<h3 class="mb-4 border-t pb-4 pt-4 lg:pb-0">
|
||||||
|
{{ $t("a.Fremdeinschätzungen") }}
|
||||||
|
</h3>
|
||||||
|
<ul
|
||||||
|
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
||||||
|
>
|
||||||
|
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||||
|
<h5 class="mb-4 text-gray-700">«{{ $t("receivedEvaluation.no") }}»</h5>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
||||||
|
<p
|
||||||
|
class="ml-4 inline-block text-7xl font-bold"
|
||||||
|
data-cy="self-evaluation-fail"
|
||||||
|
>
|
||||||
|
{{ feedbackEvaluationCounts?.fail }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||||
|
<h5 class="mb-4 text-gray-700">«{{ $t("receivedEvaluation.yes") }}»</h5>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
||||||
|
<p
|
||||||
|
class="ml-4 inline-block text-7xl font-bold"
|
||||||
|
data-cy="self-evaluation-success"
|
||||||
|
>
|
||||||
|
{{ feedbackEvaluationCounts?.pass }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||||
|
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
||||||
|
<p
|
||||||
|
class="ml-4 inline-block text-7xl font-bold"
|
||||||
|
data-cy="self-evaluation-unknown"
|
||||||
|
>
|
||||||
|
{{ feedbackEvaluationCounts?.unknown }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/course/${props.courseSlug}/competence/criteria`"
|
:to="`/course/${props.courseSlug}/competence/criteria`"
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,17 @@ const summaries = computed(() => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const headerTitle = computed(() => {
|
||||||
|
const canHaveFeedback =
|
||||||
|
selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment_visible ??
|
||||||
|
false;
|
||||||
|
if (canHaveFeedback) {
|
||||||
|
return t("a.Selbst- und Fremdeinschätzungen");
|
||||||
|
} else {
|
||||||
|
return t("a.Selbsteinschätzungen");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
log.info("SelfEvaluationsAndFeedbackPage created");
|
log.info("SelfEvaluationsAndFeedbackPage created");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -39,7 +50,7 @@ log.info("SelfEvaluationsAndFeedbackPage created");
|
||||||
<div v-if="isLoaded">
|
<div v-if="isLoaded">
|
||||||
<div class="container-large">
|
<div class="container-large">
|
||||||
<div class="col flex items-center justify-between pb-4">
|
<div class="col flex items-center justify-between pb-4">
|
||||||
<h2 class="py-4">{{ $t("a.Selbst- und Fremdeinschätzungen") }}</h2>
|
<h2 class="py-4">{{ headerTitle }}</h2>
|
||||||
<ItDropdownSelect
|
<ItDropdownSelect
|
||||||
v-model="selectedCircle"
|
v-model="selectedCircle"
|
||||||
class="text-bold w-24 min-w-[18rem] border-2 border-gray-300"
|
class="text-bold w-24 min-w-[18rem] border-2 border-gray-300"
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,10 @@ interface FeedbackSummaryCounts {
|
||||||
|
|
||||||
export interface FeedbackSummaryAggregates {
|
export interface FeedbackSummaryAggregates {
|
||||||
// totals across all learning units in the course session
|
// totals across all learning units in the course session
|
||||||
self_assessment_counts: FeedbackSummaryCounts;
|
self_assessment: FeedbackSummaryCounts;
|
||||||
feedback_assessment_counts: FeedbackSummaryCounts;
|
feedback_assessment: FeedbackSummaryCounts;
|
||||||
|
// does this course have any feedback?
|
||||||
|
feedback_assessment_visible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FeedbackAssessmentSummary {
|
interface FeedbackAssessmentSummary {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from vbv_lernwelt.self_evaluation_feedback.views import (
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# /requester/* URLs -> For the user who requests feedback
|
# /requester/* URLs -> For the user who requests feedback
|
||||||
path(
|
path(
|
||||||
"requester/<int:course_session_id>/feedbacks/summaries",
|
"requester/<signed_int:course_session_id>/feedbacks/summaries",
|
||||||
get_self_evaluation_feedbacks_as_requester,
|
get_self_evaluation_feedbacks_as_requester,
|
||||||
name="get_self_evaluation_feedbacks_as_requester",
|
name="get_self_evaluation_feedbacks_as_requester",
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ class AssessmentCounts(NamedTuple):
|
||||||
fail_count: int
|
fail_count: int
|
||||||
unknown_count: int
|
unknown_count: int
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_count(self):
|
||||||
|
return self.pass_count + self.fail_count + self.unknown_count
|
||||||
|
|
||||||
|
|
||||||
def get_self_evaluation_feedback_counts(
|
def get_self_evaluation_feedback_counts(
|
||||||
feedback: SelfEvaluationFeedback,
|
feedback: SelfEvaluationFeedback,
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ from vbv_lernwelt.self_evaluation_feedback.serializers import (
|
||||||
SelfEvaluationFeedbackSerializer,
|
SelfEvaluationFeedbackSerializer,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.self_evaluation_feedback.utils import (
|
from vbv_lernwelt.self_evaluation_feedback.utils import (
|
||||||
calculate_aggregate,
|
|
||||||
get_self_assessment_counts,
|
get_self_assessment_counts,
|
||||||
get_self_evaluation_feedback_counts,
|
get_self_evaluation_feedback_counts,
|
||||||
|
AssessmentCounts,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
@ -105,7 +105,6 @@ def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
|
||||||
all_feedback_assessment_counts = []
|
all_feedback_assessment_counts = []
|
||||||
|
|
||||||
for learning_unit in LearningUnit.objects.filter(
|
for learning_unit in LearningUnit.objects.filter(
|
||||||
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value,
|
|
||||||
course_category__course=course_session.course,
|
course_category__course=course_session.course,
|
||||||
):
|
):
|
||||||
# this is not a problem in real life, but in the test environment
|
# this is not a problem in real life, but in the test environment
|
||||||
|
|
@ -161,12 +160,34 @@ def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
self_assessment_counts_aggregate = calculate_aggregate(
|
self_assessment_counts_aggregate = AssessmentCounts(
|
||||||
counts=all_self_assessment_counts
|
pass_count=sum(x.pass_count for x in all_self_assessment_counts),
|
||||||
|
fail_count=sum(x.fail_count for x in all_self_assessment_counts),
|
||||||
|
unknown_count=sum(x.unknown_count for x in all_self_assessment_counts),
|
||||||
|
)
|
||||||
|
received_feedback_counts_aggregate = AssessmentCounts(
|
||||||
|
pass_count=sum(x.pass_count for x in all_feedback_assessment_counts),
|
||||||
|
fail_count=sum(x.fail_count for x in all_feedback_assessment_counts),
|
||||||
|
unknown_count=sum(x.unknown_count for x in all_feedback_assessment_counts),
|
||||||
)
|
)
|
||||||
|
|
||||||
feedback_assessment_counts_aggregate = calculate_aggregate(
|
# pad the feedback counts with unknowns for the
|
||||||
counts=all_feedback_assessment_counts
|
# learning units where we have no feedback yet
|
||||||
|
feedback_assessment_counts_aggregate = AssessmentCounts(
|
||||||
|
pass_count=received_feedback_counts_aggregate.pass_count,
|
||||||
|
fail_count=received_feedback_counts_aggregate.fail_count,
|
||||||
|
unknown_count=self_assessment_counts_aggregate.total_count
|
||||||
|
- received_feedback_counts_aggregate.total_count
|
||||||
|
+ received_feedback_counts_aggregate.unknown_count,
|
||||||
|
)
|
||||||
|
|
||||||
|
# check if there are any learning units with mentor feedback
|
||||||
|
feedback_assessment_visible = (
|
||||||
|
LearningUnit.objects.filter(
|
||||||
|
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value,
|
||||||
|
course_category__course=course_session.course,
|
||||||
|
).count()
|
||||||
|
> 0
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
|
|
@ -176,6 +197,7 @@ def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
|
||||||
Circle.objects.filter(id__in=circle_ids).values("id", "title")
|
Circle.objects.filter(id__in=circle_ids).values("id", "title")
|
||||||
),
|
),
|
||||||
"aggregates": {
|
"aggregates": {
|
||||||
|
"feedback_assessment_visible": feedback_assessment_visible,
|
||||||
"feedback_assessment": {
|
"feedback_assessment": {
|
||||||
"pass": feedback_assessment_counts_aggregate.pass_count,
|
"pass": feedback_assessment_counts_aggregate.pass_count,
|
||||||
"fail": feedback_assessment_counts_aggregate.fail_count,
|
"fail": feedback_assessment_counts_aggregate.fail_count,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue