Add dashboard persons code for Berufsbildner

This commit is contained in:
Daniel Egger 2024-07-22 17:06:53 +02:00
parent 4b2fcc09ec
commit 61ee85668e
23 changed files with 215 additions and 108 deletions

View File

@ -16,6 +16,7 @@ const props = defineProps<{
courseSession: CourseSession; courseSession: CourseSession;
learningContent: LearningContentAssignment | LearningContentEdoniqTest; learningContent: LearningContentAssignment | LearningContentEdoniqTest;
showTitle: boolean; showTitle: boolean;
userSelectionIds?: string[];
}>(); }>();
log.debug("AssignmentSubmissionProgress created", stringifyParse(props)); log.debug("AssignmentSubmissionProgress created", stringifyParse(props));
@ -35,7 +36,8 @@ onMounted(async () => {
await loadAssignmentCompletionStatusData( await loadAssignmentCompletionStatusData(
props.learningContent.content_assignment.id, props.learningContent.content_assignment.id,
props.courseSession.id, props.courseSession.id,
props.learningContent.id props.learningContent.id,
props.userSelectionIds
); );
state.submissionProgressStatusCount = { state.submissionProgressStatusCount = {

View File

@ -7,6 +7,7 @@ const props = defineProps<{
assignmentsCompleted: number; assignmentsCompleted: number;
avgPassed: number; avgPassed: number;
courseSlug: string; courseSlug: string;
detailsLink?: string;
}>(); }>();
const progress = computed(() => { const progress = computed(() => {
@ -20,7 +21,7 @@ const progress = computed(() => {
<template> <template>
<BaseBox <BaseBox
:details-link="`/statistic/${courseSlug}/assignment`" :details-link="props.detailsLink || `/statistic/${courseSlug}/assignment`"
data-cy="dashboard.stats.assignments" data-cy="dashboard.stats.assignments"
> >
<template #title>{{ $t("a.Kompetenznachweis-Elemente") }}</template> <template #title>{{ $t("a.Kompetenznachweis-Elemente") }}</template>

View File

@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, type Ref, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { import {
type DashboardRoleKeyType, type DashboardRoleKeyType,
fetchMentorCompetenceSummary, fetchMentorCompetenceSummary,
} from "@/services/dashboard"; } from "@/services/dashboard";
import type { AssignmentsStatisticsType, BaseStatisticsType } from "@/gql/graphql"; import type { BaseStatisticsType } from "@/gql/graphql";
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue"; import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
import AssignmentSummaryBox from "@/components/dashboard/AssignmentSummaryBox.vue"; import AssignmentSummaryBox from "@/components/dashboard/AssignmentSummaryBox.vue";
@ -43,6 +43,7 @@ onMounted(async () => {
:assignments-completed="assignmentStats.summary.completed_count" :assignments-completed="assignmentStats.summary.completed_count"
:avg-passed="assignmentStats.summary.average_passed" :avg-passed="assignmentStats.summary.average_passed"
:course-slug="props.courseSlug" :course-slug="props.courseSlug"
:details-link="`/statistic/berufsbildner/${props.courseSlug}/assignment`"
/> />
</div> </div>
</div> </div>

View File

@ -131,7 +131,7 @@ export function useCourseSessionDetailQuery(courSessionId?: string) {
function filterMembers(userSelectionIds: string[] | null = null) { function filterMembers(userSelectionIds: string[] | null = null) {
return (courseSessionDetail.value?.users ?? []).filter((u) => { return (courseSessionDetail.value?.users ?? []).filter((u) => {
if (userSelectionIds) { if (userSelectionIds && userSelectionIds.length > 0) {
return userSelectionIds.includes(u.user_id) && u.role === "MEMBER"; return userSelectionIds.includes(u.user_id) && u.role === "MEMBER";
} }
return u.role === "MEMBER"; return u.role === "MEMBER";
@ -495,7 +495,7 @@ export function getVvRoleDisplay(role: DashboardPersonRoleType) {
switch (role) { switch (role) {
case "LEARNING_MENTOR": case "LEARNING_MENTOR":
return t("a.Lernbegleitung"); return t("a.Lernbegleitung");
case "LEARNING_MENTEE": case "PARTICIPANT":
return t("a.Teilnehmer"); return t("a.Teilnehmer");
case "EXPERT": case "EXPERT":
return t("a.Experte"); return t("a.Experte");
@ -512,7 +512,7 @@ export function getUkRoleDisplay(role: DashboardPersonRoleType) {
switch (role) { switch (role) {
case "LEARNING_MENTOR": case "LEARNING_MENTOR":
return t("a.Praxisbildner"); return t("a.Praxisbildner");
case "LEARNING_MENTEE": case "PARTICIPANT":
return t("a.Teilnehmer"); return t("a.Teilnehmer");
case "EXPERT": case "EXPERT":
return t("a.Trainer"); return t("a.Trainer");
@ -579,7 +579,7 @@ export function useDashboardPersonsDueDates(
return refDate >= dayjs().startOf("day"); return refDate >= dayjs().startOf("day");
}); });
// attach `LEARNING_MENTEE` to due dates for `LEARNING_MENTOR` persons // attach `PARTICIPANT` to due dates for `LEARNING_MENTOR` persons
currentDueDates.value.forEach((dueDate) => { currentDueDates.value.forEach((dueDate) => {
if (dueDate.course_session.my_role === "LEARNING_MENTOR") { if (dueDate.course_session.my_role === "LEARNING_MENTOR") {
dueDate.persons = dashboardPersons.value.filter((person) => { dueDate.persons = dashboardPersons.value.filter((person) => {
@ -589,7 +589,7 @@ export function useDashboardPersonsDueDates(
.includes(dueDate.course_session.id) .includes(dueDate.course_session.id)
) { ) {
return person.course_sessions.some( return person.course_sessions.some(
(cs) => cs.user_role === "LEARNING_MENTEE" (cs) => cs.user_role === "PARTICIPANT"
); );
} }
}); });

View File

@ -26,7 +26,7 @@ const documents = {
"\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument, "\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument,
"\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n }\n }\n": types.DashboardCourseDataDocument, "\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n }\n }\n": types.DashboardCourseDataDocument,
"\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n competence_certificate_title\n competence_certificate_id\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_evaluation_percent\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument, "\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n competence_certificate_title\n competence_certificate_id\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_evaluation_percent\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument,
"\n query mentorCourseStatistics($courseId: ID!, $agentRole: String!) {\n mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n competence_certificate_id\n competence_certificate_title\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_evaluation_percent\n average_passed\n }\n }\n }\n }\n }\n": types.MentorCourseStatisticsDocument, "\n query mentorCourseStatistics($courseId: ID!, $agentRole: String!) {\n mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n competence_certificate_id\n competence_certificate_title\n details_url\n learning_content_id\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_evaluation_percent\n average_passed\n }\n }\n }\n }\n }\n": types.MentorCourseStatisticsDocument,
"\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $learningContentType: String!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n learning_content_type: $learningContentType\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument, "\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $learningContentType: String!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n learning_content_type: $learningContentType\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
}; };
@ -99,7 +99,7 @@ export function graphql(source: "\n query courseStatistics($courseId: ID!) {\n
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function graphql(source: "\n query mentorCourseStatistics($courseId: ID!, $agentRole: String!) {\n mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n competence_certificate_id\n competence_certificate_title\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_evaluation_percent\n average_passed\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query mentorCourseStatistics($courseId: ID!, $agentRole: String!) {\n mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n competence_certificate_id\n competence_certificate_title\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_evaluation_percent\n average_passed\n }\n }\n }\n }\n }\n"]; export function graphql(source: "\n query mentorCourseStatistics($courseId: ID!, $agentRole: String!) {\n mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n competence_certificate_id\n competence_certificate_title\n details_url\n learning_content_id\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_evaluation_percent\n average_passed\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query mentorCourseStatistics($courseId: ID!, $agentRole: String!) {\n mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n competence_certificate_id\n competence_certificate_title\n details_url\n learning_content_id\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_evaluation_percent\n average_passed\n }\n }\n }\n }\n }\n"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */

File diff suppressed because one or more lines are too long

View File

@ -59,6 +59,7 @@ type AssignmentStatisticsRecordType {
competence_certificate_id: ID competence_certificate_id: ID
competence_certificate_title: String competence_certificate_title: String
metrics: AssignmentCompletionMetricsType! metrics: AssignmentCompletionMetricsType!
learning_content_id: ID!
details_url: String! details_url: String!
} }

View File

@ -547,6 +547,7 @@ export const DASHBOARD_MENTOR_COMPETENCE_SUMMARY = graphql(`
competence_certificate_id competence_certificate_id
competence_certificate_title competence_certificate_title
details_url details_url
learning_content_id
deadline deadline
metrics { metrics {
_id _id

View File

@ -19,10 +19,13 @@ import { useTranslation } from "i18next-vue";
const { t } = useTranslation(); const { t } = useTranslation();
const props = defineProps<{ export interface Props {
courseSession: CourseSession; courseSession: CourseSession;
learningContent: LearningContentAssignment | LearningContentEdoniqTest; learningContent: LearningContentAssignment | LearningContentEdoniqTest;
}>(); userSelectionIds?: string[];
}
const props = defineProps<Props>();
log.debug("AssignmentDetails created", stringifyParse(props)); log.debug("AssignmentDetails created", stringifyParse(props));
@ -45,11 +48,13 @@ const isPraxisAssignment = computed(() => {
}); });
onMounted(async () => { onMounted(async () => {
log.debug("AssignmentDetails mounted", props.learningContent, props.userSelectionIds);
const { gradedUsers, assignmentSubmittedUsers } = const { gradedUsers, assignmentSubmittedUsers } =
await loadAssignmentCompletionStatusData( await loadAssignmentCompletionStatusData(
props.learningContent.content_assignment.id, props.learningContent.content_assignment.id,
props.courseSession.id, props.courseSession.id,
props.learningContent.id props.learningContent.id,
props.userSelectionIds ?? []
); );
state.gradedUsers = gradedUsers; state.gradedUsers = gradedUsers;
state.assignmentSubmittedUsers = assignmentSubmittedUsers; state.assignmentSubmittedUsers = assignmentSubmittedUsers;
@ -111,13 +116,14 @@ function findUserPointsHtml(userId: string) {
:course-session="courseSession" :course-session="courseSession"
:learning-content="learningContent" :learning-content="learningContent"
:show-title="false" :show-title="false"
:user-selection-ids="props.userSelectionIds"
/> />
</div> </div>
<div v-if="courseSessionDetailResult.filterMembers().length" class="mt-6"> <div v-if="courseSessionDetailResult.filterMembers().length" class="mt-6">
<ul> <ul>
<ItPersonRow <ItPersonRow
v-for="csu in courseSessionDetailResult.filterMembers()" v-for="csu in courseSessionDetailResult.filterMembers(props.userSelectionIds)"
:key="csu.user_id" :key="csu.user_id"
:name="`${csu.first_name} ${csu.last_name}`" :name="`${csu.first_name} ${csu.last_name}`"
:avatar-url="csu.avatar_url" :avatar-url="csu.avatar_url"

View File

@ -5,6 +5,7 @@ import * as log from "loglevel";
import { computed, onMounted } from "vue"; import { computed, onMounted } from "vue";
import type { LearningContentAssignment, LearningContentEdoniqTest } from "@/types"; import type { LearningContentAssignment, LearningContentEdoniqTest } from "@/types";
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables"; import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
import { getPreviousRoute } from "@/router/history";
const props = defineProps<{ const props = defineProps<{
courseSlug: string; courseSlug: string;
@ -25,16 +26,22 @@ const lpQueryResult = useCourseData(props.courseSlug);
const learningContentAssignment = computed(() => { const learningContentAssignment = computed(() => {
return lpQueryResult.findLearningContent(props.assignmentId); return lpQueryResult.findLearningContent(props.assignmentId);
}); });
const previousRoute = getPreviousRoute();
const backRoute = computed(() => {
if (previousRoute?.path.endsWith("/assignment")) {
return previousRoute;
}
return `/course/${props.courseSlug}/cockpit`;
});
</script> </script>
<template> <template>
<div v-if="!loading" class="bg-gray-200"> <div v-if="!loading" class="bg-gray-200">
<div class="container-large"> <div class="container-large">
<nav class="py-4 pb-4"> <nav class="py-4 pb-4">
<router-link <router-link class="btn-text inline-flex items-center pl-0" :to="backRoute">
class="btn-text inline-flex items-center pl-0"
:to="`/course/${props.courseSlug}/cockpit`"
>
<it-icon-arrow-left /> <it-icon-arrow-left />
<span>{{ $t("general.back") }}</span> <span>{{ $t("general.back") }}</span>
</router-link> </router-link>

View File

@ -365,7 +365,7 @@ watch(selectedRegion, () => {
(['SUPERVISOR', 'EXPERT'].includes(cs.my_role) && (['SUPERVISOR', 'EXPERT'].includes(cs.my_role) &&
cs.user_role === 'MEMBER') || cs.user_role === 'MEMBER') ||
(cs.my_role === 'LEARNING_MENTOR' && (cs.my_role === 'LEARNING_MENTOR' &&
cs.user_role === 'LEARNING_MENTEE') cs.user_role === 'PARTICIPANT')
" "
> >
<router-link <router-link

View File

@ -0,0 +1,59 @@
<script setup lang="ts">
import { useCourseData, useCurrentCourseSession } from "@/composables";
import AssignmentDetails from "@/pages/cockpit/assignmentsPage/AssignmentDetails.vue";
import * as log from "loglevel";
import { computed } from "vue";
import type { LearningContentAssignment, LearningContentEdoniqTest } from "@/types";
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
import { getPreviousRoute } from "@/router/history";
const props = defineProps<{
courseSlug: string;
assignmentId: string;
agentRole: string;
participantUserIds: string[];
}>();
log.debug("AgentAssignmentDetail created", props.courseSlug, props.agentRole);
const courseSession = useCurrentCourseSession();
const { loading } = useExpertCockpitPageData(
props.courseSlug,
props.participantUserIds
);
const lpQueryResult = useCourseData(props.courseSlug);
const learningContentAssignment = computed(() => {
return lpQueryResult.findLearningContent(props.assignmentId);
});
</script>
<template>
<div v-if="!loading" class="bg-gray-200">
<div class="container-large">
<nav class="py-4 pb-4">
<router-link
class="btn-text inline-flex items-center pl-0"
:to="`/statistic/${props.agentRole}/${props.courseSlug}/assignment`"
>
<it-icon-arrow-left />
<span>{{ $t("general.back") }}</span>
</router-link>
</nav>
<main>
<div class="bg-white p-6">
<!-- prettier-ignore -->
<AssignmentDetails
v-if="learningContentAssignment"
:course-session="courseSession"
:learning-content="learningContentAssignment as (LearningContentAssignment | LearningContentEdoniqTest)"
:user-selection-ids="participantUserIds"
/>
</div>
</main>
</div>
</div>
</template>
<style scoped></style>

View File

@ -1,56 +1,45 @@
<script setup lang="ts"> <script setup lang="ts">
import { useCourseData, useCurrentCourseSession } from "@/composables"; import { useCurrentCourseSession } from "@/composables";
import AssignmentDetails from "@/pages/cockpit/assignmentsPage/AssignmentDetails.vue";
import * as log from "loglevel"; import * as log from "loglevel";
import { computed, onMounted } from "vue"; import { onMounted, ref } from "vue";
import type { LearningContentAssignment, LearningContentEdoniqTest } from "@/types"; import { fetchDashboardPersons } from "@/services/dashboard";
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables"; import AgentAssignmentDetail from "@/pages/dashboard/agentAssignment/AgentAssignmentDetail.vue";
const props = defineProps<{ const props = defineProps<{
courseSlug: string; courseSlug: string;
assignmentId: string; assignmentId: string;
agentRole: string;
}>(); }>();
log.debug("AgentAssignmentDetailPage created", props.courseSlug); log.debug("AgentAssignmentDetailPage created", props.courseSlug, props.agentRole);
const courseSession = useCurrentCourseSession(); const courseSession = useCurrentCourseSession();
const { loading } = useExpertCockpitPageData(props.courseSlug);
const loading = ref(true);
const participantUserIds = ref<string[]>([]);
onMounted(async () => { onMounted(async () => {
log.debug("AgentAssignmentDetailPage mounted"); log.debug("AgentAssignmentDetailPage mounted", courseSession);
});
const lpQueryResult = useCourseData(props.courseSlug); const personData = await fetchDashboardPersons("default");
const participants = personData?.filter((p) => {
const learningContentAssignment = computed(() => { return p.course_sessions.find(
return lpQueryResult.findLearningContent(props.assignmentId); (cs) => cs.id === courseSession.value.id && cs.my_role === "BERUFSBILDNER"
);
});
participantUserIds.value = participants?.map((p) => p.user_id);
loading.value = false;
}); });
</script> </script>
<template> <template>
<div v-if="!loading" class="bg-gray-200"> <AgentAssignmentDetail
<div class="container-large"> v-if="participantUserIds.length"
<nav class="py-4 pb-4"> :assignment-id="props.assignmentId"
<router-link :agent-role="props.agentRole"
class="btn-text inline-flex items-center pl-0" :course-slug="props.courseSlug"
:to="`/course/${props.courseSlug}/cockpit`" :participant-user-ids="participantUserIds"
> />
<it-icon-arrow-left />
<span>{{ $t("general.back") }}</span>
</router-link>
</nav>
<main>
<div class="bg-white p-6">
<!-- prettier-ignore -->
<AssignmentDetails
v-if="learningContentAssignment"
:course-session="courseSession"
:learning-content="learningContentAssignment as (LearningContentAssignment | LearningContentEdoniqTest)"
/>
</div>
</main>
</div>
</div>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@ -72,6 +72,7 @@ onMounted(async () => {
:course-statistics="agentAssignmentData" :course-statistics="agentAssignmentData"
:course-session-name="courseSessionName" :course-session-name="courseSessionName"
:circle-meta="circleMeta" :circle-meta="circleMeta"
:detail-base-url="`/statistic/${props.agentRole}/${props.courseSlug}/assignment/`"
></AssignmentList> ></AssignmentList>
</div> </div>
</div> </div>

View File

@ -19,6 +19,7 @@ const props = defineProps<{
courseStatistics: BaseStatisticsType; courseStatistics: BaseStatisticsType;
courseSessionName: (sessionId: string) => string; courseSessionName: (sessionId: string) => string;
circleMeta: (circleId: string) => StatisticsCircleDataType; circleMeta: (circleId: string) => StatisticsCircleDataType;
detailBaseUrl?: string;
}>(); }>();
const statisticFilter: Ref<typeof StatisticFilterList | null> = ref(null); const statisticFilter: Ref<typeof StatisticFilterList | null> = ref(null);
@ -51,6 +52,14 @@ async function exportData() {
const filteredItems = statisticFilter.value.getFilteredItems(); const filteredItems = statisticFilter.value.getFilteredItems();
await exportDataAsXls(filteredItems, exportCompetenceElements, userStore.language); await exportDataAsXls(filteredItems, exportCompetenceElements, userStore.language);
} }
const itemDetailUrl = (item: AssignmentStatisticsRecordType) => {
if (props.detailBaseUrl) {
return `${props.detailBaseUrl}${item.learning_content_id}?courseSessionId=${item.course_session_id}`;
}
return item.details_url;
};
</script> </script>
<template> <template>
@ -114,8 +123,7 @@ async function exportData() {
></ItProgress> ></ItProgress>
<router-link <router-link
class="underline" class="underline"
target="_blank" :to="itemDetailUrl(item as AssignmentStatisticsRecordType)"
:to="(item as AssignmentStatisticsRecordType).details_url"
> >
{{ $t("a.Details anschauen") }} {{ $t("a.Details anschauen") }}
</router-link> </router-link>

View File

@ -1,5 +1,5 @@
import { useCourseSessionDetailQuery } from "@/composables"; import { useCourseSessionDetailQuery } from "@/composables";
import { itGet } from "@/fetchHelpers"; import { itGet, itPost } from "@/fetchHelpers";
import type { import type {
Assignment, Assignment,
AssignmentCompletion, AssignmentCompletion,
@ -19,12 +19,16 @@ export interface GradedUser {
export async function loadAssignmentCompletionStatusData( export async function loadAssignmentCompletionStatusData(
assignmentId: string, assignmentId: string,
courseSessionId: string, courseSessionId: string,
learningContentId: string learningContentId: string,
userSelectionIds: string[] | undefined = undefined
) { ) {
const courseSessionDetailResult = useCourseSessionDetailQuery(); const courseSessionDetailResult = useCourseSessionDetailQuery();
const assignmentCompletionData = (await itGet( const assignmentCompletionData = (await itPost(
`/api/assignment/${assignmentId}/${courseSessionId}/status/` `/api/assignment/${assignmentId}/${courseSessionId}/status/`,
{
user_selection_ids: userSelectionIds ?? [],
}
)) as UserAssignmentCompletionStatus[]; )) as UserAssignmentCompletionStatus[];
const members = courseSessionDetailResult.filterMembers(); const members = courseSessionDetailResult.filterMembers();

View File

@ -26,7 +26,8 @@ export type DashboardPersonRoleType =
| "EXPERT" | "EXPERT"
| "MEMBER" | "MEMBER"
| "LEARNING_MENTOR" | "LEARNING_MENTOR"
| "LEARNING_MENTEE"; | "BERUFSBILDNER"
| "PARTICIPANT";
export type DashboardRoleKeyType = export type DashboardRoleKeyType =
| "Supervisor" | "Supervisor"

View File

@ -101,7 +101,7 @@ class AssignmentCompletionMutation(graphene.Mutation):
): ):
if not can_evaluate_assignments( if not can_evaluate_assignments(
evaluation_user=info.context.user, evaluation_user=info.context.user,
assignment_user_id=assignment_user_id, assignment_user_ids=[assignment_user_id],
course_session_id=course_session_id, course_session_id=course_session_id,
): ):
raise PermissionDenied() raise PermissionDenied()

View File

@ -139,7 +139,7 @@ def resolve_assignment_completion(
if str(assignment_user_id) == str(info.context.user.id) or can_evaluate_assignments( if str(assignment_user_id) == str(info.context.user.id) or can_evaluate_assignments(
evaluation_user=info.context.user, evaluation_user=info.context.user,
assignment_user_id=assignment_user_id, assignment_user_ids=[assignment_user_id],
course_session_id=course_session_id, course_session_id=course_session_id,
): ):
course_id = CourseSession.objects.get(id=course_session_id).course_id course_id = CourseSession.objects.get(id=course_session_id).course_id

View File

@ -9,14 +9,23 @@ from vbv_lernwelt.iam.permissions import can_evaluate_assignments
logger = structlog.get_logger(__name__) logger = structlog.get_logger(__name__)
@api_view(["GET"]) @api_view(["GET", "POST"])
def request_assignment_completion_status(request, assignment_id, course_session_id): def request_assignment_completion_status(request, assignment_id, course_session_id):
# TODO quickfix before GraphQL... # TODO quickfix before GraphQL...
if can_evaluate_assignments(request.user, course_session_id):
user_selection_ids = request.data.get("user_selection_ids", [])
if can_evaluate_assignments(
request.user, course_session_id, assignment_user_ids=user_selection_ids
):
qs = AssignmentCompletion.objects.filter( qs = AssignmentCompletion.objects.filter(
course_session_id=course_session_id, course_session_id=course_session_id,
assignment_id=assignment_id, assignment_id=assignment_id,
).values( )
if len(user_selection_ids) > 0:
qs = qs.filter(assignment_user_id__in=user_selection_ids)
values = qs.values(
"id", "id",
"assignment_user_id", "assignment_user_id",
"completion_status", "completion_status",
@ -28,7 +37,7 @@ def request_assignment_completion_status(request, assignment_id, course_session_
) )
# Convert the learning_content_page_id to a string # Convert the learning_content_page_id to a string
data = list(qs) # Evaluate the queryset data = list(values) # Evaluate the queryset
for item in data: for item in data:
if item["evaluation_points"] is not None: if item["evaluation_points"] is not None:
# only `evaluation_points_final` is relevant for the frontend # only `evaluation_points_final` is relevant for the frontend

View File

@ -41,6 +41,7 @@ class AssignmentStatisticsRecordType(graphene.ObjectType):
competence_certificate_id = graphene.ID() competence_certificate_id = graphene.ID()
competence_certificate_title = graphene.String() competence_certificate_title = graphene.String()
metrics = graphene.Field(AssignmentCompletionMetricsType, required=True) metrics = graphene.Field(AssignmentCompletionMetricsType, required=True)
learning_content_id = graphene.ID(required=True)
details_url = graphene.String(required=True) details_url = graphene.String(required=True)
@ -101,13 +102,14 @@ def get_assignment_completion_metrics(
user_selection_ids: List[str] | None, user_selection_ids: List[str] | None,
urql_id_postfix: str = "", urql_id_postfix: str = "",
) -> AssignmentCompletionMetricsType: ) -> AssignmentCompletionMetricsType:
csu_qs = CourseSessionUser.objects.filter(
course_session_id=course_session.id, role=CourseSessionUser.Role.MEMBER
)
if user_selection_ids: if user_selection_ids:
course_session_users = user_selection_ids csu_qs = csu_qs.filter(user_id__in=user_selection_ids)
else:
course_session_users = CourseSessionUser.objects.filter( course_session_users = csu_qs.values_list("user", flat=True)
course_session=course_session,
role=CourseSessionUser.Role.MEMBER,
).values_list("user", flat=True)
assignment_completions = AssignmentCompletion.objects.filter( assignment_completions = AssignmentCompletion.objects.filter(
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value, completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
@ -165,17 +167,16 @@ def create_record(
return AssignmentStatisticsRecordType( return AssignmentStatisticsRecordType(
# make sure it's unique, across all types of assignments! # make sure it's unique, across all types of assignments!
_id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}@{urql_id_postfix}", _id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}@{urql_id_postfix}", # noqa
# noqa
course_session_id=str(course_session_assignment.course_session.id), # noqa course_session_id=str(course_session_assignment.course_session.id), # noqa
circle_id=learning_content.get_circle().id, # noqa circle_id=learning_content.get_circle().id, # noqa
course_session_assignment_id=str(course_session_assignment.id), # noqa course_session_assignment_id=str(course_session_assignment.id), # noqa
generation=course_session_assignment.course_session.generation, # noqa generation=course_session_assignment.course_session.generation, # noqa
assignment_type_translation_key=due_date.assignment_type_translation_key, assignment_type_translation_key=due_date.assignment_type_translation_key, # noqa
competence_certificate_id=str(competence_certificate.id), # noqa competence_certificate_id=str(competence_certificate.id), # noqa
competence_certificate_title=competence_certificate.title, # noqa competence_certificate_title=competence_certificate.title, # noqa
# noqa
assignment_title=learning_content.content_assignment.title, # noqa assignment_title=learning_content.content_assignment.title, # noqa
learning_content_id=str(learning_content.id), # noqa
metrics=get_assignment_completion_metrics( # noqa metrics=get_assignment_completion_metrics( # noqa
course_session=course_session_assignment.course_session, # noqa course_session=course_session_assignment.course_session, # noqa
assignment=learning_content.content_assignment, # noqa assignment=learning_content.content_assignment, # noqa

View File

@ -200,28 +200,34 @@ def _create_person_list_with_roles(user):
# add persons where request.user is mentor # add persons where request.user is mentor
for cs in course_sessions: for cs in course_sessions:
def _add_agent_relation(my_role, user_role):
course_session_entry = _create_course_session_dict(cs, my_role, user_role)
participant_user = relation.participant.user
if participant_user.id not in result_persons:
person_data = create_user_dict(participant_user)
person_data["course_sessions"] = [course_session_entry]
result_persons[participant_user] = person_data
else:
# user is already in result_persons
result_persons[participant_user]["course_sessions"].append(
course_session_entry
)
if "LEARNING_MENTOR" in cs.roles: if "LEARNING_MENTOR" in cs.roles:
for relation in AgentParticipantRelation.objects.filter( for relation in AgentParticipantRelation.objects.filter(
agent=user, participant__course_session_id=cs.id agent=user, participant__course_session_id=cs.id, role="LEARNING_MENTOR"
): ):
course_session_entry = _create_course_session_dict( _add_agent_relation("LEARNING_MENTOR", "PARTICIPANT")
cs,
"LEARNING_MENTOR",
"LEARNING_MENTEE",
)
participant_user = relation.participant.user
if participant_user.id not in result_persons: if "BERUFSBILDNER" in cs.roles:
person_data = create_user_dict(participant_user) for relation in AgentParticipantRelation.objects.filter(
person_data["course_sessions"] = [course_session_entry] agent=user, participant__course_session_id=cs.id, role="BERUFSBILDNER"
result_persons[participant_user] = person_data ):
else: _add_agent_relation("BERUFSBILDNER", "PARTICIPANT")
# user is already in result_persons
result_persons[participant_user]["course_sessions"].append(
course_session_entry
)
# add persons where request.user is mentee # add persons where request.user is lerning mentee
mentor_relation_qs = AgentParticipantRelation.objects.filter( mentor_relation_qs = AgentParticipantRelation.objects.filter(
participant__user=user, participant__user=user,
role=AgentParticipantRoleType.LEARNING_MENTOR.value, role=AgentParticipantRoleType.LEARNING_MENTOR.value,
@ -230,7 +236,7 @@ def _create_person_list_with_roles(user):
cs = mentor_relation.participant.course_session cs = mentor_relation.participant.course_session
course_session_entry = _create_course_session_dict( course_session_entry = _create_course_session_dict(
cs, cs,
"LEARNING_MENTEE", "PARTICIPANT",
"LEARNING_MENTOR", "LEARNING_MENTOR",
) )

View File

@ -97,7 +97,6 @@ def is_agent_for_user(
qs = AgentParticipantRelation.objects.filter( qs = AgentParticipantRelation.objects.filter(
agent=agent, agent=agent,
participant=csu, participant=csu,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
) )
if roles and len(roles) > 0: if roles and len(roles) > 0:
@ -159,7 +158,9 @@ def is_course_session_member(user, course_session_id: int | None = None):
def can_evaluate_assignments( def can_evaluate_assignments(
evaluation_user: User, course_session_id: int, assignment_user_id: str | None = None evaluation_user: User,
course_session_id: int,
assignment_user_ids: list[str] | None = None,
): ):
if evaluation_user.is_superuser: if evaluation_user.is_superuser:
return True return True
@ -167,20 +168,28 @@ def can_evaluate_assignments(
is_supervisor = CourseSessionGroup.objects.filter( is_supervisor = CourseSessionGroup.objects.filter(
supervisor=evaluation_user, course_session__id=course_session_id supervisor=evaluation_user, course_session__id=course_session_id
).exists() ).exists()
if is_supervisor:
return True
is_expert = CourseSessionUser.objects.filter( is_expert = CourseSessionUser.objects.filter(
course_session_id=course_session_id, course_session_id=course_session_id,
user=evaluation_user, user=evaluation_user,
role=CourseSessionUser.Role.EXPERT, role=CourseSessionUser.Role.EXPERT,
).exists() ).exists()
if is_expert:
return True
is_mentor = is_learning_mentor_for_user( if assignment_user_ids and len(assignment_user_ids) > 0:
mentor=evaluation_user, is_agent_array = []
participant_user_id=assignment_user_id, for assignment_user_id in assignment_user_ids:
course_session_id=course_session_id, is_agent_array.append(
) is_agent_for_user(
agent=evaluation_user,
return is_supervisor or is_expert or is_mentor participant_user_id=assignment_user_id,
course_session_id=course_session_id,
)
)
return all(is_agent_array)
def course_sessions_for_user_qs(user): def course_sessions_for_user_qs(user):