Merged develop into feature/vueuse-update
This commit is contained in:
commit
a59e1689e3
|
|
@ -1,31 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
|
||||
import { computed } from "vue";
|
||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||
|
||||
const expertCockpitStore = useExpertCockpitStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const circleDates = computed(() => {
|
||||
const dueDates = courseSession.value.due_dates.filter((dueDate) => {
|
||||
if (!expertCockpitStore.currentCircle) return false;
|
||||
return expertCockpitStore.currentCircle.id == dueDate?.circle?.id;
|
||||
});
|
||||
return dueDates.slice(0, 4);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<h3 class="heading-3">{{ $t("Nächste Termine") }}</h3>
|
||||
<div
|
||||
v-for="dueDate in circleDates"
|
||||
:key="dueDate.id"
|
||||
class="border-t border-gray-500 pt-2"
|
||||
>
|
||||
<DueDateSingle :due-date="dueDate" :single-line="true"></DueDateSingle>
|
||||
</div>
|
||||
<div v-if="circleDates.length === 0">{{ $t("dueDates.noDueDatesAvailable") }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
2
|
||||
<script setup lang="ts">
|
||||
import AssignmentSubmissionProgress from "@/components/assignment/AssignmentSubmissionProgress.vue";
|
||||
import type {
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@ const progress = computed(() => ({
|
|||
<div class="flex items-center">
|
||||
<i18next :translation="$t('a.NUMBER Elemente abgeschlossen')">
|
||||
<template #NUMBER>
|
||||
<span class="mr-3 text-4xl font-bold">{{ totalAssignments }}</span>
|
||||
<span class="mr-3 text-xl font-bold">{{ totalAssignments }}</span>
|
||||
</template>
|
||||
</i18next>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<i18next :translation="$t('a.xOfY Punkten erreicht')">
|
||||
<template #xOfY>
|
||||
<span class="mr-3 text-4xl font-bold">
|
||||
<span class="mr-3 text-xl font-bold">
|
||||
{{
|
||||
$t("a.VALUE von MAXIMUM", {
|
||||
VALUE: props.achievedPointsCount,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
<script setup lang="ts">
|
||||
import type { Ref } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import type { ProgressDashboardAssignmentType } from "@/gql/graphql";
|
||||
import { fetchProgressData } from "@/services/dashboard";
|
||||
import AssignmentProgressSummaryBox from "@/components/dashboard/AssignmentProgressSummaryBox.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseId: string;
|
||||
courseSlug: string;
|
||||
sessionToContinueId: string;
|
||||
}>();
|
||||
|
||||
const DEFAULT_ASSIGNMENT = {
|
||||
_id: "",
|
||||
points_achieved_count: 0,
|
||||
points_max_count: 0,
|
||||
total_count: 0,
|
||||
};
|
||||
const assignment: Ref<ProgressDashboardAssignmentType | null> = ref(DEFAULT_ASSIGNMENT);
|
||||
|
||||
const competenceCertificateUrl = computed(() => {
|
||||
return `/course/${props.courseSlug}/competence/certificates?courseSessionId=${props.sessionToContinueId}`;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await fetchProgressData(props.courseId);
|
||||
assignment.value = data?.assignment ?? null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="assignment">
|
||||
<div class="w-[395px]">
|
||||
<AssignmentProgressSummaryBox
|
||||
:total-assignments="assignment.total_count"
|
||||
:achieved-points-count="assignment.points_achieved_count"
|
||||
:max-points-count="assignment.points_max_count"
|
||||
:details-link="competenceCertificateUrl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -6,6 +6,7 @@ import BaseBox from "@/components/dashboard/BaseBox.vue";
|
|||
const props = defineProps<{
|
||||
assignmentsCompleted: number;
|
||||
avgPassed: number;
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
const progress = computed(() => {
|
||||
|
|
@ -19,7 +20,7 @@ const progress = computed(() => {
|
|||
|
||||
<template>
|
||||
<BaseBox
|
||||
:details-link="'/statistic/assignment'"
|
||||
:details-link="`/statistic/${courseSlug}/assignment`"
|
||||
data-cy="dashboard.stats.assignments"
|
||||
>
|
||||
<template #title>{{ $t("a.Kompetenznachweis-Elemente") }}</template>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import BaseBox from "@/components/dashboard/BaseBox.vue";
|
|||
const props = defineProps<{
|
||||
daysCompleted: number;
|
||||
avgParticipantsPresent: number;
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
const progressRecord = computed(() => {
|
||||
|
|
@ -18,7 +19,10 @@ const progressRecord = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<BaseBox :details-link="'/statistic/attendance'" data-cy="dashboard.stats.attendance">
|
||||
<BaseBox
|
||||
:details-link="`/statistic/${props.courseSlug}/attendance`"
|
||||
data-cy="dashboard.stats.attendance"
|
||||
>
|
||||
<template #title>{{ $t("a.Anwesenheit") }}</template>
|
||||
<template #content>
|
||||
<div class="flex items-center">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ defineProps<{
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col space-y-4 bg-white p-6">
|
||||
<div class="flex h-full flex-col space-y-4 bg-white">
|
||||
<h4 class="mb-1 font-bold">
|
||||
<slot name="title"></slot>
|
||||
</h4>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<script setup lang="ts">
|
||||
import type { Ref } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import type { ProgressDashboardCompetenceType } from "@/gql/graphql";
|
||||
import CompetenceSummaryBox from "@/components/dashboard/CompetenceSummaryBox.vue";
|
||||
import { fetchProgressData } from "@/services/dashboard";
|
||||
|
||||
const props = defineProps<{
|
||||
courseId: string;
|
||||
courseSlug: string;
|
||||
sessionToContinueId: string;
|
||||
}>();
|
||||
|
||||
const DEFAULT_COMPETENCE = { _id: "", total_count: 0, success_count: 0, fail_count: 0 };
|
||||
const competence: Ref<ProgressDashboardCompetenceType | null> = ref(DEFAULT_COMPETENCE);
|
||||
|
||||
const competenceCriteriaUrl = computed(() => {
|
||||
return `/course/${props.courseSlug}/competence/self-evaluation-and-feedback?courseSessionId=${props.sessionToContinueId}`;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await fetchProgressData(props.courseId);
|
||||
competence.value = data?.competence ?? null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="competence" class="w-[395px]">
|
||||
<CompetenceSummaryBox
|
||||
:fail-count="competence.fail_count"
|
||||
:success-count="competence.success_count"
|
||||
:details-link="competenceCriteriaUrl"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -13,11 +13,11 @@ defineProps<{
|
|||
<template #title>{{ $t("a.Selbsteinschätzungen") }}</template>
|
||||
<template #content>
|
||||
<div class="flex items-center">
|
||||
<it-icon-smiley-happy class="mr-4 h-12 w-12"></it-icon-smiley-happy>
|
||||
<it-icon-smiley-happy class="mr-4 h-[28px] w-[28px]"></it-icon-smiley-happy>
|
||||
<i18next :translation="$t('a.{NUMBER} Das kann ich')">
|
||||
<template #NUMBER>
|
||||
<span
|
||||
class="mr-3 text-4xl font-bold"
|
||||
class="mr-3 text-2xl font-bold"
|
||||
data-cy="dashboard.stats.competence.success"
|
||||
>
|
||||
{{ successCount }}
|
||||
|
|
@ -26,11 +26,13 @@ defineProps<{
|
|||
</i18next>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<it-icon-smiley-thinking class="mr-4 h-12 w-12"></it-icon-smiley-thinking>
|
||||
<it-icon-smiley-thinking
|
||||
class="mr-4 h-[28px] w-[28px]"
|
||||
></it-icon-smiley-thinking>
|
||||
<i18next :translation="$t('a.{NUMBER} Das will ich nochmals anschauen')">
|
||||
<template #NUMBER>
|
||||
<span
|
||||
class="mr-3 text-4xl font-bold"
|
||||
class="mr-3 text-2xl font-bold"
|
||||
data-cy="dashboard.stats.competence.fail"
|
||||
>
|
||||
{{ failCount }}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useDashboardStore } from "@/stores/dashboard";
|
||||
import { getCockpitUrl } from "@/utils/utils";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const allDueDates = courseSessionsStore.allDueDates();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="dashboardStore.currentDashboardConfig">
|
||||
<h4 class="mb-6 text-xl font-bold">{{ $t("a.Aktueller Lehrgang") }}</h4>
|
||||
|
||||
<div class="mb-6 border border-gray-300 p-6">
|
||||
<h3 class="mb-6">{{ dashboardStore.currentDashboardConfig.name }}</h3>
|
||||
<router-link
|
||||
class="btn-blue"
|
||||
target="_blank"
|
||||
:to="getCockpitUrl(dashboardStore.currentDashboardConfig.slug)"
|
||||
>
|
||||
{{ $t("a.Cockpit anschauen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<router-link
|
||||
v-if="dashboardStore.dashboardConfigs.length > 1"
|
||||
class="block text-sm underline"
|
||||
to="/statistic/list"
|
||||
>
|
||||
{{ $t("a.Alle Lehrgänge anzeigen") }}
|
||||
</router-link>
|
||||
|
||||
<h3 class="mb-6 mt-16 text-xl font-bold">{{ $t("a.AlleTermine") }}</h3>
|
||||
<DueDatesList
|
||||
:due-dates="allDueDates"
|
||||
:max-count="13"
|
||||
:show-top-border="true"
|
||||
:show-all-due-dates-link="true"
|
||||
:show-bottom-border="true"
|
||||
:show-course-session="true"
|
||||
></DueDatesList>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import type { DashboardCourseConfigType, WidgetType } from "@/services/dashboard";
|
||||
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
||||
import CompetenceSummary from "@/components/dashboard/CompetenceSummary.vue";
|
||||
import AssignmentSummary from "@/components/dashboard/AssignmentSummary.vue";
|
||||
import MentorOpenTasksCount from "@/components/dashboard/MentorOpenTasksCount.vue";
|
||||
import MentorMenteeCount from "@/components/dashboard/MentorMenteeCount.vue";
|
||||
import MentorCompetenceSummary from "@/components/dashboard/MentorCompetenceSummary.vue";
|
||||
import { getCockpitUrl, getLearningMentorUrl, getLearningPathUrl } from "@/utils/utils";
|
||||
import UkStatistics from "@/components/dashboard/UkStatistics.vue";
|
||||
|
||||
const mentorWidgets = [
|
||||
"MentorTasksWidget",
|
||||
"MentorPersonWidget",
|
||||
"MentorCompetenceWidget",
|
||||
];
|
||||
const progressWidgets = ["CompetenceWidget", "CompetenceCertificateWidget"];
|
||||
|
||||
const props = defineProps<{
|
||||
courseConfig: DashboardCourseConfigType | undefined;
|
||||
}>();
|
||||
|
||||
const courseSlug = computed(() => props.courseConfig?.course_slug ?? "");
|
||||
const courseName = computed(() => props.courseConfig?.course_title ?? "");
|
||||
const numberOfMentorWidgets = computed(() => {
|
||||
return (
|
||||
props.courseConfig?.widgets?.filter((widget) => mentorWidgets.includes(widget))
|
||||
.length ?? 0
|
||||
);
|
||||
});
|
||||
const numberOfProgressWidgets = computed(() => {
|
||||
return (
|
||||
props.courseConfig?.widgets?.filter((widget) => progressWidgets.includes(widget))
|
||||
.length ?? 0
|
||||
);
|
||||
});
|
||||
|
||||
function hasWidget(widget: WidgetType) {
|
||||
return props.courseConfig?.widgets?.includes(widget) ?? false;
|
||||
}
|
||||
|
||||
const actionButtonProps = computed<{ href: string; text: string; cyKey: string }>(
|
||||
() => {
|
||||
if (props.courseConfig?.role_key === "Supervisor") {
|
||||
return {
|
||||
href: getCockpitUrl(props.courseConfig?.course_slug),
|
||||
text: "Cockpit anschauen",
|
||||
cyKey: "cockpit-dashboard-link",
|
||||
};
|
||||
}
|
||||
if (props.courseConfig?.role_key === "Trainer") {
|
||||
return {
|
||||
href: getCockpitUrl(props.courseConfig?.course_slug),
|
||||
text: "Cockpit anschauen",
|
||||
cyKey: "cockpit-dashboard-link",
|
||||
};
|
||||
}
|
||||
if (props.courseConfig?.role_key === "MentorVV") {
|
||||
return {
|
||||
href: getLearningMentorUrl(props.courseConfig?.course_slug),
|
||||
text: "a.Übersicht anschauen",
|
||||
cyKey: "lm-dashboard-link",
|
||||
};
|
||||
}
|
||||
return {
|
||||
href: getLearningPathUrl(props.courseConfig?.course_slug),
|
||||
text: "Weiter lernen",
|
||||
cyKey: "progress-dashboard-continue-course-link",
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
function hasActionButton(): boolean {
|
||||
return props.courseConfig?.role_key !== "MentorUK";
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="courseConfig" class="mb-14 space-y-8">
|
||||
<div class="flex flex-col space-y-8 bg-white p-6">
|
||||
<div class="border-b border-gray-300 pb-8">
|
||||
<div class="flex flex-row items-start justify-between">
|
||||
<h3 class="mb-4 text-3xl" data-cy="db-course-title">{{ courseName }}</h3>
|
||||
<a
|
||||
v-if="hasActionButton()"
|
||||
:href="actionButtonProps.href"
|
||||
class="btn-blue"
|
||||
:data-cy="actionButtonProps.cyKey"
|
||||
>
|
||||
{{ $t(actionButtonProps.text) }}
|
||||
</a>
|
||||
</div>
|
||||
<p>
|
||||
<span class="rounded bg-gray-300 px-2 py-1">
|
||||
{{ $t(courseConfig.role_key) }}
|
||||
</span>
|
||||
<router-link
|
||||
v-if="courseConfig.has_preview"
|
||||
:to="getLearningPathUrl(courseConfig.course_slug)"
|
||||
class="inline-block pl-6"
|
||||
target="_blank"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<span>{{ $t("a.VorschauTeilnehmer") }}</span>
|
||||
<it-icon-external-link class="ml-1 !h-4 !w-4" />
|
||||
</div>
|
||||
</router-link>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
hasWidget('ProgressWidget') &&
|
||||
courseConfig.session_to_continue_id &&
|
||||
courseSlug
|
||||
"
|
||||
class="border-b border-gray-300 pb-8 last:border-0"
|
||||
>
|
||||
<LearningPathDiagram
|
||||
:key="courseSlug"
|
||||
:course-slug="courseSlug"
|
||||
:course-session-id="courseConfig.session_to_continue_id"
|
||||
diagram-type="horizontal"
|
||||
></LearningPathDiagram>
|
||||
</div>
|
||||
<div
|
||||
v-if="numberOfProgressWidgets"
|
||||
class="flex flex-col flex-wrap gap-x-[60px] border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
||||
>
|
||||
<AssignmentSummary
|
||||
v-if="hasWidget('CompetenceCertificateWidget')"
|
||||
:course-slug="courseSlug"
|
||||
:session-to-continue-id="courseConfig.session_to_continue_id"
|
||||
:course-id="courseConfig.course_id"
|
||||
/>
|
||||
<CompetenceSummary
|
||||
v-if="hasWidget('CompetenceWidget')"
|
||||
:course-slug="courseSlug"
|
||||
:session-to-continue-id="courseConfig.session_to_continue_id"
|
||||
:course-id="courseConfig.course_id"
|
||||
></CompetenceSummary>
|
||||
</div>
|
||||
<div
|
||||
v-if="hasWidget('UKStatisticsWidget')"
|
||||
class="flex flex-col flex-wrap gap-x-[60px] border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
||||
>
|
||||
<UkStatistics :course-slug="courseSlug" :course-id="courseConfig.course_id" />
|
||||
</div>
|
||||
<div
|
||||
v-if="numberOfMentorWidgets > 0"
|
||||
class="flex flex-col flex-wrap items-stretch md:flex-row"
|
||||
>
|
||||
<MentorMenteeCount
|
||||
v-if="hasWidget('MentorPersonWidget')"
|
||||
:course-id="courseConfig.course_id"
|
||||
:course-slug="courseConfig?.course_slug"
|
||||
/>
|
||||
<MentorOpenTasksCount
|
||||
v-if="hasWidget('MentorTasksWidget')"
|
||||
:course-id="courseConfig.course_id"
|
||||
:course-slug="courseSlug"
|
||||
/>
|
||||
<MentorCompetenceSummary
|
||||
v-if="hasWidget('MentorCompetenceWidget')"
|
||||
:course-id="courseConfig.course_id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -7,6 +7,7 @@ const props = defineProps<{
|
|||
feedbackCount: number;
|
||||
statisfactionMax: number;
|
||||
statisfactionAvg: number;
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
const satisfactionColor = computed(() => {
|
||||
|
|
@ -15,7 +16,10 @@ const satisfactionColor = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<BaseBox :details-link="'/statistic/feedback'" data-cy="dashboard.stats.feedback">
|
||||
<BaseBox
|
||||
:details-link="`/statistic/${courseSlug}/feedback`"
|
||||
data-cy="dashboard.stats.feedback"
|
||||
>
|
||||
<template #title>{{ $t("a.Feedback Teilnehmer") }}</template>
|
||||
<template #content>
|
||||
<div class="flex items-center">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
<script setup lang="ts">
|
||||
import type { Ref } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { fetchMentorCompetenceSummary } from "@/services/dashboard";
|
||||
import type { AssignmentsStatisticsType } from "@/gql/graphql";
|
||||
import BaseBox from "@/components/dashboard/BaseBox.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseId: string;
|
||||
}>();
|
||||
|
||||
const summary: Ref<AssignmentsStatisticsType | null> = ref(null);
|
||||
|
||||
onMounted(async () => {
|
||||
summary.value = await fetchMentorCompetenceSummary(props.courseId);
|
||||
console.log(summary.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="summary" class="w-[325px]">
|
||||
<BaseBox
|
||||
:details-link="`/dashboard/persons-competence?course=${props.courseId}`"
|
||||
data-cy="dashboard.mentor.competenceSummary"
|
||||
>
|
||||
<template #title>{{ $t("Kompetenznachweise") }}</template>
|
||||
<template #content>
|
||||
<div class="flex flex-row space-x-3 bg-white">
|
||||
<div
|
||||
class="flex h-[47px] items-center justify-center py-1 pr-3 text-3xl font-bold"
|
||||
>
|
||||
<span>{{ summary.summary.total_passed }}</span>
|
||||
</div>
|
||||
<p class="ml-3 mt-0 leading-[47px]">{{ $t("Bestanden") }}</p>
|
||||
</div>
|
||||
<div class="flex flex-row space-x-3 bg-white pb-6">
|
||||
<div
|
||||
class="flex h-[47px] items-center justify-center py-1 pr-3 text-3xl font-bold"
|
||||
>
|
||||
<span>{{ summary.summary.total_failed }}</span>
|
||||
</div>
|
||||
<p class="ml-3 mt-0 leading-[47px]">{{ $t("Nicht bestanden") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</BaseBox>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<script setup lang="ts">
|
||||
import type { Ref } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { fetchMenteeCount } from "@/services/dashboard";
|
||||
import BaseBox from "@/components/dashboard/BaseBox.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseId: string;
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
const menteeCount: Ref<number> = ref(0);
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await fetchMenteeCount(props.courseId);
|
||||
menteeCount.value = data?.mentee_count;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-[325px]">
|
||||
<BaseBox
|
||||
:details-link="`/dashboard/persons?course=${props.courseId}`"
|
||||
data-cy="dashboard.mentor.competenceSummary"
|
||||
>
|
||||
<template #title>{{ $t("a.Personen") }}</template>
|
||||
<template #content>
|
||||
<div class="flex flex-row space-x-3 bg-white pb-6">
|
||||
<div
|
||||
class="flex h-[74px] items-center justify-center py-1 pr-3 text-3xl font-bold"
|
||||
>
|
||||
<span>{{ menteeCount }}</span>
|
||||
</div>
|
||||
<p class="ml-3 mt-0 leading-[74px]">
|
||||
{{ $t("a.Personen, die du begleitest") }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</BaseBox>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<script setup lang="ts">
|
||||
import type { Ref } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { fetchOpenTasksCount } from "@/services/dashboard";
|
||||
import BaseBox from "@/components/dashboard/BaseBox.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseId: string;
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
const openTaskCount: Ref<number> = ref(0);
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await fetchOpenTasksCount(props.courseId);
|
||||
openTaskCount.value = data?.open_task_count;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-[325px]">
|
||||
<BaseBox
|
||||
:details-link="`/course/${props.courseSlug}/learning-mentor/tasks`"
|
||||
data-cy="dashboard.mentor.competenceSummary"
|
||||
>
|
||||
<template #title>{{ $t("Zu erledigen") }}</template>
|
||||
<template #content>
|
||||
<div class="flex flex-row space-x-3 bg-white pb-6">
|
||||
<div
|
||||
class="flex h-[74px] w-[74px] items-center justify-center rounded-full border-2 border-green-500 px-3 py-1 text-3xl font-bold"
|
||||
>
|
||||
<span>{{ openTaskCount }}</span>
|
||||
</div>
|
||||
<p class="ml-3 mt-0 leading-[74px]">{{ $t("Elemente zu erledigen") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</BaseBox>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const allDueDates = courseSessionsStore.allDueDates();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h4 class="mb-6 text-xl font-bold">{{ $t("a.AlleTermine") }}</h4>
|
||||
<DueDatesList
|
||||
:due-dates="allDueDates"
|
||||
:max-count="13"
|
||||
:show-top-border="true"
|
||||
:show-all-due-dates-link="true"
|
||||
:show-bottom-border="true"
|
||||
:show-course-session="true"
|
||||
></DueDatesList>
|
||||
</template>
|
||||
|
|
@ -1,65 +1,97 @@
|
|||
<script setup lang="ts">
|
||||
import { useDashboardStore } from "@/stores/dashboard";
|
||||
import { computed } from "vue";
|
||||
import CourseStatistics from "@/components/dashboard/CourseStatistics.vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import AttendanceSummaryBox from "@/components/dashboard/AttendanceSummaryBox.vue";
|
||||
import type { CourseStatisticsType } from "@/gql/graphql";
|
||||
import AssignmentSummaryBox from "@/components/dashboard/AssignmentSummaryBox.vue";
|
||||
import FeedbackSummaryBox from "@/components/dashboard/FeedbackSummaryBox.vue";
|
||||
import CompetenceSummaryBox from "@/components/dashboard/CompetenceSummaryBox.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseId: string;
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
const statistics = ref<CourseStatisticsType | null>(null);
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
const statistics = computed(() => {
|
||||
return dashboardStore.currentDashBoardData as CourseStatisticsType;
|
||||
});
|
||||
|
||||
const courseSessionSelectionMetrics = computed(() => {
|
||||
return statistics.value.course_session_selection_metrics;
|
||||
});
|
||||
|
||||
const attendanceDayPresences = computed(() => {
|
||||
return statistics.value.attendance_day_presences.summary;
|
||||
return (
|
||||
statistics?.value?.attendance_day_presences?.summary ?? {
|
||||
days_completed: 0,
|
||||
participants_present: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const assigmentSummary = computed(() => {
|
||||
return statistics.value.assignments.summary;
|
||||
return (
|
||||
statistics?.value?.assignments.summary ?? {
|
||||
average_passed: 0,
|
||||
completed_count: 0,
|
||||
total_passed: 0,
|
||||
total_failed: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const competenceSummary = computed(() => {
|
||||
return statistics.value.competences.summary;
|
||||
return (
|
||||
statistics?.value?.competences.summary ?? {
|
||||
fail_total: 0,
|
||||
success_total: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const feebackSummary = computed(() => {
|
||||
return statistics.value.feedback_responses.summary;
|
||||
return (
|
||||
statistics?.value?.feedback_responses.summary ?? {
|
||||
satisfaction_average: 0,
|
||||
satisfaction_max: 0,
|
||||
total_responses: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
statistics.value = await dashboardStore.loadStatisticsDatav2(props.courseId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="statistics" class="mb-14 space-y-8">
|
||||
<CourseStatistics
|
||||
:session-count="courseSessionSelectionMetrics.session_count"
|
||||
:participant-count="courseSessionSelectionMetrics.participant_count"
|
||||
:expert-count="courseSessionSelectionMetrics.expert_count"
|
||||
/>
|
||||
<div class="grid auto-rows-fr grid-cols-1 gap-8 xl:grid-cols-2">
|
||||
<div v-if="statistics" class="space-y-8">
|
||||
<div
|
||||
class="flex flex-col flex-wrap justify-between gap-x-5 border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
||||
>
|
||||
<AttendanceSummaryBox
|
||||
class="flex-grow"
|
||||
:days-completed="attendanceDayPresences.days_completed"
|
||||
:avg-participants-present="attendanceDayPresences.participants_present"
|
||||
:course-slug="props.courseSlug"
|
||||
/>
|
||||
<AssignmentSummaryBox
|
||||
class="flex-grow"
|
||||
:assignments-completed="assigmentSummary.completed_count"
|
||||
:avg-passed="assigmentSummary.average_passed"
|
||||
:course-slug="props.courseSlug"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col flex-wrap gap-x-5 border-b border-gray-300 align-top last:border-0 md:flex-row"
|
||||
>
|
||||
<FeedbackSummaryBox
|
||||
:feedback-count="feebackSummary.total_responses"
|
||||
:statisfaction-max="feebackSummary.satisfaction_max"
|
||||
:statisfaction-avg="feebackSummary.satisfaction_average"
|
||||
:course-slug="props.courseSlug"
|
||||
/>
|
||||
<CompetenceSummaryBox
|
||||
:fail-count="competenceSummary.fail_total"
|
||||
:success-count="competenceSummary.success_total"
|
||||
details-link="/statistic/competence"
|
||||
:details-link="`/statistic/${courseSlug}/competence`"
|
||||
:course-slug="props.courseSlug"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<script lang="ts" setup>
|
||||
import { useDashboardPersonsDueDates } from "@/composables";
|
||||
import { computed } from "vue";
|
||||
import _ from "lodash";
|
||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
courseSessionId: string;
|
||||
circleId?: string;
|
||||
maxCount?: number;
|
||||
showAllButton?: boolean;
|
||||
}>(),
|
||||
{
|
||||
maxCount: 3,
|
||||
circleId: undefined,
|
||||
showAllButton: false,
|
||||
}
|
||||
);
|
||||
|
||||
const { loading, currentDueDates } = useDashboardPersonsDueDates();
|
||||
|
||||
const filteredDueDates = computed(() => {
|
||||
let dueDates = currentDueDates.value.filter(
|
||||
(dueDate) => dueDate.course_session_id === props.courseSessionId
|
||||
);
|
||||
|
||||
if (props.circleId) {
|
||||
dueDates = dueDates.filter((dueDate) => dueDate.circle?.id === props.circleId);
|
||||
}
|
||||
return _.take(dueDates, props.maxCount);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="loading" class="m-8 flex justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
<div v-else class="flex flex-col space-y-2">
|
||||
<h3 class="heading-3">{{ $t("Nächste Termine") }}</h3>
|
||||
<div
|
||||
v-for="dueDate in filteredDueDates"
|
||||
:key="dueDate.id"
|
||||
class="border-t border-gray-500 pt-2"
|
||||
>
|
||||
<DueDateSingle :due-date="dueDate"></DueDateSingle>
|
||||
</div>
|
||||
<div v-if="filteredDueDates.length === 0">
|
||||
{{ $t("dueDates.noDueDatesAvailable") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<router-link
|
||||
v-if="showAllButton"
|
||||
class="btn-secondary mt-4"
|
||||
:to="`/dashboard/due-dates?session=${courseSessionId}`"
|
||||
>
|
||||
{{ $t("a.Alle Termine anzeigen") }}
|
||||
</router-link>
|
||||
</template>
|
||||
|
|
@ -1,41 +1,46 @@
|
|||
<script lang="ts" setup>
|
||||
import type { CourseSession, DueDate } from "@/types";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import dayjs from "dayjs";
|
||||
import type { DashboardDueDate } from "@/services/dashboard";
|
||||
import { computed } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const props = defineProps<{
|
||||
dueDate: DueDate;
|
||||
dueDate: DashboardDueDate;
|
||||
singleLine?: boolean;
|
||||
showCourseSession?: boolean;
|
||||
}>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const dateType = t(props.dueDate.date_type_translation_key);
|
||||
const assignmentType = t(props.dueDate.assignment_type_translation_key);
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const courseSession = courseSessionsStore.allCourseSessions.find(
|
||||
(cs: CourseSession) => cs.id === props.dueDate.course_session_id
|
||||
);
|
||||
|
||||
if (!courseSession) {
|
||||
throw new Error("Course session not found");
|
||||
}
|
||||
|
||||
const url = courseSession.actions.includes("expert-cockpit")
|
||||
? props.dueDate.url_expert
|
||||
: props.dueDate.url;
|
||||
|
||||
const courseSessionTitle = computed(() => {
|
||||
if (props.dueDate.course_session_id) {
|
||||
return (
|
||||
courseSessionsStore.getCourseSessionById(props.dueDate.course_session_id)
|
||||
?.title ?? ""
|
||||
);
|
||||
const urlText = computed(() => {
|
||||
let result = "";
|
||||
if (dateType) {
|
||||
result += dateType;
|
||||
}
|
||||
return "";
|
||||
|
||||
if (assignmentType && !props.dueDate.title.startsWith(assignmentType)) {
|
||||
result += " " + assignmentType;
|
||||
}
|
||||
|
||||
if (props.dueDate.title) {
|
||||
result += " " + props.dueDate.title;
|
||||
}
|
||||
|
||||
return result.trim();
|
||||
});
|
||||
|
||||
const showAsUrl = computed(() => {
|
||||
return ["SUPERVISOR", "EXPERT", "MEMBER"].includes(
|
||||
props.dueDate.course_session.my_role
|
||||
);
|
||||
});
|
||||
|
||||
const url = computed(() => {
|
||||
if (["SUPERVISOR", "EXPERT"].includes(props.dueDate.course_session.my_role)) {
|
||||
return props.dueDate.url_expert;
|
||||
}
|
||||
return props.dueDate.url;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -46,34 +51,42 @@ const courseSessionTitle = computed(() => {
|
|||
>
|
||||
<div class="space-y-1">
|
||||
<div>
|
||||
<a class="underline" :href="url">
|
||||
<span class="text-bold">
|
||||
{{ dayjs(props.dueDate.start).format("D. MMMM YYYY") }}:
|
||||
<template v-if="dateType">
|
||||
{{ dateType }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ assignmentType }}
|
||||
</template>
|
||||
{{ " " }}
|
||||
<a v-if="showAsUrl" :href="url">
|
||||
<span class="text-bold text-gray-900">
|
||||
{{ dayjs(props.dueDate.start).format("dddd D. MMMM YYYY") }}
|
||||
</span>
|
||||
<template v-if="assignmentType && dateType">
|
||||
{{ assignmentType }}:
|
||||
{{ props.dueDate.title }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ props.dueDate.title }}
|
||||
</template>
|
||||
</a>
|
||||
<span v-else class="text-bold text-gray-900">
|
||||
{{ dayjs(props.dueDate.start).format("dddd D. MMMM YYYY") }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<a v-if="showAsUrl" class="underline" :href="url">
|
||||
<span class="text-bold">
|
||||
{{ urlText }}
|
||||
</span>
|
||||
</a>
|
||||
<span v-else class="text-bold">
|
||||
{{ urlText }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-small text-gray-900">
|
||||
<div>
|
||||
<span v-if="props.showCourseSession ?? courseSessionTitle">
|
||||
{{ courseSessionTitle }}:
|
||||
<span v-if="props.dueDate.course_session.is_uk">
|
||||
{{ props.dueDate.course_session.session_title }}:
|
||||
</span>
|
||||
{{ $t("a.Circle") }} «{{ props.dueDate.circle?.title }}»
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="props.dueDate.persons?.length" class="flex gap-2">
|
||||
<div v-for="person in props.dueDate.persons" :key="person.user_id">
|
||||
<img
|
||||
class="inline-block h-11 w-11 rounded-full"
|
||||
:src="person.avatar_url_small || '/static/avatars/myvbv-default-avatar.png'"
|
||||
:alt="`${person.first_name} ${person.last_name}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
|
||||
import type { DueDate } from "@/types";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
maxCount: number;
|
||||
dueDates: DueDate[];
|
||||
showTopBorder: boolean;
|
||||
showBottomBorder: boolean;
|
||||
showAllDueDatesLink: boolean;
|
||||
showCourseSession: boolean;
|
||||
}>();
|
||||
|
||||
const allDueDates = computed(() => {
|
||||
return props.dueDates;
|
||||
});
|
||||
|
||||
const dueDatesDisplayed = computed(() => {
|
||||
return props.dueDates.slice(0, props.maxCount);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ul :class="showBottomBorder ? '' : 'no-border-last'">
|
||||
<li
|
||||
v-for="dueDate in dueDatesDisplayed"
|
||||
:key="dueDate.id"
|
||||
class="cy-single-due-date"
|
||||
:class="{ 'first:border-t': props.showTopBorder, 'border-b': true }"
|
||||
>
|
||||
<DueDateSingle
|
||||
:due-date="dueDate"
|
||||
:show-course-session="props.showCourseSession"
|
||||
></DueDateSingle>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="allDueDates.length === 0">{{ $t("dueDates.noDueDatesAvailable") }}</div>
|
||||
<div
|
||||
v-if="showAllDueDatesLink && allDueDates.length > 0"
|
||||
class="flex items-center pt-6"
|
||||
>
|
||||
<a href="/appointments">{{ $t("dueDates.showAllDueDates") }}</a>
|
||||
<it-icon-arrow-right />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.no-border-last li:last-child {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<DueDatesList
|
||||
:due-dates="allDueDates"
|
||||
:max-count="props.maxCount"
|
||||
:show-top-border="props.showTopBorder"
|
||||
show-all-due-dates-link
|
||||
show-bottom-border
|
||||
:show-course-session="false"
|
||||
></DueDatesList>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
|
||||
const props = defineProps<{
|
||||
maxCount: number;
|
||||
showTopBorder: boolean;
|
||||
}>();
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const allDueDates = courseSession.value.due_dates;
|
||||
</script>
|
||||
|
|
@ -59,9 +59,9 @@ const selectedCourseSessionTitle = computed(() => {
|
|||
const appointmentsUrl = computed(() => {
|
||||
const currentCourseSession = courseSessionsStore.currentCourseSession;
|
||||
if (currentCourseSession) {
|
||||
return `/course/${currentCourseSession.course.slug}/appointments`;
|
||||
return `/dashboard/due-dates?session=${currentCourseSession.id}`;
|
||||
} else {
|
||||
return `/appointments`;
|
||||
return `/dashboard/due-dates`;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -121,6 +121,12 @@ const hasLearningMentor = computed(() => {
|
|||
const courseSession = courseSessionsStore.currentCourseSession;
|
||||
return courseSession.actions.includes("learning-mentor");
|
||||
});
|
||||
|
||||
const mentorTabTitle = computed(() =>
|
||||
courseSessionsStore.currentCourseSession?.course.configuration.is_uk
|
||||
? "a.Praxisbildner"
|
||||
: "a.Lernbegleitung"
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -265,7 +271,7 @@ const hasLearningMentor = computed(() => {
|
|||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inLearningMentor() }"
|
||||
>
|
||||
{{ t("a.Lernbegleitung") }}
|
||||
{{ t(mentorTabTitle) }}
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import {
|
|||
getLearningPathUrl,
|
||||
getMediaCenterUrl,
|
||||
} from "@/utils/utils";
|
||||
import { computed } from "vue";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
|
@ -30,12 +32,20 @@ defineProps<{
|
|||
|
||||
const emit = defineEmits(["closemodal", "logout"]);
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
|
||||
const clickLink = (to: string | undefined) => {
|
||||
if (to) {
|
||||
router.push(to);
|
||||
emit("closemodal");
|
||||
}
|
||||
};
|
||||
|
||||
const mentorTabTitle = computed(() =>
|
||||
courseSessionsStore.currentCourseSession?.course.configuration.is_uk
|
||||
? "a.Praxisbildner"
|
||||
: "a.Lernbegleitung"
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -97,7 +107,7 @@ const clickLink = (to: string | undefined) => {
|
|||
data-cy="navigation-mobile-mentor-link"
|
||||
@click="clickLink(getLearningMentorUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("a.Lernbegleitung") }}
|
||||
{{ $t(mentorTabTitle) }}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
import { useLearningMentees } from "@/services/learningMentees";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useCSRFFetch } from "@/fetchHelpers";
|
||||
import { computed } from "vue";
|
||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const { summary, fetchData } = useLearningMentees(courseSession.value.id);
|
||||
const { isLoading, summary, fetchData } = useLearningMentees(courseSession.value.id);
|
||||
|
||||
const removeMyMentee = async (menteeId: string) => {
|
||||
await useCSRFFetch(
|
||||
|
|
@ -12,65 +14,69 @@ const removeMyMentee = async (menteeId: string) => {
|
|||
).delete();
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const noMenteesText = computed(() =>
|
||||
courseSession.value.course.configuration.is_uk
|
||||
? "a.Aktuell begleitest du niemanden als Praxisbildner."
|
||||
: "a.Aktuell begleitest du niemanden als Lernbegleitung."
|
||||
);
|
||||
</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 v-if="isLoading" class="m-8 flex justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
<div v-else>
|
||||
<h2 class="heading-2 py-6">{{ $t("a.Personen, die du begleitest") }}</h2>
|
||||
<div v-if="(summary?.participants?.length ?? 0) > 0" 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>
|
||||
</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>
|
||||
{{ 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>
|
||||
</template>
|
||||
</div>
|
||||
<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.") }}
|
||||
{{ $t(noMenteesText) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -68,19 +68,38 @@ const inviteMentor = async () => {
|
|||
showInvitationModal.value = false;
|
||||
inviteeEmail.value = "";
|
||||
};
|
||||
|
||||
const myLearningMentors = computed(() =>
|
||||
courseSession.value.course.configuration.is_uk
|
||||
? "Meine Praxisbildner"
|
||||
: "Meine Lernbegleiter"
|
||||
);
|
||||
|
||||
const inviteLearningMentor = computed(() =>
|
||||
courseSession.value.course.configuration.is_uk
|
||||
? "Neuen Praxisbildner einladen"
|
||||
: "a.Neue Lernbegleitung einladen"
|
||||
);
|
||||
|
||||
const noLearningMentors = computed(() =>
|
||||
courseSession.value.course.configuration.is_uk
|
||||
? "a.Aktuell hast du noch keine Person als Praxisbildner eingeladen."
|
||||
: "a.Aktuell hast du noch keine Person als Lernbegleitung eingeladen."
|
||||
);
|
||||
</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>
|
||||
<h2 data-cy="lm-my-lms-title" class="heading-2">{{ $t(myLearningMentors) }}</h2>
|
||||
<div>
|
||||
<button
|
||||
class="btn-secondary flex items-center"
|
||||
data-cy="lm-invite-mentor-button"
|
||||
@click="showInvitationModal = true"
|
||||
>
|
||||
<it-icon-add class="it-icon mr-2 h-6 w-6" />
|
||||
{{ $t("a.Neue Lernbegleitung einladen") }}
|
||||
{{ $t(inviteLearningMentor) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -143,16 +162,14 @@ const inviteMentor = async () => {
|
|||
<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.")
|
||||
}}
|
||||
{{ $t(noLearningMentors) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<ItModal v-model="showInvitationModal">
|
||||
<template #title>{{ $t("a.Neue Lernbegleitung einladen") }}</template>
|
||||
<template #title>{{ $t(inviteLearningMentor) }}</template>
|
||||
<template #body>
|
||||
<div class="flex flex-col">
|
||||
<label for="mentor-email">{{ $t("a.E-Mail Adresse") }}</label>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { computed } from "vue";
|
||||
|
||||
const currentCourseSession = useCurrentCourseSession();
|
||||
|
||||
const actionNoLearningMentors = computed(() =>
|
||||
currentCourseSession.value.course.configuration.is_uk
|
||||
? "a.Aktuell hast du noch keine Person als Praxisbildner eingeladen. Lade jetzt jemanden ein."
|
||||
: "a.Aktuell hast du noch keine Person als Lernbegleitung eingeladen. Lade jetzt jemanden ein."
|
||||
);
|
||||
|
||||
const inviteLearningMentorShort = computed(() =>
|
||||
currentCourseSession.value.course.configuration.is_uk
|
||||
? "Neuen Praxisbildner einladen"
|
||||
: "a.Neue Lernbegleitung einladen"
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -9,11 +22,7 @@ const currentCourseSession = useCurrentCourseSession();
|
|||
<it-icon-info class="it-icon h-6 w-6 text-sky-700" />
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
{{
|
||||
$t(
|
||||
"a.Aktuell hast du noch keine Person als Lernbegleitung eingeladen. Lade jetzt jemanden ein."
|
||||
)
|
||||
}}
|
||||
{{ $t(actionNoLearningMentors) }}
|
||||
</div>
|
||||
<router-link
|
||||
:to="{
|
||||
|
|
@ -22,7 +31,7 @@ const currentCourseSession = useCurrentCourseSession();
|
|||
}"
|
||||
class="btn-blue px-4 py-2 font-bold"
|
||||
>
|
||||
{{ $t("a.Lernbegleitung einladen") }}
|
||||
{{ $t(inviteLearningMentorShort) }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
||||
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
||||
import { computed } from "vue";
|
||||
import { useCourseDataWithCompletion } from "@/composables";
|
||||
import { useCourseCircleProgress, useCourseDataWithCompletion } from "@/composables";
|
||||
|
||||
export type DiagramType = "horizontal" | "horizontalSmall" | "singleSmall";
|
||||
|
||||
|
|
@ -46,14 +46,31 @@ const wrapperClasses = computed(() => {
|
|||
}
|
||||
return classes;
|
||||
});
|
||||
|
||||
const { inProgressCirclesCount, circlesCount } = useCourseCircleProgress(
|
||||
lpQueryResult.circles
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="wrapperClasses">
|
||||
<LearningPathCircle
|
||||
v-for="circle in circles"
|
||||
:key="circle.id"
|
||||
:sectors="calculateCircleSectorData(circle)"
|
||||
></LearningPathCircle>
|
||||
<div>
|
||||
<h4
|
||||
v-if="diagramType === 'horizontal' && circles.length > 0"
|
||||
class="mb-4 font-bold"
|
||||
>
|
||||
{{
|
||||
$t("learningPathPage.progressText", {
|
||||
inProgressCount: inProgressCirclesCount,
|
||||
allCount: circlesCount,
|
||||
})
|
||||
}}
|
||||
</h4>
|
||||
<div :class="wrapperClasses">
|
||||
<LearningPathCircle
|
||||
v-for="circle in circles"
|
||||
:key="circle.id"
|
||||
:sectors="calculateCircleSectorData(circle)"
|
||||
></LearningPathCircle>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@ const previousRoute = getPreviousRoute();
|
|||
|
||||
const learningUnitHasFeedbackPage = computed(
|
||||
() =>
|
||||
courseSession.value.course.configuration.enable_learning_mentor && !isReadOnly.value
|
||||
courseSession.value.course.configuration.enable_learning_mentor &&
|
||||
!courseSession.value.course.configuration.is_uk &&
|
||||
!isReadOnly.value
|
||||
);
|
||||
|
||||
const currentQuestion = computed(() => questions.value[questionIndex.value]);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import SmileyCell from "@/components/selfEvaluationFeedback/SmileyCell.vue";
|
|||
|
||||
const props = defineProps<{
|
||||
summary: LearningUnitSummary;
|
||||
hideDetailLink?: boolean;
|
||||
}>();
|
||||
|
||||
const hasFeedbackReceived = computed(() => {
|
||||
|
|
@ -34,6 +35,7 @@ const feedbackProviderName = computed(() => {
|
|||
</div>
|
||||
<span class="pl-4 underline">
|
||||
<router-link
|
||||
v-if="!props.hideDetailLink"
|
||||
:to="props.summary.detail_url"
|
||||
:data-cy="`self-eval-${summary.id}-detail-url`"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const numFeedbacks = computed(() => {
|
|||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await itGet(
|
||||
const data: { amount: number } = await itGet(
|
||||
`/api/core/feedback/${props.courseSession.id}/${props.circleId}/`
|
||||
);
|
||||
completeFeedbacks.value = data.amount;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
<script setup lang="ts">
|
||||
import { useSelfEvaluationFeedbackSummaries } from "@/services/selfEvaluationFeedback";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useCurrentCourseSession, useEvaluationWithFeedback } from "@/composables";
|
||||
import { computed, ref } from "vue";
|
||||
import FeedbackByLearningUnitSummary from "@/components/selfEvaluationFeedback/FeedbackByLearningUnitSummary.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { t } from "i18next";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
const props = defineProps<{
|
||||
profileUserId: string;
|
||||
}>();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const selfEvaluationFeedbackSummaries = useSelfEvaluationFeedbackSummaries(
|
||||
courseSession.value.id,
|
||||
props.profileUserId
|
||||
);
|
||||
|
||||
const course = computed(() => courseSession.value.course);
|
||||
const isLoaded = computed(() => !selfEvaluationFeedbackSummaries.loading.value);
|
||||
const selectedCircle = ref({ name: t("a.AlleCircle"), id: "_all" });
|
||||
|
||||
|
|
@ -37,13 +38,19 @@ const summaries = computed(() => {
|
|||
);
|
||||
});
|
||||
|
||||
const hasEvaluationFeedback = useEvaluationWithFeedback().hasFeedback;
|
||||
|
||||
const headerTitle = computed(() => {
|
||||
if (course.value.configuration.enable_learning_mentor) {
|
||||
if (hasEvaluationFeedback.value) {
|
||||
return t("a.Selbst- und Fremdeinschätzungen");
|
||||
} else {
|
||||
return t("a.Selbsteinschätzungen");
|
||||
}
|
||||
});
|
||||
|
||||
const isOwnEvaluation = computed(() => {
|
||||
return props.profileUserId === userStore.id;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -63,6 +70,7 @@ const headerTitle = computed(() => {
|
|||
v-for="summary in summaries"
|
||||
:key="summary.id"
|
||||
:summary="summary"
|
||||
:hide-detail-link="!isOwnEvaluation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -28,104 +28,106 @@ const isLoaded = computed(() => !selfEvaluationFeedbackSummaries.loading.value);
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="isLoaded">
|
||||
<!-- Self Evaluation -->
|
||||
<div class="bg-white px-8 py-4 lg:mb-8 lg:py-8">
|
||||
<div class="mb-8">
|
||||
<h3 class="mb-4 pb-4 lg:pb-0">
|
||||
{{ $t("a.Selbsteinschätzungen") }}
|
||||
</h3>
|
||||
<ul
|
||||
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
||||
<div>
|
||||
<template v-if="isLoaded">
|
||||
<!-- Self Evaluation -->
|
||||
<div class="bg-white px-8 py-4 lg:mb-8 lg:py-8">
|
||||
<div class="mb-8">
|
||||
<h3 class="mb-4 pb-4 lg:pb-0">
|
||||
{{ $t("a.Selbsteinschätzungen") }}
|
||||
</h3>
|
||||
<ul
|
||||
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
||||
>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.no") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-fail"
|
||||
>
|
||||
{{ selfAssessmentCounts?.fail }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.yes") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-success"
|
||||
>
|
||||
{{ selfAssessmentCounts?.pass }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-unknown"
|
||||
>
|
||||
{{ selfAssessmentCounts?.unknown }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Feedback Evaluation -->
|
||||
<div
|
||||
v-if="courseSession.course.configuration.enable_learning_mentor"
|
||||
class="border-t pt-8"
|
||||
>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.no") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-fail"
|
||||
>
|
||||
{{ selfAssessmentCounts?.fail }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.yes") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-success"
|
||||
>
|
||||
{{ selfAssessmentCounts?.pass }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-unknown"
|
||||
>
|
||||
{{ selfAssessmentCounts?.unknown }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Feedback Evaluation -->
|
||||
<div
|
||||
v-if="courseSession.course.configuration.enable_learning_mentor"
|
||||
class="border-t pt-8"
|
||||
>
|
||||
<h3 class="mb-4 pb-4 lg:pb-0">
|
||||
{{ $t("a.Fremdeinschätzungen") }}
|
||||
</h3>
|
||||
<ul
|
||||
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
||||
<h3 class="mb-4 pb-4 lg:pb-0">
|
||||
{{ $t("a.Fremdeinschätzungen") }}
|
||||
</h3>
|
||||
<ul
|
||||
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
||||
>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("receivedEvaluation.no") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
||||
<p class="ml-4 inline-block text-7xl font-bold">
|
||||
{{ feedbackEvaluationCounts?.fail }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("receivedEvaluation.yes") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
||||
<p class="ml-4 inline-block text-7xl font-bold">
|
||||
{{ feedbackEvaluationCounts?.pass }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
||||
<p class="ml-4 inline-block text-7xl font-bold">
|
||||
{{ feedbackEvaluationCounts?.unknown }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Show All (always)-->
|
||||
<button
|
||||
class="btn-text inline-flex items-center py-2 pl-0 pt-8"
|
||||
@click="emit('showAll')"
|
||||
>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("receivedEvaluation.no") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
||||
<p class="ml-4 inline-block text-7xl font-bold">
|
||||
{{ feedbackEvaluationCounts?.fail }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("receivedEvaluation.yes") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
||||
<p class="ml-4 inline-block text-7xl font-bold">
|
||||
{{ feedbackEvaluationCounts?.pass }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
||||
<p class="ml-4 inline-block text-7xl font-bold">
|
||||
{{ feedbackEvaluationCounts?.unknown }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<span>{{ $t("general.showAll") }}</span>
|
||||
<it-icon-arrow-right></it-icon-arrow-right>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Show All (always)-->
|
||||
<button
|
||||
class="btn-text inline-flex items-center py-2 pl-0 pt-8"
|
||||
@click="emit('showAll')"
|
||||
>
|
||||
<span>{{ $t("general.showAll") }}</span>
|
||||
<it-icon-arrow-right></it-icon-arrow-right>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,29 @@
|
|||
import { useCSRFFetch } from "@/fetchHelpers";
|
||||
import type { CourseStatisticsType } from "@/gql/graphql";
|
||||
import { graphqlClient } from "@/graphql/client";
|
||||
import { COURSE_QUERY, COURSE_SESSION_DETAIL_QUERY } from "@/graphql/queries";
|
||||
import {
|
||||
COMPETENCE_NAVI_CERTIFICATE_FOR_USER_QUERY,
|
||||
COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||
COURSE_QUERY,
|
||||
COURSE_SESSION_DETAIL_QUERY,
|
||||
} from "@/graphql/queries";
|
||||
import {
|
||||
circleFlatChildren,
|
||||
circleFlatLearningContents,
|
||||
circleFlatLearningUnits,
|
||||
someFinishedInLearningSequence,
|
||||
} from "@/services/circle";
|
||||
import type {
|
||||
DashboardDueDate,
|
||||
DashboardPersonRoleType,
|
||||
DashboardPersonType,
|
||||
} from "@/services/dashboard";
|
||||
import {
|
||||
courseIdForCourseSlug,
|
||||
fetchDashboardDueDates,
|
||||
fetchDashboardPersons,
|
||||
fetchStatisticData,
|
||||
} from "@/services/dashboard";
|
||||
import { presignUpload, uploadFile } from "@/services/files";
|
||||
import { useCompletionStore } from "@/stores/completion";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
|
|
@ -14,11 +31,13 @@ import { useDashboardStore } from "@/stores/dashboard";
|
|||
import { useUserStore } from "@/stores/user";
|
||||
import type {
|
||||
ActionCompetence,
|
||||
CircleType,
|
||||
Course,
|
||||
CourseCompletion,
|
||||
CourseCompletionStatus,
|
||||
CourseSession,
|
||||
CourseSessionDetail,
|
||||
DashboardPersonsPageMode,
|
||||
LearningContentWithCompletion,
|
||||
LearningMentor,
|
||||
LearningPathType,
|
||||
|
|
@ -26,9 +45,11 @@ import type {
|
|||
PerformanceCriteria,
|
||||
} from "@/types";
|
||||
import { useQuery } from "@urql/vue";
|
||||
import dayjs from "dayjs";
|
||||
import { t } from "i18next";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import log from "loglevel";
|
||||
import type { ComputedRef } from "vue";
|
||||
import type { ComputedRef, Ref } from "vue";
|
||||
import { computed, onMounted, ref, watchEffect } from "vue";
|
||||
|
||||
export function useCurrentCourseSession() {
|
||||
|
|
@ -166,7 +187,7 @@ export function useCourseData(courseSlug: string) {
|
|||
log.error(result.error);
|
||||
}
|
||||
|
||||
course.value = result.data?.course as Course;
|
||||
course.value = result.data?.course as unknown as Course;
|
||||
actionCompetences.value = result.data?.course
|
||||
?.action_competences as ActionCompetence[];
|
||||
learningPath.value = result.data?.course?.learning_path as LearningPathType;
|
||||
|
|
@ -487,3 +508,223 @@ export function useMyLearningMentors() {
|
|||
loading,
|
||||
};
|
||||
}
|
||||
|
||||
export function getVvRoleDisplay(role: DashboardPersonRoleType) {
|
||||
switch (role) {
|
||||
case "LEARNING_MENTOR":
|
||||
return t("a.Lernbegleitung");
|
||||
case "LEARNING_MENTEE":
|
||||
return t("a.Teilnehmer");
|
||||
case "EXPERT":
|
||||
return t("a.Experte");
|
||||
case "MEMBER":
|
||||
return t("a.Teilnehmer");
|
||||
case "SUPERVISOR":
|
||||
return t("a.Regionenleiter");
|
||||
default:
|
||||
return role;
|
||||
}
|
||||
}
|
||||
|
||||
export function getUkRoleDisplay(role: DashboardPersonRoleType) {
|
||||
switch (role) {
|
||||
case "LEARNING_MENTOR":
|
||||
return t("a.Praxisbildner");
|
||||
case "LEARNING_MENTEE":
|
||||
return t("a.Teilnehmer");
|
||||
case "EXPERT":
|
||||
return t("a.Trainer");
|
||||
case "MEMBER":
|
||||
return t("a.Teilnehmer");
|
||||
case "SUPERVISOR":
|
||||
return t("a.Regionenleiter");
|
||||
default:
|
||||
return role;
|
||||
}
|
||||
}
|
||||
|
||||
export function useDashboardPersonsDueDates(
|
||||
mode: DashboardPersonsPageMode = "default"
|
||||
) {
|
||||
const dashboardPersons = ref<DashboardPersonType[]>([]);
|
||||
const dashboardDueDates = ref<DashboardDueDate[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
// due dates from today to future
|
||||
const currentDueDates = ref<DashboardDueDate[]>([]);
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [persons, dueDates] = await Promise.all([
|
||||
fetchDashboardPersons(mode),
|
||||
fetchDashboardDueDates(),
|
||||
]);
|
||||
dashboardPersons.value = persons;
|
||||
|
||||
// attach role name to persons
|
||||
dashboardPersons.value.forEach((person) => {
|
||||
person.course_sessions.forEach((cs) => {
|
||||
if (cs.is_uk) {
|
||||
cs.my_role_display = getUkRoleDisplay(cs.my_role);
|
||||
cs.user_role_display = getUkRoleDisplay(cs.user_role);
|
||||
} else if (cs.is_vv) {
|
||||
cs.my_role_display = getVvRoleDisplay(cs.my_role);
|
||||
cs.user_role_display = getVvRoleDisplay(cs.user_role);
|
||||
} else {
|
||||
cs.my_role_display = "";
|
||||
cs.user_role_display = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dashboardDueDates.value = dueDates.map((dueDate) => {
|
||||
const dateType = t(dueDate.date_type_translation_key);
|
||||
const assignmentType = t(dueDate.assignment_type_translation_key);
|
||||
dueDate.translatedType = dateType;
|
||||
if (assignmentType) {
|
||||
dueDate.translatedType += " " + assignmentType;
|
||||
}
|
||||
return dueDate;
|
||||
});
|
||||
|
||||
currentDueDates.value = dashboardDueDates.value.filter((dueDate) => {
|
||||
let refDate = dayjs(dueDate.start);
|
||||
if (dueDate.end) {
|
||||
refDate = dayjs(dueDate.end);
|
||||
}
|
||||
|
||||
return refDate >= dayjs().startOf("day");
|
||||
});
|
||||
|
||||
// attach `LEARNING_MENTEE` to due dates for `LEARNING_MENTOR` persons
|
||||
currentDueDates.value.forEach((dueDate) => {
|
||||
if (dueDate.course_session.my_role === "LEARNING_MENTOR") {
|
||||
dueDate.persons = dashboardPersons.value.filter((person) => {
|
||||
if (
|
||||
person.course_sessions
|
||||
.map((cs) => cs.id)
|
||||
.includes(dueDate.course_session.id)
|
||||
) {
|
||||
return person.course_sessions.some(
|
||||
(cs) => cs.user_role === "LEARNING_MENTEE"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(fetchData);
|
||||
|
||||
return {
|
||||
dashboardPersons,
|
||||
dashboardDueDates,
|
||||
currentDueDates,
|
||||
loading,
|
||||
};
|
||||
}
|
||||
|
||||
export function useCourseCircleProgress(circles: Ref<CircleType[] | undefined>) {
|
||||
const inProgressCirclesCount = computed(() => {
|
||||
if (circles.value?.length) {
|
||||
return circles.value.filter(
|
||||
(circle) =>
|
||||
circle.learning_sequences.filter((ls) => someFinishedInLearningSequence(ls))
|
||||
.length
|
||||
).length;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const circlesCount = computed(() => {
|
||||
return circles.value?.length ?? 0;
|
||||
});
|
||||
|
||||
return { inProgressCirclesCount, circlesCount };
|
||||
}
|
||||
|
||||
export function useCourseStatisticsv2(courseSlug: string) {
|
||||
const dashboardStore = useDashboardStore();
|
||||
const courseStatistics = ref<CourseStatisticsType | null>(null);
|
||||
const loading = ref(false);
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
await dashboardStore.loadDashboardDetails();
|
||||
const courseId = courseIdForCourseSlug(
|
||||
dashboardStore.dashboardConfigsv2,
|
||||
courseSlug
|
||||
);
|
||||
try {
|
||||
if (courseId) {
|
||||
courseStatistics.value = await fetchStatisticData(courseId);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const courseSessionName = (courseSessionId: string) => {
|
||||
return courseStatistics?.value?.course_session_properties?.sessions.find(
|
||||
(session) => session.id === courseSessionId
|
||||
)?.name;
|
||||
};
|
||||
|
||||
const circleMeta = (circleId: string) => {
|
||||
return courseStatistics?.value?.course_session_properties.circles.find(
|
||||
(circle) => circle.id === circleId
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(fetchData);
|
||||
|
||||
return {
|
||||
courseStatistics,
|
||||
loading,
|
||||
courseSessionName,
|
||||
circleMeta,
|
||||
};
|
||||
}
|
||||
|
||||
export function useCertificateQuery(userId: string | undefined, courseSlug: string) {
|
||||
const certificatesQuery = (() => {
|
||||
const courseSession = useCurrentCourseSession();
|
||||
if (userId) {
|
||||
return useQuery({
|
||||
query: COMPETENCE_NAVI_CERTIFICATE_FOR_USER_QUERY,
|
||||
variables: {
|
||||
courseSlug: courseSlug,
|
||||
courseSessionId: courseSession.value.id,
|
||||
userId: userId,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return useQuery({
|
||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||
variables: {
|
||||
courseSlug: courseSlug,
|
||||
courseSessionId: courseSession.value.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
return { certificatesQuery };
|
||||
}
|
||||
|
||||
export function useEvaluationWithFeedback() {
|
||||
const currentCourseSession = useCurrentCourseSession();
|
||||
const hasFeedback = computed(
|
||||
() =>
|
||||
currentCourseSession.value.course.configuration.enable_learning_mentor &&
|
||||
!currentCourseSession.value.course.configuration.is_uk
|
||||
);
|
||||
|
||||
return { hasFeedback };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@ export const itFetch = (url: RequestInfo, options: RequestInit) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const itPost = (url: RequestInfo, data: unknown, options: RequestInit = {}) => {
|
||||
export const itPost = <T>(
|
||||
url: RequestInfo,
|
||||
data: unknown,
|
||||
options: RequestInit = {}
|
||||
) => {
|
||||
options = Object.assign({}, options);
|
||||
|
||||
const headers = Object.assign(
|
||||
|
|
@ -56,11 +60,11 @@ export const itPost = (url: RequestInfo, data: unknown, options: RequestInit = {
|
|||
return response.json().catch(() => {
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
});
|
||||
}) as Promise<T>;
|
||||
};
|
||||
|
||||
export const itGet = (url: RequestInfo) => {
|
||||
return itPost(url, {}, { method: "GET" });
|
||||
export const itGet = <T>(url: RequestInfo) => {
|
||||
return itPost<T>(url, {}, { method: "GET" });
|
||||
};
|
||||
|
||||
export const itDelete = (url: RequestInfo) => {
|
||||
|
|
@ -81,17 +85,17 @@ export function bustItGetCache(key?: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export const itGetCached = (
|
||||
export const itGetCached = <T>(
|
||||
url: RequestInfo,
|
||||
options = {
|
||||
reload: false,
|
||||
}
|
||||
): Promise<any> => {
|
||||
): Promise<T> => {
|
||||
if (!itGetPromiseCache.has(url.toString()) || options.reload) {
|
||||
itGetPromiseCache.set(url.toString(), itGet(url));
|
||||
itGetPromiseCache.set(url.toString(), itGet<T>(url));
|
||||
}
|
||||
|
||||
return itGetPromiseCache.get(url.toString()) as Promise<any>;
|
||||
return itGetPromiseCache.get(url.toString()) as Promise<T>;
|
||||
};
|
||||
|
||||
export const useCSRFFetch = createFetch({
|
||||
|
|
|
|||
|
|
@ -19,11 +19,14 @@ const documents = {
|
|||
"\n query attendanceCheckQuery($courseSessionId: ID!) {\n course_session_attendance_course(id: $courseSessionId) {\n id\n attendance_user_list {\n user_id\n status\n }\n }\n }\n": types.AttendanceCheckQueryDocument,
|
||||
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
|
||||
"\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
|
||||
"\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateForUserQueryDocument,
|
||||
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument,
|
||||
"\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.CourseQueryDocument,
|
||||
"\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n }\n": types.DashboardConfigDocument,
|
||||
"\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.CourseQueryDocument,
|
||||
"\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n }\n }\n": types.DashboardConfigDocument,
|
||||
"\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument,
|
||||
"\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument,
|
||||
"\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n }\n }\n": types.DashboardCourseDataDocument,
|
||||
"\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument,
|
||||
"\n query mentorCourseStatistics($courseId: ID!) {\n mentor_course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n }\n }\n": types.MentorCourseStatisticsDocument,
|
||||
"\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $learningContentType: String!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n learning_content_type: $learningContentType\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
|
||||
};
|
||||
|
||||
|
|
@ -65,6 +68,10 @@ export function graphql(source: "\n query assignmentCompletionQuery(\n $assi
|
|||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
|
@ -72,11 +79,11 @@ export function graphql(source: "\n query courseSessionDetail($courseSessionId:
|
|||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n }\n"): (typeof documents)["\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n }\n }\n"): (typeof documents)["\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
|
@ -84,7 +91,15 @@ export function graphql(source: "\n query dashboardProgress($courseId: ID!) {\n
|
|||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n"): (typeof documents)["\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n }\n }\n"): (typeof documents)["\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n"): (typeof documents)["\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query mentorCourseStatistics($courseId: ID!) {\n mentor_course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query mentorCourseStatistics($courseId: ID!) {\n mentor_course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,6 @@
|
|||
type Query {
|
||||
course_statistics(course_id: ID!): CourseStatisticsType
|
||||
mentor_course_statistics(course_id: ID!): BaseStatisticsType
|
||||
course_progress(course_id: ID!): CourseProgressType
|
||||
dashboard_config: [DashboardConfigType!]!
|
||||
learning_path(id: ID, slug: String, course_id: ID, course_slug: String): LearningPathObjectType
|
||||
|
|
@ -20,6 +21,7 @@ type Query {
|
|||
learning_content_document_list: LearningContentDocumentListObjectType
|
||||
competence_certificate(id: ID, slug: String): CompetenceCertificateObjectType
|
||||
competence_certificate_list(id: ID, slug: String, course_id: ID, course_slug: String): CompetenceCertificateListObjectType
|
||||
competence_certificate_list_for_user(id: ID, slug: String, course_id: ID, course_slug: String, user_id: UUID): CompetenceCertificateListObjectType
|
||||
assignment(id: ID, slug: String): AssignmentObjectType
|
||||
assignment_completion(assignment_id: ID!, course_session_id: ID!, learning_content_page_id: ID, assignment_user_id: UUID): AssignmentCompletionObjectType
|
||||
}
|
||||
|
|
@ -29,15 +31,59 @@ type CourseStatisticsType {
|
|||
course_id: ID!
|
||||
course_title: String!
|
||||
course_slug: String!
|
||||
course_session_properties: StatisticsCourseSessionPropertiesType!
|
||||
course_session_selection_ids: [ID]!
|
||||
user_selection_ids: [ID]
|
||||
assignments: AssignmentsStatisticsType!
|
||||
course_session_properties: StatisticsCourseSessionPropertiesType!
|
||||
course_session_selection_metrics: StatisticsCourseSessionsSelectionMetricType!
|
||||
attendance_day_presences: AttendanceDayPresencesStatisticsType!
|
||||
feedback_responses: FeedbackStatisticsResponsesType!
|
||||
assignments: AssignmentsStatisticsType!
|
||||
competences: CompetencesStatisticsType!
|
||||
}
|
||||
|
||||
type AssignmentsStatisticsType {
|
||||
_id: ID!
|
||||
records: [AssignmentStatisticsRecordType!]!
|
||||
summary: AssignmentStatisticsSummaryType!
|
||||
}
|
||||
|
||||
type AssignmentStatisticsRecordType {
|
||||
_id: ID!
|
||||
course_session_id: ID!
|
||||
course_session_assignment_id: ID!
|
||||
circle_id: ID!
|
||||
generation: String!
|
||||
assignment_type_translation_key: String!
|
||||
assignment_title: String!
|
||||
deadline: DateTime!
|
||||
metrics: AssignmentCompletionMetricsType!
|
||||
details_url: String!
|
||||
}
|
||||
|
||||
"""
|
||||
The `DateTime` scalar type represents a DateTime
|
||||
value as specified by
|
||||
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
"""
|
||||
scalar DateTime
|
||||
|
||||
type AssignmentCompletionMetricsType {
|
||||
_id: ID!
|
||||
passed_count: Int!
|
||||
failed_count: Int!
|
||||
unranked_count: Int!
|
||||
ranking_completed: Boolean!
|
||||
average_passed: Float!
|
||||
}
|
||||
|
||||
type AssignmentStatisticsSummaryType {
|
||||
_id: ID!
|
||||
completed_count: Int!
|
||||
average_passed: Float!
|
||||
total_passed: Int!
|
||||
total_failed: Int!
|
||||
}
|
||||
|
||||
type StatisticsCourseSessionPropertiesType {
|
||||
_id: ID!
|
||||
sessions: [StatisticsCourseSessionDataType!]!
|
||||
|
|
@ -79,13 +125,6 @@ type PresenceRecordStatisticsType {
|
|||
details_url: String!
|
||||
}
|
||||
|
||||
"""
|
||||
The `DateTime` scalar type represents a DateTime
|
||||
value as specified by
|
||||
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
"""
|
||||
scalar DateTime
|
||||
|
||||
type AttendanceSummaryStatisticsType {
|
||||
_id: ID!
|
||||
days_completed: Int!
|
||||
|
|
@ -116,40 +155,6 @@ type FeedbackStatisticsSummaryType {
|
|||
total_responses: Int!
|
||||
}
|
||||
|
||||
type AssignmentsStatisticsType {
|
||||
_id: ID!
|
||||
records: [AssignmentStatisticsRecordType!]!
|
||||
summary: AssignmentStatisticsSummaryType!
|
||||
}
|
||||
|
||||
type AssignmentStatisticsRecordType {
|
||||
_id: ID!
|
||||
course_session_id: ID!
|
||||
course_session_assignment_id: ID!
|
||||
circle_id: ID!
|
||||
generation: String!
|
||||
assignment_type_translation_key: String!
|
||||
assignment_title: String!
|
||||
deadline: DateTime!
|
||||
metrics: AssignmentCompletionMetricsType!
|
||||
details_url: String!
|
||||
}
|
||||
|
||||
type AssignmentCompletionMetricsType {
|
||||
_id: ID!
|
||||
passed_count: Int!
|
||||
failed_count: Int!
|
||||
unranked_count: Int!
|
||||
ranking_completed: Boolean!
|
||||
average_passed: Float!
|
||||
}
|
||||
|
||||
type AssignmentStatisticsSummaryType {
|
||||
_id: ID!
|
||||
completed_count: Int!
|
||||
average_passed: Float!
|
||||
}
|
||||
|
||||
type CompetencesStatisticsType {
|
||||
_id: ID!
|
||||
summary: CompetencePerformanceStatisticsSummaryType!
|
||||
|
|
@ -173,12 +178,22 @@ type CompetenceRecordStatisticsType {
|
|||
details_url: String!
|
||||
}
|
||||
|
||||
type BaseStatisticsType {
|
||||
_id: ID!
|
||||
course_id: ID!
|
||||
course_title: String!
|
||||
course_slug: String!
|
||||
course_session_selection_ids: [ID]!
|
||||
user_selection_ids: [ID]
|
||||
assignments: AssignmentsStatisticsType!
|
||||
}
|
||||
|
||||
type CourseProgressType {
|
||||
_id: ID!
|
||||
course_id: ID!
|
||||
session_to_continue_id: ID
|
||||
competence: ProgressDashboardCompetenceType!
|
||||
assignment: ProgressDashboardAssignmentType!
|
||||
competence: ProgressDashboardCompetenceType
|
||||
assignment: ProgressDashboardAssignmentType
|
||||
}
|
||||
|
||||
type ProgressDashboardCompetenceType {
|
||||
|
|
@ -208,6 +223,7 @@ enum DashboardType {
|
|||
PROGRESS_DASHBOARD
|
||||
SIMPLE_DASHBOARD
|
||||
MENTOR_DASHBOARD
|
||||
PRAXISBILDNER_DASHBOARD
|
||||
}
|
||||
|
||||
type CourseConfigurationObjectType {
|
||||
|
|
@ -215,6 +231,8 @@ type CourseConfigurationObjectType {
|
|||
enable_circle_documents: Boolean!
|
||||
enable_learning_mentor: Boolean!
|
||||
enable_competence_certificates: Boolean!
|
||||
is_vv: Boolean!
|
||||
is_uk: Boolean!
|
||||
}
|
||||
|
||||
type LearningPathObjectType implements CoursePageInterface {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const AttendanceSummaryStatisticsType = "AttendanceSummaryStatisticsType"
|
|||
export const AttendanceUserInputType = "AttendanceUserInputType";
|
||||
export const AttendanceUserObjectType = "AttendanceUserObjectType";
|
||||
export const AttendanceUserStatus = "AttendanceUserStatus";
|
||||
export const BaseStatisticsType = "BaseStatisticsType";
|
||||
export const Boolean = "Boolean";
|
||||
export const CircleLightObjectType = "CircleLightObjectType";
|
||||
export const CircleObjectType = "CircleObjectType";
|
||||
|
|
|
|||
|
|
@ -117,6 +117,42 @@ export const COMPETENCE_NAVI_CERTIFICATE_QUERY = graphql(`
|
|||
}
|
||||
`);
|
||||
|
||||
export const COMPETENCE_NAVI_CERTIFICATE_FOR_USER_QUERY = graphql(`
|
||||
query competenceCertificateForUserQuery(
|
||||
$courseSlug: String!
|
||||
$courseSessionId: ID!
|
||||
$userId: UUID!
|
||||
) {
|
||||
competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {
|
||||
...CoursePageFields
|
||||
competence_certificates {
|
||||
...CoursePageFields
|
||||
assignments {
|
||||
...CoursePageFields
|
||||
assignment_type
|
||||
max_points
|
||||
completion(course_session_id: $courseSessionId) {
|
||||
id
|
||||
completion_status
|
||||
submitted_at
|
||||
evaluation_points
|
||||
evaluation_max_points
|
||||
evaluation_passed
|
||||
}
|
||||
learning_content {
|
||||
...CoursePageFields
|
||||
circle {
|
||||
id
|
||||
title
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const COURSE_SESSION_DETAIL_QUERY = graphql(`
|
||||
query courseSessionDetail($courseSessionId: ID!) {
|
||||
course_session(id: $courseSessionId) {
|
||||
|
|
@ -220,6 +256,7 @@ export const COURSE_QUERY = graphql(`
|
|||
enable_circle_documents
|
||||
enable_learning_mentor
|
||||
enable_competence_certificates
|
||||
is_uk
|
||||
}
|
||||
action_competences {
|
||||
competence_id
|
||||
|
|
@ -304,6 +341,7 @@ export const DASHBOARD_CONFIG = graphql(`
|
|||
enable_circle_documents
|
||||
enable_learning_mentor
|
||||
enable_competence_certificates
|
||||
is_uk
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -331,6 +369,16 @@ export const DASHBOARD_COURSE_SESSION_PROGRESS = graphql(`
|
|||
}
|
||||
`);
|
||||
|
||||
export const DASHBOARD_COURSE_DATA = graphql(`
|
||||
query dashboardCourseData($courseId: ID!) {
|
||||
course_progress(course_id: $courseId) {
|
||||
_id
|
||||
course_id
|
||||
session_to_continue_id
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
||||
query courseStatistics($courseId: ID!) {
|
||||
course_statistics(course_id: $courseId) {
|
||||
|
|
@ -400,6 +448,8 @@ export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
|||
_id
|
||||
completed_count
|
||||
average_passed
|
||||
total_passed
|
||||
total_failed
|
||||
}
|
||||
records {
|
||||
_id
|
||||
|
|
@ -442,3 +492,45 @@ export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
|||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const DASHBOARD_MENTOR_COMPETENCE_SUMMARY = graphql(`
|
||||
query mentorCourseStatistics($courseId: ID!) {
|
||||
mentor_course_statistics(course_id: $courseId) {
|
||||
_id
|
||||
course_id
|
||||
course_title
|
||||
course_slug
|
||||
course_session_selection_ids
|
||||
user_selection_ids
|
||||
assignments {
|
||||
_id
|
||||
summary {
|
||||
_id
|
||||
completed_count
|
||||
average_passed
|
||||
total_passed
|
||||
total_failed
|
||||
}
|
||||
records {
|
||||
_id
|
||||
course_session_id
|
||||
course_session_assignment_id
|
||||
circle_id
|
||||
generation
|
||||
assignment_title
|
||||
assignment_type_translation_key
|
||||
details_url
|
||||
deadline
|
||||
metrics {
|
||||
_id
|
||||
passed_count
|
||||
failed_count
|
||||
unranked_count
|
||||
ranking_completed
|
||||
average_passed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
|
|
|||
|
|
@ -1,179 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import type { DueDate } from "@/types";
|
||||
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
||||
import { useCourseData } from "@/composables";
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const UNFILTERED = Number.MAX_SAFE_INTEGER.toString();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
|
||||
type Item = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type CourseItem = Item & {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
const courses: CourseItem[] = courseSessionsStore.uniqueCourseSessionsByCourse.map(
|
||||
(cs) => ({
|
||||
id: cs.course.id,
|
||||
name: cs.course.title,
|
||||
slug: cs.course.slug,
|
||||
})
|
||||
);
|
||||
const selectedCourse = ref<CourseItem>(courses[0]);
|
||||
|
||||
const courseSessions = computed(() => {
|
||||
return [
|
||||
{
|
||||
id: UNFILTERED,
|
||||
name: t("a.AlleDurchführungen"),
|
||||
},
|
||||
...courseSessionsStore.allCourseSessions
|
||||
.filter((cs) => cs.course.id === selectedCourse.value.id)
|
||||
.map((cs) => ({ id: cs.id, name: cs.title })),
|
||||
];
|
||||
});
|
||||
const selectedSession = ref<Item>(courseSessions.value[0]);
|
||||
|
||||
// pre-select course and session if we are in a course session
|
||||
if (courseSessionsStore.currentCourseSession) {
|
||||
const session = courseSessionsStore.currentCourseSession;
|
||||
const { id: courseId, title: courseName, slug: courseSlug } = session.course;
|
||||
selectedCourse.value = { id: courseId, name: courseName, slug: courseSlug };
|
||||
const { id: sessionId, title: sessionName } = session;
|
||||
selectedSession.value = { id: sessionId, name: sessionName };
|
||||
}
|
||||
|
||||
const initialItemCircle: Item = {
|
||||
id: UNFILTERED,
|
||||
name: t("a.AlleCircle"),
|
||||
};
|
||||
const circles = ref<Item[]>([initialItemCircle]);
|
||||
const selectedCircle = ref<Item>(circles.value[0]);
|
||||
|
||||
async function loadCircleValues() {
|
||||
if (selectedCourse.value) {
|
||||
const learningPathQuery = useCourseData(selectedCourse.value.slug);
|
||||
await learningPathQuery.resultPromise;
|
||||
circles.value = [
|
||||
initialItemCircle,
|
||||
...(learningPathQuery.circles.value ?? []).map((circle) => ({
|
||||
id: circle.id,
|
||||
name: circle.title,
|
||||
})),
|
||||
];
|
||||
} else {
|
||||
circles.value = [initialItemCircle];
|
||||
}
|
||||
|
||||
selectedCircle.value = circles.value[0];
|
||||
}
|
||||
|
||||
watch(selectedCourse, async () => {
|
||||
selectedSession.value = courseSessions.value[0];
|
||||
await loadCircleValues();
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await loadCircleValues();
|
||||
});
|
||||
|
||||
const appointments = computed(() => {
|
||||
return courseSessionsStore
|
||||
.allDueDates()
|
||||
.filter(
|
||||
(dueDate) =>
|
||||
isMatchingCourse(dueDate) &&
|
||||
isMatchingSession(dueDate) &&
|
||||
isMatchingCircle(dueDate)
|
||||
);
|
||||
});
|
||||
|
||||
const isMatchingSession = (dueDate: DueDate) =>
|
||||
selectedSession.value.id === UNFILTERED ||
|
||||
dueDate.course_session_id === selectedSession.value.id;
|
||||
|
||||
const isMatchingCircle = (dueDate: DueDate) =>
|
||||
selectedCircle.value.id === UNFILTERED ||
|
||||
dueDate.circle?.id === selectedCircle.value.id;
|
||||
|
||||
const isMatchingCourse = (dueDate: DueDate) =>
|
||||
courseSessions.value.map((cs) => cs.id).includes(dueDate.course_session_id);
|
||||
|
||||
const numAppointmentsToShow = ref(7);
|
||||
const canLoadMore = computed(() => {
|
||||
return numAppointmentsToShow.value < appointments.value.length;
|
||||
});
|
||||
|
||||
async function loadAdditionalAppointments() {
|
||||
numAppointmentsToShow.value *= 2;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<div class="container-large px-8 py-8">
|
||||
<header class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<h1>{{ $t("a.AlleTermine") }}</h1>
|
||||
<div>
|
||||
<ItDropdownSelect
|
||||
v-model="selectedCourse"
|
||||
data-cy="appointments-course-select"
|
||||
:items="courses"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div class="flex flex-col space-x-0 bg-white lg:flex-row lg:space-x-3">
|
||||
<ItDropdownSelect
|
||||
v-model="selectedSession"
|
||||
data-cy="appointments-session-select"
|
||||
:items="courseSessions"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
<ItDropdownSelect
|
||||
v-model="selectedCircle"
|
||||
data-cy="appointments-circle-select"
|
||||
:items="circles"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<div class="bg-white px-5">
|
||||
<DueDatesList
|
||||
:show-top-border="false"
|
||||
:show-bottom-border="canLoadMore"
|
||||
:due-dates="appointments"
|
||||
:show-all-due-dates-link="false"
|
||||
:max-count="numAppointmentsToShow"
|
||||
data-cy="appointments-list"
|
||||
:show-course-session="true"
|
||||
/>
|
||||
<button
|
||||
v-if="canLoadMore"
|
||||
class="py-4 underline"
|
||||
data-cy="load-more-notifications"
|
||||
@click="loadAdditionalAppointments()"
|
||||
>
|
||||
{{ $t("notifications.load_more") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.no-border-last li:last-child {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -41,8 +41,8 @@ const userStore = useUserStore();
|
|||
class="bg-white p-4 lg:p-8"
|
||||
@submit.prevent="
|
||||
userStore.handleLogin(
|
||||
state.username,
|
||||
state.password,
|
||||
state.username.trim(),
|
||||
state.password.trim(),
|
||||
route.query.next as string
|
||||
)
|
||||
"
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ const updateItems = async (_items: []) => {
|
|||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await itGet("/api/notify/email_notification_settings/");
|
||||
const response: any = await itGet("/api/notify/email_notification_settings/");
|
||||
items.value = items.value.map((item) => {
|
||||
item.checked = response.includes(item.value);
|
||||
return item;
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ function findUserPointsHtml(userId: string) {
|
|||
"%)";
|
||||
|
||||
if (!gradedUser.passed) {
|
||||
result += ` <span class="my-2 rounded-md bg-error-red-200 px-2.5 py-0.5">${t(
|
||||
result += ` <span class="my-2 rounded-md bg-error-red-200 px-2.5 py-0.5 inline-block leading-5">${t(
|
||||
"a.Nicht bestanden"
|
||||
)}</span>`;
|
||||
}
|
||||
|
|
@ -157,10 +157,11 @@ function findUserPointsHtml(userId: string) {
|
|||
</div>
|
||||
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
<p
|
||||
v-if="findGradedUser(csu.user_id) && !isPraxisAssignment"
|
||||
class="text-left md:text-right"
|
||||
v-html="findUserPointsHtml(csu.user_id)"
|
||||
></div>
|
||||
></p>
|
||||
</section>
|
||||
</template>
|
||||
<template #link>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composab
|
|||
import SubmissionsOverview from "@/components/cockpit/SubmissionsOverview.vue";
|
||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||
import log from "loglevel";
|
||||
import CockpitDates from "@/components/cockpit/CockpitDates.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import UserStatusCount from "@/components/cockpit/UserStatusCount.vue";
|
||||
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
||||
import CourseSessionDueDatesList from "@/components/dueDates/CourseSessionDueDatesList.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
|
|
@ -105,7 +105,11 @@ const courseSessionDetailResult = useCourseSessionDetailQuery();
|
|||
</div>
|
||||
|
||||
<div class="mb-4 bg-white p-6">
|
||||
<CockpitDates></CockpitDates>
|
||||
<CourseSessionDueDatesList
|
||||
:course-session-id="courseSession.id"
|
||||
:circle-id="expertCockpitStore.currentCircle.id"
|
||||
:max-count="4"
|
||||
></CourseSessionDueDatesList>
|
||||
</div>
|
||||
<SubmissionsOverview
|
||||
:course-session="courseSession"
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const circleDocumentsResultData = ref<CircleDocument[]>([]);
|
|||
let courseSessionDocumentsUrl = "";
|
||||
|
||||
async function fetchDocuments() {
|
||||
const result = await fetchCourseSessionDocuments(courseSession.value?.id);
|
||||
const result: any = await fetchCourseSessionDocuments(courseSession.value?.id);
|
||||
if (result.length > 0) {
|
||||
circleDocumentsResultData.value = result;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ log.debug("CompetenceCertificateComponent setup");
|
|||
const props = defineProps<{
|
||||
competenceCertificate: CompetenceCertificate;
|
||||
detailView: boolean;
|
||||
frontendUrl?: string;
|
||||
}>();
|
||||
|
||||
const totalPointsEvaluatedAssignments = computed(() => {
|
||||
|
|
@ -40,6 +41,12 @@ const progressStatusCount = computed(() => {
|
|||
props.competenceCertificate.assignments
|
||||
);
|
||||
});
|
||||
|
||||
const frontendUrl = computed(() => {
|
||||
return props.frontendUrl
|
||||
? props.frontendUrl
|
||||
: props.competenceCertificate.frontend_url;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -90,7 +97,7 @@ const progressStatusCount = computed(() => {
|
|||
|
||||
<div v-if="!props.detailView">
|
||||
<router-link
|
||||
:to="competenceCertificate.frontend_url"
|
||||
:to="frontendUrl"
|
||||
class="btn-text mt-4 inline-flex items-center py-2 pl-0"
|
||||
:data-cy="`certificate-${competenceCertificate.slug}-detail-link`"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,38 +1,38 @@
|
|||
<script setup lang="ts">
|
||||
import log from "loglevel";
|
||||
import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
|
||||
import { useQuery } from "@urql/vue";
|
||||
import { computed, onMounted } from "vue";
|
||||
import { computed } from "vue";
|
||||
import type { CompetenceCertificate } from "@/types";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useCertificateQuery } from "@/composables";
|
||||
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
|
||||
import { getCertificates } from "@/services/competence";
|
||||
import { getPreviousRoute } from "@/router/history";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
certificateSlug: string;
|
||||
userId?: string;
|
||||
}>();
|
||||
|
||||
log.debug("CompetenceCertificateDetailPage setup", props);
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const certificatesQuery = useQuery({
|
||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||
variables: {
|
||||
courseSlug: props.courseSlug,
|
||||
courseSessionId: courseSession.value.id,
|
||||
},
|
||||
});
|
||||
const certificatesQuery = useCertificateQuery(
|
||||
props.userId,
|
||||
props.courseSlug
|
||||
).certificatesQuery;
|
||||
|
||||
const certificate = computed(() => {
|
||||
return (
|
||||
(certificatesQuery.data.value?.competence_certificate_list
|
||||
?.competence_certificates as unknown as CompetenceCertificate[]) ?? []
|
||||
).find((cc) => cc.slug.endsWith(props.certificateSlug));
|
||||
});
|
||||
const certificates = getCertificates(
|
||||
certificatesQuery.data.value,
|
||||
props.userId ?? null
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
// log.debug("AssignmentView mounted", props.assignmentId, props.userId);
|
||||
if (!certificates) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
(certificates.competence_certificates as unknown as CompetenceCertificate[]) ?? []
|
||||
).find((cc) => cc.slug.endsWith(props.certificateSlug));
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -40,11 +40,14 @@ onMounted(async () => {
|
|||
<div class="container-large">
|
||||
<nav class="py-4">
|
||||
<router-link
|
||||
class="btn-text inline-flex items-center pl-0"
|
||||
:to="`/course/${props.courseSlug}/competence/certificates`"
|
||||
:to="
|
||||
getPreviousRoute() || `/course/${props.courseSlug}/competence/certificates`
|
||||
"
|
||||
class="btn-text inline-flex items-center p-0"
|
||||
data-cy="back-button"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>{{ $t("general.back") }}</span>
|
||||
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
|
||||
<span class="inline">{{ $t("general.back") }}</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
<div v-if="certificate">
|
||||
|
|
|
|||
|
|
@ -1,64 +1,81 @@
|
|||
<script setup lang="ts">
|
||||
import log from "loglevel";
|
||||
import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
|
||||
import { useQuery } from "@urql/vue";
|
||||
import { computed, onMounted } from "vue";
|
||||
import type { CompetenceCertificate } from "@/types";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useCertificateQuery } from "@/composables";
|
||||
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
|
||||
import {
|
||||
assignmentsMaxEvaluationPoints,
|
||||
assignmentsUserPoints,
|
||||
} from "@/pages/competence/utils";
|
||||
import { useRoute } from "vue-router";
|
||||
import { getCertificates } from "@/services/competence";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
userId?: string;
|
||||
}>();
|
||||
|
||||
log.debug("CompetenceCertificateListPage setup", props);
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const route = useRoute();
|
||||
|
||||
const certificatesQuery = useQuery({
|
||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||
variables: {
|
||||
courseSlug: props.courseSlug,
|
||||
courseSessionId: courseSession.value.id,
|
||||
},
|
||||
});
|
||||
const certificatesQuery = useCertificateQuery(
|
||||
props.userId,
|
||||
props.courseSlug
|
||||
).certificatesQuery;
|
||||
|
||||
const competenceCertificates = computed(() => {
|
||||
const certificates = getCertificates(
|
||||
certificatesQuery.data.value,
|
||||
props.userId ?? null
|
||||
);
|
||||
|
||||
if (!certificates) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
(certificatesQuery.data.value?.competence_certificate_list
|
||||
?.competence_certificates as unknown as CompetenceCertificate[]) ?? []
|
||||
(certificates?.competence_certificates as unknown as CompetenceCertificate[]) ?? []
|
||||
);
|
||||
});
|
||||
|
||||
const assignments = computed(() => {
|
||||
return competenceCertificates.value.flatMap((cc) => cc.assignments);
|
||||
return competenceCertificates?.value?.flatMap((cc) => cc.assignments);
|
||||
});
|
||||
|
||||
const totalPointsEvaluatedAssignments = computed(() => {
|
||||
return assignmentsMaxEvaluationPoints(assignments.value);
|
||||
return assignmentsMaxEvaluationPoints(assignments.value ?? []);
|
||||
});
|
||||
|
||||
const userPointsEvaluatedAssignments = computed(() => {
|
||||
return assignmentsUserPoints(assignments.value);
|
||||
return assignmentsUserPoints(assignments.value ?? []);
|
||||
});
|
||||
|
||||
const numAssignmentsEvaluated = computed(() => {
|
||||
return assignments.value.filter((a) => {
|
||||
return (assignments.value ?? []).filter((a) => {
|
||||
return a.completion?.completion_status === "EVALUATION_SUBMITTED";
|
||||
}).length;
|
||||
});
|
||||
|
||||
const certificateFrontendUrl = function (frontendUrl: string) {
|
||||
if (props.userId) {
|
||||
const pathSegments = frontendUrl.split("/");
|
||||
const lastSegment = pathSegments[pathSegments.length - 1];
|
||||
|
||||
// Assuming you want to navigate to the current path + last segment
|
||||
return `${route.path}/${lastSegment}`;
|
||||
}
|
||||
return frontendUrl;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// log.debug("AssignmentView mounted", props.assignmentId, props.userId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-large">
|
||||
<div v-if="assignments" class="container-large">
|
||||
<h2 class="mb-4 lg:py-4">{{ $t("a.Kompetenznachweise") }}</h2>
|
||||
|
||||
<div class="mb-4 bg-white p-8" data-cy="certificate-total-points-text">
|
||||
|
|
@ -92,6 +109,7 @@ onMounted(async () => {
|
|||
<CompetenceCertificateComponent
|
||||
:competence-certificate="competenceCertificate"
|
||||
:detail-view="false"
|
||||
:frontend-url="certificateFrontendUrl(competenceCertificate.frontend_url)"
|
||||
></CompetenceCertificateComponent>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import * as log from "loglevel";
|
||||
import { onMounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useCurrentCourseSession, useEvaluationWithFeedback } from "@/composables";
|
||||
|
||||
log.debug("CompetenceParentPage created");
|
||||
|
||||
|
|
@ -29,6 +29,7 @@ function routeInSelfEvaluationAndFeedback() {
|
|||
}
|
||||
|
||||
const currentCourseSession = useCurrentCourseSession();
|
||||
const hasEvaluationFeedback = useEvaluationWithFeedback().hasFeedback;
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("CompetenceParentPage mounted", props.courseSlug);
|
||||
|
|
@ -72,7 +73,7 @@ onMounted(async () => {
|
|||
class="block py-3"
|
||||
>
|
||||
{{
|
||||
currentCourseSession.course.configuration.enable_learning_mentor
|
||||
hasEvaluationFeedback
|
||||
? $t("a.Selbst- und Fremdeinschätzungen")
|
||||
: $t("a.Selbsteinschätzungen")
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
<script setup lang="ts">
|
||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||
import log from "loglevel";
|
||||
import { useDashboardPersonsDueDates } from "@/composables";
|
||||
import { computed } from "vue";
|
||||
import _ from "lodash";
|
||||
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
|
||||
|
||||
log.debug("DashboardAsideWidget created");
|
||||
|
||||
const { loading, dashboardPersons, dashboardDueDates } = useDashboardPersonsDueDates();
|
||||
|
||||
const displayDueDates = computed(() => {
|
||||
return _.take(dashboardDueDates.value, 6);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="loading" class="m-8 flex justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
<div v-else>
|
||||
<section class="border-b p-8">
|
||||
<h3 class="mb-4">{{ dashboardPersons.length }} {{ $t("a.Personen") }}</h3>
|
||||
<div>
|
||||
<router-link class="btn-secondary" to="/dashboard/persons">
|
||||
{{ $t("a.Alle Personen anzeigen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="dashboardDueDates.length > 0" class="p-8">
|
||||
<h3>{{ $t("a.Termine") }}</h3>
|
||||
|
||||
<div v-for="dueDate in displayDueDates" :key="dueDate.id">
|
||||
<div class="border-b">
|
||||
<DueDateSingle :due-date="dueDate" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<router-link class="btn-secondary mt-4" to="/dashboard/due-dates">
|
||||
{{ $t("a.Alle Termine anzeigen") }}
|
||||
</router-link>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
<script setup lang="ts">
|
||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||
import log from "loglevel";
|
||||
import { useDashboardPersonsDueDates } from "@/composables";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import _ from "lodash";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
|
||||
import { getPreviousRoute } from "@/router/history";
|
||||
|
||||
log.debug("DashboardPersonsPage created");
|
||||
|
||||
const UNFILTERED = "0";
|
||||
|
||||
type DropboxItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type CourseItem = DropboxItem & {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { loading, currentDueDates: dashboardDueDates } = useDashboardPersonsDueDates();
|
||||
|
||||
const courses = computed(() => {
|
||||
return [
|
||||
{
|
||||
id: UNFILTERED,
|
||||
name: `${t("Lehrgang")}: ${t("a.Alle")}`,
|
||||
slug: "",
|
||||
},
|
||||
..._(dashboardDueDates.value)
|
||||
.map((dueDate) => {
|
||||
return {
|
||||
name: dueDate.course_session.course_title,
|
||||
id: dueDate.course_session.course_id,
|
||||
slug: dueDate.course_session.course_slug,
|
||||
};
|
||||
})
|
||||
.uniqBy("id")
|
||||
.orderBy("name")
|
||||
.value(),
|
||||
];
|
||||
});
|
||||
|
||||
const selectedCourse = ref<CourseItem>(courses.value[0]);
|
||||
|
||||
const courseSessions = computed(() => {
|
||||
let sessions = _(dashboardDueDates.value)
|
||||
.map((dueDate) => {
|
||||
return Object.assign({}, dueDate.course_session, {
|
||||
name: dueDate.course_session.session_title,
|
||||
id: dueDate.course_session.id,
|
||||
});
|
||||
})
|
||||
.uniqBy("id")
|
||||
.orderBy("name")
|
||||
.value();
|
||||
|
||||
// filter by selected course
|
||||
if (selectedCourse.value.id !== UNFILTERED) {
|
||||
sessions = sessions.filter((cs) => cs.course_id === selectedCourse.value.id);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: UNFILTERED,
|
||||
name: `${t("Durchführung")}: ${t("a.Alle")}`,
|
||||
},
|
||||
...sessions,
|
||||
];
|
||||
});
|
||||
|
||||
const selectedSessionRouteQuery = useRouteQuery("session", UNFILTERED, {
|
||||
mode: "replace",
|
||||
});
|
||||
const selectedSession = ref<DropboxItem>(courseSessions.value[0]);
|
||||
|
||||
watch(selectedSession, () => {
|
||||
// @ts-ignore
|
||||
selectedSessionRouteQuery.value = selectedSession.value.id;
|
||||
});
|
||||
|
||||
watch(courseSessions, () => {
|
||||
if (courseSessions.value.length > 0 && selectedSessionRouteQuery.value) {
|
||||
// preselect session from route query
|
||||
const selectedSessionFromRoute = courseSessions.value.find(
|
||||
(cs) => cs.id === selectedSessionRouteQuery.value
|
||||
);
|
||||
if (selectedSessionFromRoute) {
|
||||
selectedSession.value = selectedSessionFromRoute;
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectedSession.value = courseSessions.value[0];
|
||||
});
|
||||
|
||||
const filteredDueDates = computed(() => {
|
||||
return _.orderBy(
|
||||
dashboardDueDates.value
|
||||
.filter((dueDate) => {
|
||||
if (selectedCourse.value.id === UNFILTERED) {
|
||||
return true;
|
||||
}
|
||||
return dueDate.course_session.course_id === selectedCourse.value.id;
|
||||
})
|
||||
.filter((dueDate) => {
|
||||
if (selectedSession.value.id === UNFILTERED) {
|
||||
return true;
|
||||
}
|
||||
return dueDate.course_session.id === selectedSession.value.id;
|
||||
})
|
||||
.filter((dueDate) => {
|
||||
if (selectedCircle.value.id === UNFILTERED) {
|
||||
return true;
|
||||
}
|
||||
return dueDate.circle?.id === selectedCircle.value.id;
|
||||
})
|
||||
.filter((dueDate) => {
|
||||
if (selectedType.value.id === UNFILTERED) {
|
||||
return true;
|
||||
}
|
||||
return dueDate.translatedType === selectedType.value.id;
|
||||
}),
|
||||
|
||||
["start"]
|
||||
);
|
||||
});
|
||||
|
||||
const filtersVisible = computed(() => {
|
||||
return (
|
||||
courses.value.length > 2 ||
|
||||
courseSessions.value.length > 2 ||
|
||||
circles.value.length > 2 ||
|
||||
dueDateTypes.value.length > 2
|
||||
);
|
||||
});
|
||||
|
||||
const circles = computed(() => {
|
||||
const dueDatesBySelectedCourse = dashboardDueDates.value.filter((dueDate) => {
|
||||
if (selectedCourse.value.id === UNFILTERED) {
|
||||
return true;
|
||||
}
|
||||
return dueDate.course_session.course_id === selectedCourse.value.id;
|
||||
});
|
||||
const circleList = _(dueDatesBySelectedCourse)
|
||||
.filter((dueDate) => {
|
||||
return !!dueDate.circle;
|
||||
})
|
||||
.map((dueDate) => {
|
||||
return {
|
||||
name: dueDate.circle?.title ?? "",
|
||||
id: dueDate.circle?.id ?? "",
|
||||
};
|
||||
})
|
||||
.uniqBy("id")
|
||||
.orderBy("name")
|
||||
.value();
|
||||
|
||||
return [
|
||||
{
|
||||
id: UNFILTERED,
|
||||
name: `${t("Circle")}: ${t("a.Alle")}`,
|
||||
},
|
||||
...circleList,
|
||||
];
|
||||
});
|
||||
const selectedCircle = ref<DropboxItem>(circles.value[0]);
|
||||
|
||||
const dueDateTypes = computed(() => {
|
||||
const types = _(dashboardDueDates.value)
|
||||
.map((dueDate) => {
|
||||
return {
|
||||
name: dueDate.translatedType,
|
||||
id: dueDate.translatedType,
|
||||
};
|
||||
})
|
||||
.uniqBy("id")
|
||||
.orderBy("name")
|
||||
.value();
|
||||
|
||||
return [
|
||||
{
|
||||
id: UNFILTERED,
|
||||
name: t("a.AlleTypen"),
|
||||
},
|
||||
...types,
|
||||
];
|
||||
});
|
||||
|
||||
const selectedType = ref<DropboxItem>(dueDateTypes.value[0]);
|
||||
|
||||
watch(selectedCourse, async () => {
|
||||
selectedSession.value = courseSessions.value[0];
|
||||
selectedCircle.value = circles.value[0];
|
||||
selectedType.value = dueDateTypes.value[0];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="loading" class="m-8 flex justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
<div v-else class="bg-gray-200">
|
||||
<div class="container-large">
|
||||
<router-link
|
||||
:to="getPreviousRoute() || '/'"
|
||||
class="btn-text inline-flex items-center p-0"
|
||||
data-cy="back-button"
|
||||
>
|
||||
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
|
||||
<span class="inline">{{ $t("general.back") }}</span>
|
||||
</router-link>
|
||||
|
||||
<h2 class="my-4">{{ $t("a.Termine") }}</h2>
|
||||
<div class="bg-white px-4 py-2">
|
||||
<section
|
||||
v-if="filtersVisible"
|
||||
class="flex flex-col space-x-0 border-b bg-white lg:flex-row lg:space-x-3"
|
||||
>
|
||||
<ItDropdownSelect
|
||||
v-model="selectedCourse"
|
||||
data-cy="select-course"
|
||||
:items="courses"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
|
||||
<ItDropdownSelect
|
||||
v-model="selectedSession"
|
||||
data-cy="select-session"
|
||||
:items="courseSessions"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
|
||||
<ItDropdownSelect
|
||||
v-model="selectedCircle"
|
||||
data-cy="select-circle"
|
||||
:items="circles"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
|
||||
<ItDropdownSelect
|
||||
v-model="selectedType"
|
||||
data-cy="select-type"
|
||||
:items="dueDateTypes"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
</section>
|
||||
|
||||
<section data-cy="due-date-list">
|
||||
<div v-for="dueDate in filteredDueDates" :key="dueDate.id" class="border-b">
|
||||
<DueDateSingle :single-line="true" :due-date="dueDate"></DueDateSingle>
|
||||
</div>
|
||||
<div v-if="!filteredDueDates.length" class="mt-4">
|
||||
<p>{{ $t("dueDates.noDueDatesAvailable") }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,33 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
import type { Component } from "vue";
|
||||
import { onMounted } from "vue";
|
||||
import StatisticPage from "@/pages/dashboard/StatisticPage.vue";
|
||||
import ProgressPage from "@/pages/dashboard/ProgressPage.vue";
|
||||
import SimpleDates from "@/components/dashboard/SimpleDates.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { useDashboardStore } from "@/stores/dashboard";
|
||||
import type { DashboardType } from "@/gql/graphql";
|
||||
import SimpleCoursePage from "@/pages/dashboard/SimpleCoursePage.vue";
|
||||
import type { DashboardCourseConfigType } from "@/services/dashboard";
|
||||
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";
|
||||
import CoursePanel from "@/components/dashboard/CoursePanel.vue";
|
||||
import DashboardAsideWidget from "@/pages/dashboard/DashboardAsideWidget.vue";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
interface DashboardPage {
|
||||
main: Component;
|
||||
aside: Component;
|
||||
onMounted(async () => {
|
||||
await dashboardStore.loadDashboardDetails();
|
||||
});
|
||||
|
||||
function newDashboardConfigForId(id: string): DashboardCourseConfigType | undefined {
|
||||
return dashboardStore.dashboardConfigsv2.find((config) => config.course_id == id);
|
||||
}
|
||||
|
||||
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);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -35,30 +23,24 @@ onMounted(dashboardStore.loadDashboardDetails);
|
|||
<LoadingSpinner />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="dashboardStore.currentDashboardConfig"
|
||||
v-else-if="dashboardStore.dashboardConfigsv2.length"
|
||||
class="flex flex-col lg:flex-row"
|
||||
>
|
||||
<main class="grow bg-gray-200 lg:order-2">
|
||||
<div class="m-8">
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h1 data-cy="dashboard-title">Dashboard</h1>
|
||||
<ItDropdownSelect
|
||||
:model-value="dashboardStore.currentDashboardConfig"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
:items="dashboardStore.dashboardConfigs"
|
||||
@update:model-value="dashboardStore.switchAndLoadDashboardConfig"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
|
||||
<component
|
||||
:is="boards[dashboardStore.currentDashboardConfig.dashboard_type].main"
|
||||
></component>
|
||||
<!-- new way of dashboard -->
|
||||
<ul>
|
||||
<li
|
||||
v-for="config in dashboardStore.dashboardConfigsv2"
|
||||
:key="config.course_id"
|
||||
>
|
||||
<CoursePanel :course-config="newDashboardConfigForId(config.course_id)" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
<aside class="m-8 lg:order-1 lg:w-[343px]">
|
||||
<component
|
||||
:is="boards[dashboardStore.currentDashboardConfig.dashboard_type].aside"
|
||||
></component>
|
||||
<aside class="lg:order-2 lg:w-[384px] xl:w-[512px]">
|
||||
<DashboardAsideWidget />
|
||||
</aside>
|
||||
</div>
|
||||
<NoCourseSession v-else class="container-medium mt-14" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,394 @@
|
|||
<script setup lang="ts">
|
||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||
import log from "loglevel";
|
||||
import { useDashboardPersonsDueDates } from "@/composables";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import _ from "lodash";
|
||||
import type { DashboardPersonCourseSessionType } from "@/services/dashboard";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import type { DashboardPersonsPageMode } from "@/types";
|
||||
|
||||
log.debug("DashboardPersonsPage created");
|
||||
|
||||
export interface Props {
|
||||
mode?: DashboardPersonsPageMode;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
mode: "default",
|
||||
});
|
||||
|
||||
const UNFILTERED = Number.MAX_SAFE_INTEGER.toString();
|
||||
|
||||
type MenuItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { loading, dashboardPersons } = useDashboardPersonsDueDates(props.mode);
|
||||
|
||||
const courses = computed(() => {
|
||||
return [
|
||||
{
|
||||
id: UNFILTERED,
|
||||
name: `${t("Lehrgang")}: ${t("a.Alle")}`,
|
||||
},
|
||||
..._(dashboardPersons.value)
|
||||
.flatMap((person) => person.course_sessions)
|
||||
.map((cs) => {
|
||||
return { name: cs.course_title, id: cs.course_id };
|
||||
})
|
||||
.uniqBy("id")
|
||||
.orderBy("name")
|
||||
.value(),
|
||||
];
|
||||
});
|
||||
const selectedCourse = ref<MenuItem>(courses.value[0]);
|
||||
const selectedCourseRouteQuery = useRouteQuery("course", UNFILTERED, {
|
||||
mode: "replace",
|
||||
});
|
||||
|
||||
watch(selectedCourse, () => {
|
||||
selectedCourseRouteQuery.value = selectedCourse.value.id;
|
||||
});
|
||||
watch(courses, () => {
|
||||
if (selectedCourseRouteQuery.value !== UNFILTERED) {
|
||||
selectedCourse.value =
|
||||
courses.value.find((course) => course.id === selectedCourseRouteQuery.value) ||
|
||||
courses.value[0];
|
||||
}
|
||||
});
|
||||
|
||||
const regions = computed(() => {
|
||||
let values = _(dashboardPersons.value)
|
||||
.flatMap((person) => person.course_sessions)
|
||||
.map((cs) => {
|
||||
return Object.assign({}, cs, { name: cs.region, id: cs.region });
|
||||
})
|
||||
.filter((cs) => !!cs.region)
|
||||
.uniqBy("id")
|
||||
.orderBy("name")
|
||||
.value();
|
||||
|
||||
// filter by selected course
|
||||
if (selectedCourse.value.id !== UNFILTERED) {
|
||||
values = values.filter((cs) => cs.course_id === selectedCourse.value.id);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: UNFILTERED,
|
||||
name: `${t("Region")}: ${t("a.Alle")}`,
|
||||
},
|
||||
...values,
|
||||
];
|
||||
});
|
||||
const selectedRegion = ref<MenuItem>(regions.value[0]);
|
||||
|
||||
const courseSessions = computed(() => {
|
||||
let values = _(dashboardPersons.value)
|
||||
.flatMap((person) => person.course_sessions)
|
||||
.map((cs) => {
|
||||
return Object.assign({}, cs, { name: cs.session_title, id: cs.id });
|
||||
})
|
||||
.uniqBy("id")
|
||||
.orderBy("name")
|
||||
.value();
|
||||
|
||||
// filter by selected course
|
||||
if (selectedCourse.value.id !== UNFILTERED) {
|
||||
values = values.filter((cs) => cs.course_id === selectedCourse.value.id);
|
||||
}
|
||||
// filter by selected region
|
||||
if (selectedRegion.value.id !== UNFILTERED) {
|
||||
values = values.filter((cs) => cs.region === selectedRegion.value.id);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: UNFILTERED,
|
||||
name: `${t("Durchführung")}: ${t("a.Alle")}`,
|
||||
},
|
||||
...values,
|
||||
];
|
||||
});
|
||||
const selectedSession = ref<MenuItem>(courseSessions.value[0]);
|
||||
|
||||
const generations = computed(() => {
|
||||
const values = _(dashboardPersons.value)
|
||||
.flatMap((person) => person.course_sessions)
|
||||
.map((cs) => {
|
||||
return Object.assign({}, cs, { name: cs.generation, id: cs.generation });
|
||||
})
|
||||
.filter((cs) => !!cs.generation)
|
||||
.uniqBy("id")
|
||||
.orderBy("name")
|
||||
.value();
|
||||
|
||||
return [
|
||||
{
|
||||
id: UNFILTERED,
|
||||
name: `${t("Generation")}: ${t("a.Alle")}`,
|
||||
},
|
||||
...values,
|
||||
];
|
||||
});
|
||||
const selectedGeneration = ref<MenuItem>(generations.value[0]);
|
||||
|
||||
const roles = computed(() => {
|
||||
const values = _(dashboardPersons.value)
|
||||
.flatMap((person) => person.course_sessions)
|
||||
.map((cs) => {
|
||||
return Object.assign({}, cs, {
|
||||
name: cs.user_role_display,
|
||||
id: cs.user_role_display,
|
||||
});
|
||||
})
|
||||
.uniqBy("id")
|
||||
.orderBy("name")
|
||||
.value();
|
||||
|
||||
return [
|
||||
{
|
||||
id: "",
|
||||
name: `${t("Rolle")}: ${t("a.Alle")}`,
|
||||
},
|
||||
...values,
|
||||
];
|
||||
});
|
||||
const selectedRole = ref<MenuItem>(roles.value[0]);
|
||||
|
||||
const filteredPersons = computed(() => {
|
||||
return _.orderBy(
|
||||
dashboardPersons.value
|
||||
.filter((person) => {
|
||||
if (selectedCourse.value.id === UNFILTERED) {
|
||||
return true;
|
||||
}
|
||||
return person.course_sessions.some(
|
||||
(cs) => cs.course_id === selectedCourse.value.id
|
||||
);
|
||||
})
|
||||
.filter((person) => {
|
||||
if (selectedSession.value.id === UNFILTERED) {
|
||||
return true;
|
||||
}
|
||||
return person.course_sessions.some((cs) => cs.id === selectedSession.value.id);
|
||||
})
|
||||
.filter((person) => {
|
||||
if (selectedRegion.value.id === UNFILTERED) {
|
||||
return true;
|
||||
}
|
||||
return person.course_sessions.some(
|
||||
(cs) => cs.region === selectedRegion.value.id
|
||||
);
|
||||
})
|
||||
.filter((person) => {
|
||||
if (selectedGeneration.value.id === UNFILTERED) {
|
||||
return true;
|
||||
}
|
||||
return person.course_sessions.some(
|
||||
(cs) => cs.generation === selectedGeneration.value.id
|
||||
);
|
||||
})
|
||||
.filter((person) => {
|
||||
if (selectedRole.value.id === "") {
|
||||
return true;
|
||||
}
|
||||
return person.course_sessions.some(
|
||||
(cs) => cs.user_role_display === selectedRole.value.id
|
||||
);
|
||||
}),
|
||||
["last_name", "first_name"]
|
||||
);
|
||||
});
|
||||
|
||||
const filtersVisible = computed(() => {
|
||||
return (
|
||||
courses.value.length > 2 ||
|
||||
courseSessions.value.length > 2 ||
|
||||
regions.value.length > 2 ||
|
||||
generations.value.length > 2 ||
|
||||
roles.value.length > 2
|
||||
);
|
||||
});
|
||||
|
||||
function personRoleDisplayValue(personCourseSession: DashboardPersonCourseSessionType) {
|
||||
if (
|
||||
["SUPERVISOR", "EXPERT", "LEARNING_MENTOR"].includes(personCourseSession.user_role)
|
||||
) {
|
||||
return personCourseSession.user_role_display;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
watch(selectedCourse, () => {
|
||||
selectedRegion.value = regions.value[0];
|
||||
});
|
||||
|
||||
watch(selectedRegion, () => {
|
||||
selectedSession.value = courseSessions.value[0];
|
||||
selectedGeneration.value = generations.value[0];
|
||||
selectedRole.value = roles.value[0];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="loading" class="m-8 flex justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
<div v-else class="bg-gray-200">
|
||||
<div class="container-large">
|
||||
<router-link
|
||||
:to="`/`"
|
||||
class="btn-text inline-flex items-center p-0"
|
||||
data-cy="back-to-learning-path-button"
|
||||
>
|
||||
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
|
||||
<span class="inline">{{ $t("general.back") }}</span>
|
||||
</router-link>
|
||||
<h2 class="my-4">{{ $t("a.Personen") }}</h2>
|
||||
<div class="bg-white px-4 py-2">
|
||||
<section
|
||||
v-if="filtersVisible"
|
||||
class="flex flex-col space-x-0 border-b bg-white lg:flex-row lg:space-x-3"
|
||||
>
|
||||
<ItDropdownSelect
|
||||
v-if="courses.length > 2"
|
||||
v-model="selectedCourse"
|
||||
data-cy="select-course"
|
||||
:items="courses"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
|
||||
<ItDropdownSelect
|
||||
v-if="regions.length > 2"
|
||||
v-model="selectedRegion"
|
||||
data-cy="select-region"
|
||||
:items="regions"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
|
||||
<ItDropdownSelect
|
||||
v-if="courseSessions.length > 2"
|
||||
v-model="selectedSession"
|
||||
data-cy="select-course"
|
||||
:items="courseSessions"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
|
||||
<ItDropdownSelect
|
||||
v-if="generations.length > 2"
|
||||
v-model="selectedGeneration"
|
||||
data-cy="select-generation"
|
||||
:items="generations"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
|
||||
<ItDropdownSelect
|
||||
v-if="roles.length > 2"
|
||||
v-model="selectedRole"
|
||||
data-cy="select-role"
|
||||
:items="roles"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
</section>
|
||||
<div
|
||||
v-for="person in filteredPersons"
|
||||
:key="person.user_id"
|
||||
data-cy="person"
|
||||
class="flex flex-col justify-between gap-4 border-b p-2 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
|
||||
>
|
||||
<div class="w-full flex-auto md:w-1/3">
|
||||
<div class="flex items-center space-x-2">
|
||||
<img
|
||||
class="inline-block h-11 w-11 rounded-full"
|
||||
:src="
|
||||
person.avatar_url_small ||
|
||||
'/static/avatars/myvbv-default-avatar.png'
|
||||
"
|
||||
:alt="`${person.first_name} ${person.last_name}`"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-bold">
|
||||
{{ person.first_name }}
|
||||
{{ person.last_name }}
|
||||
</div>
|
||||
<div class="text-gray-900">{{ person.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex-auto items-start md:w-2/3">
|
||||
<div
|
||||
v-for="cs in person.course_sessions"
|
||||
:key="cs.id"
|
||||
class="w-full border-b pb-2 pt-2 first:pt-0 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div class="flex flex-col md:flex-row md:items-center">
|
||||
<div v-if="props.mode === 'default'" class="md:w-1/2">
|
||||
<div class="text-gray-900">{{ cs.course_title }}</div>
|
||||
<div v-if="cs.is_uk">{{ cs.session_title }}</div>
|
||||
</div>
|
||||
<div v-if="props.mode === 'competenceMetrics'" class="md:w-1/3">
|
||||
<div
|
||||
v-if="cs.competence_metrics?.passed_count || 0 > 0"
|
||||
class="my-2 w-fit rounded-md bg-green-200 px-2.5 py-0.5"
|
||||
>
|
||||
{{ $t("a.Bestanden") }}:
|
||||
{{ cs.competence_metrics?.passed_count }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="props.mode === 'default'" class="md:w-1/4">
|
||||
{{ personRoleDisplayValue(cs) }}
|
||||
</div>
|
||||
<div v-else-if="props.mode === 'competenceMetrics'" class="md:w-1/3">
|
||||
<div
|
||||
v-if="cs.competence_metrics?.failed_count || 0 > 0"
|
||||
class="my-2 w-fit rounded-md bg-error-red-200 px-2.5 py-0.5"
|
||||
>
|
||||
{{ $t("a.Nicht Bestanden") }}:
|
||||
{{ cs.competence_metrics?.failed_count }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md:w-1/4 md:text-right">
|
||||
<div
|
||||
v-if="
|
||||
(['SUPERVISOR', 'EXPERT'].includes(cs.my_role) &&
|
||||
cs.user_role === 'MEMBER') ||
|
||||
(cs.my_role === 'LEARNING_MENTOR' &&
|
||||
cs.user_role === 'LEARNING_MENTEE')
|
||||
"
|
||||
>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'profileLearningPath',
|
||||
params: {
|
||||
userId: person.user_id,
|
||||
courseSlug: cs.course_slug,
|
||||
},
|
||||
query: { courseSessionId: cs.id },
|
||||
}"
|
||||
class="link w-full lg:text-right"
|
||||
>
|
||||
{{ $t("a.Profil anzeigen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,25 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
import { useDashboardStore } from "@/stores/dashboard";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { computed } from "vue";
|
||||
import type {
|
||||
AssignmentCompletionMetricsType,
|
||||
AssignmentStatisticsRecordType,
|
||||
CourseStatisticsType,
|
||||
StatisticsCircleDataType,
|
||||
} from "@/gql/graphql";
|
||||
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
||||
import { useCourseStatistics } from "@/composables";
|
||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||
import { getDateString } from "@/components/dueDates/dueDatesUtils";
|
||||
import dayjs from "dayjs";
|
||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
const statistics = computed(() => {
|
||||
return dashboardStore.currentDashBoardData as CourseStatisticsType;
|
||||
});
|
||||
|
||||
const { courseSessionName, circleMeta } = useCourseStatistics();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const props = defineProps<{
|
||||
courseStatistics: CourseStatisticsType;
|
||||
courseSessionName: (sessionId: string) => string;
|
||||
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
||||
}>();
|
||||
|
||||
const assignmentStats = (metrics: AssignmentCompletionMetricsType) => {
|
||||
if (!metrics.ranking_completed) {
|
||||
|
|
@ -43,20 +39,14 @@ const total = (metrics: AssignmentCompletionMetricsType) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<main v-if="statistics">
|
||||
<main>
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h3>{{ $t("a.Kompetenznachweis-Elemente") }}</h3>
|
||||
<ItDropdownSelect
|
||||
:model-value="dashboardStore.currentDashboardConfig"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
:items="dashboardStore.dashboardConfigs"
|
||||
@update:model-value="dashboardStore.switchAndLoadDashboardConfig"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<div v-if="statistics.assignments.records" class="mt-8 bg-white">
|
||||
<div v-if="courseStatistics?.assignments.records" class="mt-8 bg-white">
|
||||
<StatisticFilterList
|
||||
:course-session-properties="statistics.course_session_properties"
|
||||
:items="statistics.assignments.records"
|
||||
:course-session-properties="courseStatistics?.course_session_properties"
|
||||
:items="courseStatistics.assignments.records"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="flex justify-between">
|
||||
|
|
@ -89,8 +79,8 @@ const total = (metrics: AssignmentCompletionMetricsType) => {
|
|||
<div v-else>Noch nicht bestätigt</div>
|
||||
<ItProgress
|
||||
:status-count="
|
||||
assignmentStats((item as AssignmentStatisticsRecordType).metrics)
|
||||
"
|
||||
assignmentStats((item as AssignmentStatisticsRecordType).metrics)
|
||||
"
|
||||
></ItProgress>
|
||||
<router-link
|
||||
class="underline"
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { useDashboardStore } from "@/stores/dashboard";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { computed } from "vue";
|
||||
import type { CourseStatisticsType, PresenceRecordStatisticsType } from "@/gql/graphql";
|
||||
import type {
|
||||
CourseStatisticsType,
|
||||
PresenceRecordStatisticsType,
|
||||
StatisticsCircleDataType,
|
||||
} from "@/gql/graphql";
|
||||
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||
import { useCourseStatistics } from "@/composables";
|
||||
import { getDateString } from "@/components/dueDates/dueDatesUtils";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
const statistics = computed(() => {
|
||||
return dashboardStore.currentDashBoardData as CourseStatisticsType;
|
||||
});
|
||||
|
||||
const { courseSessionName, circleMeta } = useCourseStatistics();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const props = defineProps<{
|
||||
courseStatistics: CourseStatisticsType;
|
||||
courseSessionName: (sessionId: string) => string;
|
||||
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
||||
}>();
|
||||
|
||||
const attendanceStats = (present: number, total: number) => {
|
||||
return {
|
||||
|
|
@ -27,20 +26,17 @@ const attendanceStats = (present: number, total: number) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<main v-if="statistics">
|
||||
<main>
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h3>{{ $t("Anwesenheit") }}</h3>
|
||||
<ItDropdownSelect
|
||||
:model-value="dashboardStore.currentDashboardConfig"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
:items="dashboardStore.dashboardConfigs"
|
||||
@update:model-value="dashboardStore.switchAndLoadDashboardConfig"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<div v-if="statistics.attendance_day_presences.records" class="mt-8 bg-white">
|
||||
<div
|
||||
v-if="courseStatistics?.attendance_day_presences.records"
|
||||
class="mt-8 bg-white"
|
||||
>
|
||||
<StatisticFilterList
|
||||
:course-session-properties="statistics.course_session_properties"
|
||||
:items="statistics.attendance_day_presences.records"
|
||||
:course-session-properties="courseStatistics.course_session_properties"
|
||||
:items="courseStatistics.attendance_day_presences.records"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="flex justify-between">
|
||||
|
|
|
|||
|
|
@ -1,38 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import { useDashboardStore } from "@/stores/dashboard";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { computed } from "vue";
|
||||
import type {
|
||||
CompetenceRecordStatisticsType,
|
||||
CourseStatisticsType,
|
||||
StatisticsCircleDataType,
|
||||
} from "@/gql/graphql";
|
||||
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
||||
import { useCourseStatistics } from "@/composables";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
const statistics = computed(() => {
|
||||
return dashboardStore.currentDashBoardData as CourseStatisticsType;
|
||||
});
|
||||
|
||||
const { courseSessionName, circleMeta } = useCourseStatistics();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const props = defineProps<{
|
||||
courseStatistics: CourseStatisticsType;
|
||||
courseSessionName: (sessionId: string) => string;
|
||||
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main v-if="statistics">
|
||||
<main>
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h3>{{ $t("a.Selbsteinschätzung") }}</h3>
|
||||
<ItDropdownSelect
|
||||
:model-value="dashboardStore.currentDashboardConfig"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
:items="dashboardStore.dashboardConfigs"
|
||||
@update:model-value="dashboardStore.switchAndLoadDashboardConfig"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<div v-if="statistics.competences.records" class="mt-8 bg-white">
|
||||
<div v-if="courseStatistics?.competences.records" class="mt-8 bg-white">
|
||||
<StatisticFilterList
|
||||
:course-session-properties="statistics.course_session_properties"
|
||||
:items="statistics.competences.records"
|
||||
:course-session-properties="courseStatistics.course_session_properties"
|
||||
:items="courseStatistics.competences.records"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="flex justify-between">
|
||||
|
|
|
|||
|
|
@ -1,40 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
import { useDashboardStore } from "@/stores/dashboard";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { computed } from "vue";
|
||||
import type {
|
||||
CourseStatisticsType,
|
||||
FeedbackStatisticsRecordType,
|
||||
PresenceRecordStatisticsType,
|
||||
StatisticsCircleDataType,
|
||||
} from "@/gql/graphql";
|
||||
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
||||
import { useCourseStatistics } from "@/composables";
|
||||
import { getBlendedColorForRating } from "@/utils/ratingToColor";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
const statistics = computed(() => {
|
||||
return dashboardStore.currentDashBoardData as CourseStatisticsType;
|
||||
});
|
||||
|
||||
const { courseSessionName, circleMeta } = useCourseStatistics();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const props = defineProps<{
|
||||
courseStatistics: CourseStatisticsType;
|
||||
courseSessionName: (sessionId: string) => string;
|
||||
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main v-if="statistics">
|
||||
<main>
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<h3>{{ $t("a.Feedback Teilnehmer") }}</h3>
|
||||
<ItDropdownSelect
|
||||
:model-value="dashboardStore.currentDashboardConfig"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
:items="dashboardStore.dashboardConfigs"
|
||||
@update:model-value="dashboardStore.switchAndLoadDashboardConfig"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<div v-if="statistics.feedback_responses.records" class="mt-8 bg-white">
|
||||
<div v-if="courseStatistics?.feedback_responses.records" class="mt-8 bg-white">
|
||||
<StatisticFilterList
|
||||
:course-session-properties="statistics.course_session_properties"
|
||||
:items="statistics.feedback_responses.records"
|
||||
:course-session-properties="courseStatistics.course_session_properties"
|
||||
:items="courseStatistics.feedback_responses.records"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="flex justify-between">
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import { useDashboardStore } from "@/stores/dashboard";
|
||||
import { onMounted } from "vue";
|
||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||
import { useCourseStatisticsv2 } from "@/composables";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
onMounted(dashboardStore.loadDashboardDetails);
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
const { courseStatistics, loading, courseSessionName, circleMeta } =
|
||||
useCourseStatisticsv2(props.courseSlug);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<div v-if="dashboardStore.loading" class="m-8 flex justify-center">
|
||||
<div v-if="loading" class="m-8 flex justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
<div v-else class="container-large flex flex-col space-y-8">
|
||||
|
|
@ -17,7 +20,11 @@ onMounted(dashboardStore.loadDashboardDetails);
|
|||
<it-icon-arrow-left />
|
||||
<span>{{ $t("general.back") }}</span>
|
||||
</router-link>
|
||||
<router-view></router-view>
|
||||
<router-view
|
||||
:course-statistics="courseStatistics"
|
||||
:course-session-name="courseSessionName"
|
||||
:circle-meta="circleMeta"
|
||||
></router-view>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,19 @@ onMounted(() => {
|
|||
>
|
||||
<ul class="flex flex-col lg:flex-row">
|
||||
<li
|
||||
data-cy="lm-mentees-navigation-link"
|
||||
class="border-t-2 border-t-transparent"
|
||||
: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>
|
||||
<li
|
||||
v-if="!courseSession.course.configuration.is_uk"
|
||||
class="border-t-2 border-t-transparent lg:ml-12"
|
||||
:class="{
|
||||
'border-b-2 border-b-blue-900': route.name
|
||||
?.toString()
|
||||
|
|
@ -41,19 +53,7 @@ onMounted(() => {
|
|||
: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") }}
|
||||
{{ $t("a.Aufgaben") }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -10,8 +10,11 @@ const isMyMentorsVisible = computed(() =>
|
|||
courseSession.value.actions.includes("learning-mentor::edit-mentors")
|
||||
);
|
||||
|
||||
const isMyMenteesVisible = computed(() =>
|
||||
courseSession.value.actions.includes("learning-mentor::guide-members")
|
||||
const isMyMenteesVisible = computed(
|
||||
() =>
|
||||
courseSession.value.actions.includes("learning-mentor::guide-members") ||
|
||||
courseSession.value.actions.includes("is_expert") ||
|
||||
courseSession.value.actions.includes("is_supervisor")
|
||||
);
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -63,8 +63,15 @@ const showDocumentSection = computed(() => {
|
|||
);
|
||||
});
|
||||
|
||||
const expertAsContact = computed(() => {
|
||||
return (
|
||||
lpQueryResult.course.value?.configuration.enable_learning_mentor &&
|
||||
lpQueryResult.course.value?.configuration.is_vv
|
||||
);
|
||||
});
|
||||
|
||||
const courseConfig = computed(() => {
|
||||
if (lpQueryResult.course.value?.configuration.enable_learning_mentor) {
|
||||
if (expertAsContact.value) {
|
||||
return {
|
||||
contactDescription: "circlePage.contactLearningMentorDescription",
|
||||
contactButton: "circlePage.contactLearningMentorButton",
|
||||
|
|
@ -98,7 +105,7 @@ interface Mentor {
|
|||
|
||||
const experts = computed<Expert[] | null>(() => {
|
||||
if (courseConfig.value.showContact) {
|
||||
if (lpQueryResult.course.value?.configuration.enable_learning_mentor) {
|
||||
if (expertAsContact.value) {
|
||||
if (mentors.value?.length > 0) {
|
||||
return mentors.value.map((m: Mentor) => m.mentor);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const courseSession = useCurrentCourseSession();
|
|||
const circleDocumentsResultData = ref<CircleDocument[]>([]);
|
||||
|
||||
async function fetchDocuments() {
|
||||
const result = await fetchCourseSessionDocuments(courseSession.value?.id);
|
||||
const result: any = await fetchCourseSessionDocuments(courseSession.value?.id);
|
||||
if (result.length > 0) {
|
||||
circleDocumentsResultData.value = result;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const documents = ref<BlockDocument[]>([]);
|
|||
|
||||
onMounted(async () => {
|
||||
log.debug("DocumentListBlock mounted");
|
||||
const response = await itGetCached(`/api/course/page/${props.content.slug}/`);
|
||||
const response: any = await itGetCached(`/api/course/page/${props.content.slug}/`);
|
||||
documents.value = response.documents;
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ async function startTest() {
|
|||
extendedTimeTest.value = true;
|
||||
}
|
||||
|
||||
const response = await itPost("/api/core/edoniq-test/redirect/", {
|
||||
const response: any = await itPost("/api/core/edoniq-test/redirect/", {
|
||||
learning_content_id: props.content.id,
|
||||
extended_time_test: extendedTimeTest.value,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
import DueDatesShortList from "@/components/dueDates/DueDatesShortList.vue";
|
||||
import LearningPathListView from "@/pages/learningPath/learningPathPage/LearningPathListView.vue";
|
||||
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
||||
import CircleProgress from "@/pages/learningPath/learningPathPage/LearningPathProgress.vue";
|
||||
|
|
@ -8,8 +7,12 @@ import type { ViewType } from "@/pages/learningPath/learningPathPage/LearningPat
|
|||
import LearningPathViewSwitch from "@/pages/learningPath/learningPathPage/LearningPathViewSwitch.vue";
|
||||
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
||||
import { computed, ref } from "vue";
|
||||
import { useCourseDataWithCompletion } from "@/composables";
|
||||
import { someFinishedInLearningSequence } from "@/services/circle";
|
||||
import {
|
||||
useCourseCircleProgress,
|
||||
useCourseDataWithCompletion,
|
||||
useCurrentCourseSession,
|
||||
} from "@/composables";
|
||||
import CourseSessionDueDatesList from "@/components/dueDates/CourseSessionDueDatesList.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
|
|
@ -28,20 +31,11 @@ const lpQueryResult = useCourseDataWithCompletion(props.courseSlug);
|
|||
const learningPath = computed(() => lpQueryResult.learningPath.value);
|
||||
const course = computed(() => lpQueryResult.course.value);
|
||||
|
||||
const circlesCount = computed(() => {
|
||||
return lpQueryResult.circles.value?.length ?? 0;
|
||||
});
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const inProgressCirclesCount = computed(() => {
|
||||
if (lpQueryResult.circles.value?.length) {
|
||||
return lpQueryResult.circles.value.filter(
|
||||
(circle) =>
|
||||
circle.learning_sequences.filter((ls) => someFinishedInLearningSequence(ls))
|
||||
.length
|
||||
).length;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
const { inProgressCirclesCount, circlesCount } = useCourseCircleProgress(
|
||||
lpQueryResult.circles
|
||||
);
|
||||
|
||||
const changeViewType = (viewType: ViewType) => {
|
||||
selectedView.value = viewType;
|
||||
|
|
@ -72,10 +66,10 @@ const changeViewType = (viewType: ViewType) => {
|
|||
|
||||
<!-- Right -->
|
||||
<div v-if="!useMobileLayout" class="flex-grow">
|
||||
<div class="text-bold pb-3">
|
||||
{{ $t("learningPathPage.nextDueDates") }}
|
||||
</div>
|
||||
<DueDatesShortList :max-count="2" :show-top-border="true"></DueDatesShortList>
|
||||
<CourseSessionDueDatesList
|
||||
:course-session-id="courseSession.id"
|
||||
:max-count="2"
|
||||
></CourseSessionDueDatesList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ const executePayment = () => {
|
|||
redirect_url: fullHost,
|
||||
address: address.value,
|
||||
product: props.courseType,
|
||||
}).then((res) => {
|
||||
}).then((res: any) => {
|
||||
console.log("Going to next page", res.next_step_url);
|
||||
window.location.href = res.next_step_url;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,63 +1,102 @@
|
|||
<script setup lang="ts">
|
||||
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";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
|
||||
type SubMenuType = "OVERVIEW" | "DETAILS";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const props = defineProps<{
|
||||
userId: string;
|
||||
courseSlug: string;
|
||||
certificateSlug?: string;
|
||||
}>();
|
||||
|
||||
interface SubMenuItem {
|
||||
type: SubMenuType;
|
||||
label: string;
|
||||
url: string;
|
||||
inMenu: boolean;
|
||||
routeMatch: string[];
|
||||
}
|
||||
|
||||
const MENU_ENTRIES: SubMenuItem[] = [
|
||||
{ type: "OVERVIEW", label: "a.Übersicht" },
|
||||
const SUBPAGES: SubMenuItem[] = [
|
||||
{
|
||||
type: "DETAILS",
|
||||
label: useCurrentCourseSession().value.course.configuration.enable_learning_mentor
|
||||
label: "a.Übersicht",
|
||||
url: `/course/${props.courseSlug}/profile/${props.userId}/competence`,
|
||||
inMenu: true,
|
||||
routeMatch: ["competenceMain"],
|
||||
},
|
||||
{
|
||||
label: useCurrentCourseSession().value.course.configuration.is_vv
|
||||
? "a.Selbst- und Fremdeinschätzungen"
|
||||
: "a.Selbsteinschätzungen",
|
||||
url: `/course/${props.courseSlug}/profile/${props.userId}/competence/evaluations`,
|
||||
inMenu: true,
|
||||
routeMatch: ["competenceEvaluations"],
|
||||
},
|
||||
];
|
||||
|
||||
const active = ref<SubMenuItem>(MENU_ENTRIES[0]);
|
||||
const selectDetails = () => {
|
||||
active.value = MENU_ENTRIES[1];
|
||||
};
|
||||
if (useCurrentCourseSession().value.course.configuration.is_uk) {
|
||||
SUBPAGES.push(
|
||||
{
|
||||
label: "Kompetenznachweise",
|
||||
url: `/course/${props.courseSlug}/profile/${props.userId}/competence/certificates`,
|
||||
inMenu: true,
|
||||
routeMatch: ["competenceCertificates", "competenceCertificateDetail"],
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
url: "",
|
||||
inMenu: false,
|
||||
routeMatch: [],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function convertRouteRecordNameToString(
|
||||
routeRecordName: string | symbol | undefined
|
||||
): string {
|
||||
if (!routeRecordName) {
|
||||
return "";
|
||||
}
|
||||
if (typeof routeRecordName === "symbol") {
|
||||
// Convert symbol to string explicitly
|
||||
return routeRecordName.toString();
|
||||
} else {
|
||||
// It's already a string, return as is
|
||||
return routeRecordName;
|
||||
}
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CockpitProfileContent>
|
||||
<template #side>
|
||||
<div v-for="(entry, index) in MENU_ENTRIES" :key="index" class="mb-2">
|
||||
<button
|
||||
<div
|
||||
v-for="(entry, index) in SUBPAGES.filter((p) => p.inMenu)"
|
||||
:key="index"
|
||||
class="mb-2"
|
||||
>
|
||||
<router-link
|
||||
:to="entry.url"
|
||||
class="flex w-full items-center space-x-2 p-2 pr-4 text-blue-900 hover:bg-gray-200 lg:pr-8"
|
||||
:class="{ 'text-bold bg-gray-200': active.type === entry.type }"
|
||||
@click="active = entry"
|
||||
:class="{
|
||||
'text-bold bg-gray-200': route.matched.some((record) =>
|
||||
entry.routeMatch.includes(convertRouteRecordNameToString(record?.name))
|
||||
),
|
||||
}"
|
||||
>
|
||||
<span>{{ $t(entry.label) }}</span>
|
||||
</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<div class="container-large">
|
||||
<SelfEvaluationAndFeedbackOverview
|
||||
v-if="active.type === 'OVERVIEW'"
|
||||
<router-view
|
||||
:profile-user-id="props.userId"
|
||||
@show-all="selectDetails"
|
||||
/>
|
||||
<SelfEvaluationAndFeedbackList
|
||||
v-else-if="active.type === 'DETAILS'"
|
||||
class="w-full"
|
||||
:profile-user-id="props.userId"
|
||||
/>
|
||||
:user-id="props.userId"
|
||||
:course-slug="useCurrentCourseSession().value.course.slug"
|
||||
:certificate-slug="certificateSlug ? certificateSlug : ''"
|
||||
></router-view>
|
||||
</div>
|
||||
</template>
|
||||
</CockpitProfileContent>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,16 @@ const props = defineProps<{
|
|||
const { t } = useTranslation();
|
||||
|
||||
const pages = ref([
|
||||
{ label: t("general.learningPath"), route: "profileLearningPath" },
|
||||
{ label: t("a.KompetenzNavi"), route: "profileCompetence" },
|
||||
{
|
||||
label: t("general.learningPath"),
|
||||
route: "profileLearningPath",
|
||||
routeMatch: "profileLearningPath",
|
||||
},
|
||||
{
|
||||
label: t("a.KompetenzNavi"),
|
||||
route: "competenceMain",
|
||||
routeMatch: "profileCompetence",
|
||||
},
|
||||
]);
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
|
@ -54,7 +62,11 @@ onMounted(() => {
|
|||
v-for="page in pages"
|
||||
:key="page.route"
|
||||
class="relative top-px mr-12 pb-3"
|
||||
:class="[route.name === page.route ? 'border-b-2 border-blue-900 pb-3' : '']"
|
||||
:class="[
|
||||
route.matched.some((record) => record.name === page.routeMatch)
|
||||
? 'border-b-2 border-blue-900 pb-3'
|
||||
: '',
|
||||
]"
|
||||
>
|
||||
<router-link :to="{ name: page.route }">
|
||||
{{ page.label }}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,10 @@ const loginRequired = (to: RouteLocationNormalized) => {
|
|||
return !to.meta?.public;
|
||||
};
|
||||
|
||||
export async function handleCurrentCourseSession(to: RouteLocationNormalized) {
|
||||
export async function handleCurrentCourseSession(
|
||||
to: RouteLocationNormalized,
|
||||
options?: { unset?: boolean }
|
||||
) {
|
||||
// register after login hooks
|
||||
const userStore = useUserStore();
|
||||
if (userStore.loggedIn) {
|
||||
|
|
@ -59,9 +62,12 @@ export async function handleCurrentCourseSession(to: RouteLocationNormalized) {
|
|||
if (to.params.courseSlug) {
|
||||
courseSessionsStore._currentCourseSlug = to.params.courseSlug as string;
|
||||
} else {
|
||||
courseSessionsStore._currentCourseSlug = "";
|
||||
if (options?.unset) {
|
||||
courseSessionsStore._currentCourseSlug = "";
|
||||
}
|
||||
}
|
||||
if (!courseSessionsStore.loaded) {
|
||||
console.log("handleCurrentCourseSession: loadCourseSessionsData");
|
||||
await courseSessionsStore.loadCourseSessionsData();
|
||||
}
|
||||
}
|
||||
|
|
@ -77,6 +83,7 @@ export async function handleCourseSessionAsQueryParam(to: RouteLocationNormalize
|
|||
if (userStore.loggedIn) {
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
if (!courseSessionsStore.loaded) {
|
||||
console.log("handleCourseSessionAsQueryParam: loadCourseSessionsData");
|
||||
await courseSessionsStore.loadCourseSessionsData();
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +96,7 @@ export async function handleCourseSessionAsQueryParam(to: RouteLocationNormalize
|
|||
return {
|
||||
path: to.path,
|
||||
query: restOfQuery,
|
||||
replace: true,
|
||||
// replace: true,
|
||||
};
|
||||
} else {
|
||||
// courseSessionId is invalid for current user -> redirect to home
|
||||
|
|
@ -109,7 +116,9 @@ export async function handleAcceptLearningMentorInvitation(
|
|||
return;
|
||||
}
|
||||
|
||||
return `/onboarding/vv-${user.language}/account/create?next=${encodeURIComponent(
|
||||
const redirectCourse = to.query.uk ? `uk` : `vv-${user.language}`;
|
||||
|
||||
return `/onboarding/${redirectCourse}/account/create?next=${encodeURIComponent(
|
||||
to.fullPath
|
||||
)}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,19 +7,32 @@ import type {
|
|||
} from "vue-router";
|
||||
|
||||
const routeHistory: RouteLocationNormalized[] = [];
|
||||
const MAX_HISTORY = 10; // for example, store the last 10 visited routes
|
||||
const MAX_HISTORY = 10;
|
||||
let isFirstNavigation = true;
|
||||
|
||||
let lastNavigationWasPush = false;
|
||||
|
||||
export function setLastNavigationWasPush(value: boolean) {
|
||||
lastNavigationWasPush = value;
|
||||
}
|
||||
|
||||
export function getLastNavigationWasPush() {
|
||||
return lastNavigationWasPush;
|
||||
}
|
||||
|
||||
export const addToHistory: NavigationGuard = (to, from, next) => {
|
||||
// Add the current route to the history, and ensure it doesn't exceed the maximum length
|
||||
if (isFirstNavigation) {
|
||||
isFirstNavigation = false;
|
||||
} else {
|
||||
} else if (lastNavigationWasPush) {
|
||||
routeHistory.push(from);
|
||||
}
|
||||
|
||||
if (routeHistory.length > MAX_HISTORY) {
|
||||
routeHistory.shift();
|
||||
}
|
||||
|
||||
lastNavigationWasPush = false;
|
||||
next();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
redirectToLoginIfRequired,
|
||||
updateLoggedIn,
|
||||
} from "@/router/guards";
|
||||
import { addToHistory } from "@/router/history";
|
||||
import { addToHistory, setLastNavigationWasPush } from "@/router/history";
|
||||
import { onboardingRedirect } from "@/router/onboarding";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
|
||||
|
|
@ -60,6 +60,19 @@ const router = createRouter({
|
|||
name: "home",
|
||||
component: DashboardPage,
|
||||
},
|
||||
{
|
||||
path: "/dashboard/persons",
|
||||
component: () => import("@/pages/dashboard/DashboardPersonsPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "/dashboard/persons-competence",
|
||||
component: () => import("@/pages/dashboard/DashboardPersonsPage.vue"),
|
||||
props: { mode: "competenceMetrics" },
|
||||
},
|
||||
{
|
||||
path: "/dashboard/due-dates",
|
||||
component: () => import("@/pages/dashboard/DashboardDueDatesPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/media",
|
||||
props: true,
|
||||
|
|
@ -166,6 +179,37 @@ const router = createRouter({
|
|||
component: () => import("@/pages/userProfile/CompetenceProfilePage.vue"),
|
||||
props: true,
|
||||
name: "profileCompetence",
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "competenceMain",
|
||||
component: () =>
|
||||
import(
|
||||
"@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackOverview.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "evaluations",
|
||||
name: "competenceEvaluations",
|
||||
component: () =>
|
||||
import(
|
||||
"@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackList.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "certificates/:certificateSlug",
|
||||
name: "competenceCertificateDetail",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "certificates",
|
||||
name: "competenceCertificates",
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateListPage.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -177,16 +221,16 @@ const router = createRouter({
|
|||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorOverviewPage.vue"),
|
||||
name: "learningMentorOverview",
|
||||
},
|
||||
{
|
||||
path: "participants",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorParticipantsPage.vue"),
|
||||
name: "mentorsAndParticipants",
|
||||
},
|
||||
{
|
||||
path: "tasks",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorOverviewPage.vue"),
|
||||
name: "learningMentorOverview",
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback/:learningUnitId",
|
||||
component: () =>
|
||||
|
|
@ -264,7 +308,7 @@ const router = createRouter({
|
|||
],
|
||||
},
|
||||
{
|
||||
path: "/statistic",
|
||||
path: "/statistic/:courseSlug",
|
||||
props: true,
|
||||
component: () => import("@/pages/dashboard/statistic/StatisticParentPage.vue"),
|
||||
children: [
|
||||
|
|
@ -316,14 +360,6 @@ const router = createRouter({
|
|||
path: "/notifications",
|
||||
component: () => import("@/pages/NotificationsPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "/appointments",
|
||||
component: () => import("@/pages/AppointmentsPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/appointments",
|
||||
component: () => import("@/pages/AppointmentsPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "/onboarding/:courseType",
|
||||
props: true,
|
||||
|
|
@ -387,9 +423,26 @@ router.beforeEach(updateLoggedIn);
|
|||
router.beforeEach(redirectToLoginIfRequired);
|
||||
|
||||
// register after login hooks
|
||||
router.beforeEach(handleCurrentCourseSession);
|
||||
router.beforeEach(handleCourseSessionAsQueryParam);
|
||||
router.beforeEach(async (to) => await handleCurrentCourseSession(to));
|
||||
router.beforeEach(async (to) => await handleCourseSessionAsQueryParam(to));
|
||||
|
||||
// only unset the current course session in the after hook
|
||||
router.afterEach(async (to) => await handleCurrentCourseSession(to, { unset: true }));
|
||||
|
||||
router.beforeEach(addToHistory);
|
||||
|
||||
// Wrap router.replace to track when it's called
|
||||
const originalReplace = router.replace;
|
||||
router.replace = function (to) {
|
||||
setLastNavigationWasPush(false);
|
||||
return originalReplace.call(this, to);
|
||||
};
|
||||
|
||||
// Wrap router.push to track when it's called
|
||||
const originalPush = router.push;
|
||||
router.push = function (to) {
|
||||
setLastNavigationWasPush(true);
|
||||
return originalPush.call(this, to);
|
||||
};
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
import type {
|
||||
CompetenceCertificateForUserQueryQuery,
|
||||
CompetenceCertificateListObjectType,
|
||||
CompetenceCertificateQueryQuery,
|
||||
} from "@/gql/graphql";
|
||||
import type { PerformanceCriteria } from "@/types";
|
||||
import groupBy from "lodash/groupBy";
|
||||
|
||||
|
|
@ -17,3 +22,42 @@ export function calcPerformanceCriteriaStatusCount(criteria: PerformanceCriteria
|
|||
FAIL: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Type guards
|
||||
export function isCompetenceCertificateForUserQueryQuery(
|
||||
data: any
|
||||
): data is CompetenceCertificateForUserQueryQuery {
|
||||
return (
|
||||
(data as CompetenceCertificateForUserQueryQuery)
|
||||
.competence_certificate_list_for_user !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function isCompetenceCertificateQueryQuery(
|
||||
data: any
|
||||
): data is CompetenceCertificateQueryQuery {
|
||||
return (
|
||||
(data as CompetenceCertificateQueryQuery).competence_certificate_list !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function getCertificates(
|
||||
data: any,
|
||||
userId: string | null
|
||||
): CompetenceCertificateListObjectType | null {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
let certificates = null;
|
||||
|
||||
if (userId && isCompetenceCertificateForUserQueryQuery(data)) {
|
||||
certificates = data.competence_certificate_list_for_user;
|
||||
} else if (isCompetenceCertificateQueryQuery(data)) {
|
||||
certificates = data.competence_certificate_list;
|
||||
} else {
|
||||
// Handle case where data does not match expected types
|
||||
console.error("Data structure is not recognized!");
|
||||
return null;
|
||||
}
|
||||
return (certificates as unknown as CompetenceCertificateListObjectType) ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,93 @@ import {
|
|||
DASHBOARD_CONFIG,
|
||||
DASHBOARD_COURSE_SESSION_PROGRESS,
|
||||
DASHBOARD_COURSE_STATISTICS,
|
||||
DASHBOARD_MENTOR_COMPETENCE_SUMMARY,
|
||||
} from "@/graphql/queries";
|
||||
|
||||
import { itGetCached } from "@/fetchHelpers";
|
||||
import type {
|
||||
AssignmentsStatisticsType,
|
||||
CourseProgressType,
|
||||
CourseStatisticsType,
|
||||
DashboardConfigType,
|
||||
} from "@/gql/graphql";
|
||||
import type { DashboardPersonsPageMode, DueDate } from "@/types";
|
||||
|
||||
export type DashboardPersonRoleType =
|
||||
| "SUPERVISOR"
|
||||
| "EXPERT"
|
||||
| "MEMBER"
|
||||
| "LEARNING_MENTOR"
|
||||
| "LEARNING_MENTEE";
|
||||
|
||||
export type DashboardRoleKeyType =
|
||||
| "Supervisor"
|
||||
| "Trainer"
|
||||
| "Member"
|
||||
| "MentorUK"
|
||||
| "MentorVV";
|
||||
|
||||
export type WidgetType =
|
||||
| "ProgressWidget"
|
||||
| "CompetenceWidget"
|
||||
| "MentorTasksWidget"
|
||||
| "MentorPersonWidget"
|
||||
| "MentorCompetenceWidget"
|
||||
| "CompetenceCertificateWidget"
|
||||
| "UKStatisticsWidget";
|
||||
|
||||
export type DashboardPersonCourseSessionType = {
|
||||
id: string;
|
||||
session_title: string;
|
||||
course_id: string;
|
||||
course_title: string;
|
||||
course_slug: string;
|
||||
region: string;
|
||||
generation: string;
|
||||
user_role: DashboardPersonRoleType;
|
||||
user_role_display: string;
|
||||
my_role: DashboardPersonRoleType;
|
||||
my_role_display: string;
|
||||
is_uk: boolean;
|
||||
is_vv: boolean;
|
||||
competence_metrics?: {
|
||||
passed_count: number;
|
||||
failed_count: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type DashboardPersonType = {
|
||||
user_id: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
course_sessions: DashboardPersonCourseSessionType[];
|
||||
avatar_url: string;
|
||||
avatar_url_small: string;
|
||||
competence_metrics?: {
|
||||
passed_count: number;
|
||||
failed_count: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type DashboardCourseConfigType = {
|
||||
course_id: string;
|
||||
course_slug: string;
|
||||
course_title: string;
|
||||
role_key: DashboardRoleKeyType;
|
||||
is_uk: boolean;
|
||||
is_vv: boolean;
|
||||
is_mentor: boolean;
|
||||
widgets: WidgetType[];
|
||||
has_preview: boolean;
|
||||
session_to_continue_id: string;
|
||||
};
|
||||
|
||||
export type DashboardDueDate = DueDate & {
|
||||
course_session: DashboardPersonCourseSessionType;
|
||||
translatedType: string;
|
||||
persons?: DashboardPersonType[];
|
||||
};
|
||||
|
||||
export const fetchStatisticData = async (
|
||||
courseId: string
|
||||
|
|
@ -47,6 +127,7 @@ export const fetchProgressData = async (
|
|||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchDashboardConfig = async (): Promise<DashboardConfigType[] | null> => {
|
||||
try {
|
||||
const res = await graphqlClient.query(DASHBOARD_CONFIG, {});
|
||||
|
|
@ -55,9 +136,63 @@ export const fetchDashboardConfig = async (): Promise<DashboardConfigType[] | nu
|
|||
console.error("Error fetching dashboard config:", res.error);
|
||||
}
|
||||
|
||||
return res.data?.dashboard_config || null;
|
||||
return (res.data?.dashboard_config as unknown as DashboardConfigType[]) || null;
|
||||
} catch (error) {
|
||||
console.error("Error fetching dashboard config:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchMentorCompetenceSummary = async (
|
||||
courseId: string
|
||||
): Promise<AssignmentsStatisticsType | null> => {
|
||||
try {
|
||||
const res = await graphqlClient.query(DASHBOARD_MENTOR_COMPETENCE_SUMMARY, {
|
||||
courseId,
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
console.error("Error fetching data for course ID:", courseId, res.error);
|
||||
}
|
||||
return res.data?.mentor_course_statistics?.assignments || null;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data for course ID: ${courseId}`, error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export async function fetchDashboardPersons(mode: DashboardPersonsPageMode) {
|
||||
let url = "/api/dashboard/persons/";
|
||||
if (mode === "competenceMetrics") {
|
||||
url += "?with_competence_metrics=true";
|
||||
}
|
||||
return await itGetCached<DashboardPersonType[]>(url);
|
||||
}
|
||||
|
||||
export async function fetchDashboardDueDates() {
|
||||
return await itGetCached<DashboardDueDate[]>("/api/dashboard/duedates/");
|
||||
}
|
||||
|
||||
export async function fetchDashboardConfigv2() {
|
||||
return await itGetCached<DashboardCourseConfigType[]>("/api/dashboard/config/");
|
||||
}
|
||||
|
||||
export async function fetchMenteeCount(courseId: string) {
|
||||
return await itGetCached<{ mentee_count: number }>(
|
||||
`/api/dashboard/course/${courseId}/mentees/`
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchOpenTasksCount(courseId: string) {
|
||||
return await itGetCached<{ open_task_count: number }>(
|
||||
`/api/dashboard/course/${courseId}/open_tasks/`
|
||||
);
|
||||
}
|
||||
|
||||
export function courseIdForCourseSlug(
|
||||
dashboardConfigs: DashboardCourseConfigType[],
|
||||
courseSlug: string
|
||||
) {
|
||||
const config = dashboardConfigs.find((config) => config.course_slug === courseSlug);
|
||||
return config?.course_id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export function useEntities() {
|
|||
const countries: Ref<Country[]> = ref([]);
|
||||
const organisations: Ref<Organisation[]> = ref([]);
|
||||
|
||||
itGetCached("/api/core/entities/").then((res) => {
|
||||
itGetCached("/api/core/entities/").then((res: any) => {
|
||||
countries.value = res.countries;
|
||||
organisations.value = res.organisations;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export async function uploadCircleDocument(
|
|||
throw new Error("No file selected");
|
||||
}
|
||||
|
||||
const startData = await startFileUpload(data, courseSessionId);
|
||||
const startData: any = await startFileUpload(data, courseSessionId);
|
||||
|
||||
await uploadFile(startData, data.file);
|
||||
const response = itPost(`/api/core/file/finish/`, {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export const useLearningMentees = (
|
|||
error.value = null;
|
||||
|
||||
itGet(`/api/mentor/${courseSessionId}/summary`)
|
||||
.then((response) => {
|
||||
.then((response: any) => {
|
||||
summary.value = response;
|
||||
})
|
||||
.catch((err) => (error.value = err))
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export const useCompletionStore = defineStore({
|
|||
}
|
||||
|
||||
if (courseSessionId) {
|
||||
const completionData = await itPost("/api/course/completion/mark/", {
|
||||
const completionData: any = await itPost("/api/course/completion/mark/", {
|
||||
page_id: page.id,
|
||||
completion_status: page.completion_status,
|
||||
course_session_id: courseSessionId,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { itGetCached } from "@/fetchHelpers";
|
||||
import type { CourseSession, DueDate } from "@/types";
|
||||
import type { CourseSession } from "@/types";
|
||||
import eventBus from "@/utils/eventBus";
|
||||
import { useRouteLookups } from "@/utils/route";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import dayjs from "dayjs";
|
||||
import uniqBy from "lodash/uniqBy";
|
||||
import log from "loglevel";
|
||||
import { defineStore } from "pinia";
|
||||
|
|
@ -25,13 +24,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
|
||||
const userStore = useUserStore();
|
||||
if (userStore.loggedIn) {
|
||||
// TODO: refactor after implementing of Klassenkonzept
|
||||
await Promise.all(
|
||||
allCourseSessions.value.map(async (cs) => {
|
||||
sortDueDates(cs.due_dates);
|
||||
})
|
||||
);
|
||||
|
||||
if (!allCourseSessions.value) {
|
||||
throw `No courseSessionData found for user`;
|
||||
}
|
||||
|
|
@ -137,37 +129,12 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
return Boolean(hasPreview && (inLearningPath() || inCompetenceProfile()));
|
||||
});
|
||||
|
||||
function allDueDates() {
|
||||
const allDueDatesReturn: DueDate[] = [];
|
||||
|
||||
allCourseSessions.value?.forEach((cs) => {
|
||||
allDueDatesReturn.push(...cs.due_dates);
|
||||
});
|
||||
|
||||
sortDueDates(allDueDatesReturn);
|
||||
return allDueDatesReturn;
|
||||
}
|
||||
|
||||
function sortDueDates(dueDates: DueDate[]) {
|
||||
dueDates.sort((a, b) => {
|
||||
const dateA = dayjs(a.start);
|
||||
const dateB = dayjs(b.start);
|
||||
|
||||
if (!dateA.isValid() && !dateB.isValid()) return 0; // If both are invalid, they are equal
|
||||
if (!dateA.isValid()) return 1; // If dateA is invalid, it goes after dateB
|
||||
if (!dateB.isValid()) return -1; // If dateB is invalid, it goes after dateA
|
||||
|
||||
return dateA.diff(dateB); // sort by `start`
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
uniqueCourseSessionsByCourse,
|
||||
allCurrentCourseSessions,
|
||||
getCourseSessionById,
|
||||
switchCourseSessionById,
|
||||
isCourseSessionPreviewActive,
|
||||
allDueDates,
|
||||
|
||||
// use `useCurrentCourseSession` whenever possible
|
||||
currentCourseSession,
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import type {
|
|||
CourseProgressType,
|
||||
CourseStatisticsType,
|
||||
DashboardConfigType,
|
||||
DashboardType,
|
||||
} from "@/gql/graphql";
|
||||
import type { DashboardCourseConfigType } from "@/services/dashboard";
|
||||
import {
|
||||
fetchDashboardConfig,
|
||||
fetchProgressData,
|
||||
fetchDashboardConfigv2,
|
||||
fetchStatisticData,
|
||||
} from "@/services/dashboard";
|
||||
import { defineStore } from "pinia";
|
||||
|
|
@ -15,6 +15,7 @@ import { ref } from "vue";
|
|||
|
||||
export const useDashboardStore = defineStore("dashboard", () => {
|
||||
const dashboardConfigs: Ref<DashboardConfigType[]> = ref([]);
|
||||
const dashboardConfigsv2: Ref<DashboardCourseConfigType[]> = ref([]);
|
||||
const currentDashboardConfig: Ref<DashboardConfigType | undefined> = ref();
|
||||
const dashBoardDataCache: Record<
|
||||
string,
|
||||
|
|
@ -24,21 +25,21 @@ export const useDashboardStore = defineStore("dashboard", () => {
|
|||
ref(null);
|
||||
const loading = ref(false);
|
||||
|
||||
const loadDashboardData = async (type: DashboardType, id: string) => {
|
||||
let data;
|
||||
switch (type) {
|
||||
case "STATISTICS_DASHBOARD":
|
||||
data = await fetchStatisticData(id);
|
||||
break;
|
||||
case "PROGRESS_DASHBOARD":
|
||||
data = await fetchProgressData(id);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
dashBoardDataCache[id] = data;
|
||||
currentDashBoardData.value = data;
|
||||
};
|
||||
// const loadDashboardData = async (type: DashboardType, id: string) => {
|
||||
// let data;
|
||||
// switch (type) {
|
||||
// case "STATISTICS_DASHBOARD":
|
||||
// data = await fetchStatisticData(id);
|
||||
// break;
|
||||
// case "PROGRESS_DASHBOARD":
|
||||
// data = await fetchProgressData(id);
|
||||
// break;
|
||||
// default:
|
||||
// return;
|
||||
// }
|
||||
// dashBoardDataCache[id] = data;
|
||||
// currentDashBoardData.value = data;
|
||||
// };
|
||||
|
||||
const switchAndLoadDashboardConfig = async (config: DashboardConfigType) => {
|
||||
currentDashboardConfig.value = config;
|
||||
|
|
@ -56,29 +57,47 @@ export const useDashboardStore = defineStore("dashboard", () => {
|
|||
|
||||
const loadDashboardDetails = async () => {
|
||||
loading.value = true;
|
||||
dashboardConfigsv2.value = await fetchDashboardConfigv2();
|
||||
console.log("got dashboard config v2: ", dashboardConfigsv2.value);
|
||||
try {
|
||||
if (!currentDashboardConfig.value) {
|
||||
await loadDashboardConfig();
|
||||
return;
|
||||
}
|
||||
const { id, dashboard_type } = currentDashboardConfig.value;
|
||||
if (dashBoardDataCache[id]) {
|
||||
currentDashBoardData.value = dashBoardDataCache[id];
|
||||
return;
|
||||
}
|
||||
await loadDashboardData(dashboard_type, id);
|
||||
// if (!currentDashboardConfig.value) {
|
||||
// await loadDashboardConfig();
|
||||
// return;
|
||||
// }
|
||||
// const { id, dashboard_type } = currentDashboardConfig.value;
|
||||
// if (dashBoardDataCache[id]) {
|
||||
// currentDashBoardData.value = dashBoardDataCache[id];
|
||||
// return;
|
||||
// }
|
||||
// // await loadDashboardData(dashboard_type, id);
|
||||
} finally {
|
||||
console.log("done loading dashboard details");
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadStatisticsData = async (id: string) => {
|
||||
const data = await fetchStatisticData(id);
|
||||
dashBoardDataCache[id] = data;
|
||||
currentDashBoardData.value = data;
|
||||
};
|
||||
|
||||
const loadStatisticsDatav2 = async (id: string) => {
|
||||
const data = await fetchStatisticData(id);
|
||||
dashBoardDataCache[id] = data;
|
||||
return data;
|
||||
};
|
||||
|
||||
return {
|
||||
dashboardConfigs,
|
||||
dashboardConfigsv2,
|
||||
currentDashboardConfig,
|
||||
switchAndLoadDashboardConfig,
|
||||
loadDashboardConfig,
|
||||
loadDashboardDetails,
|
||||
currentDashBoardData,
|
||||
loading,
|
||||
loadStatisticsData,
|
||||
loadStatisticsDatav2,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export const useMediaLibraryStore = defineStore({
|
|||
return this.mediaLibraryPage;
|
||||
}
|
||||
log.debug("load mediaLibraryPageData");
|
||||
const mediaLibraryPageData = await itGet(`/api/course/page/${slug}/`);
|
||||
const mediaLibraryPageData: any = await itGet(`/api/course/page/${slug}/`);
|
||||
|
||||
if (!mediaLibraryPageData) {
|
||||
throw `No mediaLibraryPageData found with: ${slug}`;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const useNotificationsStore = defineStore("notifications", () => {
|
|||
}
|
||||
|
||||
async function updateUnreadCount() {
|
||||
const data = await itGet("/notifications/api/unread_count/");
|
||||
const data: any = await itGet("/notifications/api/unread_count/");
|
||||
hasUnread.value = data.unread_count !== 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ export const useUserStore = defineStore({
|
|||
});
|
||||
},
|
||||
async fetchUser() {
|
||||
const data = await itGetCached("/api/core/me/");
|
||||
const data: any = await itGetCached("/api/core/me/");
|
||||
this.$state = data;
|
||||
this.loggedIn = true;
|
||||
await setLocale(data.language);
|
||||
|
|
|
|||
|
|
@ -193,6 +193,8 @@ export interface CourseConfiguration {
|
|||
enable_circle_documents: boolean;
|
||||
enable_learning_mentor: boolean;
|
||||
enable_competence_certificates: boolean;
|
||||
is_uk: boolean;
|
||||
is_vv: boolean;
|
||||
}
|
||||
|
||||
export interface Course {
|
||||
|
|
@ -207,6 +209,8 @@ export interface CourseCategory {
|
|||
id: string;
|
||||
name: string;
|
||||
general: boolean;
|
||||
is_uk: boolean;
|
||||
is_vv: boolean;
|
||||
}
|
||||
|
||||
export type MediaLibraryContentBlockValue = {
|
||||
|
|
@ -451,7 +455,6 @@ export interface CourseSession {
|
|||
title: string;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
due_dates: DueDate[];
|
||||
actions: string[];
|
||||
}
|
||||
|
||||
|
|
@ -607,3 +610,5 @@ export type User = {
|
|||
course_session_experts: any[];
|
||||
language: string;
|
||||
};
|
||||
|
||||
export type DashboardPersonsPageMode = "default" | "competenceMetrics";
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
import { login } from "./helpers";
|
||||
|
||||
// constants
|
||||
const COURSE_SELECT = "[data-cy=appointments-course-select]";
|
||||
const SESSION_SELECT = "[data-cy=appointments-session-select]";
|
||||
const CIRCLE_SELECT = "[data-cy=appointments-circle-select]";
|
||||
const APPOINTMENTS = "[data-cy=appointments-list]";
|
||||
|
||||
describe("appointments.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
login("test-student2@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/appointments");
|
||||
});
|
||||
|
||||
it("preselects first course (Test Lehrgang)", () => {
|
||||
cy.visit("/course/test-lehrgang/appointments");
|
||||
cy.get(COURSE_SELECT).should("contain", "Test Lehrgang");
|
||||
cy.get(SESSION_SELECT).should("contain", "Bern");
|
||||
cy.get(CIRCLE_SELECT).should("contain", "Alle");
|
||||
|
||||
cy.get(".cy-single-due-date").should("have.length", 5);
|
||||
});
|
||||
|
||||
it("can filter by circle", () => {
|
||||
cy.get(CIRCLE_SELECT).click();
|
||||
cy.get(CIRCLE_SELECT).contains("Fahrzeug").click();
|
||||
|
||||
// THEN
|
||||
cy.get(APPOINTMENTS).should("not.contain", "Keine Termine");
|
||||
});
|
||||
|
||||
it("can switch course session", () => {
|
||||
cy.get(SESSION_SELECT).click();
|
||||
cy.get(SESSION_SELECT).contains("Zürich").click();
|
||||
cy.get(SESSION_SELECT).should("contain", "Zürich");
|
||||
|
||||
// THEN
|
||||
cy.get(APPOINTMENTS).should("contain", "Keine Termine");
|
||||
});
|
||||
});
|
||||
|
|
@ -101,7 +101,7 @@ describe("circle.cy.js", () => {
|
|||
.should("contain", "Feedback");
|
||||
|
||||
cy.visit("/course/test-lehrgang/learn/reisen");
|
||||
cy.get("[data-cy=\"lp-learning-sequence\"]").should("have.length", 3);
|
||||
cy.get("[data-cy=\"lp-learning-content\"]").should("have.length", 9);
|
||||
cy.get("[data-cy=\"lp-learning-sequence\"]").should("have.length", 4);
|
||||
cy.get("[data-cy=\"lp-learning-content\"]").should("have.length", 11);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ describe("selfEvaluation.cy.js", () => {
|
|||
cy.get("[data-cy=\"self-evaluation-unknown\"]").should("have.text", "4");
|
||||
|
||||
|
||||
// learning unit id = 687 also known as:
|
||||
// learning unit id = 692 also known as:
|
||||
// Bedarfsanalyse, Ist- und Soll-Situation <<Reisen>>
|
||||
const identifier = "self-eval-687"
|
||||
const identifier = "self-eval-692"
|
||||
|
||||
// data in KompetenzNavi/Selbsteinschätzungen is correct
|
||||
cy.visit("/course/test-lehrgang/competence/self-evaluation-and-feedback");
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("attendance");
|
||||
cy.url().should("contain", "/statistic/attendance");
|
||||
cy.url().should("contain", "/statistic/test-lehrgang/attendance");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
|
|
@ -63,14 +63,6 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("overall summary box", () => {
|
||||
it("contains correct numbers (members, experts etc.)", () => {
|
||||
getDashboardStatistics("participant.count").should("have.text", "4");
|
||||
getDashboardStatistics("expert.count").should("have.text", "2");
|
||||
getDashboardStatistics("session.count").should("have.text", "2");
|
||||
});
|
||||
});
|
||||
|
||||
describe("feedback summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
getDashboardStatistics("feedback.average").should("have.text", "3.3");
|
||||
|
|
@ -78,7 +70,7 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("feedback");
|
||||
cy.url().should("contain", "/statistic/feedback");
|
||||
cy.url().should("contain", "/statistic/test-lehrgang/feedback");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
|
|
@ -96,7 +88,7 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("competence");
|
||||
cy.url().should("contain", "/statistic/competence");
|
||||
cy.url().should("contain", "/statistic/test-lehrgang/competence");
|
||||
|
||||
// might be improved: roughly check
|
||||
// that the correct data is displayed
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
import { login } from "./helpers";
|
||||
|
||||
function selectDropboxItem(dropboxSelector, item) {
|
||||
cy.get(dropboxSelector).click();
|
||||
cy.get(dropboxSelector).contains(item).click();
|
||||
}
|
||||
|
||||
describe("dueDates.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
login("test-student2@example.com", "test");
|
||||
cy.visit("/dashboard/due-dates");
|
||||
});
|
||||
|
||||
it("can filter due dates by dropbox selects", () => {
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 7);
|
||||
|
||||
// can filter by session
|
||||
selectDropboxItem('[data-cy="select-session"]', "Zürich");
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 1);
|
||||
selectDropboxItem('[data-cy="select-session"]', "Bern");
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 6);
|
||||
selectDropboxItem('[data-cy="select-session"]', "Alle");
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 7);
|
||||
|
||||
// can filter by circle
|
||||
selectDropboxItem('[data-cy="select-circle"]', "Fahrzeug");
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 6);
|
||||
selectDropboxItem('[data-cy="select-circle"]', "Reisen");
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 1);
|
||||
selectDropboxItem('[data-cy="select-circle"]', "Alle");
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 7);
|
||||
|
||||
// can filter by types
|
||||
selectDropboxItem('[data-cy="select-type"]', "Präsenzkurs");
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 3);
|
||||
selectDropboxItem('[data-cy="select-type"]', "Bewertung");
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 1);
|
||||
|
||||
// combination
|
||||
selectDropboxItem('[data-cy="select-session"]', "Bern");
|
||||
selectDropboxItem('[data-cy="select-type"]', "Präsenzkurs");
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 2);
|
||||
selectDropboxItem('[data-cy="select-session"]', "Zürich");
|
||||
cy.get('[data-cy="due-date-list"]').children().should("have.length", 1);
|
||||
selectDropboxItem('[data-cy="select-type"]', "Bewertung");
|
||||
cy.get('[data-cy="due-date-list"]').should("contain", "Keine Termine");
|
||||
});
|
||||
});
|
||||
|
|
@ -1,11 +1,20 @@
|
|||
export const MENTOR_OVERVIEW_URL = "/course/versicherungsvermittler-in/learning-mentor";
|
||||
export const MENTOR_MENTEES_URL = "/course/versicherungsvermittler-in/learning-mentor/participants";
|
||||
export const MENTOR_TASKS_URL_VV =
|
||||
"/course/versicherungsvermittler-in/learning-mentor/tasks";
|
||||
export const MENTOR_MENTEES_URL_VV =
|
||||
"/course/versicherungsvermittler-in/learning-mentor";
|
||||
export const MENTOR_MENTEES_URL_UK = "/course/test-lehrgang/learning-mentor";
|
||||
|
||||
export const MAIN_NAVIGATION_MENTOR_LINK =
|
||||
"[data-cy=navigation-learning-mentor-link]";
|
||||
|
||||
export const MENTOR_DASHBOARD_LINK = "[data-cy=lm-dashboard-link]";
|
||||
export const MEMBER_DASHBOARD_LINK = "[data-cy=progress-dashboard-continue-course-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]";
|
||||
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]";
|
||||
|
|
@ -17,7 +26,5 @@ 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]";
|
||||
|
||||
|
||||
|
||||
|
||||
export const MENTEE_MENTORS_TITLE = "[data-cy=lm-my-lms-title]";
|
||||
export const MENTEE_INVITE_MENTOR = "[data-cy=lm-invite-mentor-button]";
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
import {login} from "../../helpers";
|
||||
import { login } from "../../helpers";
|
||||
import {
|
||||
MAIN_NAVIGATION_MENTOR_LINK,
|
||||
MEMBER_DASHBOARD_LINK,
|
||||
MENTEE_INVITE_MENTOR,
|
||||
MENTEE_MENTOR_LIST_ITEM,
|
||||
MENTEE_MENTOR_REMOVE,
|
||||
MENTEE_MENTORS_TITLE,
|
||||
MENTOR_MENTEES_NAVIGATION_LINK,
|
||||
MENTOR_MENTEES_URL,
|
||||
MENTOR_MENTEES_URL_UK,
|
||||
MENTOR_MENTEES_URL_VV,
|
||||
MENTOR_MY_MENTEES,
|
||||
MENTOR_MY_MENTORS,
|
||||
MENTOR_OVERVIEW_NAVIGATION_LINK,
|
||||
MENTOR_OVERVIEW_URL
|
||||
MENTOR_TASKS_URL_VV,
|
||||
} from "../constants";
|
||||
|
||||
describe("memberOnly.cy.js", () => {
|
||||
|
|
@ -23,22 +27,22 @@ describe("memberOnly.cy.js", () => {
|
|||
});
|
||||
|
||||
it("shows NO mentees navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.visit(MENTOR_TASKS_URL_VV);
|
||||
cy.get(MENTOR_MENTEES_NAVIGATION_LINK).should("not.exist");
|
||||
})
|
||||
});
|
||||
|
||||
it("shows NO overview navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.visit(MENTOR_TASKS_URL_VV);
|
||||
cy.get(MENTOR_OVERVIEW_NAVIGATION_LINK).should("not.exist");
|
||||
})
|
||||
});
|
||||
|
||||
it("shows NO mentees", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.get(MENTOR_MY_MENTEES).should("not.exist");
|
||||
});
|
||||
|
||||
it("shows my mentors", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.get(MENTOR_MY_MENTORS).should("exist");
|
||||
});
|
||||
|
||||
|
|
@ -47,12 +51,44 @@ describe("memberOnly.cy.js", () => {
|
|||
const mentor = "Micheala Weber-Mentor";
|
||||
|
||||
// when
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.contains(MENTEE_MENTOR_LIST_ITEM, mentor)
|
||||
.find(MENTEE_MENTOR_REMOVE)
|
||||
.click();
|
||||
|
||||
// then
|
||||
cy.contains(MENTOR_MY_MENTORS, mentor).should("not.exist");
|
||||
})
|
||||
});
|
||||
|
||||
it("uses term Lernbegleitung in VV-course", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.get(MAIN_NAVIGATION_MENTOR_LINK).should("contain", "Lernbegleitung");
|
||||
cy.get(MENTEE_MENTORS_TITLE).should("contain", "Meine Lernbegleiter");
|
||||
cy.get(MENTEE_INVITE_MENTOR).should(
|
||||
"contain",
|
||||
"Neue Lernbegleitung einladen"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("memberOnly.cy.js üK", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset --set-only-is-uk-flag");
|
||||
login("test-student1@example.com", "test");
|
||||
});
|
||||
|
||||
it("shows my mentors", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL_UK);
|
||||
cy.get(MENTOR_MY_MENTORS).should("exist");
|
||||
});
|
||||
|
||||
it("uses term Praxisbildner in UK-course", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL_UK);
|
||||
cy.get(MAIN_NAVIGATION_MENTOR_LINK).should("contain", "Praxisbildner");
|
||||
cy.get(MENTEE_MENTORS_TITLE).should("contain", "Meine Praxisbildner");
|
||||
cy.get(MENTEE_INVITE_MENTOR).should(
|
||||
"contain",
|
||||
"Neuen Praxisbildner einladen"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {login} from "../../helpers";
|
||||
import { login } from "../../helpers";
|
||||
import {
|
||||
MEMBER_DASHBOARD_LINK,
|
||||
MENTEE_MENTOR_LIST_ITEM,
|
||||
|
|
@ -8,11 +8,11 @@ import {
|
|||
MENTOR_MENTEE_PROFILE,
|
||||
MENTOR_MENTEE_REMOVE,
|
||||
MENTOR_MENTEES_NAVIGATION_LINK,
|
||||
MENTOR_MENTEES_URL,
|
||||
MENTOR_MENTEES_URL_VV,
|
||||
MENTOR_MY_MENTEES,
|
||||
MENTOR_MY_MENTORS,
|
||||
MENTOR_OVERVIEW_NAVIGATION_LINK,
|
||||
MENTOR_OVERVIEW_URL
|
||||
MENTOR_TASKS_URL_VV,
|
||||
} from "../constants";
|
||||
|
||||
describe("mentorAndMember.cy.js", () => {
|
||||
|
|
@ -27,34 +27,34 @@ describe("mentorAndMember.cy.js", () => {
|
|||
});
|
||||
|
||||
it("shows the learning mentor navigation", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.visit(MENTOR_TASKS_URL_VV);
|
||||
cy.get(MENTOR_MAIN_NAVIGATION).should("exist");
|
||||
});
|
||||
|
||||
it("shows the mentees navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.visit(MENTOR_TASKS_URL_VV);
|
||||
cy.get(MENTOR_MENTEES_NAVIGATION_LINK).click();
|
||||
cy.url().should("include", MENTOR_MENTEES_URL);
|
||||
})
|
||||
cy.url().should("include", MENTOR_MENTEES_URL_VV);
|
||||
});
|
||||
|
||||
it("shows the overview navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.visit(MENTOR_TASKS_URL_VV);
|
||||
cy.get(MENTOR_OVERVIEW_NAVIGATION_LINK).click();
|
||||
cy.url().should("include", MENTOR_OVERVIEW_URL);
|
||||
})
|
||||
cy.url().should("include", MENTOR_TASKS_URL_VV);
|
||||
});
|
||||
|
||||
it("shows my mentees", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.get(MENTOR_MY_MENTEES).should("exist");
|
||||
});
|
||||
|
||||
it("shows my mentors", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.get(MENTOR_MY_MENTORS).should("exist");
|
||||
});
|
||||
|
||||
it("shows the correct mentees", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.get(MENTOR_MY_MENTEES).should("contain", "Viktor Vollgas");
|
||||
});
|
||||
|
||||
|
|
@ -63,50 +63,55 @@ describe("mentorAndMember.cy.js", () => {
|
|||
const mentee = "Viktor Vollgas";
|
||||
|
||||
// when
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee)
|
||||
.find(MENTOR_MENTEE_PROFILE)
|
||||
.click();
|
||||
|
||||
// then
|
||||
const expectedMenteeProfileUrl = "/course/versicherungsvermittler-in/profile/5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||
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.visit(MENTOR_MENTEES_URL_VV);
|
||||
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");
|
||||
})
|
||||
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.visit(MENTOR_MENTEES_URL_VV);
|
||||
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.visit(MENTOR_MENTEES_URL_VV);
|
||||
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");
|
||||
})
|
||||
cy.contains(
|
||||
"Aktuell hast du noch keine Person als Lernbegleitung eingeladen"
|
||||
).should("exist");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {login} from "../../helpers";
|
||||
import { login } from "../../helpers";
|
||||
import {
|
||||
MENTOR_DASHBOARD_LINK,
|
||||
MENTOR_MAIN_NAVIGATION,
|
||||
|
|
@ -6,11 +6,11 @@ import {
|
|||
MENTOR_MENTEE_PROFILE,
|
||||
MENTOR_MENTEE_REMOVE,
|
||||
MENTOR_MENTEES_NAVIGATION_LINK,
|
||||
MENTOR_MENTEES_URL,
|
||||
MENTOR_MENTEES_URL_VV,
|
||||
MENTOR_MY_MENTEES,
|
||||
MENTOR_MY_MENTORS,
|
||||
MENTOR_OVERVIEW_NAVIGATION_LINK,
|
||||
MENTOR_OVERVIEW_URL
|
||||
MENTOR_TASKS_URL_VV,
|
||||
} from "../constants";
|
||||
|
||||
describe("mentorOnly.cy.js", () => {
|
||||
|
|
@ -22,38 +22,38 @@ describe("mentorOnly.cy.js", () => {
|
|||
it("shows the correct dashboard", () => {
|
||||
cy.visit("/");
|
||||
cy.get(MENTOR_DASHBOARD_LINK).click();
|
||||
cy.url().should("include", MENTOR_OVERVIEW_URL);
|
||||
cy.url().should("include", MENTOR_MENTEES_URL_VV);
|
||||
});
|
||||
|
||||
it("shows the learning mentor navigation", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.get(MENTOR_MAIN_NAVIGATION).should("exist");
|
||||
});
|
||||
|
||||
it("shows the mentees navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.visit(MENTOR_TASKS_URL_VV);
|
||||
cy.get(MENTOR_MENTEES_NAVIGATION_LINK).click();
|
||||
cy.url().should("include", MENTOR_MENTEES_URL);
|
||||
})
|
||||
cy.url().should("include", MENTOR_MENTEES_URL_VV);
|
||||
});
|
||||
|
||||
it("shows the overview navigation link", () => {
|
||||
cy.visit(MENTOR_OVERVIEW_URL);
|
||||
cy.visit(MENTOR_TASKS_URL_VV);
|
||||
cy.get(MENTOR_OVERVIEW_NAVIGATION_LINK).click();
|
||||
cy.url().should("include", MENTOR_OVERVIEW_URL);
|
||||
})
|
||||
cy.url().should("include", MENTOR_TASKS_URL_VV);
|
||||
});
|
||||
|
||||
it("shows my mentees", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.get(MENTOR_MY_MENTEES).should("exist");
|
||||
});
|
||||
|
||||
it("shows no mentors", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.get(MENTOR_MY_MENTORS).should("not.exist");
|
||||
});
|
||||
|
||||
it("shows the correct mentees", () => {
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.get(MENTOR_MY_MENTEES).should("contain", "Robert Student-plus-Mentor");
|
||||
cy.get(MENTOR_MY_MENTEES).should("contain", "Viktor Vollgas");
|
||||
});
|
||||
|
|
@ -63,29 +63,32 @@ describe("mentorOnly.cy.js", () => {
|
|||
const mentee = "Viktor Vollgas";
|
||||
|
||||
// when
|
||||
cy.visit(MENTOR_MENTEES_URL);
|
||||
cy.visit(MENTOR_MENTEES_URL_VV);
|
||||
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee)
|
||||
.find(MENTOR_MENTEE_PROFILE)
|
||||
.click();
|
||||
|
||||
// then
|
||||
const expectedMenteeProfileUrl = "/course/versicherungsvermittler-in/profile/5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||
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.visit(MENTOR_MENTEES_URL_VV);
|
||||
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")
|
||||
})
|
||||
cy.contains(MENTOR_MENTEE_LIST_ITEM, "Robert Student-plus-Mentor").should(
|
||||
"exist"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,17 +16,19 @@ describe("login.cy.js", () => {
|
|||
cy.get("#username").type("test-student1@example.com");
|
||||
cy.get("#password").type("test");
|
||||
|
||||
cy.get("[data-cy=\"login-button\"]").click();
|
||||
cy.get('[data-cy="login-button"]').click();
|
||||
cy.request("/api/core/me").its("status").should("eq", 200);
|
||||
|
||||
cy.get("[data-cy=\"dashboard-title\"]").should("contain", "Dashboard");
|
||||
cy.get('[data-cy="db-course-title"]')
|
||||
.first()
|
||||
.should("contain", "Test Lehrgang");
|
||||
});
|
||||
|
||||
it("can login with helper function", () => {
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/");
|
||||
cy.request("/api/core/me").its("status").should("eq", 200);
|
||||
cy.get("[data-cy=\"dashboard-title\"]").should("contain", "Dashboard");
|
||||
cy.get('[data-cy="db-course-title"]').should("contain", "Test Lehrgang");
|
||||
});
|
||||
|
||||
it("login will redirect to requested page", () => {
|
||||
|
|
@ -36,9 +38,9 @@ describe("login.cy.js", () => {
|
|||
cy.get("#username").type("test-student1@example.com");
|
||||
cy.get("#password").type("test");
|
||||
|
||||
cy.get("[data-cy=\"login-button\"]").click();
|
||||
cy.get('[data-cy="login-button"]').click();
|
||||
|
||||
cy.get("[data-cy=\"learning-path-title\"]").should(
|
||||
cy.get('[data-cy="learning-path-title"]').should(
|
||||
"contain",
|
||||
"Test Lehrgang"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
# pylint: disable=unused-wildcard-import,wildcard-import,wrong-import-position
|
||||
import os
|
||||
|
||||
from environs import Env
|
||||
|
||||
script_path = os.path.abspath(__file__)
|
||||
script_dir = os.path.dirname(script_path)
|
||||
|
||||
env = Env()
|
||||
env.read_env(f"{script_dir}/../../../env_secrets/local_daniel.env", recurse=False)
|
||||
|
||||
from .base import * # noqa
|
||||
|
|
@ -39,6 +39,13 @@ from vbv_lernwelt.course.views import (
|
|||
request_course_completion_for_user,
|
||||
)
|
||||
from vbv_lernwelt.course_session.views import get_course_session_documents
|
||||
from vbv_lernwelt.dashboard.views import (
|
||||
get_dashboard_config,
|
||||
get_dashboard_due_dates,
|
||||
get_dashboard_persons,
|
||||
get_mentee_count,
|
||||
get_mentor_open_tasks_count,
|
||||
)
|
||||
from vbv_lernwelt.edoniq_test.views import (
|
||||
export_students,
|
||||
export_students_and_trainers,
|
||||
|
|
@ -116,6 +123,14 @@ urlpatterns = [
|
|||
re_path(r"api/notify/email_notification_settings/$", email_notification_settings,
|
||||
name='email_notification_settings'),
|
||||
|
||||
# dashboard
|
||||
path(r"api/dashboard/persons/", get_dashboard_persons, name="get_dashboard_persons"),
|
||||
path(r"api/dashboard/duedates/", get_dashboard_due_dates, name="get_dashboard_due_dates"),
|
||||
path(r"api/dashboard/config/", get_dashboard_config, name="get_dashboard_config"),
|
||||
path(r"api/dashboard/course/<str:course_id>/mentees/", get_mentee_count, name="get_mentee_count"),
|
||||
path(r"api/dashboard/course/<str:course_id>/open_tasks/", get_mentor_open_tasks_count,
|
||||
name="get_mentor_open_tasks_count"),
|
||||
|
||||
# course
|
||||
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
||||
path(r"api/course/page/<slug_or_id>/", course_page_api_view,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ from wagtail.blocks.list_block import ListBlock, ListValue
|
|||
from wagtail.rich_text import RichText
|
||||
|
||||
|
||||
def create_uk_fahrzeug_casework(course_id=COURSE_UK, competence_certificate=None):
|
||||
def create_uk_fahrzeug_casework(
|
||||
course_id=COURSE_UK, competence_certificate=None, with_documents=False
|
||||
):
|
||||
assignment_list_page = (
|
||||
CoursePage.objects.get(course_id=course_id)
|
||||
.get_children()
|
||||
|
|
@ -40,7 +42,6 @@ def create_uk_fahrzeug_casework(course_id=COURSE_UK, competence_certificate=None
|
|||
needs_expert_evaluation=True,
|
||||
competence_certificate=competence_certificate,
|
||||
effort_required="ca. 5 Stunden",
|
||||
solution_sample=ContentDocument.objects.get(title="Musterlösung Fahrzeug"),
|
||||
intro_text=replace_whitespace(
|
||||
"""
|
||||
<h3>Ausgangslage</h3>
|
||||
|
|
@ -70,6 +71,11 @@ def create_uk_fahrzeug_casework(course_id=COURSE_UK, competence_certificate=None
|
|||
evaluation_document_url="/static/media/assignments/UK_03_09_NACH_KN_Beurteilungsraster.pdf",
|
||||
evaluation_description="Diese geleitete Fallarbeit wird auf Grund des folgenden Beurteilungsintrument bewertet.",
|
||||
)
|
||||
if with_documents:
|
||||
assignment.solution_sample = ContentDocument.objects.get(
|
||||
title="Musterlösung Fahrzeug"
|
||||
)
|
||||
assignment.save()
|
||||
|
||||
assignment.evaluation_tasks = []
|
||||
assignment.evaluation_tasks.append(
|
||||
|
|
@ -3591,7 +3597,7 @@ def create_uk_reflection(course_id=COURSE_UK):
|
|||
assignment = AssignmentFactory(
|
||||
parent=assignment_list_page,
|
||||
assignment_type=AssignmentType.REFLECTION.name,
|
||||
title=f"Reflexion",
|
||||
title="Reflexion",
|
||||
effort_required="ca. 1 Stunde",
|
||||
intro_text=replace_whitespace(
|
||||
"""
|
||||
|
|
@ -3747,7 +3753,7 @@ def create_uk_fr_reflection(course_id=COURSE_UK_FR, circle_title="Véhicule"):
|
|||
assignment = AssignmentFactory(
|
||||
parent=assignment_list_page,
|
||||
assignment_type=AssignmentType.REFLECTION.name,
|
||||
title=f"Reflexion",
|
||||
title="Reflexion",
|
||||
effort_required="",
|
||||
intro_text=replace_whitespace(
|
||||
"""
|
||||
|
|
@ -3900,7 +3906,7 @@ def create_uk_it_reflection(course_id=COURSE_UK_FR, circle_title="Véhicule"):
|
|||
assignment = AssignmentFactory(
|
||||
parent=assignment_list_page,
|
||||
assignment_type=AssignmentType.REFLECTION.name,
|
||||
title=f"Riflessione",
|
||||
title="Riflessione",
|
||||
effort_required="",
|
||||
intro_text=replace_whitespace(
|
||||
"""
|
||||
|
|
@ -4053,7 +4059,7 @@ def create_vv_reflection(
|
|||
assignment = AssignmentFactory(
|
||||
parent=assignment_list_page,
|
||||
assignment_type=AssignmentType.REFLECTION.name,
|
||||
title=f"Reflexion",
|
||||
title="Reflexion",
|
||||
effort_required="ca. 1 Stunde",
|
||||
intro_text=replace_whitespace(
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ class AssignmentObjectType(DjangoObjectType):
|
|||
lp = self.find_attached_learning_content()
|
||||
if lp:
|
||||
learning_content_page_id = lp.id
|
||||
|
||||
if not assignment_user_id:
|
||||
assignment_user_id = getattr(info.context, "assignment_user_id", None)
|
||||
|
||||
return resolve_assignment_completion(
|
||||
info=info,
|
||||
course_session_id=course_session_id,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ from vbv_lernwelt.competence.models import (
|
|||
CompetenceCertificateList,
|
||||
)
|
||||
from vbv_lernwelt.course.graphql.types import resolve_course_page
|
||||
from vbv_lernwelt.course.models import CourseSessionUser
|
||||
from vbv_lernwelt.iam.permissions import can_view_profile
|
||||
|
||||
|
||||
class CompetenceCertificateQuery(object):
|
||||
|
|
@ -24,6 +26,15 @@ class CompetenceCertificateQuery(object):
|
|||
course_slug=graphene.String(),
|
||||
)
|
||||
|
||||
competence_certificate_list_for_user = graphene.Field(
|
||||
CompetenceCertificateListObjectType,
|
||||
id=graphene.ID(),
|
||||
slug=graphene.String(),
|
||||
course_id=graphene.ID(),
|
||||
course_slug=graphene.String(),
|
||||
user_id=graphene.UUID(),
|
||||
)
|
||||
|
||||
def resolve_competence_certificate(root, info, id=None, slug=None):
|
||||
return resolve_course_page(CompetenceCertificate, root, info, id=id, slug=slug)
|
||||
|
||||
|
|
@ -39,3 +50,26 @@ class CompetenceCertificateQuery(object):
|
|||
course_id=course_id,
|
||||
course_slug=course_slug,
|
||||
)
|
||||
|
||||
def resolve_competence_certificate_list_for_user(
|
||||
root, info, id=None, slug=None, course_id=None, course_slug=None, user_id=None
|
||||
):
|
||||
try:
|
||||
course_session_user = CourseSessionUser.objects.get(user__id=user_id)
|
||||
except CourseSessionUser.DoesNotExist:
|
||||
return None
|
||||
|
||||
if not can_view_profile(info.context.user, course_session_user):
|
||||
return None
|
||||
|
||||
setattr(info.context, "assignment_user_id", user_id)
|
||||
|
||||
return resolve_course_page(
|
||||
CompetenceCertificateList,
|
||||
root,
|
||||
info,
|
||||
id=id,
|
||||
slug=slug,
|
||||
course_id=course_id,
|
||||
course_slug=course_slug,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
from vbv_lernwelt.assignment.models import AssignmentType
|
||||
from vbv_lernwelt.course_session.models import (
|
||||
CourseSessionAssignment,
|
||||
CourseSessionEdoniqTest,
|
||||
)
|
||||
|
||||
|
||||
def query_competence_course_session_assignments(course_session_ids, circle_ids=None):
|
||||
if circle_ids is None:
|
||||
circle_ids = []
|
||||
|
||||
result = []
|
||||
|
||||
for csa in CourseSessionAssignment.objects.filter(
|
||||
course_session_id__in=course_session_ids,
|
||||
learning_content__content_assignment__assignment_type__in=[
|
||||
AssignmentType.CASEWORK.value,
|
||||
],
|
||||
learning_content__content_assignment__competence_certificate__isnull=False,
|
||||
):
|
||||
if circle_ids and csa.learning_content.get_circle().id not in circle_ids:
|
||||
continue
|
||||
result.append(csa)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def query_competence_course_session_edoniq_tests(course_session_ids, circle_ids=None):
|
||||
if circle_ids is None:
|
||||
circle_ids = []
|
||||
|
||||
result = []
|
||||
|
||||
for cset in CourseSessionEdoniqTest.objects.filter(
|
||||
course_session_id__in=course_session_ids,
|
||||
learning_content__content_assignment__competence_certificate__isnull=False,
|
||||
):
|
||||
if circle_ids and cset.learning_content.get_circle().id not in circle_ids:
|
||||
continue
|
||||
result.append(cset)
|
||||
|
||||
return result
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
from django.utils import timezone
|
||||
from graphene_django.utils import GraphQLTestCase
|
||||
|
||||
from vbv_lernwelt.assignment.models import Assignment, AssignmentCompletion
|
||||
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||
from vbv_lernwelt.course.creators.test_utils import (
|
||||
add_course_session_group_supervisor,
|
||||
add_course_session_user,
|
||||
create_course_session_group,
|
||||
create_user,
|
||||
)
|
||||
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||
|
||||
|
||||
class TestCertificateList(GraphQLTestCase):
|
||||
GRAPHQL_URL = "/server/graphql/"
|
||||
|
||||
def setUp(self):
|
||||
create_default_users()
|
||||
self.course = create_test_course(include_vv=False, with_sessions=True)
|
||||
self.course_session = CourseSession.objects.get(title="Test Bern 2022 a")
|
||||
assignment = Assignment.objects.get(
|
||||
slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
|
||||
)
|
||||
|
||||
self.supervisor = create_user("supervisor")
|
||||
group = create_course_session_group(course_session=self.course_session)
|
||||
add_course_session_group_supervisor(group=group, user=self.supervisor)
|
||||
|
||||
self.member_one = create_user("member one")
|
||||
add_course_session_user(
|
||||
course_session=self.course_session,
|
||||
user=self.member_one,
|
||||
role=CourseSessionUser.Role.MEMBER,
|
||||
)
|
||||
|
||||
self.member_two = create_user("member two")
|
||||
add_course_session_user(
|
||||
course_session=self.course_session,
|
||||
user=self.member_two,
|
||||
role=CourseSessionUser.Role.MEMBER,
|
||||
)
|
||||
|
||||
AssignmentCompletion.objects.create(
|
||||
assignment_user=self.member_one,
|
||||
assignment=assignment,
|
||||
learning_content_page=assignment.find_attached_learning_content(),
|
||||
course_session=self.course_session,
|
||||
completion_status="SUBMITTED",
|
||||
submitted_at=timezone.now(),
|
||||
completion_data={},
|
||||
evaluation_max_points=10,
|
||||
evaluation_points=5,
|
||||
evaluation_passed=False,
|
||||
)
|
||||
|
||||
AssignmentCompletion.objects.create(
|
||||
assignment_user=self.member_two,
|
||||
assignment=assignment,
|
||||
learning_content_page=assignment.find_attached_learning_content(),
|
||||
course_session=self.course_session,
|
||||
completion_status="SUBMITTED",
|
||||
submitted_at=timezone.now(),
|
||||
completion_data={},
|
||||
evaluation_max_points=10,
|
||||
evaluation_points=10,
|
||||
evaluation_passed=True,
|
||||
)
|
||||
|
||||
def test_supervisor_userprofile_certificate_summary(self):
|
||||
self.client.force_login(self.supervisor)
|
||||
|
||||
query = f"""query competenceCertificateForUserQuery(
|
||||
$courseSlug: String!,
|
||||
$courseSessionId: ID!,
|
||||
$userId: UUID!
|
||||
) {{
|
||||
competence_certificate_list_for_user(
|
||||
course_slug: $courseSlug,
|
||||
user_id: $userId
|
||||
) {{
|
||||
...CoursePageFields
|
||||
competence_certificates {{
|
||||
...CoursePageFields
|
||||
assignments {{
|
||||
...CoursePageFields
|
||||
assignment_type
|
||||
max_points
|
||||
completion(course_session_id: $courseSessionId) {{
|
||||
id
|
||||
completion_status
|
||||
submitted_at
|
||||
evaluation_points
|
||||
evaluation_max_points
|
||||
evaluation_passed
|
||||
__typename
|
||||
}}
|
||||
learning_content {{
|
||||
...CoursePageFields
|
||||
circle {{
|
||||
id
|
||||
title
|
||||
slug
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
}}
|
||||
|
||||
fragment CoursePageFields on CoursePageInterface {{
|
||||
title
|
||||
id
|
||||
slug
|
||||
content_type
|
||||
frontend_url
|
||||
__typename
|
||||
}}
|
||||
|
||||
"""
|
||||
variables = {
|
||||
"courseSessionId": str(self.course_session.id),
|
||||
"courseSlug": self.course.slug,
|
||||
"userId": str(self.member_one.id),
|
||||
}
|
||||
|
||||
# WHEN
|
||||
response = self.query(query, variables=variables)
|
||||
|
||||
# THEN
|
||||
self.assertResponseNoErrors(response)
|
||||
|
||||
certificates = response.json()["data"]["competence_certificate_list_for_user"][
|
||||
"competence_certificates"
|
||||
]
|
||||
self.assertEqual(len(certificates), 1)
|
||||
|
||||
assignments = certificates[0]["assignments"]
|
||||
self.assertEqual(len(assignments), 2)
|
||||
|
||||
completion1 = assignments[0]["completion"]
|
||||
self.assertEqual(completion1["completion_status"], "SUBMITTED")
|
||||
self.assertEqual(completion1["evaluation_points"], 5)
|
||||
self.assertEqual(completion1["evaluation_max_points"], 10)
|
||||
self.assertEqual(completion1["evaluation_passed"], False)
|
||||
|
||||
completion2 = assignments[1]["completion"]
|
||||
self.assertIsNone(completion2)
|
||||
|
||||
def test_member_cannot_see_other_user_certificate_summary(self):
|
||||
self.client.force_login(self.member_one)
|
||||
|
||||
query = f"""query competenceCertificateForUserQuery(
|
||||
$courseSlug: String!,
|
||||
$courseSessionId: ID!,
|
||||
$userId: UUID!
|
||||
) {{
|
||||
competence_certificate_list_for_user(
|
||||
course_slug: $courseSlug,
|
||||
user_id: $userId
|
||||
) {{
|
||||
...CoursePageFields
|
||||
competence_certificates {{
|
||||
...CoursePageFields
|
||||
assignments {{
|
||||
...CoursePageFields
|
||||
assignment_type
|
||||
max_points
|
||||
completion(course_session_id: $courseSessionId) {{
|
||||
id
|
||||
completion_status
|
||||
submitted_at
|
||||
evaluation_points
|
||||
evaluation_max_points
|
||||
evaluation_passed
|
||||
__typename
|
||||
}}
|
||||
learning_content {{
|
||||
...CoursePageFields
|
||||
circle {{
|
||||
id
|
||||
title
|
||||
slug
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
}}
|
||||
|
||||
fragment CoursePageFields on CoursePageInterface {{
|
||||
title
|
||||
id
|
||||
slug
|
||||
content_type
|
||||
frontend_url
|
||||
__typename
|
||||
}}
|
||||
|
||||
"""
|
||||
variables = {
|
||||
"courseSessionId": str(self.course_session.id),
|
||||
"courseSlug": self.course.slug,
|
||||
"userId": str(self.member_two.id),
|
||||
}
|
||||
|
||||
# WHEN
|
||||
response = self.query(query, variables=variables)
|
||||
|
||||
# THEN
|
||||
self.assertResponseNoErrors(response)
|
||||
self.assertIsNone(
|
||||
response.json()["data"]["competence_certificate_list_for_user"]
|
||||
)
|
||||
|
||||
def test_member_userprofile_certificate_summary(self):
|
||||
self.client.force_login(self.member_one)
|
||||
|
||||
query = f"""query competenceCertificateForUserQuery(
|
||||
$courseSlug: String!,
|
||||
$courseSessionId: ID!,
|
||||
) {{
|
||||
competence_certificate_list(
|
||||
course_slug: $courseSlug,
|
||||
) {{
|
||||
...CoursePageFields
|
||||
competence_certificates {{
|
||||
...CoursePageFields
|
||||
assignments {{
|
||||
...CoursePageFields
|
||||
assignment_type
|
||||
max_points
|
||||
completion(course_session_id: $courseSessionId) {{
|
||||
id
|
||||
completion_status
|
||||
submitted_at
|
||||
evaluation_points
|
||||
evaluation_max_points
|
||||
evaluation_passed
|
||||
__typename
|
||||
}}
|
||||
learning_content {{
|
||||
...CoursePageFields
|
||||
circle {{
|
||||
id
|
||||
title
|
||||
slug
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
}}
|
||||
|
||||
fragment CoursePageFields on CoursePageInterface {{
|
||||
title
|
||||
id
|
||||
slug
|
||||
content_type
|
||||
frontend_url
|
||||
__typename
|
||||
}}
|
||||
|
||||
"""
|
||||
variables = {
|
||||
"courseSessionId": str(self.course_session.id),
|
||||
"courseSlug": self.course.slug,
|
||||
}
|
||||
|
||||
# WHEN
|
||||
response = self.query(query, variables=variables)
|
||||
|
||||
# THEN
|
||||
self.assertResponseNoErrors(response)
|
||||
|
||||
certificates = response.json()["data"]["competence_certificate_list"][
|
||||
"competence_certificates"
|
||||
]
|
||||
self.assertEqual(len(certificates), 1)
|
||||
|
||||
assignments = certificates[0]["assignments"]
|
||||
self.assertEqual(len(assignments), 2)
|
||||
|
||||
completion1 = assignments[0]["completion"]
|
||||
self.assertEqual(completion1["completion_status"], "SUBMITTED")
|
||||
self.assertEqual(completion1["evaluation_points"], 5)
|
||||
self.assertEqual(completion1["evaluation_max_points"], 10)
|
||||
self.assertEqual(completion1["evaluation_passed"], False)
|
||||
|
||||
completion2 = assignments[1]["completion"]
|
||||
self.assertIsNone(completion2)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue