vbv/client/src/pages/dashboard/agentAssignment/AgentCompetenceGradeDetailP...

203 lines
6.4 KiB
Vue

<script setup lang="ts">
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
import type { CompetenceCertificateObjectType } from "@/gql/graphql";
import { graphqlClient } from "@/graphql/client";
import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
import { calcCompetenceCertificateGrade } from "@/pages/competence/utils";
import { percentToRoundedGrade } from "@/services/assignmentService";
import { type DashboardPersonType, fetchDashboardPersons } from "@/services/dashboard";
import type { CompetenceCertificateAssignment } from "@/types";
import _ from "lodash";
import log from "loglevel";
import { computed, onMounted, ref } from "vue";
const props = defineProps<{
agentRole: string;
courseSlug: string;
competenceCertificateId: string;
courseSessionId: string;
}>();
log.debug("AgentCompetenceGradeDetailPage created", props);
const loading = ref(true);
const participants = ref<DashboardPersonType[]>([]);
const participantUserIds = computed(() => {
return (participants.value ?? []).map((p) => p.user_id);
});
const courseSession = computed(() => {
return participants.value[0]?.course_sessions.find(
(cs) => cs.id === props.courseSessionId
);
});
const certificateData = ref<CompetenceCertificateObjectType | undefined>(undefined);
function userGrade(userId: string) {
if (certificateData.value) {
const assignmentsWithUserCompletions = _.cloneDeep(
certificateData.value.assignments
);
for (const assignment of assignmentsWithUserCompletions) {
assignment.completions = assignment.completions?.filter(
(c) => c?.assignment_user?.id === userId
);
}
return calcCompetenceCertificateGrade(
assignmentsWithUserCompletions as unknown as CompetenceCertificateAssignment[],
false
);
}
}
const totalAverageGrade = computed(() => {
if (certificateData.value) {
let divisor = 0;
const assignmentAverageGrades = certificateData.value.assignments.map(
(assignment) => {
const relevantCompletions = (assignment.completions ?? []).filter(
(c) => c?.completion_status == "EVALUATION_SUBMITTED"
);
if (relevantCompletions.length === 0) {
return 0;
}
const averagePercent =
_.sumBy(relevantCompletions, (c) => c?.evaluation_percent ?? 0) /
relevantCompletions.length;
if (averagePercent > 0.0001) {
divisor += assignment.competence_certificate_weight ?? 1;
}
return averagePercent * (assignment.competence_certificate_weight ?? 1);
}
);
return percentToRoundedGrade(
_.sum(assignmentAverageGrades) / (divisor ?? 1),
false
);
}
return undefined;
});
onMounted(async () => {
log.debug("AgentAssignmentDetailPage mounted");
const personData = await fetchDashboardPersons("default");
participants.value = personData?.filter((p) => {
return p.course_sessions.find(
(cs) => cs.id === props.courseSessionId && cs.my_role === "BERUFSBILDNER"
);
});
const res = await graphqlClient.query(COMPETENCE_NAVI_CERTIFICATE_QUERY, {
courseSlug: props.courseSlug,
courseSessionId: props.courseSessionId,
userIds: participantUserIds.value,
});
// @ts-ignore
certificateData.value =
res.data?.competence_certificate_list?.competence_certificates.find(
// @ts-ignore
(cc) => cc.id === props.competenceCertificateId
);
loading.value = false;
});
</script>
<template>
<div class="bg-gray-200">
<div v-if="loading" class="m-8 flex justify-center">
<LoadingSpinner />
</div>
<div v-else class="container-large flex flex-col space-y-4">
<router-link
class="btn-text inline-flex items-center pl-0"
:to="`/statistic/${props.agentRole}/${props.courseSlug}/competence-grade`"
data-cy="back-button"
>
<it-icon-arrow-left />
<span>{{ $t("general.back") }}</span>
</router-link>
<div>
<h2 class="mb-8">{{ certificateData?.title }}</h2>
<div class="border-b bg-white px-6 py-6">
{{ courseSession?.session_title }}
</div>
<div
class="heading-3 border-b bg-white px-6 py-6"
data-cy="total-average-grade"
>
{{ $t("a.Durchschnittsnote") }}:
{{ totalAverageGrade }}
</div>
<div class="bg-white px-4 py-2">
<div
v-for="person in participants"
:key="person.user_id"
:data-cy="`person-${person.user_id}`"
class="flex flex-col justify-between gap-4 border-b p-2 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
>
<div class="w-full flex-auto md:w-1/2">
<div class="flex items-center space-x-2">
<img
class="inline-block h-11 w-11 rounded-full"
:src="
person.avatar_url_small ||
'/static/avatars/myvbv-default-avatar.png'
"
:alt="`${person.first_name} ${person.last_name}`"
/>
<div>
<div class="text-bold">
{{ person.first_name }}
{{ person.last_name }}
</div>
<div class="text-gray-900">{{ person.email }}</div>
</div>
</div>
</div>
<div class="flex flex-auto items-center gap-2 md:w-1/4">
<div>{{ $t("a.Note") }}:</div>
<div class="min-w-12 text-center">
<div
class="rounded px-2 py-1 font-bold"
:class="{ 'bg-red-400': (userGrade(person.user_id) ?? 4) < 4 }"
>
{{ userGrade(person.user_id) }}
</div>
</div>
</div>
<div class="w-full flex-auto items-end md:w-1/4 md:text-end">
<router-link
:to="{
name: 'profileLearningPath',
params: {
userId: person.user_id,
courseSlug: props.courseSlug,
},
query: { courseSessionId: props.courseSessionId },
}"
data-cy="person-learning-path-link"
class="link w-full lg:text-right"
>
{{ $t("a.Profil anzeigen") }}
</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</template>