Merged in feat/geteilter-bereich (pull request #304)
Feat: Teilnehmer kann auch Mentor sein Approved-by: Daniel Egger
This commit is contained in:
commit
24aa5d9b8c
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import EvaluationIntro from "@/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue";
|
||||
import EvaluationSummary from "@/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue";
|
||||
import EvaluationTask from "@/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue";
|
||||
import EvaluationIntro from "@/components/assignment/evaluation/EvaluationIntro.vue";
|
||||
import EvaluationSummary from "@/components/assignment/evaluation/EvaluationSummary.vue";
|
||||
import EvaluationTask from "@/components/assignment/evaluation/EvaluationTask.vue";
|
||||
import type {
|
||||
Assignment,
|
||||
AssignmentCompletion,
|
||||
|
|
@ -30,12 +30,10 @@ const courseSession = useCurrentCourseSession();
|
|||
const task = computed(() => props.assignment.evaluation_tasks[props.taskIndex]);
|
||||
|
||||
const expertData = computed(() => {
|
||||
const data = (props.assignmentCompletion?.completion_data?.[task.value.id]
|
||||
?.expert_data ?? {
|
||||
return (props.assignmentCompletion?.completion_data?.[task.value.id]?.expert_data ?? {
|
||||
points: 0,
|
||||
text: "",
|
||||
}) as ExpertData;
|
||||
return data;
|
||||
});
|
||||
|
||||
const text = computed(() => {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
2
|
||||
<script setup lang="ts">
|
||||
import AssignmentSubmissionProgress from "@/components/cockpit/AssignmentSubmissionProgress.vue";
|
||||
import AssignmentSubmissionProgress from "@/components/assignment/AssignmentSubmissionProgress.vue";
|
||||
import type {
|
||||
CourseSession,
|
||||
LearningContent,
|
||||
|
|
@ -10,7 +10,7 @@ import type {
|
|||
import log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import FeedbackSubmissionProgress from "@/components/cockpit/mentor/FeedbackSubmissionProgress.vue";
|
||||
import FeedbackSubmissionProgress from "@/components/selfEvaluationFeedback/FeedbackSubmissionProgress.vue";
|
||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||
import {
|
||||
useCourseDataWithCompletion,
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ if (!courseSession) {
|
|||
throw new Error("Course session not found");
|
||||
}
|
||||
|
||||
const isExpert = courseSessionsStore.hasCockpit(courseSession);
|
||||
const url = isExpert ? props.dueDate.url_expert : props.dueDate.url;
|
||||
const url = courseSession.actions.includes("expert-cockpit")
|
||||
? props.dueDate.url_expert
|
||||
: props.dueDate.url;
|
||||
|
||||
const courseSessionTitle = computed(() => {
|
||||
if (props.dueDate.course_session_id) {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@ import { useTranslation } from "i18next-vue";
|
|||
import { useRouteLookups } from "@/utils/route";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { getCompetenceNaviUrl, getLearningPathUrl } from "@/utils/utils";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
|
||||
const { inCompetenceProfile, inLearningPath } = useRouteLookups();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const cockpit = useCockpitStore();
|
||||
|
||||
const { t } = useTranslation();
|
||||
</script>
|
||||
|
|
@ -34,7 +32,6 @@ const { t } = useTranslation();
|
|||
</router-link>
|
||||
|
||||
<router-link
|
||||
v-if="cockpit.hasExpertCockpitType"
|
||||
data-cy="preview-competence-profile-link"
|
||||
:to="getCompetenceNaviUrl(courseSession.course.slug)"
|
||||
class="preview-nav-item"
|
||||
|
|
|
|||
|
|
@ -18,11 +18,10 @@ import CoursePreviewBar from "@/components/header/CoursePreviewBar.vue";
|
|||
import {
|
||||
getCockpitUrl,
|
||||
getCompetenceNaviUrl,
|
||||
getLearningMentorManagementUrl,
|
||||
getLearningMentorUrl,
|
||||
getLearningPathUrl,
|
||||
getMediaCenterUrl,
|
||||
} from "@/utils/utils";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
|
||||
log.debug("MainNavigationBar created");
|
||||
|
||||
|
|
@ -70,47 +69,62 @@ onMounted(() => {
|
|||
log.debug("MainNavigationBar mounted");
|
||||
});
|
||||
|
||||
const hasMediaLibraryMenu = computed(() => {
|
||||
if (useCockpitStore().hasMentorCockpitType) {
|
||||
return false;
|
||||
}
|
||||
return inCourse() && Boolean(courseSessionsStore.currentCourseSession);
|
||||
});
|
||||
const hasLearningPathMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("learning-path") &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
|
||||
const hasCockpitMenu = computed(() => {
|
||||
return courseSessionsStore.currentCourseSessionHasCockpit;
|
||||
});
|
||||
const hasCompetenceNaviMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("competence-navi") &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
|
||||
const hasPreviewMenu = computed(() => {
|
||||
return (
|
||||
useCockpitStore().hasExpertCockpitType || useCockpitStore().hasMentorCockpitType
|
||||
);
|
||||
});
|
||||
const hasMediaLibraryMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("media-library") &&
|
||||
inCourse()
|
||||
)
|
||||
);
|
||||
|
||||
const hasAppointmentsMenu = computed(() => {
|
||||
if (useCockpitStore().hasMentorCockpitType) {
|
||||
return false;
|
||||
}
|
||||
return userStore.loggedIn;
|
||||
});
|
||||
const hasCockpitMenu = computed(() =>
|
||||
Boolean(courseSessionsStore.currentCourseSession?.actions.includes("expert-cockpit"))
|
||||
);
|
||||
|
||||
const hasPreviewMenu = computed(() =>
|
||||
Boolean(courseSessionsStore.currentCourseSession?.actions.includes("preview"))
|
||||
);
|
||||
|
||||
const hasAppointmentsMenu = computed(() =>
|
||||
Boolean(
|
||||
courseSessionsStore.currentCourseSession?.actions.includes("appointments") &&
|
||||
userStore.loggedIn
|
||||
)
|
||||
);
|
||||
|
||||
const hasNotificationsMenu = computed(() => {
|
||||
return userStore.loggedIn;
|
||||
});
|
||||
|
||||
const hasMentorManagementMenu = computed(() => {
|
||||
if (courseSessionsStore.currentCourseSessionHasCockpit || !inCourse()) {
|
||||
const hasLearningMentor = computed(() => {
|
||||
if (!inCourse()) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
courseSessionsStore.currentCourseSession?.course.configuration
|
||||
.enable_learning_mentor ?? false
|
||||
);
|
||||
|
||||
if (!courseSessionsStore.currentCourseSession) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const courseSession = courseSessionsStore.currentCourseSession;
|
||||
return courseSession.actions.includes("learning-mentor");
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CoursePreviewBar v-if="courseSessionsStore.hasCourseSessionPreview" />
|
||||
<CoursePreviewBar v-if="courseSessionsStore.isCourseSessionPreviewActive" />
|
||||
<div v-else>
|
||||
<Teleport to="body">
|
||||
<MobileMenu
|
||||
|
|
@ -120,6 +134,11 @@ const hasMentorManagementMenu = computed(() => {
|
|||
:has-media-library-menu="hasMediaLibraryMenu"
|
||||
:has-cockpit-menu="hasCockpitMenu"
|
||||
:has-preview-menu="hasPreviewMenu"
|
||||
:has-learning-path-menu="hasLearningPathMenu"
|
||||
:has-competence-navi-menu="hasCompetenceNaviMenu"
|
||||
:has-learning-mentor="hasLearningMentor"
|
||||
:has-notifications-menu="hasNotificationsMenu"
|
||||
:has-appointments-menu="hasAppointmentsMenu"
|
||||
:media-url="
|
||||
getMediaCenterUrl(courseSessionsStore.currentCourseSession?.course?.slug)
|
||||
"
|
||||
|
|
@ -177,79 +196,77 @@ const hasMentorManagementMenu = computed(() => {
|
|||
only relevant if there is a current course session -->
|
||||
<template v-if="courseSessionsStore.currentCourseSession">
|
||||
<div class="hidden space-x-8 lg:flex">
|
||||
<template v-if="courseSessionsStore.currentCourseSessionHasCockpit">
|
||||
<router-link
|
||||
v-if="hasCockpitMenu"
|
||||
data-cy="navigation-cockpit-link"
|
||||
:to="
|
||||
getCockpitUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCockpit() }"
|
||||
>
|
||||
{{ t("cockpit.title") }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="hasCockpitMenu"
|
||||
data-cy="navigation-cockpit-link"
|
||||
:to="
|
||||
getCockpitUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCockpit() }"
|
||||
>
|
||||
{{ t("cockpit.title") }}
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
v-if="hasPreviewMenu"
|
||||
data-cy="navigation-preview-link"
|
||||
:to="
|
||||
getLearningPathUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
"
|
||||
target="_blank"
|
||||
class="nav-item"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<span>{{ t("a.VorschauTeilnehmer") }}</span>
|
||||
<it-icon-external-link class="ml-2" />
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link
|
||||
data-cy="navigation-learning-path-link"
|
||||
:to="
|
||||
getLearningPathUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inLearningPath() }"
|
||||
>
|
||||
{{ t("general.learningPath") }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="hasPreviewMenu"
|
||||
data-cy="navigation-preview-link"
|
||||
:to="
|
||||
getLearningPathUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
"
|
||||
target="_blank"
|
||||
class="nav-item"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<span>{{ t("a.VorschauTeilnehmer") }}</span>
|
||||
<it-icon-external-link class="ml-2" />
|
||||
</div>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="hasLearningPathMenu"
|
||||
data-cy="navigation-learning-path-link"
|
||||
:to="
|
||||
getLearningPathUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inLearningPath() }"
|
||||
>
|
||||
{{ t("general.learningPath") }}
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
data-cy="navigation-competence-profile-link"
|
||||
:to="
|
||||
getCompetenceNaviUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
||||
>
|
||||
{{ t("competences.title") }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="hasCompetenceNaviMenu"
|
||||
data-cy="navigation-competence-profile-link"
|
||||
:to="
|
||||
getCompetenceNaviUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
||||
>
|
||||
{{ t("competences.title") }}
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
v-if="hasMentorManagementMenu"
|
||||
data-cy="navigation-learning-mentor-link"
|
||||
:to="
|
||||
getLearningMentorManagementUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inLearningMentor() }"
|
||||
>
|
||||
{{ t("a.Lernbegleitung") }}
|
||||
</router-link>
|
||||
</template>
|
||||
<router-link
|
||||
v-if="hasLearningMentor"
|
||||
data-cy="navigation-learning-mentor-link"
|
||||
:to="
|
||||
getLearningMentorUrl(
|
||||
courseSessionsStore.currentCourseSession.course.slug
|
||||
)
|
||||
"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inLearningMentor() }"
|
||||
>
|
||||
{{ t("a.Lernbegleitung") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import type { User } from "@/stores/user";
|
||||
import type { CourseSession } from "@/types";
|
||||
import { useRouter } from "vue-router";
|
||||
import {
|
||||
getCockpitUrl,
|
||||
getCompetenceNaviUrl,
|
||||
getLearningMentorUrl,
|
||||
getLearningPathUrl,
|
||||
getMediaCenterUrl,
|
||||
} from "@/utils/utils";
|
||||
|
|
@ -18,6 +18,11 @@ defineProps<{
|
|||
hasMediaLibraryMenu: boolean;
|
||||
hasPreviewMenu: boolean;
|
||||
hasCockpitMenu: boolean;
|
||||
hasLearningPathMenu: boolean;
|
||||
hasCompetenceNaviMenu: boolean;
|
||||
hasLearningMentor: boolean;
|
||||
hasNotificationsMenu: boolean;
|
||||
hasAppointmentsMenu: boolean;
|
||||
courseSession: CourseSession | undefined;
|
||||
mediaUrl?: string;
|
||||
user: User | undefined;
|
||||
|
|
@ -31,8 +36,6 @@ const clickLink = (to: string | undefined) => {
|
|||
emit("closemodal");
|
||||
}
|
||||
};
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -57,42 +60,47 @@ const courseSessionsStore = useCourseSessionsStore();
|
|||
<div v-if="courseSession" class="mt-6 border-b">
|
||||
<h4 class="text-sm text-gray-900">{{ courseSession.course.title }}</h4>
|
||||
<ul class="mt-6">
|
||||
<template v-if="courseSessionsStore.currentCourseSessionHasCockpit">
|
||||
<li v-if="hasCockpitMenu" class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-cockpit-link"
|
||||
@click="clickLink(getCockpitUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("cockpit.title") }}
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="hasPreviewMenu" class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-preview-link"
|
||||
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("a.VorschauTeilnehmer") }}
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
<template v-else>
|
||||
<li class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-learning-path-link"
|
||||
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("general.learningPath") }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-competence-profile-link"
|
||||
@click="clickLink(getCompetenceNaviUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("competences.title") }}
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
<li v-if="hasCockpitMenu" class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-cockpit-link"
|
||||
@click="clickLink(getCockpitUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("cockpit.title") }}
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="hasPreviewMenu" class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-preview-link"
|
||||
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("a.VorschauTeilnehmer") }}
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="hasLearningPathMenu" class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-learning-path-link"
|
||||
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("general.learningPath") }}
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="hasCompetenceNaviMenu" class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-competence-profile-link"
|
||||
@click="clickLink(getCompetenceNaviUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("competences.title") }}
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="hasLearningMentor" class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-mentor-link"
|
||||
@click="clickLink(getLearningMentorUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("a.Lernbegleitung") }}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li v-if="hasMediaLibraryMenu" class="mb-6">
|
||||
<button
|
||||
data-cy="medialibrary-link"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
<script setup lang="ts">
|
||||
import { useLearningMentees } from "@/services/learningMentees";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useCSRFFetch } from "@/fetchHelpers";
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const { summary, fetchData } = useLearningMentees(courseSession.value.id);
|
||||
|
||||
const removeMyMentee = async (menteeId: string) => {
|
||||
await useCSRFFetch(
|
||||
`/api/mentor/${courseSession.value.id}/mentors/${summary.value?.mentor_id}/remove/${menteeId}`
|
||||
).delete();
|
||||
fetchData();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="summary">
|
||||
<template v-if="summary.participants.length > 0">
|
||||
<h2 class="heading-2 py-6">{{ $t("a.Personen, die du begleitest") }}</h2>
|
||||
<div class="bg-white px-4 py-2">
|
||||
<div
|
||||
v-for="participant in summary.participants"
|
||||
:key="participant.id"
|
||||
data-cy="lm-my-mentee-list-item"
|
||||
class="flex flex-col items-start justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:items-center md:gap-16"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<img
|
||||
:alt="participant.last_name"
|
||||
class="h-11 w-11 rounded-full"
|
||||
:src="
|
||||
participant.avatar_url || '/static/avatars/myvbv-default-avatar.png'
|
||||
"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-bold">
|
||||
{{ participant.first_name }}
|
||||
{{ participant.last_name }}
|
||||
</div>
|
||||
{{ participant.email }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-x-5">
|
||||
<router-link
|
||||
data-cy="lm-my-mentee-profile"
|
||||
:to="{
|
||||
name: 'profileLearningPath',
|
||||
params: {
|
||||
userId: participant.id,
|
||||
courseSlug: courseSession.course.slug,
|
||||
},
|
||||
}"
|
||||
class="underline"
|
||||
>
|
||||
{{ $t("cockpit.profileLink") }}
|
||||
</router-link>
|
||||
<button
|
||||
class="underline"
|
||||
data-cy="lm-my-mentee-remove"
|
||||
@click="removeMyMentee(participant.id)"
|
||||
>
|
||||
{{ $t("a.Entfernen") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else>
|
||||
<h2 class="heading-2 py-6">{{ $t("a.Personen, die du begleitest") }}</h2>
|
||||
<div class="flex items-center bg-white px-4 py-2">
|
||||
<it-icon-info class="it-icon mr-2 h-6 w-6" />
|
||||
{{ $t("a.Aktuell begleitest du niemanden als Lernbegleitung.") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
<script setup lang="ts">
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import ItModal from "@/components/ui/ItModal.vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useCSRFFetch } from "@/fetchHelpers";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const showInvitationModal = ref(false);
|
||||
const inviteeEmail = ref("");
|
||||
|
||||
const {
|
||||
execute: refreshMentors,
|
||||
data: mentors,
|
||||
isFetching: isFetchingMentors,
|
||||
} = useCSRFFetch(`/api/mentor/${courseSession.value.id}/mentors`).json();
|
||||
|
||||
const {
|
||||
execute: refreshInvitations,
|
||||
data: invitations,
|
||||
isFetching: isFetchingInvitations,
|
||||
} = useCSRFFetch(`/api/mentor/${courseSession.value.id}/invitations`).json();
|
||||
|
||||
const isLoading = computed(
|
||||
() => isFetchingMentors.value || isFetchingInvitations.value
|
||||
);
|
||||
|
||||
const hasMentors = computed(() => {
|
||||
return (
|
||||
(mentors.value && mentors.value.length > 0) ||
|
||||
(invitations.value && invitations.value.length > 0)
|
||||
);
|
||||
});
|
||||
|
||||
const validEmail = computed(() => {
|
||||
const isSelfInvitation = Boolean(inviteeEmail.value === useUserStore().email);
|
||||
|
||||
if (isSelfInvitation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
return emailRegex.test(inviteeEmail.value);
|
||||
});
|
||||
|
||||
const removeInvitation = async (invitationId: string) => {
|
||||
await useCSRFFetch(
|
||||
`/api/mentor/${courseSession.value.id}/invitations/${invitationId}/delete`
|
||||
).delete();
|
||||
await refreshInvitations();
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const removeMyMentor = async (mentorId: string) => {
|
||||
await useCSRFFetch(
|
||||
`/api/mentor/${courseSession.value.id}/mentors/${mentorId}/remove/${userStore.id}`
|
||||
).delete();
|
||||
await refreshMentors();
|
||||
};
|
||||
|
||||
const inviteMentor = async () => {
|
||||
await useCSRFFetch(`/api/mentor/${courseSession.value.id}/invitations/create`).post({
|
||||
email: inviteeEmail.value,
|
||||
});
|
||||
await refreshInvitations();
|
||||
showInvitationModal.value = false;
|
||||
inviteeEmail.value = "";
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!isLoading" class="bg-gray-200">
|
||||
<div class="flex flex-row items-center justify-between py-6">
|
||||
<h2 class="heading-2">{{ $t("a.Meine Lernbegleitung") }}</h2>
|
||||
<div>
|
||||
<button
|
||||
class="btn-secondary flex items-center"
|
||||
@click="showInvitationModal = true"
|
||||
>
|
||||
<it-icon-add class="it-icon mr-2 h-6 w-6" />
|
||||
{{ $t("a.Neue Lernbegleitung einladen") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-2">
|
||||
<main>
|
||||
<div>
|
||||
<div
|
||||
v-for="invitation in invitations"
|
||||
:key="invitation.id"
|
||||
class="flex flex-col justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:gap-16"
|
||||
>
|
||||
<div class="flex flex-col md:flex-grow md:flex-row">
|
||||
<div class="flex items-center space-x-2">
|
||||
<img
|
||||
:alt="invitation.email"
|
||||
class="h-11 w-11 rounded-full"
|
||||
:src="'/static/avatars/myvbv-default-avatar.png'"
|
||||
/>
|
||||
<span class="text-bold">{{ invitation.email }}</span>
|
||||
</div>
|
||||
<div class="flex items-center pl-8">
|
||||
<it-icon-info class="it-icon mr-1 h-6 w-6" />
|
||||
{{ $t("a.Die Einladung wurde noch nicht angenommen.") }}
|
||||
</div>
|
||||
</div>
|
||||
<button class="underline" @click="removeInvitation(invitation.id)">
|
||||
{{ $t("a.Entfernen") }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-for="learningMentor in mentors"
|
||||
:key="learningMentor.id"
|
||||
data-cy="lm-my-mentor-list-item"
|
||||
class="flex flex-col justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:gap-16"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<img
|
||||
:alt="learningMentor.mentor.last_name"
|
||||
class="h-11 w-11 rounded-full"
|
||||
:src="learningMentor.mentor.avatar_url"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-bold">
|
||||
{{ learningMentor.mentor.first_name }}
|
||||
{{ learningMentor.mentor.last_name }}
|
||||
</div>
|
||||
{{ learningMentor.mentor.email }}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="underline"
|
||||
data-cy="lm-my-mentor-remove"
|
||||
@click="removeMyMentor(learningMentor.id)"
|
||||
>
|
||||
{{ $t("a.Entfernen") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hasMentors">
|
||||
<div class="j mx-1 my-3 flex w-fit items-center space-x-1 bg-sky-200 p-4">
|
||||
<it-icon-info class="it-icon mr-2 h-6 w-6 text-sky-700" />
|
||||
<span>
|
||||
{{
|
||||
$t("a.Aktuell hast du noch keine Person als Lernbegleitung eingeladen.")
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<ItModal v-model="showInvitationModal">
|
||||
<template #title>{{ $t("a.Neue Lernbegleitung einladen") }}</template>
|
||||
<template #body>
|
||||
<div class="flex flex-col">
|
||||
<label for="mentor-email">{{ $t("a.E-Mail Adresse") }}</label>
|
||||
<input id="mentor-email" v-model="inviteeEmail" type="email" />
|
||||
|
||||
<button
|
||||
:disabled="!validEmail"
|
||||
class="btn-primary mt-8"
|
||||
@click="inviteMentor()"
|
||||
>
|
||||
{{ $t("a.Einladung abschicken") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</ItModal>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -17,7 +17,7 @@ const currentCourseSession = useCurrentCourseSession();
|
|||
</div>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'learningMentorManagement',
|
||||
name: 'mentorsAndParticipants',
|
||||
params: { courseSlug: currentCourseSession.course.slug },
|
||||
}"
|
||||
class="btn-blue px-4 py-2 font-bold"
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import AssignmentItem from "@/components/cockpit/mentor/AssignmentItem.vue";
|
||||
import AssignmentItem from "@/components/learningMentor/AssignmentItem.vue";
|
||||
import type { RouteLocationRaw } from "vue-router";
|
||||
|
||||
defineProps<{
|
||||
|
|
@ -16,8 +16,8 @@ defineProps<{
|
|||
:circle-title="circleTitle"
|
||||
:pending-tasks="pendingTasks"
|
||||
:task-link="taskLink"
|
||||
:pending-tasks-label="$t('a.Ergebnisse abgegeben')"
|
||||
:task-link-pending-label="$t('a.Ergebnisse bewerten')"
|
||||
:pending-tasks-label="$t('a.Feedback freigegeben')"
|
||||
:task-link-pending-label="$t('a.Feedback geben')"
|
||||
:task-link-label="$t('a.Praxisaufträge anschauen')"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import AssignmentItem from "@/components/cockpit/mentor/AssignmentItem.vue";
|
||||
import AssignmentItem from "@/components/learningMentor/AssignmentItem.vue";
|
||||
import type { RouteLocationRaw } from "vue-router";
|
||||
|
||||
defineProps<{
|
||||
|
|
@ -17,7 +17,6 @@ import { computed, onUnmounted } from "vue";
|
|||
import { getPreviousRoute } from "@/router/history";
|
||||
import { getCompetenceNaviUrl } from "@/utils/utils";
|
||||
import SelfEvaluationRequestFeedbackPage from "@/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedbackPage.vue";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
|
||||
log.debug("LearningContent.vue setup");
|
||||
|
||||
|
|
@ -30,11 +29,8 @@ const circleStore = useCircleStore();
|
|||
const courseSession = useCurrentCourseSession();
|
||||
const courseCompletionData = useCourseDataWithCompletion();
|
||||
|
||||
const isReadOnly = computed(
|
||||
// a hack: If we are a mentor or expert, we are in read only mode
|
||||
// we might preview / view this but can't change anything (buttons are disabled)
|
||||
() => useCockpitStore().hasExpertCockpitType || useCockpitStore().hasMentorCockpitType
|
||||
);
|
||||
// if we have preview rights, we can only preview the learning content -> read only
|
||||
const isReadOnly = computed(() => courseSession.value.actions.includes("preview"));
|
||||
|
||||
const questions = computed(() => props.learningUnit?.performance_criteria ?? []);
|
||||
const numPages = computed(() => {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import { useMutation } from "@urql/vue";
|
|||
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
||||
import type { Assignment } from "@/types";
|
||||
import DateEmbedding from "@/components/dueDates/DateEmbedding.vue";
|
||||
import { useLearningMentors } from "@/composables";
|
||||
import NoMentorInformationPanel from "@/components/mentor/NoMentorInformationPanel.vue";
|
||||
import { useMyLearningMentors } from "@/composables";
|
||||
import NoMentorInformationPanel from "@/components/learningMentor/NoMentorInformationPanel.vue";
|
||||
import SampleSolution from "@/components/assignment/SampleSolution.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -29,7 +29,7 @@ const upsertAssignmentCompletionMutation = useMutation(
|
|||
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
|
||||
);
|
||||
|
||||
const learningMentors = useLearningMentors().learningMentors;
|
||||
const learningMentors = useMyLearningMentors().learningMentors;
|
||||
const selectedLearningMentor = ref();
|
||||
|
||||
const onSubmit = async () => {
|
||||
|
|
|
|||
|
|
@ -466,7 +466,7 @@ export function useFileUpload() {
|
|||
return { upload, error, loading, fileInfo };
|
||||
}
|
||||
|
||||
export function useLearningMentors() {
|
||||
export function useMyLearningMentors() {
|
||||
const learningMentors = ref<LearningMentor[]>([]);
|
||||
const currentCourseSessionId = useCurrentCourseSession().value.id;
|
||||
const loading = ref(false);
|
||||
|
|
|
|||
|
|
@ -464,6 +464,7 @@ export type DashboardConfigType = {
|
|||
};
|
||||
|
||||
export type DashboardType =
|
||||
| 'MENTOR_DASHBOARD'
|
||||
| 'PROGRESS_DASHBOARD'
|
||||
| 'SIMPLE_DASHBOARD'
|
||||
| 'STATISTICS_DASHBOARD';
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@ enum DashboardType {
|
|||
STATISTICS_DASHBOARD
|
||||
PROGRESS_DASHBOARD
|
||||
SIMPLE_DASHBOARD
|
||||
MENTOR_DASHBOARD
|
||||
}
|
||||
|
||||
type CourseConfigurationObjectType {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
|
||||
import EvaluationContainer from "@/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue";
|
||||
import EvaluationContainer from "@/components/assignment/evaluation/EvaluationContainer.vue";
|
||||
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
||||
import type { Assignment, AssignmentCompletion, CourseSessionUser } from "@/types";
|
||||
import { useQuery } from "@urql/vue";
|
||||
|
|
@ -9,7 +9,7 @@ import log from "loglevel";
|
|||
import { computed, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getPreviousRoute } from "@/router/history";
|
||||
import { getAssignmentTypeTitle } from "../../../utils/utils";
|
||||
import { getAssignmentTypeTitle } from "../../utils/utils";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
|
|
@ -42,9 +42,15 @@ function close() {
|
|||
if (previousRoute) {
|
||||
router.push(previousRoute);
|
||||
} else {
|
||||
router.push({
|
||||
path: `/course/${props.courseSlug}/cockpit`,
|
||||
});
|
||||
if (assignment.value?.assignment_type === "PRAXIS_ASSIGNMENT") {
|
||||
router.push({
|
||||
path: `/course/${props.courseSlug}/learning-mentor`,
|
||||
});
|
||||
} else {
|
||||
router.push({
|
||||
path: `/course/${props.courseSlug}/cockpit`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ import type {
|
|||
} from "@/types";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted, reactive } from "vue";
|
||||
import AssignmentSubmissionProgress from "@/components/cockpit/AssignmentSubmissionProgress.vue";
|
||||
import AssignmentSubmissionProgress from "@/components/assignment/AssignmentSubmissionProgress.vue";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
import { formatDueDate } from "../../../components/dueDates/dueDatesUtils";
|
||||
import { stringifyParse } from "@/utils/utils";
|
||||
|
|
@ -171,7 +171,7 @@ function findUserPointsHtml(userId: string) {
|
|||
props.learningContent.content_type !==
|
||||
'learnpath.LearningContentEdoniqTest'
|
||||
"
|
||||
:to="`/course/${props.courseSession.course.slug}/cockpit/assignment/${learningContent.content_assignment.id}/${csu.user_id}`"
|
||||
:to="`/course/${props.courseSession.course.slug}/assignment-evaluation/${learningContent.content_assignment.id}/${csu.user_id}`"
|
||||
class="link lg:w-full lg:text-right"
|
||||
data-cy="show-results"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -148,7 +148,10 @@ const courseSessionDetailResult = useCourseSessionDetailQuery();
|
|||
</template>
|
||||
<template #link>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/cockpit/profile/${csu.user_id}`"
|
||||
:to="{
|
||||
name: 'profileLearningPath',
|
||||
params: { userId: csu.user_id, courseSlug: props.courseSlug },
|
||||
}"
|
||||
class="link w-full lg:text-right"
|
||||
>
|
||||
{{ $t("general.profileLink") }}
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<nav class="border-b bg-white px-4 lg:px-8">
|
||||
<ul class="flex flex-col lg:flex-row">
|
||||
<li
|
||||
class="border-t-2 border-t-transparent"
|
||||
:class="{
|
||||
'border-b-2 border-b-blue-900':
|
||||
route.name === 'mentorCockpitOverview' || route.name === 'mentorCockpit',
|
||||
}"
|
||||
>
|
||||
<router-link :to="{ name: 'mentorCockpitOverview' }" class="block py-3">
|
||||
{{ $t("a.Übersicht") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li
|
||||
class="border-t-2 border-t-transparent lg:ml-12"
|
||||
:class="{
|
||||
'border-b-2 border-b-blue-900': route.name === 'mentorCockpitParticipants',
|
||||
}"
|
||||
>
|
||||
<router-link :to="{ name: 'mentorCockpitParticipants' }" class="block py-3">
|
||||
{{ $t("a.Teilnehmer") }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main class="container-large">
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import type { Assignment } from "@/services/mentorCockpit";
|
||||
import { useMentorCockpit } from "@/services/mentorCockpit";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { computed, type Ref, ref } from "vue";
|
||||
import PraxisAssignmentItem from "@/components/cockpit/mentor/PraxisAssignmentItem.vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import SelfAssignmentFeedbackAssignmentItem from "@/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue";
|
||||
|
||||
const { t } = useTranslation();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const mentorCockpitStore = useMentorCockpit(courseSession.value.id);
|
||||
const summary = mentorCockpitStore.summary;
|
||||
|
||||
const statusFilterValue = ref({ name: t("Alle"), id: "_all" });
|
||||
|
||||
const statusFilter = ref([
|
||||
{ name: t("Alle"), id: "_all" },
|
||||
{ name: t("a.Zu erledigen"), id: "todo" },
|
||||
]);
|
||||
|
||||
const circleFilterValue = ref({ name: t("a.AlleCircle"), id: "_all" });
|
||||
|
||||
const circleFilter = computed(() => {
|
||||
if (!summary.value) return [];
|
||||
|
||||
return [
|
||||
{ name: t("a.AlleCircle"), id: "_all" },
|
||||
...summary.value.circles.map((circle) => ({
|
||||
name: `Circle: ${circle.title}`,
|
||||
id: circle.id,
|
||||
})),
|
||||
];
|
||||
});
|
||||
|
||||
const filteredAssignments: Ref<Assignment[]> = computed(() => {
|
||||
if (!summary.value) return [];
|
||||
|
||||
let filtered = summary.value.assignments;
|
||||
|
||||
if (statusFilterValue.value.id !== "_all") {
|
||||
filtered = filtered.filter((item) => item.pending_evaluations > 0);
|
||||
}
|
||||
|
||||
if (circleFilterValue.value.id !== "_all") {
|
||||
filtered = filtered.filter(
|
||||
(item) => item.circle_id === String(circleFilterValue.value.id)
|
||||
);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="summary" class="bg-white">
|
||||
<div class="flex flex-col space-x-2 lg:flex-row">
|
||||
<ItDropdownSelect
|
||||
v-model="statusFilterValue"
|
||||
class="min-w-[10rem]"
|
||||
:items="statusFilter"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
<ItDropdownSelect
|
||||
v-model="circleFilterValue"
|
||||
class="min-w-[18rem]"
|
||||
:items="circleFilter"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<template v-for="item in filteredAssignments" :key="item.id">
|
||||
<PraxisAssignmentItem
|
||||
v-if="item.type === 'praxis_assignment'"
|
||||
:circle-title="mentorCockpitStore.getCircleTitleById(item.circle_id)"
|
||||
:pending-tasks="item.pending_evaluations"
|
||||
:task-link="{
|
||||
name: 'mentorCockpitPraxisAssignments',
|
||||
params: { praxisAssignmentId: item.id },
|
||||
}"
|
||||
:task-title="item.title"
|
||||
/>
|
||||
<SelfAssignmentFeedbackAssignmentItem
|
||||
v-else-if="item.type === 'self_evaluation_feedback'"
|
||||
:circle-title="mentorCockpitStore.getCircleTitleById(item.circle_id)"
|
||||
:pending-tasks="item.pending_evaluations"
|
||||
:task-link="{
|
||||
name: 'mentorCockpitSelfEvaluationFeedbackAssignments',
|
||||
params: { learningUnitId: item.id },
|
||||
}"
|
||||
:task-title="item.title"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { useMentorCockpit } from "@/services/mentorCockpit";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const { summary } = useMentorCockpit(courseSession.value.id);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="summary" class="bg-white px-4 py-2">
|
||||
<div
|
||||
v-for="participant in summary.participants"
|
||||
:key="participant.id"
|
||||
class="flex flex-col items-start justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:items-center md:gap-16"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<img
|
||||
:alt="participant.last_name"
|
||||
class="h-11 w-11 rounded-full"
|
||||
:src="participant.avatar_url || '/static/avatars/myvbv-default-avatar.png'"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-bold">
|
||||
{{ participant.first_name }}
|
||||
{{ participant.last_name }}
|
||||
</div>
|
||||
{{ participant.email }}
|
||||
</div>
|
||||
</div>
|
||||
<router-link
|
||||
:to="{ name: 'cockpitUserProfile', params: { userId: participant.id } }"
|
||||
class="underline"
|
||||
>
|
||||
{{ $t("cockpit.profileLink") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<main>
|
||||
<div>
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -11,6 +11,7 @@ import SimpleCoursePage from "@/pages/dashboard/SimpleCoursePage.vue";
|
|||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||
import CourseDetailDates from "@/components/dashboard/CourseDetailDates.vue";
|
||||
import NoCourseSession from "@/components/dashboard/NoCourseSession.vue";
|
||||
import MentorPage from "@/pages/dashboard/MentorPage.vue";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ const boards: Record<DashboardType, DashboardPage> = {
|
|||
PROGRESS_DASHBOARD: { main: ProgressPage, aside: SimpleDates },
|
||||
SIMPLE_DASHBOARD: { main: SimpleCoursePage, aside: SimpleDates },
|
||||
STATISTICS_DASHBOARD: { main: StatisticPage, aside: CourseDetailDates },
|
||||
MENTOR_DASHBOARD: { main: MentorPage, aside: SimpleDates },
|
||||
};
|
||||
|
||||
onMounted(dashboardStore.loadDashboardDetails);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import { getLearningMentorUrl } from "@/utils/utils";
|
||||
import { useDashboardStore } from "@/stores/dashboard";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="dashboardStore.currentDashboardConfig">
|
||||
<div class="mb-14">
|
||||
<div class="h-full bg-white p-6">
|
||||
<h3 class="mb-4">{{ dashboardStore.currentDashboardConfig.name }}</h3>
|
||||
<div>
|
||||
<router-link
|
||||
class="btn-blue"
|
||||
data-cy="lm-dashboard-link"
|
||||
:to="getLearningMentorUrl(dashboardStore.currentDashboardConfig.slug)"
|
||||
>
|
||||
{{ $t("a.Übersicht anschauen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -74,7 +74,7 @@ const showCompetenceCertificates = computed(() => {
|
|||
<router-link
|
||||
class="btn-blue"
|
||||
:to="getLearningPathUrl(dashboardStore.currentDashboardConfig.slug)"
|
||||
:data-cy="`continue-course-${dashboardStore.currentDashboardConfig.id}`"
|
||||
data-cy="progress-dashboard-continue-course-link"
|
||||
>
|
||||
{{ $t("general.nextStep") }}
|
||||
</router-link>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ const dashboardStore = useDashboardStore();
|
|||
<router-link
|
||||
class="btn-blue"
|
||||
:to="getCockpitUrl(dashboardStore.currentDashboardConfig.slug)"
|
||||
:data-cy="`continue-course-${dashboardStore.currentDashboardConfig.id}`"
|
||||
>
|
||||
{{ $t("a.Cockpit anschauen") }}
|
||||
</router-link>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { useCSRFFetch } from "@/fetchHelpers";
|
||||
import { getCockpitUrl } from "@/utils/utils";
|
||||
import { getLearningMentorUrl } from "@/utils/utils";
|
||||
|
||||
const props = defineProps<{
|
||||
courseId: string;
|
||||
|
|
@ -56,8 +56,8 @@ const { data, error } = useCSRFFetch(
|
|||
</template>
|
||||
</i18next>
|
||||
<div class="mt-4">
|
||||
<a class="underline" :href="getCockpitUrl(data.course_slug)">
|
||||
{{ $t("a.Cockpit anschauen") }}
|
||||
<a class="underline" :href="getLearningMentorUrl(data.course_slug)">
|
||||
{{ $t("a.Übersicht anschauen") }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,148 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import ItModal from "@/components/ui/ItModal.vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useCSRFFetch } from "@/fetchHelpers";
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const showInvitationModal = ref(false);
|
||||
const inviteeEmail = ref("");
|
||||
|
||||
const { execute: refreshMentors, data: mentors } = useCSRFFetch(
|
||||
`/api/mentor/${courseSession.value.id}/mentors`
|
||||
).json();
|
||||
|
||||
const { execute: refreshInvitations, data: invitations } = useCSRFFetch(
|
||||
`/api/mentor/${courseSession.value.id}/invitations`
|
||||
).json();
|
||||
|
||||
const hasMentors = computed(() => {
|
||||
return (
|
||||
(mentors.value && mentors.value.length > 0) ||
|
||||
(invitations.value && invitations.value.length > 0)
|
||||
);
|
||||
});
|
||||
|
||||
const validEmail = computed(() => {
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
return emailRegex.test(inviteeEmail.value);
|
||||
});
|
||||
|
||||
const removeInvitation = async (invitationId: string) => {
|
||||
await useCSRFFetch(
|
||||
`/api/mentor/${courseSession.value.id}/invitations/${invitationId}/delete`
|
||||
).delete();
|
||||
await refreshInvitations();
|
||||
};
|
||||
|
||||
const removeMentor = async (mentorId: string) => {
|
||||
await useCSRFFetch(
|
||||
`/api/mentor/${courseSession.value.id}/mentors/${mentorId}/leave`
|
||||
).delete();
|
||||
await refreshMentors();
|
||||
};
|
||||
|
||||
const inviteMentor = async () => {
|
||||
await useCSRFFetch(`/api/mentor/${courseSession.value.id}/invitations/create`).post({
|
||||
email: inviteeEmail.value,
|
||||
});
|
||||
await refreshInvitations();
|
||||
showInvitationModal.value = false;
|
||||
inviteeEmail.value = "";
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<div class="container-large">
|
||||
<header class="mb-8 mt-12">
|
||||
<h1 class="mb-8">{{ $t("a.Lernbegleitung") }}</h1>
|
||||
<p>
|
||||
{{
|
||||
$t(
|
||||
"a.Hier kannst du Personen einladen, damit sie deine Lernbegleitung werden. Zudem siehst du jederzeit eine Übersicht aller Personen, die du bereits als Lernbegleitung hinzugefügt hast."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</header>
|
||||
<main>
|
||||
<div class="bg-white p-6">
|
||||
<div class="mb-8">
|
||||
<button
|
||||
class="btn-secondary flex items-center"
|
||||
@click="showInvitationModal = true"
|
||||
>
|
||||
<it-icon-add class="it-icon mr-2 h-6 w-6" />
|
||||
{{ $t("a.Neue Lernbegleitung einladen") }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="border-t">
|
||||
<div
|
||||
v-for="invitation in invitations"
|
||||
:key="invitation.id"
|
||||
class="flex flex-col justify-between gap-4 border-b py-4 md:flex-row md:gap-16"
|
||||
>
|
||||
<div class="flex flex-col justify-between md:flex-grow md:flex-row">
|
||||
{{ invitation.email }}
|
||||
<div class="flex items-center">
|
||||
<it-icon-info class="it-icon mr-2 h-6 w-6" />
|
||||
{{ $t("a.Die Einladung wurde noch nicht angenommen.") }}
|
||||
</div>
|
||||
</div>
|
||||
<button class="underline" @click="removeInvitation(invitation.id)">
|
||||
{{ $t("a.Entfernen") }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-for="learningMentor in mentors"
|
||||
:key="learningMentor.id"
|
||||
class="flex flex-col justify-between gap-4 border-b py-4 md:flex-row md:gap-16"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<img
|
||||
:alt="learningMentor.mentor.last_name"
|
||||
class="h-11 w-11 rounded-full"
|
||||
:src="learningMentor.mentor.avatar_url"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-bold">
|
||||
{{ learningMentor.mentor.first_name }}
|
||||
{{ learningMentor.mentor.last_name }}
|
||||
</div>
|
||||
{{ learningMentor.mentor.email }}
|
||||
</div>
|
||||
</div>
|
||||
<button class="underline" @click="removeMentor(learningMentor.id)">
|
||||
{{ $t("a.Entfernen") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hasMentors" class="mt-8 flex items-center">
|
||||
<it-icon-info class="it-icon mr-2 h-6 w-6" />
|
||||
{{
|
||||
$t("a.Aktuell hast du noch keine Person als Lernbegleitung eingeladen.")
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<ItModal v-model="showInvitationModal">
|
||||
<template #title>{{ $t("a.Neue Lernbegleitung einladen") }}</template>
|
||||
<template #body>
|
||||
<div class="flex flex-col">
|
||||
<label for="mentor-email">{{ $t("a.E-Mail Adresse") }}</label>
|
||||
<input id="mentor-email" v-model="inviteeEmail" type="email" />
|
||||
|
||||
<button
|
||||
:disabled="!validEmail"
|
||||
class="btn-primary mt-8"
|
||||
@click="inviteMentor()"
|
||||
>
|
||||
{{ $t("a.Einladung abschicken") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</ItModal>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<template>
|
||||
<router-link
|
||||
:to="{ name: 'mentorCockpitOverview' }"
|
||||
:to="{ name: 'learningMentorOverview' }"
|
||||
class="btn-text mb-4 inline-flex items-center pl-0"
|
||||
>
|
||||
<it-icon-arrow-left class="it-icon"></it-icon-arrow-left>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
// if just a course session member -> hide navigation
|
||||
// and automatically redirect to mentorsAndParticipants
|
||||
const isMentoring = courseSession.value.actions.includes(
|
||||
"learning-mentor::guide-members"
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (!isMentoring) {
|
||||
router.push({ name: "mentorsAndParticipants" });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<nav
|
||||
v-if="isMentoring"
|
||||
class="border-b bg-white px-4 lg:px-8"
|
||||
data-cy="lm-main-navigation"
|
||||
>
|
||||
<ul class="flex flex-col lg:flex-row">
|
||||
<li
|
||||
class="border-t-2 border-t-transparent"
|
||||
:class="{
|
||||
'border-b-2 border-b-blue-900': route.name
|
||||
?.toString()
|
||||
.startsWith('learningMentor'),
|
||||
}"
|
||||
>
|
||||
<router-link
|
||||
data-cy="lm-overview-navigation-link"
|
||||
:to="{ name: 'learningMentorOverview' }"
|
||||
class="block py-3"
|
||||
>
|
||||
{{ $t("a.Übersicht") }}
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
<li
|
||||
data-cy="lm-mentees-navigation-link"
|
||||
class="border-t-2 border-t-transparent lg:ml-12"
|
||||
:class="{
|
||||
'border-b-2 border-b-blue-900': route.name === 'mentorsAndParticipants',
|
||||
}"
|
||||
>
|
||||
<router-link :to="{ name: 'mentorsAndParticipants' }" class="block py-3">
|
||||
{{ $t("a.Personen") }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main class="container-large">
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<script setup lang="ts">
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import PraxisAssignmentItem from "@/components/learningMentor/PraxisAssignmentItem.vue";
|
||||
import SelfAssignmentFeedbackAssignmentItem from "@/components/learningMentor/SelfAssignmentFeedbackAssignmentItem.vue";
|
||||
import { useAssignmentTodoListStore } from "@/stores/learningMentor/assignmentTodoList";
|
||||
|
||||
const assignmentTodoListStore = useAssignmentTodoListStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 class="heading-2 mb-6 mt-6">{{ $t("a.Das wurde mit dir geteilt") }}</h2>
|
||||
<div class="flex flex-col bg-white pb-5 pt-1">
|
||||
<div class="flex flex-col lg:flex-row lg:space-x-2">
|
||||
<ItDropdownSelect
|
||||
v-model="assignmentTodoListStore.selectedStatus"
|
||||
class="min-w-[10rem]"
|
||||
:items="assignmentTodoListStore.statusOptions"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
<ItDropdownSelect
|
||||
v-model="assignmentTodoListStore.selectedCircle"
|
||||
class="min-w-[18rem]"
|
||||
:items="assignmentTodoListStore.circleOptions"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<template v-if="assignmentTodoListStore.filteredAssignments.length > 0">
|
||||
<template
|
||||
v-for="item in assignmentTodoListStore.filteredAssignments"
|
||||
:key="item.id"
|
||||
>
|
||||
<PraxisAssignmentItem
|
||||
v-if="item.type === 'praxis_assignment'"
|
||||
:circle-title="item.circle_name"
|
||||
:pending-tasks="item.pending_evaluations"
|
||||
:task-link="{
|
||||
name: 'learningMentorPraxisAssignments',
|
||||
params: { praxisAssignmentId: item.id },
|
||||
}"
|
||||
:task-title="item.title"
|
||||
/>
|
||||
<SelfAssignmentFeedbackAssignmentItem
|
||||
v-else-if="item.type === 'self_evaluation_feedback'"
|
||||
:circle-title="item.circle_name"
|
||||
:pending-tasks="item.pending_evaluations"
|
||||
:task-link="{
|
||||
name: 'learningMentorSelfEvaluationFeedbackAssignments',
|
||||
params: { learningUnitId: item.id },
|
||||
}"
|
||||
:task-title="item.title"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<div v-else class="mx-5 flex flex-row items-center bg-green-200 pl-2">
|
||||
<it-icon-check class="it-icon"></it-icon-check>
|
||||
<p class="py-4 text-base">{{ $t("a.Du hast alles erledigt.") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<script setup lang="ts">
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import MyMentors from "@/components/learningMentor/MyMentors.vue";
|
||||
import MyMentees from "@/components/learningMentor/MyMentees.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const isMyMentorsVisible = computed(() =>
|
||||
courseSession.value.actions.includes("learning-mentor::edit-mentors")
|
||||
);
|
||||
|
||||
const isMyMenteesVisible = computed(() =>
|
||||
courseSession.value.actions.includes("learning-mentor::guide-members")
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-large space-y-20">
|
||||
<template v-if="isMyMenteesVisible || isMyMentorsVisible">
|
||||
<div v-if="isMyMentorsVisible">
|
||||
<MyMentors data-cy="lm-my-mentors" />
|
||||
</div>
|
||||
<div v-if="isMyMenteesVisible">
|
||||
<MyMentees data-cy="lm-my-mentees" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { Assignment, Participant } from "@/services/mentorCockpit";
|
||||
import { useMentorCockpit } from "@/services/mentorCockpit";
|
||||
import type { Assignment, Participant } from "@/services/learningMentees";
|
||||
import { useLearningMentees } from "@/services/learningMentees";
|
||||
import { computed, onMounted, type Ref } from "vue";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import log from "loglevel";
|
||||
|
|
@ -10,10 +10,10 @@ const props = defineProps<{
|
|||
}>();
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const mentorCockpitStore = useMentorCockpit(courseSession.value.id);
|
||||
const participants = computed(() => mentorCockpitStore.summary.value?.participants);
|
||||
const learningMentees = useLearningMentees(courseSession.value.id);
|
||||
const participants = computed(() => learningMentees.summary.value?.participants);
|
||||
const praxisAssignment: Ref<Assignment | null> = computed(() =>
|
||||
mentorCockpitStore.getAssignmentById(props.praxisAssignmentId)
|
||||
learningMentees.getAssignmentById(props.praxisAssignmentId)
|
||||
);
|
||||
|
||||
const getParticipantById = (id: string): Participant | null => {
|
||||
|
|
@ -22,7 +22,7 @@ const getParticipantById = (id: string): Participant | null => {
|
|||
|
||||
onMounted(() => {
|
||||
log.debug("MentorPraxisAssignment mounted");
|
||||
mentorCockpitStore.fetchData();
|
||||
learningMentees.fetchData();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -30,9 +30,7 @@ onMounted(() => {
|
|||
<div v-if="praxisAssignment">
|
||||
<div class="p-6">
|
||||
<h2 class="mb-2">{{ $t("a.Praxisauftrag") }}: {{ praxisAssignment.title }}</h2>
|
||||
<span class="text-gray-800">
|
||||
Circle «{{ mentorCockpitStore.getCircleTitleById(praxisAssignment.circle_id) }}»
|
||||
</span>
|
||||
<span class="text-gray-800">Circle «{{ praxisAssignment.circle_name }}»</span>
|
||||
<template v-if="praxisAssignment.pending_evaluations > 0">
|
||||
<div class="flex flex-row items-center space-x-2 pt-4">
|
||||
<div
|
||||
|
|
@ -92,7 +90,7 @@ onMounted(() => {
|
|||
<it-icon-check class="h-5 w-5"></it-icon-check>
|
||||
</span>
|
||||
</div>
|
||||
<span>{{ $t("a.Bewertung freigeben") }}</span>
|
||||
<span>{{ $t("a.Ergebnisse abgegeben") }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Right -->
|
||||
|
|
@ -102,14 +100,14 @@ onMounted(() => {
|
|||
class="btn-primary"
|
||||
:to="item.url"
|
||||
>
|
||||
{{ $t("a.Ergebnis bewerten") }}
|
||||
{{ $t("a.Feedback geben") }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-else-if="item.status == 'EVALUATED'"
|
||||
class="underline"
|
||||
:to="item.url"
|
||||
>
|
||||
{{ $t("a.Bewertung ansehen") }}
|
||||
{{ $t("a.Feedback ansehen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { Assignment, Participant } from "@/services/mentorCockpit";
|
||||
import { useMentorCockpit } from "@/services/mentorCockpit";
|
||||
import type { Assignment, Participant } from "@/services/learningMentees";
|
||||
import { useLearningMentees } from "@/services/learningMentees";
|
||||
import { computed, type Ref } from "vue";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
|
||||
|
|
@ -9,15 +9,15 @@ const props = defineProps<{
|
|||
}>();
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const mentorCockpitStore = useMentorCockpit(courseSession.value.id);
|
||||
const learningMentees = useLearningMentees(courseSession.value.id);
|
||||
|
||||
const selfEvaluationFeedback: Ref<Assignment | null> = computed(() =>
|
||||
mentorCockpitStore.getAssignmentById(props.learningUnitId)
|
||||
learningMentees.getAssignmentById(props.learningUnitId)
|
||||
);
|
||||
|
||||
const getParticipantById = (id: string): Participant | null => {
|
||||
if (mentorCockpitStore.summary.value?.participants) {
|
||||
const found = mentorCockpitStore.summary.value.participants.find(
|
||||
if (learningMentees.summary.value?.participants) {
|
||||
const found = learningMentees.summary.value.participants.find(
|
||||
(item) => item.id === id
|
||||
);
|
||||
return found || null;
|
||||
|
|
@ -33,9 +33,7 @@ const getParticipantById = (id: string): Participant | null => {
|
|||
{{ $t("a.Selbsteinschätzung") }}: {{ selfEvaluationFeedback.title }}
|
||||
</h2>
|
||||
<span class="text-gray-800">
|
||||
Circle «{{
|
||||
mentorCockpitStore.getCircleTitleById(selfEvaluationFeedback.circle_id)
|
||||
}}»
|
||||
Circle «{{ selfEvaluationFeedback.circle_name }}»
|
||||
</span>
|
||||
<template v-if="selfEvaluationFeedback.pending_evaluations > 0">
|
||||
<div class="flex flex-row items-center space-x-2 pt-4">
|
||||
|
|
@ -93,7 +93,7 @@ const handleContinue = () => {
|
|||
const clickExit = () => {
|
||||
console.log("clickExit");
|
||||
router.push({
|
||||
name: "mentorCockpitSelfEvaluationFeedbackAssignments",
|
||||
name: "learningMentorSelfEvaluationFeedbackAssignments",
|
||||
params: {
|
||||
learningUnitId: props.learningUnitId,
|
||||
},
|
||||
|
|
@ -172,7 +172,10 @@ watch(
|
|||
<div>
|
||||
<router-link
|
||||
class="link"
|
||||
:to="`/course/${courseSlug}/cockpit/profile/${profileUser.user_id}`"
|
||||
:to="{
|
||||
name: 'profileLearningPath',
|
||||
params: { userId: profileUser.user_id, courseSlug },
|
||||
}"
|
||||
>
|
||||
{{ $t("general.profileLink") }}
|
||||
</router-link>
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ defineEmits(["exit"]);
|
|||
<template>
|
||||
<div>
|
||||
<div class="absolute bottom-0 left-0 top-0 w-full bg-white">
|
||||
<CoursePreviewBar v-if="courseSessionsStore.hasCourseSessionPreview" />
|
||||
<CoursePreviewBar v-if="courseSessionsStore.isCourseSessionPreviewActive" />
|
||||
<div
|
||||
:class="{
|
||||
'h-content': !courseSessionsStore.hasCourseSessionPreview,
|
||||
'h-content-preview': courseSessionsStore.hasCourseSessionPreview,
|
||||
'h-content': !courseSessionsStore.isCourseSessionPreviewActive,
|
||||
'h-content-preview': courseSessionsStore.isCourseSessionPreviewActive,
|
||||
}"
|
||||
class="overflow-y-auto"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import * as log from "loglevel";
|
|||
import { computed, onMounted, ref, watchEffect } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||
import EvaluationSummary from "@/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue";
|
||||
import EvaluationSummary from "@/components/assignment/evaluation/EvaluationSummary.vue";
|
||||
import { bustItGetCache } from "@/fetchHelpers";
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import type { LearningUnit, LearningUnitPerformanceCriteria } from "@/types";
|
||||
import { useLearningMentors } from "@/composables";
|
||||
import { useMyLearningMentors } from "@/composables";
|
||||
import { computed, ref } from "vue";
|
||||
import ItButton from "@/components/ui/ItButton.vue";
|
||||
import NoMentorInformationPanel from "@/components/mentor/NoMentorInformationPanel.vue";
|
||||
import NoMentorInformationPanel from "@/components/learningMentor/NoMentorInformationPanel.vue";
|
||||
import { useSelfEvaluationFeedback } from "@/services/selfEvaluationFeedback";
|
||||
import FeedbackRequestedInformationPanel from "@/components/selfEvaluationFeedback/FeedbackRequestedInformationPanel.vue";
|
||||
import FeedbackReceived from "@/components/selfEvaluationFeedback/FeedbackReceived.vue";
|
||||
|
|
@ -24,7 +24,7 @@ const isStoredFeedbackLoading = computed(() => selfEvaluationFeedback.loading.va
|
|||
const feedbackProvider = computed(() => storedFeedback.value?.feedback_provider_user);
|
||||
|
||||
// if no feedback is stored "current session" state management (mentor selection etc.)
|
||||
const learningMentors = useLearningMentors();
|
||||
const learningMentors = useMyLearningMentors();
|
||||
const isMentorsLoading = computed(() => learningMentors.loading.value);
|
||||
|
||||
const mentors = computed(() => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import CockpitProfileContent from "@/components/cockpit/profile/CockpitProfileContent.vue";
|
||||
import CockpitProfileContent from "@/components/userProfile/UserProfileContent.vue";
|
||||
import { ref } from "vue";
|
||||
import SelfEvaluationAndFeedbackList from "@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackList.vue";
|
||||
import SelfEvaluationAndFeedbackOverview from "@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackOverview.vue";
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
||||
import { useCourseDataWithCompletion } from "@/composables";
|
||||
import CockpitProfileContent from "@/components/cockpit/profile/CockpitProfileContent.vue";
|
||||
import UserProfileContent from "@/components/userProfile/UserProfileContent.vue";
|
||||
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
||||
import LearningSequence from "@/pages/learningPath/circlePage/LearningSequence.vue";
|
||||
import { ref, watch } from "vue";
|
||||
|
|
@ -28,7 +28,7 @@ watch(lpQueryResult.learningPath, () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<CockpitProfileContent>
|
||||
<UserProfileContent>
|
||||
<template #side>
|
||||
<div
|
||||
v-for="topic in lpQueryResult.learningPath?.value?.topics ?? []"
|
||||
|
|
@ -69,5 +69,5 @@ watch(lpQueryResult.learningPath, () => {
|
|||
</li>
|
||||
</ol>
|
||||
</template>
|
||||
</CockpitProfileContent>
|
||||
</UserProfileContent>
|
||||
</template>
|
||||
|
|
@ -13,8 +13,8 @@ const props = defineProps<{
|
|||
const { t } = useTranslation();
|
||||
|
||||
const pages = ref([
|
||||
{ label: t("general.learningPath"), route: "cockpitProfileLearningPath" },
|
||||
{ label: t("a.KompetenzNavi"), route: "cockpitProfileCompetence" },
|
||||
{ label: t("general.learningPath"), route: "profileLearningPath" },
|
||||
{ label: t("a.KompetenzNavi"), route: "profileCompetence" },
|
||||
]);
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
|
@ -40,14 +40,6 @@ onMounted(() => {
|
|||
<div v-if="user" class="flex flex-col bg-gray-200">
|
||||
<div class="relative border-b bg-white shadow-md">
|
||||
<div class="container-large pb-0">
|
||||
<router-link
|
||||
class="btn-text inline-flex items-center pl-0"
|
||||
:to="`/course/${props.courseSlug}/cockpit`"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>{{ $t("general.back") }}</span>
|
||||
</router-link>
|
||||
|
||||
<div class="mb-12 mt-2 flex items-center">
|
||||
<img class="mr-8 h-48 w-48 rounded-full" :src="user.avatar_url" />
|
||||
<div class="flex flex-col">
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, vi } from "vitest";
|
||||
import * as courseSessions from "../../stores/courseSessions";
|
||||
import { expertRequired } from "../guards";
|
||||
|
||||
describe("Guards", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// creates a fresh pinia and make it active so it's automatically picked
|
||||
// up by any useStore() call without having to pass it to it:
|
||||
// `useStore(pinia)`
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("cannot route to cockpit", () => {
|
||||
vi.spyOn(courseSessions, "useCourseSessionsStore").mockReturnValue({
|
||||
currentCourseSessionHasCockpit: false,
|
||||
});
|
||||
const slug = "test";
|
||||
expect(expertRequired({ params: { courseSlug: "test" } })).toEqual(
|
||||
`/course/${slug}/learn`
|
||||
);
|
||||
});
|
||||
|
||||
it("can route to cockpit", () => {
|
||||
vi.spyOn(courseSessions, "useCourseSessionsStore").mockReturnValue({
|
||||
currentCourseSessionHasCockpit: true,
|
||||
});
|
||||
const to = {
|
||||
params: {
|
||||
courseSlug: "test",
|
||||
},
|
||||
};
|
||||
expect(expertRequired(to)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { getLoginURLNext, shouldUseSSO } from "@/router/utils";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { NavigationGuard, RouteLocationNormalized } from "vue-router";
|
||||
|
|
@ -52,16 +51,6 @@ const loginRequired = (to: RouteLocationNormalized) => {
|
|||
return !to.meta?.public;
|
||||
};
|
||||
|
||||
export const expertRequired: NavigationGuard = (to: RouteLocationNormalized) => {
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
if (courseSessionsStore.currentCourseSessionHasCockpit) {
|
||||
return true;
|
||||
} else {
|
||||
const courseSlug = to.params.courseSlug as string;
|
||||
return `/course/${courseSlug}/learn`;
|
||||
}
|
||||
};
|
||||
|
||||
export async function handleCurrentCourseSession(to: RouteLocationNormalized) {
|
||||
// register after login hooks
|
||||
const userStore = useUserStore();
|
||||
|
|
@ -112,31 +101,6 @@ export async function handleCourseSessionAsQueryParam(to: RouteLocationNormalize
|
|||
}
|
||||
}
|
||||
|
||||
export async function handleCockpit(to: RouteLocationNormalized) {
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const courseId = courseSessionsStore.currentCourseSession?.course.id || null;
|
||||
const cockpitStore = useCockpitStore();
|
||||
await cockpitStore.fetchCockpitType(courseId);
|
||||
|
||||
if (to.name === "cockpit") {
|
||||
if (cockpitStore.hasExpertCockpitType) {
|
||||
return { name: "expertCockpit", params: to.params };
|
||||
} else if (cockpitStore.hasMentorCockpitType) {
|
||||
return { name: "mentorCockpitOverview", params: to.params };
|
||||
}
|
||||
}
|
||||
|
||||
const cockpitType = to.meta?.cockpitType;
|
||||
|
||||
if (!cockpitType) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cockpitType !== cockpitStore.cockpitType) {
|
||||
return "/";
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleAcceptLearningMentorInvitation(
|
||||
to: RouteLocationNormalized
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import UKStartPage from "@/pages/start/UKStartPage.vue";
|
|||
import VVStartPage from "@/pages/start/VVStartPage.vue";
|
||||
import {
|
||||
handleAcceptLearningMentorInvitation,
|
||||
handleCockpit,
|
||||
handleCourseSessionAsQueryParam,
|
||||
handleCurrentCourseSession,
|
||||
redirectToLoginIfRequired,
|
||||
|
|
@ -141,12 +140,6 @@ const router = createRouter({
|
|||
import("../pages/learningPath/learningContentPage/LearningContentPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/mentor",
|
||||
component: () => import("@/pages/learningMentor/MentorManagementPage.vue"),
|
||||
props: true,
|
||||
name: "learningMentorManagement",
|
||||
},
|
||||
{
|
||||
path: "/lernbegleitung/:courseId/invitation/:invitationId",
|
||||
component: () => import("@/pages/learningMentor/InvitationAcceptPage.vue"),
|
||||
|
|
@ -157,115 +150,89 @@ const router = createRouter({
|
|||
public: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/profile/:userId",
|
||||
component: () => import("@/pages/userProfile/UserProfilePage.vue"),
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: "learning-path",
|
||||
component: () => import("@/pages/userProfile/LearningPathProfilePage.vue"),
|
||||
props: true,
|
||||
name: "profileLearningPath",
|
||||
},
|
||||
{
|
||||
path: "competence",
|
||||
component: () => import("@/pages/userProfile/CompetenceProfilePage.vue"),
|
||||
props: true,
|
||||
name: "profileCompetence",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/learning-mentor",
|
||||
component: () => import("@/pages/learningMentor/mentor/MentorIndexPage.vue"),
|
||||
props: true,
|
||||
name: "learningMentor",
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorOverviewPage.vue"),
|
||||
name: "learningMentorOverview",
|
||||
},
|
||||
{
|
||||
path: "participants",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorParticipantsPage.vue"),
|
||||
name: "mentorsAndParticipants",
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback/:learningUnitId",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/SelfEvaluationFeedbackPage.vue"),
|
||||
name: "mentorSelfEvaluationFeedback",
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "details",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorDetailParentPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "praxis-assignments/:praxisAssignmentId",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorPraxisAssignmentPage.vue"),
|
||||
name: "learningMentorPraxisAssignments",
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback-assignments/:learningUnitId",
|
||||
component: () =>
|
||||
import(
|
||||
"@/pages/learningMentor/mentor/MentorSelfEvaluationFeedbackAssignmentPage.vue"
|
||||
),
|
||||
name: "learningMentorSelfEvaluationFeedbackAssignments",
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/assignment-evaluation/:assignmentId/:userId",
|
||||
component: () =>
|
||||
import("@/pages/assignmentEvaluation/AssignmentEvaluationPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/cockpit",
|
||||
name: "cockpit",
|
||||
children: [
|
||||
{
|
||||
path: "expert",
|
||||
path: "",
|
||||
component: () => import("@/pages/cockpit/cockpitPage/CockpitExpertPage.vue"),
|
||||
props: true,
|
||||
name: "expertCockpit",
|
||||
meta: {
|
||||
cockpitType: "expert",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "mentor",
|
||||
component: () => import("@/pages/cockpit/cockpitPage/CockpitMentorPage.vue"),
|
||||
name: "mentorCockpit",
|
||||
meta: {
|
||||
cockpitType: "mentor",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/cockpitPage/mentor/MentorOverviewPage.vue"),
|
||||
name: "mentorCockpitOverview",
|
||||
meta: {
|
||||
cockpitType: "mentor",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "participants",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/cockpitPage/mentor/MentorParticipantsPage.vue"),
|
||||
name: "mentorCockpitParticipants",
|
||||
meta: {
|
||||
cockpitType: "mentor",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback/:learningUnitId",
|
||||
component: () =>
|
||||
import(
|
||||
"@/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedbackPage.vue"
|
||||
),
|
||||
name: "mentorSelfEvaluationFeedback",
|
||||
meta: {
|
||||
cockpitType: "mentor",
|
||||
},
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "details",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/cockpitPage/mentor/MentorDetailParentPage.vue"),
|
||||
meta: {
|
||||
cockpitType: "mentor",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "praxis-assignments/:praxisAssignmentId",
|
||||
component: () =>
|
||||
import(
|
||||
"@/pages/cockpit/cockpitPage/mentor/MentorPraxisAssignmentPage.vue"
|
||||
),
|
||||
name: "mentorCockpitPraxisAssignments",
|
||||
meta: {
|
||||
cockpitType: "mentor",
|
||||
},
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback-assignments/:learningUnitId",
|
||||
component: () =>
|
||||
import(
|
||||
"@/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignmentPage.vue"
|
||||
),
|
||||
name: "mentorCockpitSelfEvaluationFeedbackAssignments",
|
||||
meta: {
|
||||
cockpitType: "mentor",
|
||||
},
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "profile/:userId",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/profilePage/CockpitUserProfilePage.vue"),
|
||||
props: true,
|
||||
name: "cockpitUserProfile",
|
||||
children: [
|
||||
{
|
||||
path: "learning-path",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/profilePage/LearningPathProfilePage.vue"),
|
||||
props: true,
|
||||
name: "cockpitProfileLearningPath",
|
||||
},
|
||||
{
|
||||
path: "competence",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/profilePage/CompetenceProfilePage.vue"),
|
||||
props: true,
|
||||
name: "cockpitProfileCompetence",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "profile/:userId/:circleSlug",
|
||||
|
|
@ -283,14 +250,6 @@ const router = createRouter({
|
|||
import("@/pages/cockpit/assignmentsPage/AssignmentsPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "assignment/:assignmentId/:userId",
|
||||
component: () =>
|
||||
import(
|
||||
"@/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue"
|
||||
),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "attendance",
|
||||
component: () =>
|
||||
|
|
@ -431,8 +390,6 @@ router.beforeEach(redirectToLoginIfRequired);
|
|||
router.beforeEach(handleCurrentCourseSession);
|
||||
router.beforeEach(handleCourseSessionAsQueryParam);
|
||||
|
||||
router.beforeEach(handleCockpit);
|
||||
|
||||
router.beforeEach(addToHistory);
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -34,32 +34,26 @@ export interface Assignment {
|
|||
id: string;
|
||||
title: string;
|
||||
circle_id: string;
|
||||
circle_name: string;
|
||||
pending_evaluations: number;
|
||||
completions: Completion[];
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface Summary {
|
||||
export interface Summary {
|
||||
mentor_id: string;
|
||||
participants: Participant[];
|
||||
circles: Circle[];
|
||||
assignments: Assignment[];
|
||||
}
|
||||
|
||||
export const useMentorCockpit = (
|
||||
export const useLearningMentees = (
|
||||
courseSessionId: string | Ref<string> | (() => string)
|
||||
) => {
|
||||
const isLoading = ref(false);
|
||||
const summary: Ref<Summary | null> = ref(null);
|
||||
const error = ref(null);
|
||||
|
||||
const getCircleTitleById = (id: string): string => {
|
||||
if (summary.value?.circles) {
|
||||
const circle = summary.value.circles.find((circle) => String(circle.id) === id);
|
||||
return circle ? circle.title : "";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const getAssignmentById = (id: string): Assignment | null => {
|
||||
if (summary.value?.assignments) {
|
||||
const found = summary.value.assignments.find(
|
||||
|
|
@ -92,7 +86,6 @@ export const useMentorCockpit = (
|
|||
isLoading,
|
||||
summary,
|
||||
error,
|
||||
getCircleTitleById,
|
||||
fetchData,
|
||||
getAssignmentById,
|
||||
};
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
import type { CourseSession } from "@/types";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, vi } from "vitest";
|
||||
import { useCourseSessionsStore } from "../courseSessions";
|
||||
import { useUserStore } from "../user";
|
||||
|
||||
let user = {};
|
||||
let courseSessions: CourseSession[] = [];
|
||||
|
||||
describe("CourseSession Store", () => {
|
||||
vi.mock("vue-router", () => ({
|
||||
useRoute: () => ({
|
||||
path: "/course/test-course/learn/",
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/fetchHelpers", () => {
|
||||
const itGetCached = () => Promise.resolve([]);
|
||||
const itPost = () => Promise.resolve([]);
|
||||
|
||||
return {
|
||||
itGetCached,
|
||||
itPost,
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// creates a fresh pinia and make it active so it's automatically picked
|
||||
// up by any useStore() call without having to pass it to it:
|
||||
// `useStore(pinia)`
|
||||
setActivePinia(createPinia());
|
||||
user = {
|
||||
is_superuser: false,
|
||||
course_session_expert: [],
|
||||
};
|
||||
courseSessions = [
|
||||
{
|
||||
id: "1",
|
||||
created_at: "2021-05-11T10:00:00.000000Z",
|
||||
updated_at: "2023-05-11T10:00:00.000000Z",
|
||||
course: {
|
||||
id: "1",
|
||||
title: "Test Course",
|
||||
category_name: "Test Category",
|
||||
slug: "test-course",
|
||||
},
|
||||
title: "Test Course Session",
|
||||
start_date: "2022-05-11T10:00:00.000000Z",
|
||||
end_date: "2023-05-11T10:00:00.000000Z",
|
||||
learning_path_url: "/course/test-course/learn/",
|
||||
competence_url: "/course/test-course/competence/",
|
||||
course_url: "/course/test-course/",
|
||||
media_library_url: "/course/test-course/media/",
|
||||
attendance_courses: [],
|
||||
additional_json_data: {},
|
||||
documents: [],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
it("normal user has no cockpit", () => {
|
||||
const userStore = useUserStore();
|
||||
userStore.$patch(user);
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
courseSessionsStore._currentCourseSlug = "test-course";
|
||||
courseSessionsStore.allCourseSessions = courseSessions;
|
||||
|
||||
expect(courseSessionsStore.currentCourseSessionHasCockpit).toBeFalsy();
|
||||
});
|
||||
|
||||
it("superuser has cockpit", () => {
|
||||
const userStore = useUserStore();
|
||||
userStore.$patch(Object.assign(user, { is_superuser: true }));
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
courseSessionsStore._currentCourseSlug = "test-course";
|
||||
courseSessionsStore.allCourseSessions = courseSessions;
|
||||
|
||||
expect(courseSessionsStore.currentCourseSessionHasCockpit).toBeTruthy();
|
||||
});
|
||||
|
||||
it("expert has cockpit", () => {
|
||||
const userStore = useUserStore();
|
||||
userStore.$patch(
|
||||
Object.assign(user, { course_session_experts: [courseSessions[0].id] })
|
||||
);
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
courseSessionsStore._currentCourseSlug = "test-course";
|
||||
courseSessionsStore.allCourseSessions = courseSessions;
|
||||
|
||||
expect(courseSessionsStore.currentCourseSessionHasCockpit).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import { itGetCached } from "@/fetchHelpers";
|
||||
import { defineStore } from "pinia";
|
||||
import type { Ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
type CockpitType = "mentor" | "expert" | null;
|
||||
|
||||
export const useCockpitStore = defineStore("cockpit", () => {
|
||||
const cockpitType: Ref<CockpitType> = ref(null);
|
||||
const isLoading = ref(false);
|
||||
|
||||
const hasExpertCockpitType = computed(() => cockpitType.value === "expert");
|
||||
const hasMentorCockpitType = computed(() => cockpitType.value === "mentor");
|
||||
const hasNoCockpitType = computed(() => cockpitType.value === null);
|
||||
|
||||
async function fetchCockpitType(courseId: string | null) {
|
||||
if (!courseId) {
|
||||
cockpitType.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
const url = `/api/course/${courseId}/cockpit/`;
|
||||
const response = await itGetCached(url);
|
||||
cockpitType.value = response.type;
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
fetchCockpitType,
|
||||
hasExpertCockpitType,
|
||||
hasMentorCockpitType,
|
||||
hasNoCockpitType,
|
||||
isLoading,
|
||||
cockpitType,
|
||||
};
|
||||
});
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { itGetCached } from "@/fetchHelpers";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
import type { CourseSession, DueDate } from "@/types";
|
||||
import eventBus from "@/utils/eventBus";
|
||||
import { useRouteLookups } from "@/utils/route";
|
||||
|
|
@ -59,7 +58,7 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
|
||||
function selectedCourseSessionForCourse(courseSlug: string) {
|
||||
// Wir wollen pro Kurs wissen, welche Durchführung der User zuletzt ausgewählt hat.
|
||||
// Die letzte Durchführung wird im localStorage via `selectedCoruseSessionMap`
|
||||
// Die letzte Durchführung wird im localStorage via `selectedCourseSessionMap`
|
||||
// gespeichert und hier geladen.
|
||||
// Wenn noch keine Durchführung ausgewählt wurde, wird die erste Durchführung
|
||||
// in `courseSessionForCourse` zurückgegeben.
|
||||
|
|
@ -133,31 +132,11 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
return allCourseSessionsForCourse(_currentCourseSlug.value);
|
||||
});
|
||||
|
||||
const currentCourseSessionHasCockpit = computed(() => {
|
||||
if (currentCourseSession.value) {
|
||||
return hasCockpit(currentCourseSession.value);
|
||||
}
|
||||
|
||||
return false;
|
||||
const isCourseSessionPreviewActive = computed(() => {
|
||||
const hasPreview = currentCourseSession.value?.actions.includes("preview");
|
||||
return Boolean(hasPreview && (inLearningPath() || inCompetenceProfile()));
|
||||
});
|
||||
|
||||
const hasCourseSessionPreview = computed(() => {
|
||||
const isCourseExpert =
|
||||
currentCourseSession.value && currentCourseSessionHasCockpit.value;
|
||||
return Boolean(isCourseExpert && (inLearningPath() || inCompetenceProfile()));
|
||||
});
|
||||
|
||||
function hasCockpit(courseSession: CourseSession) {
|
||||
const userStore = useUserStore();
|
||||
return (
|
||||
useCockpitStore().hasMentorCockpitType ||
|
||||
useCockpitStore().hasExpertCockpitType ||
|
||||
// for legacy reasons: don't forget course session supervisors!
|
||||
userStore.course_session_experts.includes(courseSession.id) ||
|
||||
userStore.is_superuser
|
||||
);
|
||||
}
|
||||
|
||||
function allDueDates() {
|
||||
const allDueDatesReturn: DueDate[] = [];
|
||||
|
||||
|
|
@ -185,12 +164,9 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
return {
|
||||
uniqueCourseSessionsByCourse,
|
||||
allCurrentCourseSessions,
|
||||
courseSessionForCourse,
|
||||
getCourseSessionById,
|
||||
switchCourseSessionById,
|
||||
hasCockpit,
|
||||
hasCourseSessionPreview,
|
||||
currentCourseSessionHasCockpit,
|
||||
isCourseSessionPreviewActive,
|
||||
allDueDates,
|
||||
|
||||
// use `useCurrentCourseSession` whenever possible
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useLearningMentees } from "@/services/learningMentees";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
interface FilterOption {
|
||||
name: string;
|
||||
id: string | number;
|
||||
}
|
||||
|
||||
export const useAssignmentTodoListStore = defineStore("filters", () => {
|
||||
const { t } = useTranslation();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const learningMentees = useLearningMentees(courseSession.value.id);
|
||||
const summary = computed(() => learningMentees.summary.value);
|
||||
|
||||
const statusOptions = ref<FilterOption[]>([
|
||||
{ name: t("Alle"), id: "_all" },
|
||||
{ name: t("a.Zu erledigen"), id: "_todo" },
|
||||
]);
|
||||
|
||||
const selectedStatus = ref<FilterOption>(statusOptions.value[1]);
|
||||
|
||||
const allCircles = { name: t("a.AlleCircle"), id: "_all" };
|
||||
const circleOptions = computed(() => {
|
||||
if (!summary.value) return [allCircles];
|
||||
return [
|
||||
allCircles,
|
||||
...summary.value.circles.map((circle) => ({
|
||||
name: `Circle: ${circle.title}`,
|
||||
id: circle.id,
|
||||
})),
|
||||
];
|
||||
});
|
||||
|
||||
const selectedCircle = ref<FilterOption>(circleOptions.value[0]);
|
||||
|
||||
const filteredAssignments = computed(() => {
|
||||
if (!summary.value) return [];
|
||||
|
||||
let filtered = summary.value.assignments;
|
||||
|
||||
if (selectedStatus.value.id !== "_all") {
|
||||
filtered = filtered.filter((item) => item.pending_evaluations > 0);
|
||||
}
|
||||
|
||||
if (selectedCircle.value.id !== "_all") {
|
||||
filtered = filtered.filter(
|
||||
(item) => item.circle_id === String(selectedCircle.value.id)
|
||||
);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
});
|
||||
|
||||
return {
|
||||
selectedStatus,
|
||||
statusOptions,
|
||||
selectedCircle,
|
||||
circleOptions,
|
||||
filteredAssignments,
|
||||
};
|
||||
});
|
||||
|
|
@ -8,32 +8,32 @@ export function useRouteLookups() {
|
|||
}
|
||||
|
||||
function inCockpit() {
|
||||
const regex = new RegExp("/course/[^/]+/cockpit");
|
||||
const regex = new RegExp("/course/[^/]+/cockpit($|/)");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inLearningPath() {
|
||||
const regex = new RegExp("/course/[^/]+/learn");
|
||||
const regex = new RegExp("/course/[^/]+/learn($|/)");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inCompetenceProfile() {
|
||||
const regex = new RegExp("/course/[^/]+/competence");
|
||||
const regex = new RegExp("/course/[^/]+/competence($|/)");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inLearningMentor() {
|
||||
const regex = new RegExp("/course/[^/]+/mentor");
|
||||
const regex = new RegExp("/course/[^/]+/learning-mentor($|/)");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inMediaLibrary() {
|
||||
const regex = new RegExp("/course/[^/]+/media");
|
||||
const regex = new RegExp("/course/[^/]+/media($|/)");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inAppointments() {
|
||||
const regex = new RegExp("/(?:[^/]+/)?appointments");
|
||||
const regex = new RegExp("/(?:[^/]+/)?appointments($|/)");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,7 @@ function createCourseUrl(courseSlug: string | undefined, specificSub: string): s
|
|||
return "/";
|
||||
}
|
||||
|
||||
if (["learn", "media", "competence", "cockpit", "mentor"].includes(specificSub)) {
|
||||
return `/course/${courseSlug}/${specificSub}`;
|
||||
}
|
||||
return `/course/${courseSlug}`;
|
||||
return `/course/${courseSlug}/${specificSub}`;
|
||||
}
|
||||
|
||||
export function getCompetenceNaviUrl(courseSlug: string | undefined): string {
|
||||
|
|
@ -32,12 +29,12 @@ export function getLearningPathUrl(courseSlug: string | undefined): string {
|
|||
return createCourseUrl(courseSlug, "learn");
|
||||
}
|
||||
|
||||
export function getCockpitUrl(courseSlug: string | undefined): string {
|
||||
return createCourseUrl(courseSlug, "cockpit");
|
||||
export function getLearningMentorUrl(courseSlug: string | undefined): string {
|
||||
return createCourseUrl(courseSlug, "learning-mentor");
|
||||
}
|
||||
|
||||
export function getLearningMentorManagementUrl(courseSlug: string | undefined): string {
|
||||
return createCourseUrl(courseSlug, "mentor");
|
||||
export function getCockpitUrl(courseSlug: string | undefined): string {
|
||||
return createCourseUrl(courseSlug, "cockpit");
|
||||
}
|
||||
|
||||
export function getAssignmentTypeTitle(assignmentType: AssignmentType): string {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { TEST_TRAINER1_USER_ID } from "../../consts";
|
||||
import { login } from "../helpers";
|
||||
import {TEST_TRAINER1_USER_ID} from "../../consts";
|
||||
import {EXPERT_COCKPIT_URL, login} from "../helpers";
|
||||
|
||||
describe("assignmentTrainer.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -9,10 +9,8 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
|
||||
describe("Casework", () => {
|
||||
it("can open cockpit assignment page and open user assignment", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]').click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').should("contain", "Ergebnisse abgegeben");
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
|
@ -22,24 +20,16 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
});
|
||||
|
||||
it("can start evaluation and store evaluation results", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
cy.visit(EXPERT_COCKPIT_URL)
|
||||
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]').click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
||||
cy.get('[data-cy="title"]').should("contain", "Bewertung");
|
||||
cy.get('[data-cy="evaluation-duedate"]').should("exist");
|
||||
cy.get('[data-cy="instruction"]').should(
|
||||
"contain",
|
||||
"Die Gesamtpunktzahl und die daraus resultierende Note wird auf Grund des hinterlegeten Beurteilungsinstrument berechnet."
|
||||
);
|
||||
cy.get('[data-cy="instruction"]').should("contain", "Die Gesamtpunktzahl und die daraus resultierende Note wird auf Grund des hinterlegeten Beurteilungsinstrument berechnet.");
|
||||
cy.get('[data-cy="start-evaluation"]').click();
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 1 / 5"
|
||||
);
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 1 / 5");
|
||||
|
||||
// without text input the button should be disabled
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
|
|
@ -51,10 +41,7 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 2 / 5"
|
||||
);
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 2 / 5");
|
||||
cy.get('[data-cy="subtask-2"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Nicht so gut");
|
||||
cy.wait(500);
|
||||
|
|
@ -84,36 +71,28 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
|
||||
cy.wait(500);
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"evaluation_user_id",
|
||||
TEST_TRAINER1_USER_ID
|
||||
).then((ac) => {
|
||||
cy.loadAssignmentCompletion("evaluation_user_id", TEST_TRAINER1_USER_ID).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("EVALUATION_IN_PROGRESS");
|
||||
expect(JSON.stringify(ac.completion_data)).to.include("Nicht so gut");
|
||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||
expert_data: { points: 2, text: "Nicht so gut" },
|
||||
expert_data: {points: 2, text: "Nicht so gut"},
|
||||
});
|
||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||
expert_data: { points: 4, text: "Gut gemacht!" },
|
||||
expert_data: {points: 4, text: "Gut gemacht!"},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("can make complete evaluation", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
cy.visit(EXPERT_COCKPIT_URL)
|
||||
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]').click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
||||
cy.get('[data-cy="start-evaluation"]').click();
|
||||
|
||||
// step 1
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 1 / 5"
|
||||
);
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 1 / 5");
|
||||
cy.get('[data-cy="subtask-6"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 1");
|
||||
// wait for debounce
|
||||
|
|
@ -121,40 +100,28 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// step 2
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 2 / 5"
|
||||
);
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 2 / 5");
|
||||
cy.get('[data-cy="subtask-4"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 2");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// step 3
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 3 / 5"
|
||||
);
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 3 / 5");
|
||||
cy.get('[data-cy="subtask-2"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 3");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// step 4
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 4 / 5"
|
||||
);
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 4 / 5");
|
||||
cy.get('[data-cy="subtask-3"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 4");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// step 5
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 5 / 5"
|
||||
);
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 5 / 5");
|
||||
cy.get('[data-cy="subtask-2"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 5");
|
||||
cy.wait(500);
|
||||
|
|
@ -166,17 +133,12 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
cy.get('[data-cy="total-points"]').should("contain", "24");
|
||||
cy.get('[data-cy="submit-evaluation"]').click();
|
||||
|
||||
cy.get('[data-cy="result-section"]').should(
|
||||
"contain",
|
||||
"Deine Bewertung für Test Student1 wurde freigegeben"
|
||||
);
|
||||
cy.get('[data-cy="result-section"]').should("contain", "Deine Bewertung für Test Student1 wurde freigegeben");
|
||||
|
||||
// going back to cockpit should show points for student
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.reload();
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]').click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').should("contain", "Bewertung freigegeben");
|
||||
cy.get('[data-cy="Student1"]').should("contain", "17 von 24 Punkte");
|
||||
|
|
@ -186,10 +148,7 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
cy.url().should("include", "step=6");
|
||||
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"evaluation_user_id",
|
||||
TEST_TRAINER1_USER_ID
|
||||
).then((ac) => {
|
||||
cy.loadAssignmentCompletion("evaluation_user_id", TEST_TRAINER1_USER_ID).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("EVALUATION_SUBMITTED");
|
||||
expect(ac.evaluation_points).to.equal(17);
|
||||
expect(ac.evaluation_max_points).to.equal(24);
|
||||
|
|
@ -207,21 +166,16 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
//Todo: Move tests to Lernbegleitung once it is implemented
|
||||
describe("Praxis Assignment", () => {
|
||||
it("can start evaluation and store evaluation results", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]'
|
||||
).click();
|
||||
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]').click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
||||
cy.get('[data-cy="title"]').should("contain", "Feedback");
|
||||
cy.get('[data-cy="evaluation-duedate]"').should("not.exist");
|
||||
cy.get('[data-cy="instruction"]').should(
|
||||
"contain",
|
||||
"Bitte unterstütze Test Student1 und gib Feedback zum Auftrag."
|
||||
);
|
||||
cy.get('[data-cy="instruction"]').should("contain", "Bitte unterstütze Test Student1 und gib Feedback zum Auftrag.");
|
||||
cy.get('[data-cy="start-evaluation"]').click();
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 1 / 5");
|
||||
|
||||
|
|
@ -239,29 +193,24 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
cy.wait(1000);
|
||||
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"evaluation_user_id",
|
||||
TEST_TRAINER1_USER_ID
|
||||
).then((ac) => {
|
||||
cy.loadAssignmentCompletion("evaluation_user_id", TEST_TRAINER1_USER_ID).then((ac) => {
|
||||
console.log(ac.completion_status);
|
||||
expect(ac.completion_status).to.equal("EVALUATION_IN_PROGRESS");
|
||||
expect(JSON.stringify(ac.completion_data)).to.include("Nicht so gut");
|
||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||
expert_data: { points: 0, text: "Nicht so gut" },
|
||||
expert_data: {points: 0, text: "Nicht so gut"},
|
||||
});
|
||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||
expert_data: { points: 0, text: "Gut gemacht!" },
|
||||
expert_data: {points: 0, text: "Gut gemacht!"},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("can make complete evaluation", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]'
|
||||
).click();
|
||||
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]').click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
||||
|
|
@ -303,19 +252,14 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
cy.get('[data-cy="total-points"]').should("not.exist");
|
||||
cy.get('[data-cy="submit-evaluation"]').click();
|
||||
|
||||
cy.get('[data-cy="result-section"]').should(
|
||||
"contain",
|
||||
"Dein Feedback für Test Student1 wurde freigegeben"
|
||||
);
|
||||
cy.get('[data-cy="result-section"]').should("contain", "Dein Feedback für Test Student1 wurde freigegeben");
|
||||
|
||||
// going back to cockpit should show points for student
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.reload();
|
||||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]'
|
||||
).click();
|
||||
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]').click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').should("contain", "Feedback freigegeben");
|
||||
cy.get('[data-cy="Student1"]').should("not.contain", "Punkte");
|
||||
|
|
@ -325,10 +269,7 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
cy.url().should("include", "step=6");
|
||||
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"evaluation_user_id",
|
||||
TEST_TRAINER1_USER_ID
|
||||
).then((ac) => {
|
||||
cy.loadAssignmentCompletion("evaluation_user_id", TEST_TRAINER1_USER_ID).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("EVALUATION_SUBMITTED");
|
||||
expect(ac.evaluation_max_points).to.equal(0);
|
||||
const completionString = JSON.stringify(ac.completion_data);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { login } from "../helpers";
|
||||
import {EXPERT_COCKPIT_URL, login} from "../helpers";
|
||||
|
||||
describe("feedbackTrainer.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -8,7 +8,7 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
it("can open feedback results page with empty results", () => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get(
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]'
|
||||
).click();
|
||||
|
|
@ -20,7 +20,7 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
it("can open feedback results page with results", () => {
|
||||
cy.manageCommand("cypress_reset --create-feedback-responses");
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get(
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]'
|
||||
).click();
|
||||
|
|
@ -138,7 +138,7 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
it("can open feedback results page with results", () => {
|
||||
cy.manageCommand("cypress_reset --create-feedback-responses");
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||
cy.get(
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
export const EXPERT_COCKPIT_URL = "course/test-lehrgang/cockpit"
|
||||
|
||||
export const login = (username, password) => {
|
||||
cy.request({
|
||||
method: "POST", url: "/api/core/login/", body: {username, password},
|
||||
});
|
||||
cy.request({
|
||||
method: "POST", url: "/api/core/login/", body: {username, password},
|
||||
});
|
||||
};
|
||||
|
||||
export const logout = () => {
|
||||
cy.request({
|
||||
method: "POST", url: "/api/core/logout/",
|
||||
});
|
||||
cy.request({
|
||||
method: "POST", url: "/api/core/logout/",
|
||||
});
|
||||
};
|
||||
|
||||
export const BASE_URL = "/course/test-lehrgang";
|
||||
|
|
@ -15,9 +17,9 @@ export const EXPERT_LOGIN = ["test-trainer1@example.com", "test"];
|
|||
export const PARTICIPANT_LOGIN = ["test-student1@example.com", "test"];
|
||||
|
||||
export const visitCoursePage = (subPath) => {
|
||||
cy.visit(`${BASE_URL}/${subPath}`);
|
||||
cy.visit(`${BASE_URL}/${subPath}`);
|
||||
}
|
||||
|
||||
export const checkNavigationLink = (dataCy, expectedLink) => {
|
||||
cy.get(`[data-cy="${dataCy}"]`).should('have.attr', 'href').and('eq', `${BASE_URL}/${expectedLink}`);
|
||||
cy.get(`[data-cy="${dataCy}"]`).should('have.attr', 'href').and('eq', `${BASE_URL}/${expectedLink}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
Since the learning mentor behaves differently depending on the user's role, the learning mentor is tested in the
|
||||
following roles:
|
||||
|
||||
- [x] As a learning mentor (no other roles). -> `justMentor.cy.js`
|
||||
- [x] As a course session user (member, no other roles). -> `justMember.cy.js`
|
||||
- [x] As a course session user (member) and a learning mentor. `mentorAndMember.cy.js`
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
export const MENTOR_OVERVIEW_URL = "/course/versicherungsvermittler-in/learning-mentor";
|
||||
export const MENTOR_MENTEES_URL = "/course/versicherungsvermittler-in/learning-mentor/participants";
|
||||
|
||||
export const MENTOR_DASHBOARD_LINK = "[data-cy=lm-dashboard-link]";
|
||||
export const MEMBER_DASHBOARD_LINK = "[data-cy=progress-dashboard-continue-course-link]";
|
||||
export const MENTOR_MAIN_NAVIGATION = "[data-cy=lm-main-navigation]";
|
||||
export const MENTOR_OVERVIEW_NAVIGATION_LINK = "[data-cy=lm-overview-navigation-link]";
|
||||
export const MENTOR_MENTEES_NAVIGATION_LINK = "[data-cy=lm-mentees-navigation-link]";
|
||||
|
||||
// /participants
|
||||
export const MENTOR_MY_MENTEES = "[data-cy=lm-my-mentees]";
|
||||
export const MENTOR_MY_MENTORS = "[data-cy=lm-my-mentors]";
|
||||
|
||||
export const MENTOR_MENTEE_LIST_ITEM = "[data-cy=lm-my-mentee-list-item]";
|
||||
export const MENTOR_MENTEE_REMOVE = "[data-cy=lm-my-mentee-remove]";
|
||||
export const MENTOR_MENTEE_PROFILE = "[data-cy=lm-my-mentee-profile]";
|
||||
|
||||
export const MENTEE_MENTOR_LIST_ITEM = "[data-cy=lm-my-mentor-list-item]";
|
||||
export const MENTEE_MENTOR_REMOVE = "[data-cy=lm-my-mentor-remove]";
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import {login} from "../../helpers";
|
||||
import {
|
||||
MEMBER_DASHBOARD_LINK,
|
||||
MENTEE_MENTOR_LIST_ITEM,
|
||||
MENTEE_MENTOR_REMOVE,
|
||||
MENTOR_MENTEES_NAVIGATION_LINK,
|
||||
MENTOR_MENTEES_URL,
|
||||
MENTOR_MY_MENTEES,
|
||||
MENTOR_MY_MENTORS,
|
||||
MENTOR_OVERVIEW_NAVIGATION_LINK,
|
||||
MENTOR_OVERVIEW_URL
|
||||
} from "../constants";
|
||||
|
||||
describe("memberOnly.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset --create-learning-mentor");
|
||||
login("student-vv@eiger-versicherungen.ch", "test");
|
||||
});
|
||||
|
||||
it("shows the correct dashboard", () => {
|
||||
cy.visit("/");
|
||||
cy.get(MEMBER_DASHBOARD_LINK).should("exist");
|
||||
});
|
||||
|
||||
it("shows NO mentees navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.get(MENTOR_MENTEES_NAVIGATION_LINK).should("not.exist");
|
||||
})
|
||||
|
||||
it("shows NO overview navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.get(MENTOR_OVERVIEW_NAVIGATION_LINK).should("not.exist");
|
||||
})
|
||||
|
||||
it("shows NO mentees", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.get(MENTOR_MY_MENTEES).should("not.exist");
|
||||
});
|
||||
|
||||
it("shows my mentors", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.get(MENTOR_MY_MENTORS).should("exist");
|
||||
});
|
||||
|
||||
it("can remove a mentor", () => {
|
||||
// given
|
||||
const mentor = "Micheala Weber-Mentor";
|
||||
|
||||
// when
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.contains(MENTEE_MENTOR_LIST_ITEM, mentor)
|
||||
.find(MENTEE_MENTOR_REMOVE)
|
||||
.click();
|
||||
|
||||
// then
|
||||
cy.contains(MENTOR_MY_MENTORS, mentor).should("not.exist");
|
||||
})
|
||||
});
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import {login} from "../../helpers";
|
||||
import {
|
||||
MEMBER_DASHBOARD_LINK,
|
||||
MENTEE_MENTOR_LIST_ITEM,
|
||||
MENTEE_MENTOR_REMOVE,
|
||||
MENTOR_MAIN_NAVIGATION,
|
||||
MENTOR_MENTEE_LIST_ITEM,
|
||||
MENTOR_MENTEE_PROFILE,
|
||||
MENTOR_MENTEE_REMOVE,
|
||||
MENTOR_MENTEES_NAVIGATION_LINK,
|
||||
MENTOR_MENTEES_URL,
|
||||
MENTOR_MY_MENTEES,
|
||||
MENTOR_MY_MENTORS,
|
||||
MENTOR_OVERVIEW_NAVIGATION_LINK,
|
||||
MENTOR_OVERVIEW_URL
|
||||
} from "../constants";
|
||||
|
||||
describe("mentorAndMember.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset --create-learning-mentor");
|
||||
login("test-student-and-mentor2@example.com", "test");
|
||||
});
|
||||
|
||||
it("shows the correct dashboard", () => {
|
||||
cy.visit("/");
|
||||
cy.get(MEMBER_DASHBOARD_LINK).should("exist");
|
||||
});
|
||||
|
||||
it("shows the learning mentor navigation", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.get(MENTOR_MAIN_NAVIGATION).should("exist");
|
||||
});
|
||||
|
||||
it("shows the mentees navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.get(MENTOR_MENTEES_NAVIGATION_LINK).click();
|
||||
cy.url().should("include", MENTOR_MENTEES_URL);
|
||||
})
|
||||
|
||||
it("shows the overview navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.get(MENTOR_OVERVIEW_NAVIGATION_LINK).click();
|
||||
cy.url().should("include", MENTOR_OVERVIEW_URL);
|
||||
})
|
||||
|
||||
it("shows my mentees", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.get(MENTOR_MY_MENTEES).should("exist");
|
||||
});
|
||||
|
||||
it("shows my mentors", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.get(MENTOR_MY_MENTORS).should("exist");
|
||||
});
|
||||
|
||||
it("shows the correct mentees", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.get(MENTOR_MY_MENTEES).should("contain", "Viktor Vollgas");
|
||||
});
|
||||
|
||||
it("shows the profile of a mentee", () => {
|
||||
// given
|
||||
const mentee = "Viktor Vollgas";
|
||||
|
||||
// when
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee)
|
||||
.find(MENTOR_MENTEE_PROFILE)
|
||||
.click();
|
||||
|
||||
// then
|
||||
const expectedMenteeProfileUrl = "/course/versicherungsvermittler-in/profile/5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||
cy.url().should("include", expectedMenteeProfileUrl);
|
||||
cy.contains(mentee).should("exist");
|
||||
})
|
||||
|
||||
it("can remove a mentee", () => {
|
||||
// given
|
||||
const mentee = "Viktor Vollgas";
|
||||
|
||||
// when
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee)
|
||||
.find(MENTOR_MENTEE_REMOVE)
|
||||
.click();
|
||||
|
||||
// then
|
||||
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee).should("not.exist");
|
||||
cy.contains("Aktuell begleitest du niemanden als Lernbegleitung").should("exist");
|
||||
})
|
||||
|
||||
it("shows the correct mentors", () => {
|
||||
const mentor = "Micheala Weber-Mentor";
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.get(MENTOR_MY_MENTORS).should("contain", mentor);
|
||||
})
|
||||
|
||||
it("can remove a mentor", () => {
|
||||
// given
|
||||
const mentor = "Micheala Weber-Mentor";
|
||||
|
||||
// when
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.contains(MENTEE_MENTOR_LIST_ITEM, mentor)
|
||||
.find(MENTEE_MENTOR_REMOVE)
|
||||
.click();
|
||||
|
||||
// then
|
||||
cy.contains(MENTOR_MY_MENTORS, mentor).should("not.exist");
|
||||
cy.contains("Aktuell hast du noch keine Person als Lernbegleitung eingeladen").should("exist");
|
||||
})
|
||||
});
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import {login} from "../../helpers";
|
||||
import {
|
||||
MENTOR_DASHBOARD_LINK,
|
||||
MENTOR_MAIN_NAVIGATION,
|
||||
MENTOR_MENTEE_LIST_ITEM,
|
||||
MENTOR_MENTEE_PROFILE,
|
||||
MENTOR_MENTEE_REMOVE,
|
||||
MENTOR_MENTEES_NAVIGATION_LINK,
|
||||
MENTOR_MENTEES_URL,
|
||||
MENTOR_MY_MENTEES,
|
||||
MENTOR_MY_MENTORS,
|
||||
MENTOR_OVERVIEW_NAVIGATION_LINK,
|
||||
MENTOR_OVERVIEW_URL
|
||||
} from "../constants";
|
||||
|
||||
describe("mentorOnly.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset --create-learning-mentor");
|
||||
login("test-mentor1@example.com", "test");
|
||||
});
|
||||
|
||||
it("shows the correct dashboard", () => {
|
||||
cy.visit("/");
|
||||
cy.get(MENTOR_DASHBOARD_LINK).click();
|
||||
cy.url().should("include", MENTOR_OVERVIEW_URL);
|
||||
});
|
||||
|
||||
it("shows the learning mentor navigation", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.get(MENTOR_MAIN_NAVIGATION).should("exist");
|
||||
});
|
||||
|
||||
it("shows the mentees navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.get(MENTOR_MENTEES_NAVIGATION_LINK).click();
|
||||
cy.url().should("include", MENTOR_MENTEES_URL);
|
||||
})
|
||||
|
||||
it("shows the overview navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.get(MENTOR_OVERVIEW_NAVIGATION_LINK).click();
|
||||
cy.url().should("include", MENTOR_OVERVIEW_URL);
|
||||
})
|
||||
|
||||
it("shows my mentees", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.get(MENTOR_MY_MENTEES).should("exist");
|
||||
});
|
||||
|
||||
it("shows no mentors", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.get(MENTOR_MY_MENTORS).should("not.exist");
|
||||
});
|
||||
|
||||
it("shows the correct mentees", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.get(MENTOR_MY_MENTEES).should("contain", "Robert Student-plus-Mentor");
|
||||
cy.get(MENTOR_MY_MENTEES).should("contain", "Viktor Vollgas");
|
||||
});
|
||||
|
||||
it("shows the profile of a mentee", () => {
|
||||
// given
|
||||
const mentee = "Viktor Vollgas";
|
||||
|
||||
// when
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee)
|
||||
.find(MENTOR_MENTEE_PROFILE)
|
||||
.click();
|
||||
|
||||
// then
|
||||
const expectedMenteeProfileUrl = "/course/versicherungsvermittler-in/profile/5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||
cy.url().should("include", expectedMenteeProfileUrl);
|
||||
cy.contains(mentee).should("exist");
|
||||
})
|
||||
|
||||
it("can remove a mentee", () => {
|
||||
// given
|
||||
const mentee = "Viktor Vollgas";
|
||||
|
||||
// when
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee)
|
||||
.find(MENTOR_MENTEE_REMOVE)
|
||||
.click();
|
||||
|
||||
// then
|
||||
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee).should("not.exist");
|
||||
cy.contains(MENTOR_MENTEE_LIST_ITEM, "Robert Student-plus-Mentor").should("exist")
|
||||
})
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { login } from "./helpers";
|
||||
import {EXPERT_COCKPIT_URL, login} from "./helpers";
|
||||
|
||||
describe("settings.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -14,7 +14,7 @@ describe("settings.cy.js", () => {
|
|||
|
||||
it("trainer can see circle documents", () => {
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get('[data-cy="circle-documents"]').should("exist");
|
||||
});
|
||||
});
|
||||
|
|
@ -33,7 +33,7 @@ describe("settings.cy.js", () => {
|
|||
|
||||
it("trainer cannot see circle documents", () => {
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get('[data-cy="circle-documents"]').should("not.exist");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,12 +12,7 @@ from django_ratelimit.exceptions import Ratelimited
|
|||
from graphene_django.views import GraphQLView
|
||||
|
||||
from vbv_lernwelt.api.directory import list_entities
|
||||
from vbv_lernwelt.api.user import (
|
||||
get_cockpit_type,
|
||||
get_profile,
|
||||
me_user_view,
|
||||
post_avatar,
|
||||
)
|
||||
from vbv_lernwelt.api.user import get_profile, me_user_view, post_avatar
|
||||
from vbv_lernwelt.assignment.views import request_assignment_completion_status
|
||||
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
||||
from vbv_lernwelt.core.schema import schema
|
||||
|
|
@ -123,9 +118,6 @@ urlpatterns = [
|
|||
|
||||
# course
|
||||
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
||||
# path(r"api/course/sessions/<signed_int:course_session_id>/users/",
|
||||
# get_course_session_users,
|
||||
# name="get_course_session_users"),
|
||||
path(r"api/course/page/<slug_or_id>/", course_page_api_view,
|
||||
name="course_page_api_view"),
|
||||
path(r"api/course/completion/mark/", mark_course_completion_view,
|
||||
|
|
@ -136,9 +128,6 @@ urlpatterns = [
|
|||
path(r"api/course/completion/<signed_int:course_session_id>/<uuid:user_id>/",
|
||||
request_course_completion_for_user,
|
||||
name="request_course_completion_for_user"),
|
||||
path(r"api/course/<signed_int:course_id>/cockpit/",
|
||||
get_cockpit_type,
|
||||
name="get_cockpit_type"),
|
||||
|
||||
path("api/mentor/<signed_int:course_session_id>/", include("vbv_lernwelt.learning_mentor.urls")),
|
||||
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from vbv_lernwelt.course.creators.test_utils import (
|
||||
create_course,
|
||||
create_course_session,
|
||||
create_user,
|
||||
)
|
||||
from vbv_lernwelt.course.models import CourseSessionUser
|
||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
||||
|
||||
|
||||
class MeUserViewTest(APITestCase):
|
||||
def setUp(self) -> None:
|
||||
self.course, _ = create_course("Test Course")
|
||||
self.user = create_user("tester")
|
||||
self.url = reverse("get_cockpit_type", kwargs={"course_id": self.course.id})
|
||||
|
||||
def test_no_cockpit(self) -> None:
|
||||
# GIVEN
|
||||
self.client.force_login(self.user)
|
||||
|
||||
# WHEN
|
||||
response = self.client.get(self.url)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEquals(response.data["type"], None)
|
||||
|
||||
def test_mentor_cockpit(self) -> None:
|
||||
# GIVEN
|
||||
self.client.force_login(self.user)
|
||||
LearningMentor.objects.create(mentor=self.user, course=self.course)
|
||||
|
||||
# WHEN
|
||||
response = self.client.get(self.url)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEquals(response.data["type"], "mentor")
|
||||
|
||||
def test_trainer_cockpit(self) -> None:
|
||||
# GIVEN
|
||||
self.client.force_login(self.user)
|
||||
course_session = create_course_session(course=self.course, title="Test Session")
|
||||
|
||||
CourseSessionUser.objects.create(
|
||||
user=self.user,
|
||||
course_session=course_session,
|
||||
role=CourseSessionUser.Role.EXPERT,
|
||||
)
|
||||
|
||||
# WHEN
|
||||
response = self.client.get(self.url)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEquals(response.data["type"], "expert")
|
||||
|
||||
def test_supervisor_cockpit(self):
|
||||
# GIVEN
|
||||
self.client.force_login(self.user)
|
||||
course_session = create_course_session(course=self.course, title="Test Session")
|
||||
|
||||
csg = CourseSessionGroup.objects.create(
|
||||
name="Test Group",
|
||||
course=course_session.course,
|
||||
)
|
||||
|
||||
csg.course_session.add(course_session)
|
||||
csg.supervisor.add(self.user)
|
||||
|
||||
# WHEN
|
||||
response = self.client.get(self.url)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEquals(response.data["type"], "expert")
|
||||
|
|
@ -5,10 +5,8 @@ from rest_framework.permissions import IsAuthenticated
|
|||
from rest_framework.response import Response
|
||||
|
||||
from vbv_lernwelt.core.serializers import UserSerializer
|
||||
from vbv_lernwelt.course.models import Course, CourseSessionUser
|
||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||
from vbv_lernwelt.course.models import CourseSessionUser
|
||||
from vbv_lernwelt.iam.permissions import can_view_profile
|
||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
||||
from vbv_lernwelt.media_files.models import UserImage
|
||||
|
||||
|
||||
|
|
@ -35,35 +33,6 @@ def me_user_view(request):
|
|||
return Response(status=400)
|
||||
|
||||
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_cockpit_type(request, course_id: int):
|
||||
course = get_object_or_404(Course, id=course_id)
|
||||
|
||||
is_mentor = LearningMentor.objects.filter(
|
||||
mentor=request.user, course=course
|
||||
).exists()
|
||||
|
||||
is_expert = CourseSessionUser.objects.filter(
|
||||
user=request.user,
|
||||
course_session__course=course,
|
||||
role=CourseSessionUser.Role.EXPERT,
|
||||
).exists()
|
||||
|
||||
is_supervisor = CourseSessionGroup.objects.filter(
|
||||
course_session__course=course, supervisor=request.user
|
||||
).exists()
|
||||
|
||||
if is_mentor:
|
||||
cockpit_type = "mentor"
|
||||
elif is_expert or is_supervisor:
|
||||
cockpit_type = "expert"
|
||||
else:
|
||||
cockpit_type = None
|
||||
|
||||
return Response({"type": cockpit_type})
|
||||
|
||||
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_profile(request, course_session_id: int, user_id: str):
|
||||
|
|
|
|||
|
|
@ -377,11 +377,8 @@ class AssignmentCompletion(models.Model):
|
|||
]
|
||||
|
||||
def get_assignment_evaluation_frontend_url(self):
|
||||
"""
|
||||
Used by the expert to evaluate the assignment
|
||||
Example: /course/überbetriebliche-kurse/cockpit/assignment/371/18
|
||||
"""
|
||||
return f"{self.course_session.course.get_cockpit_url()}/assignment/{self.assignment.id}/{self.assignment_user.id}"
|
||||
"""Used by the expert to evaluate the assignment"""
|
||||
return f"{self.course_session.course.get_course_url()}/assignment-evaluation/{self.assignment.id}/{self.assignment_user.id}"
|
||||
|
||||
@property
|
||||
def task_completion_data(self):
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
|
|||
file.save()
|
||||
|
||||
file_id = str(file.id)
|
||||
file_url = file.url
|
||||
|
||||
completion_data_string = json.dumps(
|
||||
{
|
||||
|
|
@ -223,7 +222,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
|
|||
self.course_session,
|
||||
)
|
||||
self.assertTrue(
|
||||
f"/course/test-lehrgang/cockpit/assignment" in notification.target_url
|
||||
f"/course/test-lehrgang/assignment-evaluation" in notification.target_url
|
||||
)
|
||||
|
||||
# second submit will fail
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ TEST_STUDENT2_USER_ID = "19c40d94-15cc-4198-aaad-ef707c4b0900"
|
|||
TEST_STUDENT3_USER_ID = "bcf94dba-53bc-474b-a22d-e4af39aa042b"
|
||||
TEST_MENTOR1_USER_ID = "d1f5f5a9-5b0a-4e1a-9e1a-9e9b5b5e1b1b"
|
||||
TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID = "7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db"
|
||||
|
||||
TEST_COURSE_SESSION_BERN_ID = -1
|
||||
TEST_COURSE_SESSION_ZURICH_ID = -2
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from vbv_lernwelt.core.constants import (
|
|||
TEST_STUDENT1_USER_ID,
|
||||
TEST_STUDENT1_VV_USER_ID,
|
||||
TEST_STUDENT2_USER_ID,
|
||||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||
TEST_STUDENT3_USER_ID,
|
||||
TEST_SUPERVISOR1_USER_ID,
|
||||
TEST_TRAINER1_USER_ID,
|
||||
|
|
@ -372,6 +373,14 @@ def create_default_users(default_password="test", set_avatar=False):
|
|||
language="de",
|
||||
avatar_image="uk1.patrizia.huggel.jpg",
|
||||
)
|
||||
_create_student_user(
|
||||
id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||
email="test-student-and-mentor2@example.com",
|
||||
first_name="Robert",
|
||||
last_name="Student-plus-Mentor",
|
||||
password=default_password,
|
||||
language="de",
|
||||
)
|
||||
|
||||
|
||||
def _get_or_create_user(user_model, *args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from vbv_lernwelt.core.constants import (
|
|||
TEST_STUDENT1_USER_ID,
|
||||
TEST_STUDENT1_VV_USER_ID,
|
||||
TEST_STUDENT2_USER_ID,
|
||||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||
TEST_STUDENT3_USER_ID,
|
||||
TEST_TRAINER1_USER_ID,
|
||||
)
|
||||
|
|
@ -343,31 +344,51 @@ def command(
|
|||
attendance_course.save()
|
||||
|
||||
if create_learning_mentor:
|
||||
cs_bern = CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID)
|
||||
|
||||
uk_mentor = LearningMentor.objects.create(
|
||||
course=Course.objects.get(id=COURSE_TEST_ID),
|
||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||
course_session=cs_bern,
|
||||
)
|
||||
uk_mentor.participants.add(
|
||||
CourseSessionUser.objects.get(
|
||||
user__id=TEST_STUDENT1_USER_ID,
|
||||
course_session=CourseSession.objects.get(
|
||||
id=TEST_COURSE_SESSION_BERN_ID
|
||||
),
|
||||
course_session=cs_bern,
|
||||
)
|
||||
)
|
||||
|
||||
vv_course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
|
||||
vv_course_session = CourseSession.objects.get(course=vv_course)
|
||||
vv_mentor = LearningMentor.objects.create(
|
||||
course=vv_course,
|
||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||
course_session=vv_course_session,
|
||||
)
|
||||
|
||||
vv_mentor.participants.add(
|
||||
CourseSessionUser.objects.get(
|
||||
user__id=TEST_STUDENT1_VV_USER_ID, course_session=vv_course_session
|
||||
)
|
||||
)
|
||||
|
||||
vv_mentor.participants.add(
|
||||
CourseSessionUser.objects.get(
|
||||
user__id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||
course_session=vv_course_session,
|
||||
)
|
||||
)
|
||||
|
||||
vv_student_and_mentor = LearningMentor.objects.create(
|
||||
mentor=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
||||
course_session=vv_course_session,
|
||||
)
|
||||
|
||||
vv_student_and_mentor.participants.add(
|
||||
CourseSessionUser.objects.get(
|
||||
user__id=TEST_STUDENT1_VV_USER_ID,
|
||||
course_session=vv_course_session,
|
||||
)
|
||||
)
|
||||
|
||||
course = Course.objects.get(id=COURSE_TEST_ID)
|
||||
course.configuration.enable_circle_documents = enable_circle_documents
|
||||
course.configuration.save()
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ def add_mentor_to_course_session(
|
|||
):
|
||||
for mentor, mentee in mentor_mentee_pairs:
|
||||
lm = LearningMentor.objects.create(
|
||||
course=course_session.course,
|
||||
course_session=course_session,
|
||||
mentor=mentor,
|
||||
)
|
||||
lm.participants.add(
|
||||
|
|
|
|||
|
|
@ -101,9 +101,11 @@ def create_course_session(
|
|||
|
||||
|
||||
def add_learning_mentor(
|
||||
course: Course, mentor: User, mentee: CourseSessionUser
|
||||
course_session: CourseSession, mentor: User, mentee: CourseSessionUser
|
||||
) -> LearningMentor:
|
||||
learning_mentor = LearningMentor.objects.create(course=course, mentor=mentor)
|
||||
learning_mentor = LearningMentor.objects.create(
|
||||
course_session=course_session, mentor=mentor
|
||||
)
|
||||
learning_mentor.participants.add(mentee)
|
||||
return learning_mentor
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,10 @@ from vbv_lernwelt.competence.create_vv_new_competence_profile import (
|
|||
create_vv_new_competence_profile,
|
||||
)
|
||||
from vbv_lernwelt.competence.models import PerformanceCriteria
|
||||
from vbv_lernwelt.core.constants import TEST_MENTOR1_USER_ID
|
||||
from vbv_lernwelt.core.constants import (
|
||||
TEST_MENTOR1_USER_ID,
|
||||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||
)
|
||||
from vbv_lernwelt.core.create_default_users import default_users
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.consts import (
|
||||
|
|
@ -234,7 +237,7 @@ def create_versicherungsvermittlerin_course(
|
|||
user=User.objects.get(username=user_data["email"]),
|
||||
)
|
||||
|
||||
csu = CourseSessionUser.objects.create(
|
||||
student_1_csu = CourseSessionUser.objects.create(
|
||||
course_session=cs,
|
||||
user=User.objects.get(username="student-vv@eiger-versicherungen.ch"),
|
||||
)
|
||||
|
|
@ -257,12 +260,29 @@ def create_versicherungsvermittlerin_course(
|
|||
role=CourseSessionUser.Role.EXPERT,
|
||||
)
|
||||
|
||||
lemme = LearningMentor.objects.create(
|
||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||
course=cs.course,
|
||||
mentor_and_student_2_learning_csu = CourseSessionUser.objects.create(
|
||||
course_session=cs,
|
||||
user=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
||||
role=CourseSessionUser.Role.MEMBER,
|
||||
)
|
||||
|
||||
lemme.participants.add(csu)
|
||||
# TEST_MENTOR1_USER_ID is only mentor
|
||||
just_mentor = LearningMentor.objects.create(
|
||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||
course_session=cs,
|
||||
)
|
||||
|
||||
just_mentor.participants.add(student_1_csu)
|
||||
just_mentor.participants.add(mentor_and_student_2_learning_csu)
|
||||
|
||||
# TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID is both student and mentor
|
||||
|
||||
mentor_and_student_learning_mentor = LearningMentor.objects.create(
|
||||
mentor=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
||||
course_session=cs,
|
||||
)
|
||||
|
||||
mentor_and_student_learning_mentor.participants.add(student_1_csu)
|
||||
|
||||
for admin_email in ADMIN_EMAILS:
|
||||
CourseSessionUser.objects.create(
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class CourseCompletionApiTestCase(APITestCase):
|
|||
self.assertEqual(len(response.json()), 0)
|
||||
|
||||
def test_api_courseSession_withCourseSessionUser(self):
|
||||
csu = CourseSessionUser.objects.create(
|
||||
CourseSessionUser.objects.create(
|
||||
course_session=self.course_session,
|
||||
user=self.user,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -155,8 +155,8 @@ def get_course_sessions(request):
|
|||
|
||||
# enrich with mentor course sessions
|
||||
mentor_course_sessions = CourseSession.objects.filter(
|
||||
course__in=LearningMentor.objects.filter(mentor=request.user).values_list(
|
||||
"course", flat=True
|
||||
id__in=LearningMentor.objects.filter(mentor=request.user).values_list(
|
||||
"course_session", flat=True
|
||||
)
|
||||
).prefetch_related("course")
|
||||
|
||||
|
|
|
|||
|
|
@ -83,11 +83,16 @@ class DashboardQuery(graphene.ObjectType):
|
|||
statistics_dashboard_course_ids,
|
||||
) = get_user_statistics_dashboards(user=user)
|
||||
|
||||
course_session_dashboards = get_user_course_session_dashboards(
|
||||
(
|
||||
course_session_dashboards,
|
||||
course_session_dashboard_course_ids,
|
||||
) = get_user_course_session_dashboards(
|
||||
user=user, exclude_course_ids=statistics_dashboard_course_ids
|
||||
)
|
||||
|
||||
learning_mentor_dashboards = get_learning_mentor_dashboards(user=user)
|
||||
learning_mentor_dashboards, _ = get_learning_mentor_dashboards(
|
||||
user=user, exclude_course_ids=course_session_dashboard_course_ids
|
||||
)
|
||||
|
||||
return (
|
||||
statistic_dashboards
|
||||
|
|
@ -163,13 +168,13 @@ class DashboardQuery(graphene.ObjectType):
|
|||
|
||||
|
||||
def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Set[int]]:
|
||||
course_index = set()
|
||||
course_ids = set()
|
||||
dashboards = []
|
||||
|
||||
for group in CourseSessionGroup.objects.all():
|
||||
if can_view_course_session_group_statistics(user=user, group=group):
|
||||
course = group.course
|
||||
course_index.add(course)
|
||||
course_ids.add(course)
|
||||
dashboards.append(
|
||||
{
|
||||
"id": str(course.id),
|
||||
|
|
@ -180,31 +185,38 @@ def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Se
|
|||
}
|
||||
)
|
||||
|
||||
return dashboards, course_index
|
||||
return dashboards, course_ids
|
||||
|
||||
|
||||
def get_learning_mentor_dashboards(user: User) -> List[Dict[str, str]]:
|
||||
learning_mentor = LearningMentor.objects.filter(mentor=user)
|
||||
def get_learning_mentor_dashboards(
|
||||
user: User, exclude_course_ids: Set[int]
|
||||
) -> Tuple[List[Dict[str, str]], Set[int]]:
|
||||
learning_mentor = LearningMentor.objects.filter(mentor=user).exclude(
|
||||
course_session__course__id__in=exclude_course_ids
|
||||
)
|
||||
|
||||
dashboards = []
|
||||
course_ids = set()
|
||||
|
||||
for mentor in learning_mentor:
|
||||
course = mentor.course
|
||||
course = mentor.course_session.course
|
||||
course_ids.add(course.id)
|
||||
dashboards.append(
|
||||
{
|
||||
"id": str(course.id),
|
||||
"name": course.title,
|
||||
"slug": course.slug,
|
||||
"dashboard_type": DashboardType.SIMPLE_DASHBOARD,
|
||||
"dashboard_type": DashboardType.MENTOR_DASHBOARD,
|
||||
"course_configuration": course.configuration,
|
||||
}
|
||||
)
|
||||
|
||||
return dashboards
|
||||
return dashboards, course_ids
|
||||
|
||||
|
||||
def get_user_course_session_dashboards(
|
||||
user: User, exclude_course_ids: Set[int]
|
||||
) -> List[Dict[str, str]]:
|
||||
) -> Tuple[List[Dict[str, str]], Set[int]]:
|
||||
"""
|
||||
Edge case: what do we show to users with access to multiple
|
||||
sessions of a course, but with varying permissions?
|
||||
|
|
@ -212,6 +224,7 @@ def get_user_course_session_dashboards(
|
|||
"""
|
||||
|
||||
dashboards = []
|
||||
course_ids = set()
|
||||
|
||||
course_sessions = CourseSession.objects.exclude(course__in=exclude_course_ids)
|
||||
roles_by_course: Dict[Course, Set[DashboardType]] = {}
|
||||
|
|
@ -237,6 +250,8 @@ def get_user_course_session_dashboards(
|
|||
# fallback: just go with simple list dashboard
|
||||
resolved_dashboard_type = DashboardType.SIMPLE_DASHBOARD
|
||||
|
||||
course_ids.add(course.id)
|
||||
|
||||
dashboards.append(
|
||||
{
|
||||
"id": str(course.id),
|
||||
|
|
@ -247,4 +262,4 @@ def get_user_course_session_dashboards(
|
|||
}
|
||||
)
|
||||
|
||||
return dashboards
|
||||
return dashboards, course_ids
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ class DashboardType(Enum):
|
|||
STATISTICS_DASHBOARD = "StatisticsDashboard"
|
||||
PROGRESS_DASHBOARD = "ProgressDashboard"
|
||||
SIMPLE_DASHBOARD = "SimpleDashboard"
|
||||
MENTOR_DASHBOARD = "MentorDashboard"
|
||||
|
||||
|
||||
class DashboardConfigType(graphene.ObjectType):
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ class DashboardTestCase(GraphQLTestCase):
|
|||
role=CourseSessionUser.Role.MEMBER,
|
||||
)
|
||||
|
||||
add_learning_mentor(course=course_1, mentor=mentor, mentee=csu)
|
||||
add_learning_mentor(course_session=cs_1, mentor=mentor, mentee=csu)
|
||||
|
||||
self.client.force_login(mentor)
|
||||
|
||||
|
|
@ -291,7 +291,55 @@ class DashboardTestCase(GraphQLTestCase):
|
|||
self.assertEqual(len(response.json()["data"]["dashboard_config"]), 1)
|
||||
self.assertEqual(
|
||||
response.json()["data"]["dashboard_config"][0]["dashboard_type"],
|
||||
"SIMPLE_DASHBOARD",
|
||||
"MENTOR_DASHBOARD",
|
||||
)
|
||||
|
||||
def test_mentor_and_member_mixed(self):
|
||||
# GIVEN
|
||||
course, _ = create_course("Test Course")
|
||||
cs = create_course_session(course=course, title="Test Course Session 1")
|
||||
|
||||
# in same course session
|
||||
mentor_and_member = create_user("mentor_and_member")
|
||||
|
||||
mentee = add_course_session_user(
|
||||
course_session=cs,
|
||||
user=create_user("mentee"),
|
||||
role=CourseSessionUser.Role.MEMBER,
|
||||
)
|
||||
|
||||
add_course_session_user(
|
||||
course_session=cs,
|
||||
user=mentor_and_member,
|
||||
role=CourseSessionUser.Role.MEMBER,
|
||||
)
|
||||
|
||||
add_learning_mentor(course_session=cs, mentor=mentor_and_member, mentee=mentee)
|
||||
|
||||
# WHEN
|
||||
self.client.force_login(mentor_and_member)
|
||||
|
||||
query = """query {
|
||||
dashboard_config {
|
||||
id
|
||||
name
|
||||
slug
|
||||
dashboard_type
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
response = self.query(query)
|
||||
|
||||
# THEN
|
||||
self.assertResponseNoErrors(response)
|
||||
|
||||
# given mentor + member -> should see the PROGRESS_DASHBOARD,
|
||||
# not the MENTOR_DASHBOARD which is only for "pure" mentors.
|
||||
self.assertEqual(len(response.json()["data"]["dashboard_config"]), 1)
|
||||
self.assertEqual(
|
||||
response.json()["data"]["dashboard_config"][0]["dashboard_type"],
|
||||
"PROGRESS_DASHBOARD",
|
||||
)
|
||||
|
||||
def test_course_statistics_deny_not_allowed_user(self):
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ def has_course_access(user, course_id):
|
|||
).exists():
|
||||
return True
|
||||
|
||||
if LearningMentor.objects.filter(course_id=course_id, mentor=user).exists():
|
||||
if LearningMentor.objects.filter(
|
||||
course_session__course_id=course_id, mentor=user
|
||||
).exists():
|
||||
return True
|
||||
|
||||
return CourseSessionUser.objects.filter(
|
||||
|
|
@ -39,7 +41,45 @@ def has_course_session_access(user, course_session_id: int):
|
|||
).exists()
|
||||
|
||||
|
||||
def is_user_mentor(mentor: User, participant_user_id: str, course_session_id: int):
|
||||
def has_course_session_preview(user, course_session_id: int):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
||||
if is_course_session_member(user, course_session_id):
|
||||
return False
|
||||
|
||||
return is_learning_mentor(user, course_session_id) or is_course_session_expert(
|
||||
user, course_session_id
|
||||
)
|
||||
|
||||
|
||||
def has_media_library(user, course_session_id: int):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
||||
if CourseSessionGroup.objects.filter(course_session=course_session_id).exists():
|
||||
return True
|
||||
|
||||
return CourseSessionUser.objects.filter(
|
||||
course_session=course_session_id,
|
||||
user=user,
|
||||
).exists()
|
||||
|
||||
|
||||
def is_learning_mentor(mentor: User, course_session_id: int):
|
||||
course_session = CourseSession.objects.get(id=course_session_id)
|
||||
|
||||
if course_session is None:
|
||||
return False
|
||||
|
||||
return LearningMentor.objects.filter(
|
||||
mentor=mentor, course_session=course_session
|
||||
).exists()
|
||||
|
||||
|
||||
def is_learning_mentor_for_user(
|
||||
mentor: User, participant_user_id: str, course_session_id: int
|
||||
):
|
||||
csu = CourseSessionUser.objects.filter(
|
||||
course_session_id=course_session_id, user_id=participant_user_id
|
||||
).first()
|
||||
|
|
@ -48,7 +88,7 @@ def is_user_mentor(mentor: User, participant_user_id: str, course_session_id: in
|
|||
return False
|
||||
|
||||
return LearningMentor.objects.filter(
|
||||
course_id=csu.course_session.course_id, mentor=mentor, participants=csu
|
||||
course_session_id=course_session_id, mentor=mentor, participants=csu
|
||||
).exists()
|
||||
|
||||
|
||||
|
|
@ -98,7 +138,7 @@ def can_evaluate_assignments(
|
|||
role=CourseSessionUser.Role.EXPERT,
|
||||
).exists()
|
||||
|
||||
is_mentor = is_user_mentor(
|
||||
is_mentor = is_learning_mentor_for_user(
|
||||
mentor=evaluation_user,
|
||||
participant_user_id=assignment_user_id,
|
||||
course_session_id=course_session_id,
|
||||
|
|
@ -173,7 +213,9 @@ def has_role_in_course(user: User, course: Course) -> bool:
|
|||
).exists():
|
||||
return True
|
||||
|
||||
if LearningMentor.objects.filter(course=course, mentor=user).exists():
|
||||
if LearningMentor.objects.filter(
|
||||
course_session__course=course, mentor=user
|
||||
).exists():
|
||||
return True
|
||||
|
||||
if CourseSessionGroup.objects.filter(course=course, supervisor=user).exists():
|
||||
|
|
@ -192,6 +234,50 @@ def can_view_course(user: User, course: Course) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def has_appointments(user: User, course_session_id: int) -> bool:
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
||||
if CourseSessionGroup.objects.filter(course_session=course_session_id).exists():
|
||||
return True
|
||||
|
||||
return CourseSessionUser.objects.filter(course_session=course_session_id).exists()
|
||||
|
||||
|
||||
def has_learning_mentor(user: User, course_session_id: int) -> bool:
|
||||
course_session = CourseSession.objects.get(id=course_session_id)
|
||||
|
||||
if course_session is None:
|
||||
return False
|
||||
|
||||
if not course_session.course.configuration.enable_learning_mentor:
|
||||
return False
|
||||
|
||||
if is_learning_mentor(user, course_session_id):
|
||||
return True
|
||||
|
||||
if is_course_session_member(user, course_session_id):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def can_edit_mentors(user: User, course_session_id: int) -> bool:
|
||||
if not has_learning_mentor(user, course_session_id):
|
||||
return False
|
||||
|
||||
# limit further, since has_learning_mentor is too broad
|
||||
return is_course_session_member(user, course_session_id)
|
||||
|
||||
|
||||
def can_guide_members(user: User, course_session_id: int) -> bool:
|
||||
if not has_learning_mentor(user, course_session_id):
|
||||
return False
|
||||
|
||||
# limit further, since has_learning_mentor is too broad
|
||||
return is_learning_mentor(user, course_session_id)
|
||||
|
||||
|
||||
def can_view_profile(user: User, profile_user: CourseSessionUser) -> bool:
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
|
@ -199,7 +285,9 @@ def can_view_profile(user: User, profile_user: CourseSessionUser) -> bool:
|
|||
if user == profile_user.user:
|
||||
return True
|
||||
|
||||
if is_course_session_expert(user, profile_user.course_session.id) or is_user_mentor(
|
||||
if is_course_session_expert(
|
||||
user, profile_user.course_session.id
|
||||
) or is_learning_mentor_for_user(
|
||||
mentor=user,
|
||||
participant_user_id=profile_user.user.id,
|
||||
course_session_id=profile_user.course_session.id,
|
||||
|
|
@ -215,7 +303,7 @@ def can_view_course_completions(
|
|||
return (
|
||||
str(user.id) == target_user_id
|
||||
or is_course_session_expert(user=user, course_session_id=course_session_id)
|
||||
or is_user_mentor(
|
||||
or is_learning_mentor_for_user(
|
||||
mentor=user,
|
||||
participant_user_id=target_user_id,
|
||||
course_session_id=course_session_id,
|
||||
|
|
@ -232,6 +320,17 @@ def can_complete_learning_content(user: User, course_session_id: int) -> bool:
|
|||
def course_session_permissions(user: User, course_session_id: int) -> list[str]:
|
||||
return _action_list(
|
||||
{
|
||||
"learning-mentor": has_learning_mentor(user, course_session_id),
|
||||
"learning-mentor::edit-mentors": can_edit_mentors(user, course_session_id),
|
||||
"learning-mentor::guide-members": can_guide_members(
|
||||
user, course_session_id
|
||||
),
|
||||
"preview": has_course_session_preview(user, course_session_id),
|
||||
"media-library": has_media_library(user, course_session_id),
|
||||
"appointments": has_appointments(user, course_session_id),
|
||||
"expert-cockpit": is_course_session_expert(user, course_session_id),
|
||||
"learning-path": is_course_session_member(user, course_session_id),
|
||||
"competence-navi": is_course_session_member(user, course_session_id),
|
||||
"complete-learning-content": can_complete_learning_content(
|
||||
user, course_session_id
|
||||
),
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ class ActionTestCase(TestCase):
|
|||
def test_course_session_permissions(self):
|
||||
# GIVEN
|
||||
lm = create_user("mentor")
|
||||
LearningMentor.objects.create(
|
||||
mentor=lm,
|
||||
course=self.course,
|
||||
)
|
||||
LearningMentor.objects.create(mentor=lm, course_session=self.course_session)
|
||||
|
||||
participant = create_user("participant")
|
||||
add_course_session_user(
|
||||
|
|
@ -48,6 +45,34 @@ class ActionTestCase(TestCase):
|
|||
trainer_actions = course_session_permissions(trainer, self.course_session.id)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(len(mentor_actions), 0)
|
||||
self.assertEqual(participant_actions, ["complete-learning-content"])
|
||||
self.assertEqual(trainer_actions, ["complete-learning-content"])
|
||||
self.assertEqual(
|
||||
mentor_actions,
|
||||
[
|
||||
"learning-mentor",
|
||||
"learning-mentor::guide-members",
|
||||
"preview",
|
||||
"appointments",
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
participant_actions,
|
||||
[
|
||||
"learning-mentor",
|
||||
"learning-mentor::edit-mentors",
|
||||
"media-library",
|
||||
"appointments",
|
||||
"learning-path",
|
||||
"competence-navi",
|
||||
"complete-learning-content",
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
trainer_actions,
|
||||
[
|
||||
"preview",
|
||||
"media-library",
|
||||
"appointments",
|
||||
"expert-cockpit",
|
||||
"complete-learning-content",
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from vbv_lernwelt.course.creators.test_utils import (
|
|||
)
|
||||
from vbv_lernwelt.course.models import CourseSessionUser
|
||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||
from vbv_lernwelt.iam.permissions import has_role_in_course, is_user_mentor
|
||||
from vbv_lernwelt.iam.permissions import has_role_in_course, is_learning_mentor_for_user
|
||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
||||
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ class RoleTestCase(TestCase):
|
|||
# GIVEN
|
||||
LearningMentor.objects.create(
|
||||
mentor=self.user,
|
||||
course=self.course,
|
||||
course_session=self.course_session,
|
||||
)
|
||||
|
||||
# WHEN
|
||||
|
|
@ -75,14 +75,14 @@ class RoleTestCase(TestCase):
|
|||
|
||||
learning_mentor = LearningMentor.objects.create(
|
||||
mentor=mentor,
|
||||
course=course,
|
||||
course_session=course_session,
|
||||
)
|
||||
|
||||
learning_mentor.participants.add(member_csu)
|
||||
learning_mentor.save()
|
||||
|
||||
# WHEN
|
||||
is_mentor = is_user_mentor(
|
||||
is_mentor = is_learning_mentor_for_user(
|
||||
mentor=mentor,
|
||||
participant_user_id=member.id,
|
||||
course_session_id=course_session.id,
|
||||
|
|
@ -106,7 +106,7 @@ class RoleTestCase(TestCase):
|
|||
)
|
||||
|
||||
# WHEN
|
||||
is_mentor = is_user_mentor(
|
||||
is_mentor = is_learning_mentor_for_user(
|
||||
mentor=wanna_be_mentor,
|
||||
participant_user_id=member.id,
|
||||
course_session_id=course_session.id,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class LearningMentorAdmin(admin.ModelAdmin):
|
|||
|
||||
participant_count.short_description = "Participants"
|
||||
|
||||
list_display = ["mentor", "course", "participant_count"]
|
||||
list_display = ["mentor", "course_session", "participant_count"]
|
||||
|
||||
search_fields = ["mentor__email"]
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ def get_assignment_completions(
|
|||
)[0],
|
||||
user_id=user.id,
|
||||
last_name=user.last_name,
|
||||
url=f"/course/{course_session.course.slug}/cockpit/assignment/{assignment.id}/{user.id}",
|
||||
url=f"/course/{course_session.course.slug}/assignment-evaluation/{assignment.id}/{user.id}",
|
||||
)
|
||||
for user in sorted_participants
|
||||
]
|
||||
|
|
@ -110,19 +110,19 @@ def get_praxis_assignments(
|
|||
]
|
||||
)
|
||||
|
||||
circle_id = learning_content.get_circle().id
|
||||
circle = learning_content.get_circle()
|
||||
circle_ids.add(circle.id)
|
||||
|
||||
records.append(
|
||||
MentorAssignmentStatus(
|
||||
id=course_session_assignment.id,
|
||||
title=learning_content.content_assignment.title,
|
||||
circle_id=circle_id,
|
||||
circle_id=circle.id,
|
||||
circle_name=circle.title,
|
||||
pending_evaluations=submitted_count,
|
||||
completions=completions,
|
||||
type=MentorAssignmentStatusType.PRAXIS_ASSIGNMENT,
|
||||
)
|
||||
)
|
||||
|
||||
circle_ids.add(circle_id)
|
||||
|
||||
return records, circle_ids
|
||||
|
|
|
|||
|
|
@ -64,9 +64,6 @@ def get_self_feedback_evaluation(
|
|||
feedback_provider_user=evaluation_user,
|
||||
)
|
||||
|
||||
circle_id = learning_unit.get_circle().id
|
||||
circle_ids.add(circle_id)
|
||||
|
||||
pending_evaluations = len([f for f in feedbacks if not f.feedback_submitted])
|
||||
|
||||
completions = [
|
||||
|
|
@ -90,11 +87,15 @@ def get_self_feedback_evaluation(
|
|||
participants=participants,
|
||||
)
|
||||
|
||||
circle = learning_unit.get_circle()
|
||||
circle_ids.add(circle.id)
|
||||
|
||||
records.append(
|
||||
MentorAssignmentStatus(
|
||||
id=learning_unit.id,
|
||||
title=learning_unit.title,
|
||||
circle_id=circle_id,
|
||||
circle_id=circle.id,
|
||||
circle_name=circle.title,
|
||||
pending_evaluations=pending_evaluations,
|
||||
completions=completions,
|
||||
type=MentorAssignmentStatusType.SELF_EVALUATION_FEEDBACK,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class MentorAssignmentStatus:
|
|||
id: str
|
||||
title: str
|
||||
circle_id: str
|
||||
circle_name: str
|
||||
pending_evaluations: int
|
||||
completions: List[MentorAssignmentCompletion]
|
||||
type: MentorAssignmentStatusType
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
# Generated by Django 3.2.20 on 2024-03-19 09:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def reverse_migrate_course_session_to_course(apps, schema_editor):
|
||||
LearningMentor = apps.get_model("learning_mentor", "LearningMentor")
|
||||
for lm in LearningMentor.objects.all():
|
||||
lm.course = lm.course_session.course
|
||||
lm.save()
|
||||
|
||||
|
||||
def migrate_course_to_course_session(apps, schema_editor):
|
||||
LearningMentor = apps.get_model("learning_mentor", "LearningMentor")
|
||||
CourseSession = apps.get_model("course", "CourseSession")
|
||||
|
||||
for lm in LearningMentor.objects.all():
|
||||
# first is fine for VV there is only one course session per course
|
||||
course_session = CourseSession.objects.filter(course=lm.course).first()
|
||||
lm.course_session = course_session
|
||||
lm.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("course", "0007_auto_20240226_1553"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("learning_mentor", "0005_alter_learningmentor_mentor"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="learningmentor",
|
||||
name="course_session",
|
||||
field=models.ForeignKey(
|
||||
# this is a dummy value, it will be replaced by the migration
|
||||
default=-1,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="course.coursesession",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_course_to_course_session,
|
||||
reverse_code=reverse_migrate_course_session_to_course,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="learningmentor",
|
||||
unique_together={("mentor", "course_session")},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="learningmentor",
|
||||
name="course",
|
||||
),
|
||||
]
|
||||
|
|
@ -9,7 +9,7 @@ from vbv_lernwelt.course.models import CourseSessionUser
|
|||
|
||||
class LearningMentor(models.Model):
|
||||
mentor = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
course = models.ForeignKey("course.Course", on_delete=models.CASCADE)
|
||||
course_session = models.ForeignKey("course.CourseSession", on_delete=models.CASCADE)
|
||||
|
||||
participants = models.ManyToManyField(
|
||||
CourseSessionUser,
|
||||
|
|
@ -18,12 +18,12 @@ class LearningMentor(models.Model):
|
|||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = [["mentor", "course"]]
|
||||
unique_together = [["mentor", "course_session"]]
|
||||
verbose_name = "Lernbegleiter"
|
||||
verbose_name_plural = "Lernbegleiter"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.mentor} ({self.course.title})"
|
||||
return f"{self.mentor} ({self.course_session.title})"
|
||||
|
||||
@property
|
||||
def course_sessions(self):
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class MentorAssignmentStatusSerializer(serializers.Serializer):
|
|||
id = serializers.CharField()
|
||||
title = serializers.CharField()
|
||||
circle_id = serializers.CharField()
|
||||
circle_name = serializers.CharField()
|
||||
pending_evaluations = serializers.IntegerField()
|
||||
completions = MentorAssignmentCompletionSerializer(many=True)
|
||||
type = serializers.ReadOnlyField()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ def validate_student(sender, instance, action, reverse, model, pk_set, **kwargs)
|
|||
if action == "pre_add":
|
||||
participants = model.objects.filter(pk__in=pk_set)
|
||||
for participant in participants:
|
||||
if participant.course_session.course != instance.course:
|
||||
if participant.course_session != instance.course_session:
|
||||
raise ValidationError(
|
||||
"Participant (CourseSessionUser) does not match the course for this mentor."
|
||||
)
|
||||
if participant.user == instance.mentor:
|
||||
raise ValidationError("You cannot mentor yourself.")
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class AttendanceServicesTestCase(TestCase):
|
|||
self.assertEqual(result.status, expected_statuses[result.last_name])
|
||||
self.assertEqual(
|
||||
result.url,
|
||||
f"/course/test-lehrgang/cockpit/assignment/{self.assignment.id}/{result.user_id}",
|
||||
f"/course/test-lehrgang/assignment-evaluation/{self.assignment.id}/{result.user_id}",
|
||||
)
|
||||
|
||||
def test_praxis_assignment_status(self):
|
||||
|
|
@ -108,5 +108,6 @@ class AttendanceServicesTestCase(TestCase):
|
|||
assignment = assignments[0]
|
||||
self.assertEqual(assignment.pending_evaluations, 1)
|
||||
self.assertEqual(assignment.title, "Dummy Assignment (PRAXIS_ASSIGNMENT)")
|
||||
self.assertEqual(assignment.circle_name, "Circle")
|
||||
self.assertEqual(assignment.circle_id, self.circle.id)
|
||||
self.assertEqual(list(circle_ids)[0], self.circle.id)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,34 @@ class LearningMentorInvitationTest(APITestCase):
|
|||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
@patch("vbv_lernwelt.learning_mentor.views.send_email")
|
||||
def test_create_denies_self_invitation(self, mock_send_mail) -> None:
|
||||
# GIVEN
|
||||
self.client.force_login(self.participant)
|
||||
|
||||
add_course_session_user(
|
||||
self.course_session,
|
||||
self.participant,
|
||||
role=CourseSessionUser.Role.MEMBER,
|
||||
)
|
||||
|
||||
invite_url = reverse(
|
||||
"create_invitation", kwargs={"course_session_id": self.course_session.id}
|
||||
)
|
||||
|
||||
# WHEN
|
||||
email = self.participant.email
|
||||
response = self.client.post(invite_url, data={"email": email})
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{
|
||||
"message": "You cannot invite yourself",
|
||||
},
|
||||
)
|
||||
|
||||
@patch("vbv_lernwelt.learning_mentor.views.send_email")
|
||||
def test_create_invitation(self, mock_send_mail) -> None:
|
||||
# GIVEN
|
||||
|
|
@ -164,49 +192,11 @@ class LearningMentorInvitationTest(APITestCase):
|
|||
},
|
||||
)
|
||||
|
||||
def test_accept_invitation_role_collision(self) -> None:
|
||||
# GIVEN
|
||||
participant_cs_user = add_course_session_user(
|
||||
self.course_session,
|
||||
self.participant,
|
||||
role=CourseSessionUser.Role.MEMBER,
|
||||
)
|
||||
|
||||
invitee = create_user("invitee")
|
||||
invitation = MentorInvitation.objects.create(
|
||||
participant=participant_cs_user, email=invitee.email
|
||||
)
|
||||
# Make invitee a trainer
|
||||
add_course_session_user(
|
||||
self.course_session,
|
||||
invitee,
|
||||
role=CourseSessionUser.Role.EXPERT,
|
||||
)
|
||||
|
||||
self.client.force_login(invitee)
|
||||
|
||||
accept_url = reverse(
|
||||
"accept_invitation", kwargs={"course_session_id": self.course_session.id}
|
||||
)
|
||||
|
||||
# WHEN
|
||||
response = self.client.post(accept_url, data={"invitation_id": invitation.id})
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{
|
||||
"message": "User already has a role in this course",
|
||||
"code": "existingRole",
|
||||
},
|
||||
)
|
||||
|
||||
def test_accept_invitation(self) -> None:
|
||||
# GIVEN
|
||||
participant_cs_user = add_course_session_user(
|
||||
self.course_session,
|
||||
self.participant,
|
||||
course_session=self.course_session,
|
||||
user=self.participant,
|
||||
role=CourseSessionUser.Role.MEMBER,
|
||||
)
|
||||
|
||||
|
|
@ -229,7 +219,9 @@ class LearningMentorInvitationTest(APITestCase):
|
|||
self.assertFalse(MentorInvitation.objects.filter(id=invitation.id).exists())
|
||||
self.assertTrue(
|
||||
LearningMentor.objects.filter(
|
||||
mentor=invitee, course=self.course, participants=participant_cs_user
|
||||
mentor=invitee,
|
||||
course_session=self.course_session,
|
||||
participants=participant_cs_user,
|
||||
).exists()
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class LearningMentorAPITest(APITestCase):
|
|||
# GIVEN
|
||||
self.client.force_login(self.mentor)
|
||||
LearningMentor.objects.create(
|
||||
mentor=self.mentor, course=self.course_session.course
|
||||
mentor=self.mentor, course_session=self.course_session
|
||||
)
|
||||
|
||||
# WHEN
|
||||
|
|
@ -93,8 +93,7 @@ class LearningMentorAPITest(APITestCase):
|
|||
participants = [self.participant_1, self.participant_2, self.participant_3]
|
||||
self.client.force_login(self.mentor)
|
||||
mentor = LearningMentor.objects.create(
|
||||
mentor=self.mentor,
|
||||
course=self.course_session.course,
|
||||
mentor=self.mentor, course_session=self.course_session
|
||||
)
|
||||
mentor.participants.set(participants)
|
||||
|
||||
|
|
@ -114,14 +113,18 @@ class LearningMentorAPITest(APITestCase):
|
|||
self.assertEqual(participant_1["first_name"], "Test")
|
||||
self.assertEqual(participant_1["last_name"], "Participant_1")
|
||||
|
||||
self.assertEqual(
|
||||
response.data["mentor_id"],
|
||||
mentor.id,
|
||||
)
|
||||
|
||||
def test_api_self_evaluation_feedback(self) -> None:
|
||||
# GIVEN
|
||||
participants = [self.participant_1, self.participant_2, self.participant_3]
|
||||
self.client.force_login(self.mentor)
|
||||
|
||||
mentor = LearningMentor.objects.create(
|
||||
mentor=self.mentor,
|
||||
course=self.course_session.course,
|
||||
mentor=self.mentor, course_session=self.course_session
|
||||
)
|
||||
|
||||
mentor.participants.set(participants)
|
||||
|
|
@ -204,7 +207,7 @@ class LearningMentorAPITest(APITestCase):
|
|||
|
||||
mentor = LearningMentor.objects.create(
|
||||
mentor=self.mentor,
|
||||
course=self.course_session.course,
|
||||
course_session=self.course_session,
|
||||
)
|
||||
|
||||
participants = [self.participant_1, self.participant_2, self.participant_3]
|
||||
|
|
@ -265,8 +268,7 @@ class LearningMentorAPITest(APITestCase):
|
|||
)
|
||||
|
||||
learning_mentor = LearningMentor.objects.create(
|
||||
mentor=self.mentor,
|
||||
course=self.course_session.course,
|
||||
mentor=self.mentor, course_session=self.course_session
|
||||
)
|
||||
|
||||
learning_mentor.participants.add(participant_cs_user)
|
||||
|
|
@ -288,7 +290,41 @@ class LearningMentorAPITest(APITestCase):
|
|||
self.assertEqual(mentor_user["email"], self.mentor.email)
|
||||
self.assertEqual(mentor_user["id"], str(self.mentor.id))
|
||||
|
||||
def test_remove_user_mentor(self) -> None:
|
||||
def test_remove_participant_as_mentor(self) -> None:
|
||||
# GIVEN
|
||||
self.client.force_login(self.mentor)
|
||||
|
||||
participant_cs_user = add_course_session_user(
|
||||
self.course_session,
|
||||
create_user("participant"),
|
||||
role=CourseSessionUser.Role.MEMBER,
|
||||
)
|
||||
|
||||
learning_mentor = LearningMentor.objects.create(
|
||||
mentor=self.mentor, course_session=self.course_session
|
||||
)
|
||||
|
||||
learning_mentor.participants.add(participant_cs_user)
|
||||
|
||||
remove_url = reverse(
|
||||
"remove_participant_from_mentor",
|
||||
kwargs={
|
||||
"course_session_id": self.course_session.id,
|
||||
"mentor_id": learning_mentor.id,
|
||||
"participant_user_id": participant_cs_user.user.id,
|
||||
},
|
||||
)
|
||||
|
||||
# WHEN
|
||||
response = self.client.delete(remove_url)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
self.assertFalse(
|
||||
LearningMentor.objects.filter(participants=participant_cs_user).exists()
|
||||
)
|
||||
|
||||
def test_remove_myself_from_mentor(self) -> None:
|
||||
# GIVEN
|
||||
participant = create_user("participant")
|
||||
self.client.force_login(participant)
|
||||
|
|
@ -300,22 +336,22 @@ class LearningMentorAPITest(APITestCase):
|
|||
)
|
||||
|
||||
learning_mentor = LearningMentor.objects.create(
|
||||
mentor=self.mentor,
|
||||
course=self.course_session.course,
|
||||
mentor=self.mentor, course_session=self.course_session
|
||||
)
|
||||
|
||||
learning_mentor.participants.add(participant_cs_user)
|
||||
|
||||
remove_self_url = reverse(
|
||||
"remove_self_from_mentor",
|
||||
remove_url = reverse(
|
||||
"remove_participant_from_mentor",
|
||||
kwargs={
|
||||
"course_session_id": self.course_session.id,
|
||||
"mentor_id": learning_mentor.id,
|
||||
"participant_user_id": participant_cs_user.user.id,
|
||||
},
|
||||
)
|
||||
|
||||
# WHEN
|
||||
response = self.client.delete(remove_self_url)
|
||||
response = self.client.delete(remove_url)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
|
|
@ -326,11 +362,19 @@ class LearningMentorAPITest(APITestCase):
|
|||
def test_mentor_multiple_courses(self) -> None:
|
||||
# GIVEN
|
||||
course_a, _ = create_course("Course A")
|
||||
course_session_a = create_course_session(course=course_a, title="Test A")
|
||||
|
||||
course_b, _ = create_course("Course B")
|
||||
course_session_b = create_course_session(course=course_b, title="Test B")
|
||||
|
||||
# WHEN
|
||||
LearningMentor.objects.create(mentor=self.mentor, course=course_a)
|
||||
LearningMentor.objects.create(mentor=self.mentor, course=course_b)
|
||||
LearningMentor.objects.create(
|
||||
mentor=self.mentor, course_session=course_session_a
|
||||
)
|
||||
|
||||
LearningMentor.objects.create(
|
||||
mentor=self.mentor, course_session=course_session_b
|
||||
)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(LearningMentor.objects.count(), 2)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ urlpatterns = [
|
|||
path("summary", views.mentor_summary, name="mentor_summary"),
|
||||
path("mentors", views.list_user_mentors, name="list_user_mentors"),
|
||||
path(
|
||||
"mentors/<int:mentor_id>/leave",
|
||||
views.remove_self_from_mentor,
|
||||
name="remove_self_from_mentor",
|
||||
"mentors/<int:mentor_id>/remove/<uuid:participant_user_id>",
|
||||
views.remove_participant_from_mentor,
|
||||
name="remove_participant_from_mentor",
|
||||
),
|
||||
path("invitations", views.list_invitations, name="list_invitations"),
|
||||
path("invitations/create", views.create_invitation, name="create_invitation"),
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue