Merged in feature/improve-course-session-loading-take2 (pull request #218)
Feature/improve course session loading take2
This commit is contained in:
commit
ea8ab0adcb
|
|
@ -17,7 +17,7 @@ const assignmentType = t(props.dueDate.assignment_type_translation_key);
|
||||||
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
const courseSession = courseSessionsStore.allCourseSessions.find(
|
const courseSession = courseSessionsStore.allCourseSessions.find(
|
||||||
(cs: CourseSession) => cs.id === props.dueDate.course_session
|
(cs: CourseSession) => cs.id === props.dueDate.course_session_id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!courseSession) {
|
if (!courseSession) {
|
||||||
|
|
@ -28,10 +28,10 @@ const isExpert = courseSessionsStore.hasCockpit(courseSession);
|
||||||
const url = isExpert ? props.dueDate.url_expert : props.dueDate.url;
|
const url = isExpert ? props.dueDate.url_expert : props.dueDate.url;
|
||||||
|
|
||||||
const courseSessionTitle = computed(() => {
|
const courseSessionTitle = computed(() => {
|
||||||
if (props.dueDate.course_session) {
|
if (props.dueDate.course_session_id) {
|
||||||
return (
|
return (
|
||||||
courseSessionsStore.getCourseSessionById(props.dueDate.course_session)?.title ??
|
courseSessionsStore.getCourseSessionById(props.dueDate.course_session_id)
|
||||||
""
|
?.title ?? ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
export const dueDatesTestData = () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
start: dayjs("2023-06-14T15:00:00+02:00"),
|
|
||||||
end: dayjs("2023-06-14T18:00:00+02:00"),
|
|
||||||
title: "Präsenzkurs Kickoff",
|
|
||||||
url: "/course/überbetriebliche-kurse/learn/kickoff/präsenzkurs-kickoff",
|
|
||||||
course_session: 2,
|
|
||||||
page: 383,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
start: dayjs("2023-06-15T15:00:00+02:00"),
|
|
||||||
end: dayjs("2023-06-15T18:00:00+02:00"),
|
|
||||||
title: "Präsenzkurs Basis",
|
|
||||||
url: "/course/überbetriebliche-kurse/learn/basis/präsenzkurs-basis",
|
|
||||||
course_session: 2,
|
|
||||||
page: 397,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
start: dayjs("2023-06-16T15:00:00+02:00"),
|
|
||||||
end: dayjs("2023-06-16T18:00:00+02:00"),
|
|
||||||
title: "Präsenzkurs Fahrzeug",
|
|
||||||
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
|
||||||
course_session: 2,
|
|
||||||
page: 413,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
start: dayjs("2023-06-16T15:00:00+02:00"),
|
|
||||||
end: dayjs("2023-06-16T18:00:00+02:00"),
|
|
||||||
title: "Präsenzkurs Flugzeuge",
|
|
||||||
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
|
||||||
course_session: 2,
|
|
||||||
page: 413,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
start: dayjs("2023-07-16T11:00:00+02:00"),
|
|
||||||
end: dayjs("2023-07-16T18:00:00+02:00"),
|
|
||||||
title: "Präsenzkurs Motorräder",
|
|
||||||
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
|
||||||
course_session: 2,
|
|
||||||
page: 413,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
start: dayjs("2023-08-09T15:00:00+02:00"),
|
|
||||||
end: dayjs("2023-08-09T19:00:00+02:00"),
|
|
||||||
title: "Präsenzkurs Fahrräder",
|
|
||||||
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
|
||||||
course_session: 2,
|
|
||||||
page: 413,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
@ -37,7 +37,7 @@ import { onMounted, ref, watch } from "vue";
|
||||||
import type { Circle } from "@/services/circle";
|
import type { Circle } from "@/services/circle";
|
||||||
|
|
||||||
interface FeedbackSummary {
|
interface FeedbackSummary {
|
||||||
circle_id: number;
|
circle_id: string;
|
||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ function makeSummary(
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
selctedCircles: string[];
|
selctedCircles: string[];
|
||||||
circles: Circle[];
|
circles: Circle[];
|
||||||
courseSessionId: number;
|
courseSessionId: string;
|
||||||
url: string;
|
url: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,11 @@ type Story = StoryObj<typeof AccountMenuContent>;
|
||||||
|
|
||||||
const courseSessions = [
|
const courseSessions = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: "1",
|
||||||
title: "Bern 2023 a",
|
title: "Bern 2023 a",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: "2",
|
||||||
title: "Zürich 2023 a",
|
title: "Zürich 2023 a",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import type { CourseSession } from "@/types";
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSessions: CourseSession[];
|
courseSessions: CourseSession[];
|
||||||
user: UserState;
|
user: UserState;
|
||||||
selectedCourseSession?: number;
|
selectedCourseSession?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits(["selectCourseSession", "logout"]);
|
const emit = defineEmits(["selectCourseSession", "logout"]);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import { useRouteLookups } from "@/utils/route";
|
import { useRouteLookups } from "@/utils/route";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
import { getCompetenceBaseUrl } from "@/utils/utils";
|
import { getCompetenceNaviUrl, getLearningPathUrl } from "@/utils/utils";
|
||||||
|
|
||||||
const { inCompetenceProfile, inLearningPath } = useRouteLookups();
|
const { inCompetenceProfile, inLearningPath } = useRouteLookups();
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
@ -24,7 +24,7 @@ const { t } = useTranslation();
|
||||||
<div class="flex space-x-8">
|
<div class="flex space-x-8">
|
||||||
<router-link
|
<router-link
|
||||||
data-cy="preview-learn-path-link"
|
data-cy="preview-learn-path-link"
|
||||||
:to="courseSession.learning_path_url"
|
:to="getLearningPathUrl(courseSession.course.slug)"
|
||||||
class="preview-nav-item"
|
class="preview-nav-item"
|
||||||
:class="{ 'preview-nav-item--active': inLearningPath() }"
|
:class="{ 'preview-nav-item--active': inLearningPath() }"
|
||||||
>
|
>
|
||||||
|
|
@ -33,7 +33,7 @@ const { t } = useTranslation();
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
data-cy="preview-competence-profile-link"
|
data-cy="preview-competence-profile-link"
|
||||||
:to="getCompetenceBaseUrl(courseSession)"
|
:to="getCompetenceNaviUrl(courseSession.course.slug)"
|
||||||
class="preview-nav-item"
|
class="preview-nav-item"
|
||||||
:class="{ 'preview-nav-item--active': inCompetenceProfile() }"
|
:class="{ 'preview-nav-item--active': inCompetenceProfile() }"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export interface Item {
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
items: CourseSession[];
|
items: CourseSession[];
|
||||||
selected?: number;
|
selected?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,12 @@ import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
||||||
import { computed, onMounted, reactive } from "vue";
|
import { computed, onMounted, reactive } from "vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import CoursePreviewBar from "@/components/header/CoursePreviewBar.vue";
|
import CoursePreviewBar from "@/components/header/CoursePreviewBar.vue";
|
||||||
import { getCompetenceBaseUrl } from "@/utils/utils";
|
import {
|
||||||
|
getCockpitUrl,
|
||||||
|
getCompetenceNaviUrl,
|
||||||
|
getLearningPathUrl,
|
||||||
|
getMediaCenterUrl,
|
||||||
|
} from "@/utils/utils";
|
||||||
|
|
||||||
log.debug("MainNavigationBar created");
|
log.debug("MainNavigationBar created");
|
||||||
|
|
||||||
|
|
@ -71,7 +76,9 @@ onMounted(() => {
|
||||||
v-if="userStore.loggedIn"
|
v-if="userStore.loggedIn"
|
||||||
:show="state.showMobileNavigationMenu"
|
:show="state.showMobileNavigationMenu"
|
||||||
:course-session="courseSessionsStore.currentCourseSession"
|
:course-session="courseSessionsStore.currentCourseSession"
|
||||||
:media-url="courseSessionsStore.currentCourseSession?.media_library_url"
|
:media-url="
|
||||||
|
getMediaCenterUrl(courseSessionsStore.currentCourseSession?.course?.slug)
|
||||||
|
"
|
||||||
:user="userStore"
|
:user="userStore"
|
||||||
@closemodal="state.showMobileNavigationMenu = false"
|
@closemodal="state.showMobileNavigationMenu = false"
|
||||||
@logout="userStore.handleLogout()"
|
@logout="userStore.handleLogout()"
|
||||||
|
|
@ -129,7 +136,11 @@ onMounted(() => {
|
||||||
<template v-if="courseSessionsStore.currentCourseSessionHasCockpit">
|
<template v-if="courseSessionsStore.currentCourseSessionHasCockpit">
|
||||||
<router-link
|
<router-link
|
||||||
data-cy="navigation-cockpit-link"
|
data-cy="navigation-cockpit-link"
|
||||||
:to="`${courseSessionsStore.currentCourseSession.course_url}/cockpit`"
|
:to="
|
||||||
|
getCockpitUrl(
|
||||||
|
courseSessionsStore.currentCourseSession.course.slug
|
||||||
|
)
|
||||||
|
"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
:class="{ 'nav-item--active': inCockpit() }"
|
:class="{ 'nav-item--active': inCockpit() }"
|
||||||
>
|
>
|
||||||
|
|
@ -138,7 +149,11 @@ onMounted(() => {
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
data-cy="navigation-preview-link"
|
data-cy="navigation-preview-link"
|
||||||
:to="courseSessionsStore.currentCourseSession.learning_path_url"
|
:to="
|
||||||
|
getLearningPathUrl(
|
||||||
|
courseSessionsStore.currentCourseSession.course.slug
|
||||||
|
)
|
||||||
|
"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
>
|
>
|
||||||
|
|
@ -151,7 +166,11 @@ onMounted(() => {
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<router-link
|
<router-link
|
||||||
data-cy="navigation-learning-path-link"
|
data-cy="navigation-learning-path-link"
|
||||||
:to="courseSessionsStore.currentCourseSession.learning_path_url"
|
:to="
|
||||||
|
getLearningPathUrl(
|
||||||
|
courseSessionsStore.currentCourseSession.course.slug
|
||||||
|
)
|
||||||
|
"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
:class="{ 'nav-item--active': inLearningPath() }"
|
:class="{ 'nav-item--active': inLearningPath() }"
|
||||||
>
|
>
|
||||||
|
|
@ -161,7 +180,9 @@ onMounted(() => {
|
||||||
<router-link
|
<router-link
|
||||||
data-cy="navigation-competence-profile-link"
|
data-cy="navigation-competence-profile-link"
|
||||||
:to="
|
:to="
|
||||||
getCompetenceBaseUrl(courseSessionsStore.currentCourseSession)
|
getCompetenceNaviUrl(
|
||||||
|
courseSessionsStore.currentCourseSession.course.slug
|
||||||
|
)
|
||||||
"
|
"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
||||||
|
|
@ -176,7 +197,11 @@ onMounted(() => {
|
||||||
<div class="flex items-stretch justify-start space-x-8">
|
<div class="flex items-stretch justify-start space-x-8">
|
||||||
<router-link
|
<router-link
|
||||||
v-if="inCourse() && courseSessionsStore.currentCourseSession"
|
v-if="inCourse() && courseSessionsStore.currentCourseSession"
|
||||||
:to="courseSessionsStore.currentCourseSession.media_library_url"
|
:to="
|
||||||
|
getMediaCenterUrl(
|
||||||
|
courseSessionsStore.currentCourseSession.course.slug
|
||||||
|
)
|
||||||
|
"
|
||||||
data-cy="medialibrary-link"
|
data-cy="medialibrary-link"
|
||||||
class="nav-item-no-mobile"
|
class="nav-item-no-mobile"
|
||||||
:class="{ 'nav-item--active': inMediaLibrary() }"
|
:class="{ 'nav-item--active': inMediaLibrary() }"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,12 @@ import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type { UserState } from "@/stores/user";
|
import type { UserState } from "@/stores/user";
|
||||||
import type { CourseSession } from "@/types";
|
import type { CourseSession } from "@/types";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getCompetenceBaseUrl } from "@/utils/utils";
|
import {
|
||||||
|
getCockpitUrl,
|
||||||
|
getCompetenceNaviUrl,
|
||||||
|
getLearningPathUrl,
|
||||||
|
getMediaCenterUrl,
|
||||||
|
} from "@/utils/utils";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
@ -60,7 +65,7 @@ const courseSessionsStore = useCourseSessionsStore();
|
||||||
<li class="mb-6">
|
<li class="mb-6">
|
||||||
<button
|
<button
|
||||||
data-cy="navigation-mobile-cockpit-link"
|
data-cy="navigation-mobile-cockpit-link"
|
||||||
@click="clickLink(`${courseSession.course_url}/cockpit`)"
|
@click="clickLink(getCockpitUrl(courseSession.course.slug))"
|
||||||
>
|
>
|
||||||
{{ $t("cockpit.title") }}
|
{{ $t("cockpit.title") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -68,7 +73,7 @@ const courseSessionsStore = useCourseSessionsStore();
|
||||||
<li class="mb-6">
|
<li class="mb-6">
|
||||||
<button
|
<button
|
||||||
data-cy="navigation-mobile-preview-link"
|
data-cy="navigation-mobile-preview-link"
|
||||||
@click="clickLink(courseSession.learning_path_url)"
|
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
||||||
>
|
>
|
||||||
{{ $t("a.VorschauTeilnehmer") }}
|
{{ $t("a.VorschauTeilnehmer") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -78,7 +83,7 @@ const courseSessionsStore = useCourseSessionsStore();
|
||||||
<li class="mb-6">
|
<li class="mb-6">
|
||||||
<button
|
<button
|
||||||
data-cy="navigation-mobile-learning-path-link"
|
data-cy="navigation-mobile-learning-path-link"
|
||||||
@click="clickLink(courseSession.learning_path_url)"
|
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
||||||
>
|
>
|
||||||
{{ $t("general.learningPath") }}
|
{{ $t("general.learningPath") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -86,7 +91,7 @@ const courseSessionsStore = useCourseSessionsStore();
|
||||||
<li class="mb-6">
|
<li class="mb-6">
|
||||||
<button
|
<button
|
||||||
data-cy="navigation-mobile-competence-profile-link"
|
data-cy="navigation-mobile-competence-profile-link"
|
||||||
@click="clickLink(getCompetenceBaseUrl(courseSession))"
|
@click="clickLink(getCompetenceNaviUrl(courseSession.course.slug))"
|
||||||
>
|
>
|
||||||
{{ $t("competences.title") }}
|
{{ $t("competences.title") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -95,7 +100,7 @@ const courseSessionsStore = useCourseSessionsStore();
|
||||||
<li class="mb-6">
|
<li class="mb-6">
|
||||||
<button
|
<button
|
||||||
data-cy="medialibrary-link"
|
data-cy="medialibrary-link"
|
||||||
@click="clickLink(`${courseSession?.media_library_url}`)"
|
@click="clickLink(getMediaCenterUrl(courseSession.course.slug))"
|
||||||
>
|
>
|
||||||
{{ $t("a.Mediathek") }}
|
{{ $t("a.Mediathek") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,22 @@ export interface Props {
|
||||||
diagramType?: DiagramType;
|
diagramType?: DiagramType;
|
||||||
learningPath: LearningPath;
|
learningPath: LearningPath;
|
||||||
// set to undefined (default) to show all circles
|
// set to undefined (default) to show all circles
|
||||||
showCircleTranslationKeys?: string[];
|
showCircleSlugs?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
diagramType: "horizontal",
|
diagramType: "horizontal",
|
||||||
showCircleTranslationKeys: undefined,
|
showCircleSlugs: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const circles = computed(() =>
|
const circles = computed(() => {
|
||||||
props.learningPath.circles.filter(
|
if (props.showCircleSlugs?.length) {
|
||||||
(c) => props.showCircleTranslationKeys?.includes(c.translation_key) ?? true
|
return props.learningPath.circles.filter(
|
||||||
)
|
(c) => props.showCircleSlugs?.includes(c.slug) ?? true
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
return props.learningPath.circles;
|
||||||
|
});
|
||||||
|
|
||||||
const wrapperClasses = computed(() => {
|
const wrapperClasses = computed(() => {
|
||||||
let classes = "flex my-5";
|
let classes = "flex my-5";
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const emit = defineEmits<{
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
modelValue: () => {
|
modelValue: () => {
|
||||||
return {
|
return {
|
||||||
id: -1,
|
id: "-1",
|
||||||
name: "",
|
name: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type { CourseSession } from "@/types";
|
import type { CourseSession, CourseSessionDetail } from "@/types";
|
||||||
|
import { useQuery } from "@urql/vue";
|
||||||
|
|
||||||
|
import { COURSE_SESSION_DETAIL_QUERY } from "@/graphql/queries";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import type { ComputedRef } from "vue";
|
import type { ComputedRef } from "vue";
|
||||||
import { computed } from "vue";
|
import { computed, ref, watchEffect } from "vue";
|
||||||
|
|
||||||
export function useCurrentCourseSession() {
|
export function useCurrentCourseSession() {
|
||||||
/**
|
/**
|
||||||
|
|
@ -28,3 +32,94 @@ export function useCurrentCourseSession() {
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useCourseSessionDetailQuery(courSessionId?: string) {
|
||||||
|
if (!courSessionId) {
|
||||||
|
courSessionId = useCurrentCourseSession().value.id;
|
||||||
|
}
|
||||||
|
const queryResult = useQuery({
|
||||||
|
query: COURSE_SESSION_DETAIL_QUERY,
|
||||||
|
variables: {
|
||||||
|
courseSessionId: courSessionId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const courseSessionDetail = computed(() => {
|
||||||
|
return queryResult.data.value?.course_session as CourseSessionDetail | undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
function findAssignmentByAssignmentId(assignmentId: string) {
|
||||||
|
return (courseSessionDetail.value?.assignments ?? []).find((a) => {
|
||||||
|
return a.learning_content?.content_assignment?.id === assignmentId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAssignment(learningContentId: string) {
|
||||||
|
return (courseSessionDetail.value?.assignments ?? []).find((a) => {
|
||||||
|
return a.learning_content.id === learningContentId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findEdoniqTest(learningContentId: string) {
|
||||||
|
return (courseSessionDetail.value?.edoniq_tests ?? []).find((e) => {
|
||||||
|
return e.learning_content.id === learningContentId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAttendanceCourse(learningContentId: string) {
|
||||||
|
return (courseSessionDetail.value?.attendance_courses ?? []).find((e) => {
|
||||||
|
return e.learning_content.id === learningContentId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findUser(userId: string) {
|
||||||
|
return (courseSessionDetail.value?.users ?? []).find((u) => {
|
||||||
|
return u.user_id === userId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findCurrentUser() {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const userId = userStore.id;
|
||||||
|
return findUser(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterMembers() {
|
||||||
|
return (courseSessionDetail.value?.users ?? []).filter((u) => {
|
||||||
|
return u.role === "MEMBER";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterCircleExperts(circleSlug: string) {
|
||||||
|
return (courseSessionDetail.value?.users ?? []).filter((u) => {
|
||||||
|
return u.role === "EXPERT" && u.circles.map((c) => c.slug).includes(circleSlug);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataLoaded = ref(false);
|
||||||
|
|
||||||
|
function waitForData() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
watchEffect(() => {
|
||||||
|
if (queryResult.data.value) {
|
||||||
|
dataLoaded.value = true;
|
||||||
|
resolve(queryResult.data.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...queryResult,
|
||||||
|
courseSessionDetail,
|
||||||
|
waitForData,
|
||||||
|
findAssignmentByAssignmentId,
|
||||||
|
findAssignment,
|
||||||
|
findEdoniqTest,
|
||||||
|
findAttendanceCourse,
|
||||||
|
findUser,
|
||||||
|
findCurrentUser,
|
||||||
|
filterMembers,
|
||||||
|
filterCircleExperts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,9 @@ const documents = {
|
||||||
"\n fragment CoursePageFields on CoursePageInterface {\n title\n id\n slug\n content_type\n frontend_url\n }\n": types.CoursePageFieldsFragmentDoc,
|
"\n fragment CoursePageFields on CoursePageInterface {\n title\n id\n slug\n content_type\n frontend_url\n }\n": types.CoursePageFieldsFragmentDoc,
|
||||||
"\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 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 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 }\n assignment_user {\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
|
"\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 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 }\n assignment_user {\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
|
||||||
"\n query courseQuery($courseId: Int!) {\n course(id: $courseId) {\n id\n slug\n title\n category_name\n learning_path {\n id\n }\n }\n }\n": types.CourseQueryDocument,
|
"\n query courseQuery($courseId: ID!) {\n course(id: $courseId) {\n id\n slug\n title\n category_name\n learning_path {\n id\n }\n }\n }\n": types.CourseQueryDocument,
|
||||||
"\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 title\n id\n slug\n content_type\n frontend_url\n circle {\n ...CoursePageFields\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
|
"\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 title\n id\n slug\n content_type\n frontend_url\n circle {\n ...CoursePageFields\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
|
||||||
|
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\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 mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
|
"\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -60,11 +61,15 @@ 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.
|
* 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($courseId: Int!) {\n course(id: $courseId) {\n id\n slug\n title\n category_name\n learning_path {\n id\n }\n }\n }\n"): (typeof documents)["\n query courseQuery($courseId: Int!) {\n course(id: $courseId) {\n id\n slug\n title\n category_name\n learning_path {\n id\n }\n }\n }\n"];
|
export function graphql(source: "\n query courseQuery($courseId: ID!) {\n course(id: $courseId) {\n id\n slug\n title\n category_name\n learning_path {\n id\n }\n }\n }\n"): (typeof documents)["\n query courseQuery($courseId: ID!) {\n course(id: $courseId) {\n id\n slug\n title\n category_name\n learning_path {\n id\n }\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
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 title\n id\n slug\n content_type\n frontend_url\n circle {\n ...CoursePageFields\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 title\n id\n slug\n content_type\n frontend_url\n circle {\n ...CoursePageFields\n }\n }\n }\n }\n }\n }\n"];
|
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 title\n id\n slug\n content_type\n frontend_url\n circle {\n ...CoursePageFields\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 title\n id\n slug\n content_type\n frontend_url\n circle {\n ...CoursePageFields\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 courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\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"): (typeof documents)["\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\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"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -8,11 +8,12 @@ type Query {
|
||||||
learning_content_learning_module: LearningContentLearningModuleObjectType
|
learning_content_learning_module: LearningContentLearningModuleObjectType
|
||||||
learning_content_placeholder: LearningContentPlaceholderObjectType
|
learning_content_placeholder: LearningContentPlaceholderObjectType
|
||||||
learning_content_rich_text: LearningContentRichTextObjectType
|
learning_content_rich_text: LearningContentRichTextObjectType
|
||||||
learning_content_test: LearningContentTestObjectType
|
learning_content_test: LearningContentEdoniqTestObjectType
|
||||||
learning_content_video: LearningContentVideoObjectType
|
learning_content_video: LearningContentVideoObjectType
|
||||||
learning_content_document_list: LearningContentDocumentListObjectType
|
learning_content_document_list: LearningContentDocumentListObjectType
|
||||||
course_session_attendance_course(id: ID!, assignment_user_id: ID): CourseSessionAttendanceCourseType
|
course_session_attendance_course(id: ID!, assignment_user_id: ID): CourseSessionAttendanceCourseObjectType
|
||||||
course(id: Int): CourseObjectType
|
course(id: ID): CourseObjectType
|
||||||
|
course_session(id: ID): CourseSessionObjectType
|
||||||
competence_certificate(id: ID, slug: String): CompetenceCertificateObjectType
|
competence_certificate(id: ID, slug: String): CompetenceCertificateObjectType
|
||||||
competence_certificate_list(id: ID, slug: String, course_id: ID, course_slug: String): CompetenceCertificateListObjectType
|
competence_certificate_list(id: ID, slug: String, course_id: ID, course_slug: String): CompetenceCertificateListObjectType
|
||||||
assignment(id: ID, slug: String): AssignmentObjectType
|
assignment(id: ID, slug: String): AssignmentObjectType
|
||||||
|
|
@ -307,12 +308,167 @@ type AssignmentCompletionObjectType {
|
||||||
edoniq_extended_time_flag: Boolean!
|
edoniq_extended_time_flag: Boolean!
|
||||||
assignment_user: UserType!
|
assignment_user: UserType!
|
||||||
assignment: AssignmentObjectType!
|
assignment: AssignmentObjectType!
|
||||||
|
course_session: CourseSessionObjectType!
|
||||||
completion_status: AssignmentAssignmentCompletionCompletionStatusChoices!
|
completion_status: AssignmentAssignmentCompletionCompletionStatusChoices!
|
||||||
completion_data: GenericScalar
|
completion_data: GenericScalar
|
||||||
additional_json_data: JSONString!
|
additional_json_data: JSONString!
|
||||||
learning_content_page_id: ID
|
learning_content_page_id: ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CourseSessionObjectType {
|
||||||
|
id: ID!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
course: CourseObjectType!
|
||||||
|
title: String!
|
||||||
|
start_date: Date
|
||||||
|
end_date: Date
|
||||||
|
attendance_courses: [CourseSessionAttendanceCourseObjectType]
|
||||||
|
assignments: [CourseSessionAssignmentObjectType]
|
||||||
|
edoniq_tests: [CourseSessionEdoniqTestObjectType]
|
||||||
|
documents: [CircleDocumentObjectType]
|
||||||
|
users: [CourseSessionUserObjectsType]
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The `Date` scalar type represents a Date
|
||||||
|
value as specified by
|
||||||
|
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
|
||||||
|
"""
|
||||||
|
scalar Date
|
||||||
|
|
||||||
|
type CourseSessionAttendanceCourseObjectType {
|
||||||
|
id: ID!
|
||||||
|
learning_content: LearningContentAttendanceCourseObjectType
|
||||||
|
due_date: DueDateObjectType
|
||||||
|
location: String!
|
||||||
|
trainer: String!
|
||||||
|
course_session_id: ID
|
||||||
|
learning_content_id: ID
|
||||||
|
attendance_user_list: [AttendanceUserObjectType]
|
||||||
|
}
|
||||||
|
|
||||||
|
type LearningContentAttendanceCourseObjectType implements LearningContentInterface {
|
||||||
|
id: ID
|
||||||
|
title: String
|
||||||
|
slug: String
|
||||||
|
content_type: String
|
||||||
|
live: Boolean
|
||||||
|
translation_key: String
|
||||||
|
frontend_url: String
|
||||||
|
circle: CircleObjectType
|
||||||
|
course: CourseObjectType
|
||||||
|
minutes: Int
|
||||||
|
description: String
|
||||||
|
content: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type DueDateObjectType {
|
||||||
|
id: ID!
|
||||||
|
|
||||||
|
"""Startdatum ist Pflicht"""
|
||||||
|
start: DateTime
|
||||||
|
|
||||||
|
"""Enddatum ist optional"""
|
||||||
|
end: DateTime
|
||||||
|
|
||||||
|
"""Nur aktivieren, wenn man die Felder manuell überschreiben will"""
|
||||||
|
manual_override_fields: Boolean!
|
||||||
|
|
||||||
|
"""Title wird standarmässig vom LearningContent übernommen"""
|
||||||
|
title: String!
|
||||||
|
|
||||||
|
"""Translation Key aus dem Frontend"""
|
||||||
|
assignment_type_translation_key: String!
|
||||||
|
|
||||||
|
"""Translation Key aus dem Frontend"""
|
||||||
|
date_type_translation_key: String!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Überschreibt den Untertitel bei `assignment_type_translation_key` und `date_type_translation_key`
|
||||||
|
"""
|
||||||
|
subtitle: String!
|
||||||
|
|
||||||
|
"""
|
||||||
|
URL wird vom LearningContent übernommen (sichtbar für Member/Teilnehmer)
|
||||||
|
"""
|
||||||
|
url: String!
|
||||||
|
course_session: CourseSessionObjectType!
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttendanceUserObjectType {
|
||||||
|
user_id: UUID!
|
||||||
|
status: AttendanceUserStatus!
|
||||||
|
first_name: String
|
||||||
|
last_name: String
|
||||||
|
email: String
|
||||||
|
}
|
||||||
|
|
||||||
|
"""An enumeration."""
|
||||||
|
enum AttendanceUserStatus {
|
||||||
|
PRESENT
|
||||||
|
ABSENT
|
||||||
|
}
|
||||||
|
|
||||||
|
type CourseSessionAssignmentObjectType {
|
||||||
|
id: ID!
|
||||||
|
learning_content: LearningContentAssignmentObjectType
|
||||||
|
submission_deadline: DueDateObjectType
|
||||||
|
evaluation_deadline: DueDateObjectType
|
||||||
|
course_session_id: ID
|
||||||
|
learning_content_id: ID
|
||||||
|
}
|
||||||
|
|
||||||
|
type CourseSessionEdoniqTestObjectType {
|
||||||
|
id: ID!
|
||||||
|
learning_content: LearningContentEdoniqTestObjectType
|
||||||
|
deadline: DueDateObjectType
|
||||||
|
course_session_id: ID
|
||||||
|
learning_content_id: ID
|
||||||
|
}
|
||||||
|
|
||||||
|
type LearningContentEdoniqTestObjectType implements LearningContentInterface {
|
||||||
|
content_assignment: AssignmentObjectType
|
||||||
|
id: ID
|
||||||
|
title: String
|
||||||
|
slug: String
|
||||||
|
content_type: String
|
||||||
|
live: Boolean
|
||||||
|
translation_key: String
|
||||||
|
frontend_url: String
|
||||||
|
circle: CircleObjectType
|
||||||
|
course: CourseObjectType
|
||||||
|
minutes: Int
|
||||||
|
description: String
|
||||||
|
content: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type CircleDocumentObjectType {
|
||||||
|
id: UUID!
|
||||||
|
name: String!
|
||||||
|
course_session: CourseSessionObjectType!
|
||||||
|
learning_sequence: LearningSequenceObjectType!
|
||||||
|
file_name: String
|
||||||
|
url: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type CourseSessionUserObjectsType {
|
||||||
|
id: UUID!
|
||||||
|
role: String
|
||||||
|
user_id: UUID
|
||||||
|
first_name: String
|
||||||
|
last_name: String
|
||||||
|
email: String
|
||||||
|
avatar_url: String
|
||||||
|
circles: [CourseSessionUserExpertCircleType]
|
||||||
|
}
|
||||||
|
|
||||||
|
type CourseSessionUserExpertCircleType {
|
||||||
|
id: ID
|
||||||
|
title: String
|
||||||
|
slug: String
|
||||||
|
}
|
||||||
|
|
||||||
"""An enumeration."""
|
"""An enumeration."""
|
||||||
enum AssignmentAssignmentCompletionCompletionStatusChoices {
|
enum AssignmentAssignmentCompletionCompletionStatusChoices {
|
||||||
"""IN_PROGRESS"""
|
"""IN_PROGRESS"""
|
||||||
|
|
@ -361,21 +517,6 @@ enum LearnpathLearningContentAssignmentAssignmentTypeChoices {
|
||||||
EDONIQ_TEST
|
EDONIQ_TEST
|
||||||
}
|
}
|
||||||
|
|
||||||
type LearningContentAttendanceCourseObjectType implements LearningContentInterface {
|
|
||||||
id: ID
|
|
||||||
title: String
|
|
||||||
slug: String
|
|
||||||
content_type: String
|
|
||||||
live: Boolean
|
|
||||||
translation_key: String
|
|
||||||
frontend_url: String
|
|
||||||
circle: CircleObjectType
|
|
||||||
course: CourseObjectType
|
|
||||||
minutes: Int
|
|
||||||
description: String
|
|
||||||
content: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type LearningContentFeedbackObjectType implements LearningContentInterface {
|
type LearningContentFeedbackObjectType implements LearningContentInterface {
|
||||||
id: ID
|
id: ID
|
||||||
title: String
|
title: String
|
||||||
|
|
@ -436,22 +577,6 @@ type LearningContentRichTextObjectType implements LearningContentInterface {
|
||||||
content: String
|
content: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type LearningContentTestObjectType implements LearningContentInterface {
|
|
||||||
content_assignment: AssignmentObjectType
|
|
||||||
id: ID
|
|
||||||
title: String
|
|
||||||
slug: String
|
|
||||||
content_type: String
|
|
||||||
live: Boolean
|
|
||||||
translation_key: String
|
|
||||||
frontend_url: String
|
|
||||||
circle: CircleObjectType
|
|
||||||
course: CourseObjectType
|
|
||||||
minutes: Int
|
|
||||||
description: String
|
|
||||||
content: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type LearningContentVideoObjectType implements LearningContentInterface {
|
type LearningContentVideoObjectType implements LearningContentInterface {
|
||||||
id: ID
|
id: ID
|
||||||
title: String
|
title: String
|
||||||
|
|
@ -482,32 +607,6 @@ type LearningContentDocumentListObjectType implements LearningContentInterface {
|
||||||
content: String
|
content: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type CourseSessionAttendanceCourseType {
|
|
||||||
id: ID!
|
|
||||||
location: String!
|
|
||||||
trainer: String!
|
|
||||||
course_session_id: ID
|
|
||||||
learning_content_id: ID
|
|
||||||
due_date_id: ID
|
|
||||||
end: DateTime
|
|
||||||
start: DateTime
|
|
||||||
attendance_user_list: [AttendanceUserType]
|
|
||||||
}
|
|
||||||
|
|
||||||
type AttendanceUserType {
|
|
||||||
user_id: UUID!
|
|
||||||
status: AttendanceUserStatus!
|
|
||||||
first_name: String
|
|
||||||
last_name: String
|
|
||||||
email: String
|
|
||||||
}
|
|
||||||
|
|
||||||
"""An enumeration."""
|
|
||||||
enum AttendanceUserStatus {
|
|
||||||
PRESENT
|
|
||||||
ABSENT
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompetenceCertificateListObjectType implements CoursePageInterface {
|
type CompetenceCertificateListObjectType implements CoursePageInterface {
|
||||||
id: ID
|
id: ID
|
||||||
path: String!
|
path: String!
|
||||||
|
|
@ -577,7 +676,7 @@ type ErrorType {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttendanceCourseUserMutation {
|
type AttendanceCourseUserMutation {
|
||||||
course_session_attendance_course: CourseSessionAttendanceCourseType
|
course_session_attendance_course: CourseSessionAttendanceCourseObjectType
|
||||||
}
|
}
|
||||||
|
|
||||||
input AttendanceUserInputType {
|
input AttendanceUserInputType {
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,25 @@ export const AssignmentCompletionStatus = "AssignmentCompletionStatus";
|
||||||
export const AssignmentObjectType = "AssignmentObjectType";
|
export const AssignmentObjectType = "AssignmentObjectType";
|
||||||
export const AttendanceCourseUserMutation = "AttendanceCourseUserMutation";
|
export const AttendanceCourseUserMutation = "AttendanceCourseUserMutation";
|
||||||
export const AttendanceUserInputType = "AttendanceUserInputType";
|
export const AttendanceUserInputType = "AttendanceUserInputType";
|
||||||
|
export const AttendanceUserObjectType = "AttendanceUserObjectType";
|
||||||
export const AttendanceUserStatus = "AttendanceUserStatus";
|
export const AttendanceUserStatus = "AttendanceUserStatus";
|
||||||
export const AttendanceUserType = "AttendanceUserType";
|
|
||||||
export const Boolean = "Boolean";
|
export const Boolean = "Boolean";
|
||||||
|
export const CircleDocumentObjectType = "CircleDocumentObjectType";
|
||||||
export const CircleObjectType = "CircleObjectType";
|
export const CircleObjectType = "CircleObjectType";
|
||||||
export const CompetenceCertificateListObjectType = "CompetenceCertificateListObjectType";
|
export const CompetenceCertificateListObjectType = "CompetenceCertificateListObjectType";
|
||||||
export const CompetenceCertificateObjectType = "CompetenceCertificateObjectType";
|
export const CompetenceCertificateObjectType = "CompetenceCertificateObjectType";
|
||||||
export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
|
export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
|
||||||
export const CourseObjectType = "CourseObjectType";
|
export const CourseObjectType = "CourseObjectType";
|
||||||
export const CoursePageInterface = "CoursePageInterface";
|
export const CoursePageInterface = "CoursePageInterface";
|
||||||
export const CourseSessionAttendanceCourseType = "CourseSessionAttendanceCourseType";
|
export const CourseSessionAssignmentObjectType = "CourseSessionAssignmentObjectType";
|
||||||
|
export const CourseSessionAttendanceCourseObjectType = "CourseSessionAttendanceCourseObjectType";
|
||||||
|
export const CourseSessionEdoniqTestObjectType = "CourseSessionEdoniqTestObjectType";
|
||||||
|
export const CourseSessionObjectType = "CourseSessionObjectType";
|
||||||
|
export const CourseSessionUserExpertCircleType = "CourseSessionUserExpertCircleType";
|
||||||
|
export const CourseSessionUserObjectsType = "CourseSessionUserObjectsType";
|
||||||
|
export const Date = "Date";
|
||||||
export const DateTime = "DateTime";
|
export const DateTime = "DateTime";
|
||||||
|
export const DueDateObjectType = "DueDateObjectType";
|
||||||
export const ErrorType = "ErrorType";
|
export const ErrorType = "ErrorType";
|
||||||
export const FeedbackResponseObjectType = "FeedbackResponseObjectType";
|
export const FeedbackResponseObjectType = "FeedbackResponseObjectType";
|
||||||
export const Float = "Float";
|
export const Float = "Float";
|
||||||
|
|
@ -28,13 +36,13 @@ export const JSONString = "JSONString";
|
||||||
export const LearningContentAssignmentObjectType = "LearningContentAssignmentObjectType";
|
export const LearningContentAssignmentObjectType = "LearningContentAssignmentObjectType";
|
||||||
export const LearningContentAttendanceCourseObjectType = "LearningContentAttendanceCourseObjectType";
|
export const LearningContentAttendanceCourseObjectType = "LearningContentAttendanceCourseObjectType";
|
||||||
export const LearningContentDocumentListObjectType = "LearningContentDocumentListObjectType";
|
export const LearningContentDocumentListObjectType = "LearningContentDocumentListObjectType";
|
||||||
|
export const LearningContentEdoniqTestObjectType = "LearningContentEdoniqTestObjectType";
|
||||||
export const LearningContentFeedbackObjectType = "LearningContentFeedbackObjectType";
|
export const LearningContentFeedbackObjectType = "LearningContentFeedbackObjectType";
|
||||||
export const LearningContentInterface = "LearningContentInterface";
|
export const LearningContentInterface = "LearningContentInterface";
|
||||||
export const LearningContentLearningModuleObjectType = "LearningContentLearningModuleObjectType";
|
export const LearningContentLearningModuleObjectType = "LearningContentLearningModuleObjectType";
|
||||||
export const LearningContentMediaLibraryObjectType = "LearningContentMediaLibraryObjectType";
|
export const LearningContentMediaLibraryObjectType = "LearningContentMediaLibraryObjectType";
|
||||||
export const LearningContentPlaceholderObjectType = "LearningContentPlaceholderObjectType";
|
export const LearningContentPlaceholderObjectType = "LearningContentPlaceholderObjectType";
|
||||||
export const LearningContentRichTextObjectType = "LearningContentRichTextObjectType";
|
export const LearningContentRichTextObjectType = "LearningContentRichTextObjectType";
|
||||||
export const LearningContentTestObjectType = "LearningContentTestObjectType";
|
|
||||||
export const LearningContentVideoObjectType = "LearningContentVideoObjectType";
|
export const LearningContentVideoObjectType = "LearningContentVideoObjectType";
|
||||||
export const LearningPathObjectType = "LearningPathObjectType";
|
export const LearningPathObjectType = "LearningPathObjectType";
|
||||||
export const LearningSequenceObjectType = "LearningSequenceObjectType";
|
export const LearningSequenceObjectType = "LearningSequenceObjectType";
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export const graphqlClient = new Client({
|
||||||
cacheExchange({
|
cacheExchange({
|
||||||
schema: schema,
|
schema: schema,
|
||||||
keys: {
|
keys: {
|
||||||
AttendanceUserType: (data) => data?.user_id?.toString() ?? null,
|
AttendanceUserObjectType: (data) => data?.user_id?.toString() ?? null,
|
||||||
},
|
},
|
||||||
updates: {
|
updates: {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export const ASSIGNMENT_COMPLETION_QUERY = graphql(`
|
||||||
`);
|
`);
|
||||||
|
|
||||||
export const COURSE_QUERY = graphql(`
|
export const COURSE_QUERY = graphql(`
|
||||||
query courseQuery($courseId: Int!) {
|
query courseQuery($courseId: ID!) {
|
||||||
course(id: $courseId) {
|
course(id: $courseId) {
|
||||||
id
|
id
|
||||||
slug
|
slug
|
||||||
|
|
@ -122,3 +122,88 @@ export const COMPETENCE_NAVI_CERTIFICATE_QUERY = graphql(`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
export const COURSE_SESSION_DETAIL_QUERY = graphql(`
|
||||||
|
query courseSessionDetail($courseSessionId: ID!) {
|
||||||
|
course_session(id: $courseSessionId) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
course {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
users {
|
||||||
|
id
|
||||||
|
user_id
|
||||||
|
first_name
|
||||||
|
last_name
|
||||||
|
email
|
||||||
|
avatar_url
|
||||||
|
role
|
||||||
|
circles {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attendance_courses {
|
||||||
|
id
|
||||||
|
location
|
||||||
|
trainer
|
||||||
|
due_date {
|
||||||
|
id
|
||||||
|
start
|
||||||
|
end
|
||||||
|
}
|
||||||
|
learning_content_id
|
||||||
|
learning_content {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
circle {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assignments {
|
||||||
|
id
|
||||||
|
submission_deadline {
|
||||||
|
id
|
||||||
|
start
|
||||||
|
}
|
||||||
|
evaluation_deadline {
|
||||||
|
id
|
||||||
|
start
|
||||||
|
}
|
||||||
|
learning_content {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
content_assignment {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
assignment_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edoniq_tests {
|
||||||
|
id
|
||||||
|
deadline {
|
||||||
|
id
|
||||||
|
start
|
||||||
|
end
|
||||||
|
}
|
||||||
|
learning_content {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
content_assignment {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
assignment_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@ import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const UNFILTERED = Number.MAX_SAFE_INTEGER;
|
const UNFILTERED = Number.MAX_SAFE_INTEGER.toString();
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
|
|
||||||
type Item = {
|
type Item = {
|
||||||
id: number;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -101,14 +101,14 @@ const appointments = computed(() => {
|
||||||
|
|
||||||
const isMatchingSession = (dueDate: DueDate) =>
|
const isMatchingSession = (dueDate: DueDate) =>
|
||||||
selectedSession.value.id === UNFILTERED ||
|
selectedSession.value.id === UNFILTERED ||
|
||||||
dueDate.course_session === selectedSession.value.id;
|
dueDate.course_session_id === selectedSession.value.id;
|
||||||
|
|
||||||
const isMatchingCircle = (dueDate: DueDate) =>
|
const isMatchingCircle = (dueDate: DueDate) =>
|
||||||
selectedCircle.value.id === UNFILTERED ||
|
selectedCircle.value.id === UNFILTERED ||
|
||||||
dueDate.circle?.id === selectedCircle.value.id;
|
dueDate.circle?.id === selectedCircle.value.id;
|
||||||
|
|
||||||
const isMatchingCourse = (dueDate: DueDate) =>
|
const isMatchingCourse = (dueDate: DueDate) =>
|
||||||
courseSessions.value.map((cs) => cs.id).includes(dueDate.course_session as number);
|
courseSessions.value.map((cs) => cs.id).includes(dueDate.course_session_id);
|
||||||
|
|
||||||
const numAppointmentsToShow = ref(7);
|
const numAppointmentsToShow = ref(7);
|
||||||
const canLoadMore = computed(() => {
|
const canLoadMore = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { useUserStore } from "@/stores/user";
|
||||||
import type { CourseSession } from "@/types";
|
import type { CourseSession } from "@/types";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed, onMounted } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
|
import { getCockpitUrl, getLearningPathUrl } from "@/utils/utils";
|
||||||
|
|
||||||
log.debug("DashboardPage created");
|
log.debug("DashboardPage created");
|
||||||
|
|
||||||
|
|
@ -20,9 +21,9 @@ const allDueDates = courseSessionsStore.allDueDates();
|
||||||
const getNextStepLink = (courseSession: CourseSession) => {
|
const getNextStepLink = (courseSession: CourseSession) => {
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
if (courseSessionsStore.hasCockpit(courseSession)) {
|
if (courseSessionsStore.hasCockpit(courseSession)) {
|
||||||
return courseSession.cockpit_url;
|
return getCockpitUrl(courseSession.course.slug);
|
||||||
}
|
}
|
||||||
return courseSession.learning_path_url;
|
return getLearningPathUrl(courseSession.course.slug);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
import { useCockpitStore } from "@/stores/cockpit";
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
import { useCompetenceStore } from "@/stores/competence";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
|
|
@ -16,13 +16,15 @@ const props = defineProps<{
|
||||||
const cockpitStore = useCockpitStore();
|
const cockpitStore = useCockpitStore();
|
||||||
const competenceStore = useCompetenceStore();
|
const competenceStore = useCompetenceStore();
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
const courseSession = useCurrentCourseSession();
|
|
||||||
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
log.debug("CockpitParentPage mounted", props.courseSlug);
|
log.debug("CockpitParentPage mounted", props.courseSlug);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const members = await cockpitStore.loadCourseSessionMembers(courseSession.value.id);
|
await courseSessionDetailResult.waitForData();
|
||||||
|
const members = courseSessionDetailResult.filterMembers();
|
||||||
members.forEach((csu) => {
|
members.forEach((csu) => {
|
||||||
competenceStore.loadCompetenceProfilePage(
|
competenceStore.loadCompetenceProfilePage(
|
||||||
props.courseSlug + "-competencenavi-competences",
|
props.courseSlug + "-competencenavi-competences",
|
||||||
|
|
@ -35,7 +37,10 @@ onMounted(async () => {
|
||||||
props.courseSlug + "-lp",
|
props.courseSlug + "-lp",
|
||||||
useUserStore().id
|
useUserStore().id
|
||||||
);
|
);
|
||||||
await cockpitStore.loadCircles(props.courseSlug, courseSession.value.id);
|
await cockpitStore.loadCircles(
|
||||||
|
props.courseSlug,
|
||||||
|
courseSessionDetailResult.findCurrentUser()
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import CirclePage from "@/pages/learningPath/circlePage/CirclePage.vue";
|
import CirclePage from "@/pages/learningPath/circlePage/CirclePage.vue";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
import { computed, onMounted } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
@ -12,14 +12,14 @@ const props = defineProps<{
|
||||||
|
|
||||||
log.debug("CockpitUserCirclePage created", props.userId, props.circleSlug);
|
log.debug("CockpitUserCirclePage created", props.userId, props.circleSlug);
|
||||||
|
|
||||||
const cockpitStore = useCockpitStore();
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
log.debug("CockpitUserCirclePage mounted");
|
log.debug("CockpitUserCirclePage mounted");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { findUser } = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const user = computed(() => {
|
const user = computed(() => {
|
||||||
return cockpitStore.courseSessionMembers?.find((csu) => csu.user_id === props.userId);
|
return findUser(props.userId);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
import { useCompetenceStore } from "@/stores/competence";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
|
|
@ -7,6 +6,7 @@ import { computed, onMounted } from "vue";
|
||||||
|
|
||||||
import CompetenceDetail from "@/pages/competence/ActionCompetenceDetail.vue";
|
import CompetenceDetail from "@/pages/competence/ActionCompetenceDetail.vue";
|
||||||
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
||||||
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
@ -15,7 +15,6 @@ const props = defineProps<{
|
||||||
|
|
||||||
log.debug("CockpitUserProfilePage created", props.userId);
|
log.debug("CockpitUserProfilePage created", props.userId);
|
||||||
|
|
||||||
const cockpitStore = useCockpitStore();
|
|
||||||
const competenceStore = useCompetenceStore();
|
const competenceStore = useCompetenceStore();
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
|
|
||||||
|
|
@ -27,8 +26,10 @@ const learningPath = computed(() => {
|
||||||
return learningPathStore.learningPathForUser(props.courseSlug, props.userId);
|
return learningPathStore.learningPathForUser(props.courseSlug, props.userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { findUser } = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const user = computed(() => {
|
const user = computed(() => {
|
||||||
return cockpitStore.courseSessionMembers?.find((csu) => csu.user_id === props.userId);
|
return findUser(props.userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
function setActiveClasses(isActive: boolean) {
|
function setActiveClasses(isActive: boolean) {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,15 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||||
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
|
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
|
||||||
import EvaluationContainer from "@/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue";
|
import EvaluationContainer from "@/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue";
|
||||||
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
||||||
import type {
|
import type { Assignment, AssignmentCompletion } from "@/types";
|
||||||
Assignment,
|
|
||||||
AssignmentCompletion,
|
|
||||||
CourseSessionAssignment,
|
|
||||||
CourseSessionUser,
|
|
||||||
} from "@/types";
|
|
||||||
import { useQuery } from "@urql/vue";
|
import { useQuery } from "@urql/vue";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed, onMounted, reactive } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getPreviousRoute } from "@/router/history";
|
import { getPreviousRoute } from "@/router/history";
|
||||||
import { getAssignmentTypeTitle } from "@/utils/utils";
|
import { getAssignmentTypeTitle } from "../../../utils/utils";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -24,16 +19,6 @@ const props = defineProps<{
|
||||||
|
|
||||||
log.debug("AssignmentEvaluationPage created", props.assignmentId, props.userId);
|
log.debug("AssignmentEvaluationPage created", props.assignmentId, props.userId);
|
||||||
|
|
||||||
interface StateInterface {
|
|
||||||
courseSessionAssignment: CourseSessionAssignment | undefined;
|
|
||||||
assignmentUser: CourseSessionUser | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const state: StateInterface = reactive({
|
|
||||||
courseSessionAssignment: undefined,
|
|
||||||
assignmentUser: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
@ -41,7 +26,7 @@ const router = useRouter();
|
||||||
const queryResult = useQuery({
|
const queryResult = useQuery({
|
||||||
query: ASSIGNMENT_COMPLETION_QUERY,
|
query: ASSIGNMENT_COMPLETION_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
assignmentId: props.assignmentId,
|
assignmentId: props.assignmentId,
|
||||||
assignmentUserId: props.userId,
|
assignmentUserId: props.userId,
|
||||||
},
|
},
|
||||||
|
|
@ -49,12 +34,11 @@ const queryResult = useQuery({
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
log.debug("AssignmentView mounted", props.assignmentId, props.userId);
|
log.debug("AssignmentView mounted", props.assignmentId, props.userId);
|
||||||
|
|
||||||
state.assignmentUser = courseSession.value.users.find(
|
|
||||||
(user) => user.user_id === props.userId
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
const assignmentUser = computed(() => courseSessionDetailResult.findUser(props.userId));
|
||||||
|
|
||||||
const previousRoute = getPreviousRoute();
|
const previousRoute = getPreviousRoute();
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
|
@ -103,10 +87,7 @@ const assignment = computed(
|
||||||
<it-icon-close></it-icon-close>
|
<it-icon-close></it-icon-close>
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<div
|
<div v-if="assignment && assignmentCompletion && assignmentUser" class="relative">
|
||||||
v-if="assignment && assignmentCompletion && state.assignmentUser"
|
|
||||||
class="relative"
|
|
||||||
>
|
|
||||||
<div class="md:h-content flex flex-col md:flex-row">
|
<div class="md:h-content flex flex-col md:flex-row">
|
||||||
<div
|
<div
|
||||||
class="bg-white md:h-full md:overflow-y-auto"
|
class="bg-white md:h-full md:overflow-y-auto"
|
||||||
|
|
@ -118,12 +99,12 @@ const assignment = computed(
|
||||||
|
|
||||||
<div class="my-6 flex items-center">
|
<div class="my-6 flex items-center">
|
||||||
<img
|
<img
|
||||||
:src="state.assignmentUser?.avatar_url"
|
:src="assignmentUser?.avatar_url"
|
||||||
class="mr-4 h-11 w-11 rounded-full"
|
class="mr-4 h-11 w-11 rounded-full"
|
||||||
/>
|
/>
|
||||||
<div class="font-bold">
|
<div class="font-bold">
|
||||||
{{ state.assignmentUser?.first_name }}
|
{{ assignmentUser?.first_name }}
|
||||||
{{ state.assignmentUser?.last_name }}
|
{{ assignmentUser?.last_name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AssignmentSubmissionResponses
|
<AssignmentSubmissionResponses
|
||||||
|
|
@ -139,7 +120,7 @@ const assignment = computed(
|
||||||
>
|
>
|
||||||
<EvaluationContainer
|
<EvaluationContainer
|
||||||
:assignment-completion="assignmentCompletion"
|
:assignment-completion="assignmentCompletion"
|
||||||
:assignment-user="state.assignmentUser"
|
:assignment-user="assignmentUser"
|
||||||
:assignment="assignment"
|
:assignment="assignment"
|
||||||
@close="close()"
|
@close="close()"
|
||||||
></EvaluationContainer>
|
></EvaluationContainer>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
import EvaluationIntro from "@/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue";
|
import EvaluationIntro from "@/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue";
|
||||||
import EvaluationSummary from "@/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue";
|
import EvaluationSummary from "@/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue";
|
||||||
import EvaluationTask from "@/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue";
|
import EvaluationTask from "@/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue";
|
||||||
import { findAssignmentDetail } from "@/services/assignmentService";
|
|
||||||
import type {
|
import type {
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentCompletion,
|
AssignmentCompletion,
|
||||||
|
|
@ -14,6 +13,7 @@ import dayjs from "dayjs";
|
||||||
import { findIndex } from "lodash";
|
import { findIndex } from "lodash";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
import { computed, onMounted } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
assignmentUser: CourseSessionUser;
|
assignmentUser: CourseSessionUser;
|
||||||
|
|
@ -58,10 +58,14 @@ function editTask(task: AssignmentEvaluationTask) {
|
||||||
stepIndex.value = taskIndex + 1;
|
stepIndex.value = taskIndex + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const assignmentDetail = computed(() => findAssignmentDetail(props.assignment.id));
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
|
const assignmentDetail = computed(() => {
|
||||||
|
return courseSessionDetailResult.findAssignmentByAssignmentId(props.assignment.id);
|
||||||
|
});
|
||||||
|
|
||||||
const dueDate = computed(() =>
|
const dueDate = computed(() =>
|
||||||
dayjs(assignmentDetail.value?.evaluation_deadline_start)
|
dayjs(assignmentDetail.value?.evaluation_deadline.start)
|
||||||
);
|
);
|
||||||
|
|
||||||
const inEvaluationTask = computed(
|
const inEvaluationTask = computed(
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ async function startEvaluation() {
|
||||||
log.debug("startEvaluation");
|
log.debug("startEvaluation");
|
||||||
if (props.assignmentCompletion.completion_status !== "EVALUATION_SUBMITTED") {
|
if (props.assignmentCompletion.completion_status !== "EVALUATION_SUBMITTED") {
|
||||||
upsertAssignmentCompletionMutation.executeMutation({
|
upsertAssignmentCompletionMutation.executeMutation({
|
||||||
assignmentId: props.assignment.id.toString(),
|
assignmentId: props.assignment.id,
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
assignmentUserId: props.assignmentUser.user_id.toString(),
|
assignmentUserId: props.assignmentUser.user_id,
|
||||||
completionStatus: "EVALUATION_IN_PROGRESS",
|
completionStatus: "EVALUATION_IN_PROGRESS",
|
||||||
completionDataString: JSON.stringify({}),
|
completionDataString: JSON.stringify({}),
|
||||||
// next line used for urql
|
// next line used for urql
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||||
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
||||||
import {
|
import {
|
||||||
maxAssignmentPoints,
|
maxAssignmentPoints,
|
||||||
|
|
@ -41,9 +41,9 @@ const upsertAssignmentCompletionMutation = useMutation(
|
||||||
|
|
||||||
async function submitEvaluation() {
|
async function submitEvaluation() {
|
||||||
upsertAssignmentCompletionMutation.executeMutation({
|
upsertAssignmentCompletionMutation.executeMutation({
|
||||||
assignmentId: props.assignment.id.toString(),
|
assignmentId: props.assignment.id,
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
assignmentUserId: props.assignmentUser.user_id.toString(),
|
assignmentUserId: props.assignmentUser.user_id,
|
||||||
completionStatus: "EVALUATION_SUBMITTED",
|
completionStatus: "EVALUATION_SUBMITTED",
|
||||||
completionDataString: JSON.stringify({}),
|
completionDataString: JSON.stringify({}),
|
||||||
evaluationPoints: userPoints.value,
|
evaluationPoints: userPoints.value,
|
||||||
|
|
@ -77,13 +77,13 @@ const userPoints = computed(() =>
|
||||||
userAssignmentPoints(props.assignment, props.assignmentCompletion)
|
userAssignmentPoints(props.assignment, props.assignmentCompletion)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
const evaluationUser = computed(() => {
|
const evaluationUser = computed(() => {
|
||||||
if (props.assignmentCompletion.evaluation_user) {
|
if (props.assignmentCompletion.evaluation_user) {
|
||||||
return (courseSession.value.users ?? []).find(
|
return courseSessionDetailResult.findUser(
|
||||||
(user) => user.user_id === props.assignmentCompletion.evaluation_user
|
props.assignmentCompletion.evaluation_user
|
||||||
) as CourseSessionUser;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -66,9 +66,9 @@ async function evaluateAssignmentCompletion(completionData: AssignmentCompletion
|
||||||
log.debug("evaluateAssignmentCompletion", completionData);
|
log.debug("evaluateAssignmentCompletion", completionData);
|
||||||
|
|
||||||
upsertAssignmentCompletionMutation.executeMutation({
|
upsertAssignmentCompletionMutation.executeMutation({
|
||||||
assignmentId: props.assignment.id.toString(),
|
assignmentId: props.assignment.id,
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
assignmentUserId: props.assignmentUser.user_id.toString(),
|
assignmentUserId: props.assignmentUser.user_id,
|
||||||
completionStatus: "EVALUATION_IN_PROGRESS",
|
completionStatus: "EVALUATION_IN_PROGRESS",
|
||||||
completionDataString: JSON.stringify(completionData),
|
completionDataString: JSON.stringify(completionData),
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,17 @@
|
||||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||||
import type { StatusCount } from "@/components/ui/ItProgress.vue";
|
import type { StatusCount } from "@/components/ui/ItProgress.vue";
|
||||||
import type { GradedUser } from "@/services/assignmentService";
|
import type { GradedUser } from "@/services/assignmentService";
|
||||||
import {
|
import { loadAssignmentCompletionStatusData } from "@/services/assignmentService";
|
||||||
findAssignmentDetail,
|
|
||||||
loadAssignmentCompletionStatusData,
|
|
||||||
} from "@/services/assignmentService";
|
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
import type {
|
import type {
|
||||||
CourseSession,
|
CourseSession,
|
||||||
CourseSessionUser,
|
CourseSessionUser,
|
||||||
LearningContentAssignment,
|
LearningContentAssignment,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import dayjs from "dayjs";
|
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed, onMounted, reactive } from "vue";
|
import { computed, onMounted, reactive } from "vue";
|
||||||
import AssignmentSubmissionProgress from "@/pages/cockpit/cockpitPage/AssignmentSubmissionProgress.vue";
|
import AssignmentSubmissionProgress from "@/pages/cockpit/cockpitPage/AssignmentSubmissionProgress.vue";
|
||||||
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
|
import { formatDueDate } from "../../../components/dueDates/dueDatesUtils";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSession: CourseSession;
|
courseSession: CourseSession;
|
||||||
|
|
@ -27,7 +24,7 @@ log.debug(
|
||||||
props.learningContentAssignment.content_assignment_id
|
props.learningContentAssignment.content_assignment_id
|
||||||
);
|
);
|
||||||
|
|
||||||
const cockpitStore = useCockpitStore();
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
progressStatusCount: {} as StatusCount,
|
progressStatusCount: {} as StatusCount,
|
||||||
|
|
@ -35,6 +32,10 @@ const state = reactive({
|
||||||
assignmentSubmittedUsers: [] as CourseSessionUser[],
|
assignmentSubmittedUsers: [] as CourseSessionUser[],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const assignmentDetail = computed(() => {
|
||||||
|
return courseSessionDetailResult.findAssignment(props.learningContentAssignment.id);
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const { gradedUsers, assignmentSubmittedUsers } =
|
const { gradedUsers, assignmentSubmittedUsers } =
|
||||||
await loadAssignmentCompletionStatusData(
|
await loadAssignmentCompletionStatusData(
|
||||||
|
|
@ -45,10 +46,6 @@ onMounted(async () => {
|
||||||
state.gradedUsers = gradedUsers;
|
state.gradedUsers = gradedUsers;
|
||||||
state.assignmentSubmittedUsers = assignmentSubmittedUsers;
|
state.assignmentSubmittedUsers = assignmentSubmittedUsers;
|
||||||
});
|
});
|
||||||
|
|
||||||
const assignmentDetail = computed(() =>
|
|
||||||
findAssignmentDetail(props.learningContentAssignment.content_assignment_id)
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -60,16 +57,14 @@ const assignmentDetail = computed(() =>
|
||||||
Circle «{{ learningContentAssignment.parentCircle.title }}»
|
Circle «{{ learningContentAssignment.parentCircle.title }}»
|
||||||
</div>
|
</div>
|
||||||
<div v-if="assignmentDetail">
|
<div v-if="assignmentDetail">
|
||||||
<span>
|
<span v-if="assignmentDetail.submission_deadline?.start">
|
||||||
{{ $t("Abgabetermin Ergebnisse:") }}
|
{{ $t("Abgabetermin Ergebnisse:") }}
|
||||||
{{ dayjs(assignmentDetail.submission_deadline_start).format("DD.MM.YYYY") }}
|
{{ formatDueDate(assignmentDetail.submission_deadline.start) }}
|
||||||
</span>
|
</span>
|
||||||
<template v-if="assignmentDetail.evaluation_deadline_start">
|
<template v-if="assignmentDetail.evaluation_deadline?.start">
|
||||||
<br />
|
<br />
|
||||||
<span v-if="assignmentDetail.evaluation_deadline_start">
|
{{ $t("Freigabetermin Bewertungen:") }}
|
||||||
{{ $t("Freigabetermin Bewertungen:") }}
|
{{ formatDueDate(assignmentDetail.evaluation_deadline.start) }}
|
||||||
{{ dayjs(assignmentDetail.evaluation_deadline_start).format("DD.MM.YYYY") }}
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
|
@ -85,11 +80,11 @@ const assignmentDetail = computed(() =>
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="cockpitStore.courseSessionMembers?.length" class="mt-6">
|
<div v-if="courseSessionDetailResult.filterMembers().length" class="mt-6">
|
||||||
<ul>
|
<ul>
|
||||||
<ItPersonRow
|
<ItPersonRow
|
||||||
v-for="csu in cockpitStore.courseSessionMembers"
|
v-for="csu in courseSessionDetailResult.filterMembers()"
|
||||||
:key="csu.user_id + csu.session_title"
|
:key="csu.user_id"
|
||||||
:name="`${csu.first_name} ${csu.last_name}`"
|
:name="`${csu.first_name} ${csu.last_name}`"
|
||||||
:avatar-url="csu.avatar_url"
|
:avatar-url="csu.avatar_url"
|
||||||
:data-cy="csu.last_name"
|
:data-cy="csu.last_name"
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ onMounted(async () => {
|
||||||
const learningContentAssignment = computed(() => {
|
const learningContentAssignment = computed(() => {
|
||||||
return calcLearningContentAssignments(
|
return calcLearningContentAssignments(
|
||||||
learningPathStore.learningPathForUser(courseSession.value.course.slug, userStore.id)
|
learningPathStore.learningPathForUser(courseSession.value.course.slug, userStore.id)
|
||||||
).filter((lc) => lc.id.toString() === props.assignmentId)[0];
|
).filter((lc) => lc.id === props.assignmentId)[0];
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@
|
||||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
import type { AttendanceUserStatus } from "@/gql/graphql";
|
import type { AttendanceUserStatus } from "@/gql/graphql";
|
||||||
import { ATTENDANCE_CHECK_MUTATION } from "@/graphql/mutations";
|
import { ATTENDANCE_CHECK_MUTATION } from "@/graphql/mutations";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
import type { DropdownSelectable } from "@/types";
|
import type { DropdownSelectable } from "@/types";
|
||||||
import { useMutation } from "@urql/vue";
|
import { useMutation } from "@urql/vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
@ -16,12 +15,15 @@ import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
|
||||||
import { graphqlClient } from "@/graphql/client";
|
import { graphqlClient } from "@/graphql/client";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const cockpitStore = useCockpitStore();
|
|
||||||
const courseSession = useCurrentCourseSession();
|
|
||||||
const attendanceMutation = useMutation(ATTENDANCE_CHECK_MUTATION);
|
const attendanceMutation = useMutation(ATTENDANCE_CHECK_MUTATION);
|
||||||
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const attendanceCourses = computed(() => {
|
const attendanceCourses = computed(() => {
|
||||||
return courseSession.value.attendance_courses;
|
return courseSessionDetailResult.courseSessionDetail.value?.attendance_courses ?? [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const courseSessionDetail = computed(() => {
|
||||||
|
return courseSessionDetailResult.courseSessionDetail.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const presenceCoursesDropdownOptions = computed(() => {
|
const presenceCoursesDropdownOptions = computed(() => {
|
||||||
|
|
@ -29,9 +31,9 @@ const presenceCoursesDropdownOptions = computed(() => {
|
||||||
(attendanceCourse) =>
|
(attendanceCourse) =>
|
||||||
({
|
({
|
||||||
id: attendanceCourse.id,
|
id: attendanceCourse.id,
|
||||||
name: `${t("Präsenzkurs")} ${attendanceCourse.circle_title} ${dayjs(
|
name: `${t("Präsenzkurs")} ${
|
||||||
attendanceCourse.start
|
attendanceCourse.learning_content.circle.title
|
||||||
).format("DD.MM.YYYY")}`,
|
} ${dayjs(attendanceCourse.due_date.start).format("DD.MM.YYYY")}`,
|
||||||
} as DropdownSelectable)
|
} as DropdownSelectable)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -43,6 +45,16 @@ const state = reactive({
|
||||||
attendanceSaved: false,
|
attendanceSaved: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
attendanceCourses,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal && newVal.length > 0) {
|
||||||
|
state.attendanceCourseSelected = presenceCoursesDropdownOptions.value[0];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
function resetState() {
|
function resetState() {
|
||||||
state.userPresence = new Map<string, boolean>();
|
state.userPresence = new Map<string, boolean>();
|
||||||
state.disclaimerConfirmed = false;
|
state.disclaimerConfirmed = false;
|
||||||
|
|
@ -76,23 +88,25 @@ const onSubmit = async () => {
|
||||||
const loadAttendanceData = async () => {
|
const loadAttendanceData = async () => {
|
||||||
resetState();
|
resetState();
|
||||||
// with changing variables `useQuery` does not seem to work correctly
|
// with changing variables `useQuery` does not seem to work correctly
|
||||||
const res = await graphqlClient.query(
|
if (state.attendanceCourseSelected) {
|
||||||
ATTENDANCE_CHECK_QUERY,
|
const res = await graphqlClient.query(
|
||||||
{
|
ATTENDANCE_CHECK_QUERY,
|
||||||
courseSessionId: state.attendanceCourseSelected.id.toString(),
|
{
|
||||||
},
|
courseSessionId: state.attendanceCourseSelected.id.toString(),
|
||||||
{
|
},
|
||||||
requestPolicy: "network-only",
|
{
|
||||||
|
requestPolicy: "network-only",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const attendanceUserList =
|
||||||
|
res.data?.course_session_attendance_course?.attendance_user_list ?? [];
|
||||||
|
for (const user of attendanceUserList) {
|
||||||
|
if (!user) continue;
|
||||||
|
state.userPresence.set(user.user_id, user.status === "PRESENT");
|
||||||
|
}
|
||||||
|
if (attendanceUserList.length !== 0) {
|
||||||
|
state.attendanceSaved = true;
|
||||||
}
|
}
|
||||||
);
|
|
||||||
const attendanceUserList =
|
|
||||||
res.data?.course_session_attendance_course?.attendance_user_list ?? [];
|
|
||||||
for (const user of attendanceUserList) {
|
|
||||||
if (!user) continue;
|
|
||||||
state.userPresence.set(user.user_id.toString(), user.status === "PRESENT");
|
|
||||||
}
|
|
||||||
if (attendanceUserList.length !== 0) {
|
|
||||||
state.attendanceSaved = true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -110,89 +124,93 @@ watch(
|
||||||
() => {
|
() => {
|
||||||
log.debug("attendanceCourseSelected changed", state.attendanceCourseSelected);
|
log.debug("attendanceCourseSelected changed", state.attendanceCourseSelected);
|
||||||
loadAttendanceData();
|
loadAttendanceData();
|
||||||
}
|
},
|
||||||
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-gray-200">
|
<div class="bg-gray-200">
|
||||||
<div v-if="courseSession" class="container-large">
|
<div v-if="courseSessionDetail" class="container-large">
|
||||||
<nav class="py-4 pb-4">
|
<nav class="py-4 pb-4">
|
||||||
<router-link
|
<router-link
|
||||||
class="btn-text inline-flex items-center pl-0"
|
class="btn-text inline-flex items-center pl-0"
|
||||||
:to="`/course/${courseSession.course.slug}/cockpit`"
|
:to="`/course/${courseSessionDetail.course.slug}/cockpit`"
|
||||||
>
|
>
|
||||||
<it-icon-arrow-left />
|
<it-icon-arrow-left />
|
||||||
<span>{{ $t("general.back") }}</span>
|
<span>{{ $t("general.back") }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="pb-4 text-xl font-bold">{{ $t("Anwesenheit Präsenzkurse") }}</div>
|
<div class="pb-4 text-xl font-bold">{{ $t("Anwesenheit Präsenzkurse") }}</div>
|
||||||
<div class="flex flex-row justify-between bg-white p-6">
|
|
||||||
<ItDropdownSelect
|
|
||||||
v-model="state.attendanceCourseSelected"
|
|
||||||
:items="presenceCoursesDropdownOptions ?? []"
|
|
||||||
></ItDropdownSelect>
|
|
||||||
<div v-if="!state.attendanceSaved" class="flex flex-row items-center">
|
|
||||||
<ItCheckbox
|
|
||||||
:checkbox-item="{
|
|
||||||
value: true,
|
|
||||||
checked: state.disclaimerConfirmed,
|
|
||||||
}"
|
|
||||||
@toggle="state.disclaimerConfirmed = !state.disclaimerConfirmed"
|
|
||||||
></ItCheckbox>
|
|
||||||
<p class="w-64 pr-4 text-sm">
|
|
||||||
{{
|
|
||||||
$t(
|
|
||||||
"Ich will die Anwesenheit der untenstehenden Personen definitiv bestätigen."
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
class="btn-primary"
|
|
||||||
:disabled="!state.disclaimerConfirmed"
|
|
||||||
@click="onSubmit"
|
|
||||||
>
|
|
||||||
{{ $t("Anwesenheit bestätigen") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div v-else class="self-center">
|
|
||||||
<p class="text-base">
|
|
||||||
{{ $t("a.Die Anwesenheit wurde definitiv bestätigt") }}
|
|
||||||
</p>
|
|
||||||
<button class="btn-link link" @click="editAgain()">
|
|
||||||
{{ $t("a.Erneut bearbeiten") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4 flex flex-col bg-white p-6">
|
<section v-if="attendanceCourses.length && state.attendanceCourseSelected">
|
||||||
<div
|
<div class="flex flex-row justify-between bg-white p-6">
|
||||||
v-for="(csu, index) in cockpitStore.courseSessionMembers"
|
<ItDropdownSelect
|
||||||
:key="csu.user_id + csu.session_title"
|
v-model="state.attendanceCourseSelected"
|
||||||
>
|
:items="presenceCoursesDropdownOptions ?? []"
|
||||||
<ItPersonRow
|
></ItDropdownSelect>
|
||||||
:name="`${csu.first_name} ${csu.last_name}`"
|
<div v-if="!state.attendanceSaved" class="flex flex-row items-center">
|
||||||
:avatar-url="csu.avatar_url"
|
<ItCheckbox
|
||||||
:class="0 === index ? 'border-none' : ''"
|
:checkbox-item="{
|
||||||
>
|
value: true,
|
||||||
<template #leading>
|
checked: state.disclaimerConfirmed,
|
||||||
<ItCheckbox
|
}"
|
||||||
:disabled="state.attendanceSaved"
|
@toggle="state.disclaimerConfirmed = !state.disclaimerConfirmed"
|
||||||
:checkbox-item="{
|
></ItCheckbox>
|
||||||
value: true,
|
<p class="w-64 pr-4 text-sm">
|
||||||
checked: state.userPresence.get(csu.user_id.toString()) as boolean,
|
{{
|
||||||
}"
|
$t(
|
||||||
@toggle="
|
"Ich will die Anwesenheit der untenstehenden Personen definitiv bestätigen."
|
||||||
state.userPresence.set(
|
)
|
||||||
csu.user_id.toString(),
|
}}
|
||||||
!state.userPresence.get(csu.user_id.toString())
|
</p>
|
||||||
)
|
<button
|
||||||
"
|
class="btn-primary"
|
||||||
></ItCheckbox>
|
:disabled="!state.disclaimerConfirmed"
|
||||||
</template>
|
@click="onSubmit"
|
||||||
</ItPersonRow>
|
>
|
||||||
|
{{ $t("Anwesenheit bestätigen") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="self-center">
|
||||||
|
<p class="text-base">
|
||||||
|
{{ $t("a.Die Anwesenheit wurde definitiv bestätigt") }}
|
||||||
|
</p>
|
||||||
|
<button class="btn-link link" @click="editAgain()">
|
||||||
|
{{ $t("a.Erneut bearbeiten") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="mt-4 flex flex-col bg-white p-6">
|
||||||
|
<div
|
||||||
|
v-for="(csu, index) in courseSessionDetailResult.filterMembers()"
|
||||||
|
:key="csu.user_id"
|
||||||
|
>
|
||||||
|
<ItPersonRow
|
||||||
|
:name="`${csu.first_name} ${csu.last_name}`"
|
||||||
|
:avatar-url="csu.avatar_url"
|
||||||
|
:class="0 === index ? 'border-none' : ''"
|
||||||
|
>
|
||||||
|
<template #leading>
|
||||||
|
<ItCheckbox
|
||||||
|
:disabled="state.attendanceSaved"
|
||||||
|
:checkbox-item="{
|
||||||
|
value: true,
|
||||||
|
checked: state.userPresence.get(csu.user_id) as boolean,
|
||||||
|
}"
|
||||||
|
@toggle="
|
||||||
|
state.userPresence.set(
|
||||||
|
csu.user_id,
|
||||||
|
!state.userPresence.get(csu.user_id)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
></ItCheckbox>
|
||||||
|
</template>
|
||||||
|
</ItPersonRow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ const state = reactive({
|
||||||
statusByUser: [] as {
|
statusByUser: [] as {
|
||||||
userStatus: AssignmentCompletionStatus;
|
userStatus: AssignmentCompletionStatus;
|
||||||
progressStatus: StatusCountKey;
|
progressStatus: StatusCountKey;
|
||||||
userId: number;
|
userId: string;
|
||||||
}[],
|
}[],
|
||||||
submissionProgressStatusCount: {} as StatusCount,
|
submissionProgressStatusCount: {} as StatusCount,
|
||||||
gradingProgressStatusCount: {} as StatusCount,
|
gradingProgressStatusCount: {} as StatusCount,
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ const courseSession = useCurrentCourseSession();
|
||||||
const circleDates = computed(() => {
|
const circleDates = computed(() => {
|
||||||
const dueDates = courseSession.value.due_dates.filter((dueDate) => {
|
const dueDates = courseSession.value.due_dates.filter((dueDate) => {
|
||||||
if (!cockpitStore.currentCircle) return false;
|
if (!cockpitStore.currentCircle) return false;
|
||||||
return (
|
return cockpitStore.currentCircle.id == dueDate?.circle?.id;
|
||||||
cockpitStore.currentCircle.translation_key == dueDate?.circle?.translation_key
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
return dueDates.slice(0, 4);
|
return dueDates.slice(0, 4);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,13 @@ import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.v
|
||||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||||
import type { LearningPath } from "@/services/learningPath";
|
import type { LearningPath } from "@/services/learningPath";
|
||||||
|
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||||
import SubmissionsOverview from "@/pages/cockpit/cockpitPage/SubmissionsOverview.vue";
|
import SubmissionsOverview from "@/pages/cockpit/cockpitPage/SubmissionsOverview.vue";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
import { useCockpitStore } from "@/stores/cockpit";
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
import { useCompetenceStore } from "@/stores/competence";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import CockpitDates from "@/pages/cockpit/cockpitPage/CockpitDates.vue";
|
import CockpitDates from "@/pages/cockpit/cockpitPage/CockpitDates.vue";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -23,7 +22,7 @@ const cockpitStore = useCockpitStore();
|
||||||
const competenceStore = useCompetenceStore();
|
const competenceStore = useCompetenceStore();
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
function userCountStatusForCircle(userId: string) {
|
function userCountStatusForCircle(userId: string) {
|
||||||
if (!cockpitStore.currentCircle) return { FAIL: 0, SUCCESS: 0, UNKNOWN: 0 };
|
if (!cockpitStore.currentCircle) return { FAIL: 0, SUCCESS: 0, UNKNOWN: 0 };
|
||||||
|
|
@ -37,175 +36,178 @@ function userCountStatusForCircle(userId: string) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-gray-200">
|
<div class="bg-gray-200">
|
||||||
<div v-if="cockpitStore.currentCircle" class="container-large">
|
<div v-if="cockpitStore.circles?.length">
|
||||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
<div v-if="cockpitStore.currentCircle" class="container-large">
|
||||||
<h1>Cockpit</h1>
|
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||||
<ItDropdownSelect
|
<h1>Cockpit</h1>
|
||||||
:model-value="cockpitStore.selectedCircle"
|
<ItDropdownSelect
|
||||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
:model-value="cockpitStore.currentCircle"
|
||||||
:items="cockpitStore.circles"
|
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||||
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
:items="cockpitStore.circles"
|
||||||
></ItDropdownSelect>
|
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
||||||
</div>
|
></ItDropdownSelect>
|
||||||
<!-- Status -->
|
</div>
|
||||||
<div class="mb-4 gap-4 lg:grid lg:grid-cols-3 lg:grid-rows-none">
|
<!-- Status -->
|
||||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
<div class="mb-4 gap-4 lg:grid lg:grid-cols-3 lg:grid-rows-none">
|
||||||
<div>
|
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
<div>
|
||||||
{{ $t("Trainerunterlagen") }}
|
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||||
</h3>
|
{{ $t("Trainerunterlagen") }}
|
||||||
<div class="mb-4">
|
</h3>
|
||||||
{{ $t("cockpit.trainerFilesText") }}
|
<div class="mb-4">
|
||||||
|
{{ $t("cockpit.trainerFilesText") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
href="https://vbvbern.sharepoint.com/sites/myVBV-AFA_K-CI"
|
||||||
|
class="btn-secondary min-w-min"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ $t("MS Teams öffnen") }}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||||
<a
|
<div>
|
||||||
href="https://vbvbern.sharepoint.com/sites/myVBV-AFA_K-CI"
|
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||||
class="btn-secondary min-w-min"
|
{{ $t("a.Unterlagen für Teilnehmenden") }}
|
||||||
target="_blank"
|
</h3>
|
||||||
>
|
<div class="mb-4">
|
||||||
{{ $t("MS Teams öffnen") }}
|
{{ $t("a.Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.") }}
|
||||||
</a>
|
</div>
|
||||||
|
<!-- <div-->
|
||||||
|
<!-- v-if="courseSessionsStore.circleDocuments.length"-->
|
||||||
|
<!-- class="mb-4 flex items-center gap-x-2"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <it-icon-document />-->
|
||||||
|
<!-- {{ courseSessionsStore.circleDocuments.length }} {{ $t("a.Unterlagen") }}-->
|
||||||
|
<!-- </div>-->
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<router-link
|
||||||
|
:to="`/course/${props.courseSlug}/cockpit/documents`"
|
||||||
|
class="btn-secondary min-w-min"
|
||||||
|
>
|
||||||
|
{{ $t("a.Zum Unterlagen-Upload") }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||||
|
<div>
|
||||||
|
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||||
|
{{ $t("Anwesenheitskontrolle Präsenzkurse") }}
|
||||||
|
</h3>
|
||||||
|
<div class="mb-4">
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"Hier überprüfst und bestätigst du die Anwesenheit deiner Teilnehmenden."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<router-link
|
||||||
|
:to="`/course/${props.courseSlug}/cockpit/attendance`"
|
||||||
|
class="btn-secondary min-w-min"
|
||||||
|
>
|
||||||
|
{{ $t("Anwesenheit prüfen") }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
|
||||||
<div>
|
|
||||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
|
||||||
{{ $t("a.Unterlagen für Teilnehmenden") }}
|
|
||||||
</h3>
|
|
||||||
<div class="mb-4">
|
|
||||||
{{ $t("a.Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.") }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="courseSessionsStore.circleDocuments.length"
|
|
||||||
class="mb-4 flex items-center gap-x-2"
|
|
||||||
>
|
|
||||||
<it-icon-document />
|
|
||||||
{{ courseSessionsStore.circleDocuments.length }} {{ $t("a.Unterlagen") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<router-link
|
|
||||||
:to="`/course/${props.courseSlug}/cockpit/documents`"
|
|
||||||
class="btn-secondary min-w-min"
|
|
||||||
>
|
|
||||||
{{ $t("a.Zum Unterlagen-Upload") }}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
|
||||||
<div>
|
|
||||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
|
||||||
{{ $t("Anwesenheitskontrolle Präsenzkurse") }}
|
|
||||||
</h3>
|
|
||||||
<div class="mb-4">
|
|
||||||
{{
|
|
||||||
$t(
|
|
||||||
"Hier überprüfst und bestätigst du die Anwesenheit deiner Teilnehmenden."
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<router-link
|
|
||||||
:to="`/course/${props.courseSlug}/cockpit/attendance`"
|
|
||||||
class="btn-secondary min-w-min"
|
|
||||||
>
|
|
||||||
{{ $t("Anwesenheit prüfen") }}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4 bg-white p-6">
|
<div class="mb-4 bg-white p-6">
|
||||||
<CockpitDates></CockpitDates>
|
<CockpitDates></CockpitDates>
|
||||||
</div>
|
</div>
|
||||||
<SubmissionsOverview
|
<SubmissionsOverview
|
||||||
:course-session="courseSession"
|
:course-session="courseSession"
|
||||||
:selected-circle="cockpitStore.currentCircle.id"
|
:selected-circle="cockpitStore.currentCircle.id"
|
||||||
></SubmissionsOverview>
|
></SubmissionsOverview>
|
||||||
<div class="pt-4">
|
<div class="pt-4">
|
||||||
<!-- progress -->
|
<!-- progress -->
|
||||||
<div v-if="cockpitStore.courseSessionMembers" class="bg-white p-6">
|
<div
|
||||||
<h1 class="heading-3 mb-5">{{ $t("cockpit.progress") }}</h1>
|
v-if="courseSessionDetailResult.filterMembers().length > 0"
|
||||||
<ul>
|
class="bg-white p-6"
|
||||||
<ItPersonRow
|
>
|
||||||
v-for="csu in cockpitStore.courseSessionMembers"
|
<h1 class="heading-3 mb-5">{{ $t("cockpit.progress") }}</h1>
|
||||||
:key="csu.user_id + csu.session_title"
|
<ul>
|
||||||
:name="`${csu.first_name} ${csu.last_name}`"
|
<ItPersonRow
|
||||||
:avatar-url="csu.avatar_url"
|
v-for="csu in courseSessionDetailResult.filterMembers()"
|
||||||
>
|
:key="csu.user_id"
|
||||||
<template #center>
|
:name="`${csu.first_name} ${csu.last_name}`"
|
||||||
<div
|
:avatar-url="csu.avatar_url"
|
||||||
class="mt-2 flex w-full flex-col items-center justify-between lg:mt-0 lg:flex-row"
|
>
|
||||||
>
|
<template #center>
|
||||||
<LearningPathDiagram
|
<div
|
||||||
v-if="
|
class="mt-2 flex w-full flex-col items-center justify-between lg:mt-0 lg:flex-row"
|
||||||
learningPathStore.learningPathForUser(
|
>
|
||||||
props.courseSlug,
|
<LearningPathDiagram
|
||||||
csu.user_id
|
v-if="
|
||||||
)
|
learningPathStore.learningPathForUser(
|
||||||
"
|
props.courseSlug,
|
||||||
:learning-path="
|
csu.user_id
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:learning-path="
|
||||||
learningPathStore.learningPathForUser(
|
learningPathStore.learningPathForUser(
|
||||||
props.courseSlug,
|
props.courseSlug,
|
||||||
csu.user_id
|
csu.user_id
|
||||||
) as LearningPath
|
) as LearningPath
|
||||||
"
|
"
|
||||||
:show-circle-translation-keys="[
|
:show-circle-slugs="[cockpitStore.currentCircle.slug]"
|
||||||
cockpitStore.currentCircle.translation_key,
|
diagram-type="singleSmall"
|
||||||
]"
|
class="mr-4"
|
||||||
diagram-type="singleSmall"
|
></LearningPathDiagram>
|
||||||
class="mr-4"
|
<p class="lg:min-w-[150px]">
|
||||||
></LearningPathDiagram>
|
{{ cockpitStore.currentCircle.title }}
|
||||||
<p class="lg:min-w-[150px]">
|
</p>
|
||||||
{{ cockpitStore.currentCircle.title }}
|
<div class="ml-4 flex flex-row items-center">
|
||||||
</p>
|
<div class="mr-6 flex flex-row items-center">
|
||||||
<div class="ml-4 flex flex-row items-center">
|
<it-icon-smiley-thinking
|
||||||
<div class="mr-6 flex flex-row items-center">
|
class="mr-2 inline-block h-8 w-8"
|
||||||
<it-icon-smiley-thinking
|
></it-icon-smiley-thinking>
|
||||||
class="mr-2 inline-block h-8 w-8"
|
<p class="text-bold inline-block w-6">
|
||||||
></it-icon-smiley-thinking>
|
{{ userCountStatusForCircle(csu.user_id).FAIL }}
|
||||||
<p class="text-bold inline-block w-6">
|
</p>
|
||||||
{{ userCountStatusForCircle(csu.user_id).FAIL }}
|
</div>
|
||||||
</p>
|
<li class="mr-6 flex flex-row items-center">
|
||||||
|
<it-icon-smiley-happy
|
||||||
|
class="mr-2 inline-block h-8 w-8"
|
||||||
|
></it-icon-smiley-happy>
|
||||||
|
<p class="text-bold inline-block w-6">
|
||||||
|
{{ userCountStatusForCircle(csu.user_id).SUCCESS }}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-row items-center">
|
||||||
|
<it-icon-smiley-neutral
|
||||||
|
class="mr-2 inline-block h-8 w-8"
|
||||||
|
></it-icon-smiley-neutral>
|
||||||
|
<p class="text-bold inline-block w-6">
|
||||||
|
{{ userCountStatusForCircle(csu.user_id).UNKNOWN }}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<li class="mr-6 flex flex-row items-center">
|
|
||||||
<it-icon-smiley-happy
|
|
||||||
class="mr-2 inline-block h-8 w-8"
|
|
||||||
></it-icon-smiley-happy>
|
|
||||||
<p class="text-bold inline-block w-6">
|
|
||||||
{{ userCountStatusForCircle(csu.user_id).SUCCESS }}
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
<li class="flex flex-row items-center">
|
|
||||||
<it-icon-smiley-neutral
|
|
||||||
class="mr-2 inline-block h-8 w-8"
|
|
||||||
></it-icon-smiley-neutral>
|
|
||||||
<p class="text-bold inline-block w-6">
|
|
||||||
{{ userCountStatusForCircle(csu.user_id).UNKNOWN }}
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
<template #link>
|
||||||
<template #link>
|
<router-link
|
||||||
<router-link
|
:to="`/course/${props.courseSlug}/cockpit/profile/${csu.user_id}`"
|
||||||
:to="`/course/${props.courseSlug}/cockpit/profile/${csu.user_id}`"
|
class="link w-full lg:text-right"
|
||||||
class="link w-full lg:text-right"
|
>
|
||||||
>
|
{{ $t("general.profileLink") }}
|
||||||
{{ $t("general.profileLink") }}
|
</router-link>
|
||||||
</router-link>
|
</template>
|
||||||
</template>
|
</ItPersonRow>
|
||||||
</ItPersonRow>
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-else class="container-large mt-4">
|
||||||
<div v-else class="container-large mt-4">
|
<span class="text-lg text-orange-600">
|
||||||
<span class="text-lg text-orange-600">
|
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||||
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,20 @@ import log from "loglevel";
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, onMounted, ref } from "vue";
|
||||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||||
import { itGet } from "@/fetchHelpers";
|
import { itGet } from "@/fetchHelpers";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSession: CourseSession;
|
courseSession: CourseSession;
|
||||||
circleId: number;
|
circleId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
log.debug("FeedbackSubmissionProgress created");
|
log.debug("FeedbackSubmissionProgress created");
|
||||||
|
|
||||||
const cockpitStore = useCockpitStore();
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const completeFeedbacks = ref(0);
|
const completeFeedbacks = ref(0);
|
||||||
|
|
||||||
const numFeedbacks = computed(() => {
|
const numFeedbacks = computed(() => {
|
||||||
return cockpitStore.courseSessionMembers?.length ?? 0;
|
return courseSessionDetailResult.filterMembers().length;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
2
|
2
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AssignmentSubmissionProgress from "@/pages/cockpit/cockpitPage/AssignmentSubmissionProgress.vue";
|
import AssignmentSubmissionProgress from "@/pages/cockpit/cockpitPage/AssignmentSubmissionProgress.vue";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -14,9 +13,10 @@ import { computed } from "vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import FeedbackSubmissionProgress from "@/pages/cockpit/cockpitPage/FeedbackSubmissionProgress.vue";
|
import FeedbackSubmissionProgress from "@/pages/cockpit/cockpitPage/FeedbackSubmissionProgress.vue";
|
||||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||||
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
|
|
||||||
interface Submittable {
|
interface Submittable {
|
||||||
id: number;
|
id: string;
|
||||||
circleName: string;
|
circleName: string;
|
||||||
frontendUrl: string;
|
frontendUrl: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -27,14 +27,15 @@ interface Submittable {
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSession: CourseSession;
|
courseSession: CourseSession;
|
||||||
selectedCircle: number;
|
selectedCircle: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
log.debug("SubmissionsOverview created", props.courseSession.id);
|
log.debug("SubmissionsOverview created", props.courseSession.id);
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const cockpitStore = useCockpitStore();
|
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const submittables = computed(() => {
|
const submittables = computed(() => {
|
||||||
|
|
@ -128,7 +129,10 @@ const getIconName = (lc: LearningContent) => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white px-6 py-2">
|
<div class="bg-white px-6 py-2">
|
||||||
<div v-if="cockpitStore.courseSessionMembers" class="divide-y divide-gray-500">
|
<div
|
||||||
|
v-if="courseSessionDetailResult.filterMembers().length"
|
||||||
|
class="divide-y divide-gray-500"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="submittable in submittables"
|
v-for="submittable in submittables"
|
||||||
:key="submittable.id"
|
:key="submittable.id"
|
||||||
|
|
|
||||||
|
|
@ -4,34 +4,80 @@ import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
import { useCockpitStore } from "@/stores/cockpit";
|
||||||
import ItModal from "@/components/ui/ItModal.vue";
|
import ItModal from "@/components/ui/ItModal.vue";
|
||||||
import DocumentUploadForm from "@/pages/cockpit/documentPage/DocumentUploadForm.vue";
|
import DocumentUploadForm from "@/pages/cockpit/documentPage/DocumentUploadForm.vue";
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, onMounted, ref, watch } from "vue";
|
||||||
import { useCircleStore } from "@/stores/circle";
|
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import type { CircleDocument, DocumentUploadData } from "@/types";
|
import type { CircleDocument, DocumentUploadData } from "@/types";
|
||||||
import dialog from "@/utils/confirm-dialog";
|
import dialog from "@/utils/confirm-dialog";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { uploadCircleDocument } from "@/services/files";
|
import {
|
||||||
|
deleteCircleDocument,
|
||||||
|
fetchCourseSessionDocuments,
|
||||||
|
uploadCircleDocument,
|
||||||
|
} from "@/services/files";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
|
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
|
||||||
|
import { useCircleStore } from "@/stores/circle";
|
||||||
|
|
||||||
const cockpitStore = useCockpitStore();
|
const cockpitStore = useCockpitStore();
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
const circleStore = useCircleStore();
|
||||||
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
|
||||||
const circleStore = useCircleStore();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const showUploadModal = ref(false);
|
const showUploadModal = ref(false);
|
||||||
const showUploadErrorMessage = ref(false);
|
const showUploadErrorMessage = ref(false);
|
||||||
const isUploading = ref(false);
|
const isUploading = ref(false);
|
||||||
|
|
||||||
|
const circleDocumentsResultData = ref<CircleDocument[]>([]);
|
||||||
|
let courseSessionDocumentsUrl = "";
|
||||||
|
|
||||||
|
async function fetchDocuments() {
|
||||||
|
const result = await fetchCourseSessionDocuments(courseSession.value?.id);
|
||||||
|
if (result.length > 0) {
|
||||||
|
circleDocumentsResultData.value = result;
|
||||||
|
} else {
|
||||||
|
circleDocumentsResultData.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
log.debug("DocumentPage mounted");
|
||||||
|
if (courseSession.value?.id) {
|
||||||
|
courseSessionDocumentsUrl = `/api/core/document/list/${courseSession.value?.id}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetchDocuments();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
// workaround to load learning sequences when circle changes
|
||||||
|
() => cockpitStore.currentCircle,
|
||||||
|
async () => {
|
||||||
|
if (cockpitStore.currentCircle) {
|
||||||
|
await circleStore.loadCircle(
|
||||||
|
courseSession.value?.course.slug,
|
||||||
|
cockpitStore.currentCircle?.slug
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
const dropdownLearningSequences = computed(() =>
|
const dropdownLearningSequences = computed(() =>
|
||||||
circleStore.circle?.learningSequences.map((sequence) => ({
|
circleStore.circle?.learningSequences.map((sequence) => ({
|
||||||
id: sequence.id,
|
id: sequence.id,
|
||||||
name: sequence.title,
|
name: `${sequence.title}`,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const circleDocuments = computed(() => {
|
||||||
|
return circleDocumentsResultData.value.filter(
|
||||||
|
(d) => d.learning_sequence.circle.slug === cockpitStore.currentCircle?.slug
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const deleteDocument = async (doc: CircleDocument) => {
|
const deleteDocument = async (doc: CircleDocument) => {
|
||||||
const options = {
|
const options = {
|
||||||
title: t("circlePage.documents.deleteModalTitle"),
|
title: t("circlePage.documents.deleteModalTitle"),
|
||||||
|
|
@ -39,7 +85,10 @@ const deleteDocument = async (doc: CircleDocument) => {
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await dialog.confirm(options);
|
await dialog.confirm(options);
|
||||||
courseSessionsStore.removeDocument(doc.id);
|
await deleteCircleDocument(doc.id, courseSessionDocumentsUrl);
|
||||||
|
circleDocumentsResultData.value = circleDocumentsResultData.value.filter(
|
||||||
|
(d) => d.id !== doc.id
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.debug("rejected");
|
log.debug("rejected");
|
||||||
}
|
}
|
||||||
|
|
@ -53,11 +102,13 @@ async function uploadDocument(data: DocumentUploadData) {
|
||||||
if (!courseSessionsStore.currentCourseSession) {
|
if (!courseSessionsStore.currentCourseSession) {
|
||||||
throw new Error("No course session found");
|
throw new Error("No course session found");
|
||||||
}
|
}
|
||||||
const newDocument = await uploadCircleDocument(
|
await uploadCircleDocument(
|
||||||
data,
|
data,
|
||||||
courseSessionsStore.currentCourseSession.id
|
courseSessionsStore.currentCourseSession.id,
|
||||||
|
courseSessionDocumentsUrl
|
||||||
);
|
);
|
||||||
courseSessionsStore.addDocument(newDocument);
|
await fetchDocuments();
|
||||||
|
|
||||||
showUploadModal.value = false;
|
showUploadModal.value = false;
|
||||||
isUploading.value = false;
|
isUploading.value = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -84,7 +135,7 @@ async function uploadDocument(data: DocumentUploadData) {
|
||||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||||
<h2>{{ t("a.Unterlagen für Teilnehmenden") }}</h2>
|
<h2>{{ t("a.Unterlagen für Teilnehmenden") }}</h2>
|
||||||
<ItDropdownSelect
|
<ItDropdownSelect
|
||||||
:model-value="cockpitStore.selectedCircle"
|
:model-value="cockpitStore.currentCircle"
|
||||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||||
:items="cockpitStore.circles"
|
:items="cockpitStore.circles"
|
||||||
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
||||||
|
|
@ -96,23 +147,15 @@ async function uploadDocument(data: DocumentUploadData) {
|
||||||
{{ t("circlePage.documents.action") }}
|
{{ t("circlePage.documents.action") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul
|
<ul v-if="circleDocuments.length" class="mt-8 border-t border-t-gray-500">
|
||||||
v-if="courseSessionsStore.circleDocuments.length"
|
<DocumentListItem
|
||||||
class="mt-8 border-t border-t-gray-500"
|
v-for="doc of circleDocuments"
|
||||||
>
|
:key="doc.url"
|
||||||
<template
|
:subtitle="doc.learning_sequence.title"
|
||||||
v-for="learningSequence of courseSessionsStore.circleDocuments"
|
:can-delete="true"
|
||||||
:key="learningSequence.id"
|
:doc="doc"
|
||||||
>
|
@delete="deleteDocument(doc)"
|
||||||
<DocumentListItem
|
/>
|
||||||
v-for="doc of learningSequence.documents"
|
|
||||||
:key="doc.url"
|
|
||||||
:subtitle="learningSequence.title"
|
|
||||||
:can-delete="courseSessionsStore.canUploadCircleDocuments"
|
|
||||||
:doc="doc"
|
|
||||||
@delete="deleteDocument(doc)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<ItModal v-model="showUploadModal">
|
<ItModal v-model="showUploadModal">
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ const formData = reactive<DocumentUploadData>({
|
||||||
file: null,
|
file: null,
|
||||||
name: "",
|
name: "",
|
||||||
learningSequence: {
|
learningSequence: {
|
||||||
id: -1,
|
id: "-1",
|
||||||
name: t("circlePage.documents.chooseSequence"),
|
name: t("circlePage.documents.chooseSequence"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -56,7 +56,7 @@ function submitForm() {
|
||||||
|
|
||||||
function validateForm() {
|
function validateForm() {
|
||||||
formErrors.file = formData.file === null;
|
formErrors.file = formData.file === null;
|
||||||
formErrors.learningSequence = formData.learningSequence.id === -1;
|
formErrors.learningSequence = formData.learningSequence.id === "-1";
|
||||||
formErrors.name = formData.name === "";
|
formErrors.name = formData.name === "";
|
||||||
|
|
||||||
for (const [, value] of Object.entries(formErrors)) {
|
for (const [, value] of Object.entries(formErrors)) {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const certificatesQuery = useQuery({
|
||||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
courseSlug: props.courseSlug,
|
courseSlug: props.courseSlug,
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const certificatesQuery = useQuery({
|
||||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
courseSlug: props.courseSlug,
|
courseSlug: props.courseSlug,
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const certificatesQuery = useQuery({
|
||||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
courseSlug: props.courseSlug,
|
courseSlug: props.courseSlug,
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCircleStore } from "@/stores/circle";
|
import { useCircleStore } from "@/stores/circle";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
||||||
import type { CourseSessionUser } from "@/types";
|
import type { CourseSessionUser } from "@/types";
|
||||||
import { humanizeDuration } from "@/utils/humanizeDuration";
|
import { humanizeDuration } from "@/utils/humanizeDuration";
|
||||||
import sumBy from "lodash/sumBy";
|
import sumBy from "lodash/sumBy";
|
||||||
|
|
@ -11,6 +10,7 @@ import CircleDiagram from "./CircleDiagram.vue";
|
||||||
import CircleOverview from "./CircleOverview.vue";
|
import CircleOverview from "./CircleOverview.vue";
|
||||||
import DocumentSection from "./DocumentSection.vue";
|
import DocumentSection from "./DocumentSection.vue";
|
||||||
import LearningSequence from "./LearningSequence.vue";
|
import LearningSequence from "./LearningSequence.vue";
|
||||||
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -20,7 +20,8 @@ export interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
const circleStore = useCircleStore();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
readonly: false,
|
readonly: false,
|
||||||
|
|
@ -29,7 +30,12 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
|
|
||||||
log.debug("CirclePage created", props.readonly, props.profileUser);
|
log.debug("CirclePage created", props.readonly, props.profileUser);
|
||||||
|
|
||||||
const circleStore = useCircleStore();
|
const circleExperts = computed(() => {
|
||||||
|
if (circleStore.circle) {
|
||||||
|
return courseSessionDetailResult.filterCircleExperts(circleStore.circle.slug);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
const duration = computed(() => {
|
const duration = computed(() => {
|
||||||
if (circleStore.circle) {
|
if (circleStore.circle) {
|
||||||
|
|
@ -190,10 +196,7 @@ onMounted(async () => {
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-for="expert in circleExperts" :key="expert.user_id">
|
||||||
v-for="expert in courseSessionsStore.circleExperts"
|
|
||||||
:key="expert.user_id"
|
|
||||||
>
|
|
||||||
<div class="mb-2 mt-2 flex flex-row items-center">
|
<div class="mb-2 mt-2 flex flex-row items-center">
|
||||||
<img
|
<img
|
||||||
class="mr-2 h-[45px] rounded-full"
|
class="mr-2 h-[45px] rounded-full"
|
||||||
|
|
@ -205,8 +208,8 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
v-if="courseSessionsStore.circleExperts.length > 0"
|
v-if="circleExperts.length > 0"
|
||||||
:href="'mailto:' + courseSessionsStore.circleExperts[0].email"
|
:href="'mailto:' + circleExperts[0].email"
|
||||||
class="btn-secondary mt-4 text-xl"
|
class="btn-secondary mt-4 text-xl"
|
||||||
>
|
>
|
||||||
{{ $t("circlePage.contactExpertButton") }}
|
{{ $t("circlePage.contactExpertButton") }}
|
||||||
|
|
|
||||||
|
|
@ -8,28 +8,46 @@
|
||||||
{{ $t("circlePage.documents.userDescription") }}
|
{{ $t("circlePage.documents.userDescription") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul v-if="circleDocuments.length" class="mt-8 border-t border-t-gray-500">
|
||||||
v-if="courseSessionsStore.circleDocuments.length"
|
<DocumentListItem
|
||||||
class="mt-8 border-t border-t-gray-500"
|
v-for="doc of circleDocuments"
|
||||||
>
|
:key="doc.url"
|
||||||
<template
|
:subtitle="doc.learning_sequence.title"
|
||||||
v-for="learningSequence of courseSessionsStore.circleDocuments"
|
:doc="doc"
|
||||||
:key="learningSequence.id"
|
/>
|
||||||
>
|
|
||||||
<DocumentListItem
|
|
||||||
v-for="doc of learningSequence.documents"
|
|
||||||
:key="doc.url"
|
|
||||||
:subtitle="learningSequence.title"
|
|
||||||
:doc="doc"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
||||||
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
|
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import { useCircleStore } from "@/stores/circle";
|
||||||
|
import type { CircleDocument } from "@/types";
|
||||||
|
import { fetchCourseSessionDocuments } from "@/services/files";
|
||||||
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
const circleStore = useCircleStore();
|
||||||
|
|
||||||
|
const circleDocumentsResultData = ref<CircleDocument[]>([]);
|
||||||
|
|
||||||
|
async function fetchDocuments() {
|
||||||
|
const result = await fetchCourseSessionDocuments(courseSession.value?.id);
|
||||||
|
if (result.length > 0) {
|
||||||
|
circleDocumentsResultData.value = result;
|
||||||
|
} else {
|
||||||
|
circleDocumentsResultData.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const circleDocuments = computed(() => {
|
||||||
|
return circleDocumentsResultData.value.filter(
|
||||||
|
(d) => d.learning_sequence.circle.slug === circleStore.circle?.slug
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchDocuments();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,19 @@
|
||||||
import DateEmbedding from "@/components/dueDates/DateEmbedding.vue";
|
import DateEmbedding from "@/components/dueDates/DateEmbedding.vue";
|
||||||
import type { Assignment } from "@/types";
|
import type { Assignment } from "@/types";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import type { Dayjs } from "dayjs";
|
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assignment: Assignment;
|
assignment: Assignment;
|
||||||
dueDate?: Dayjs;
|
submissionDeadlineStart?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
dueDate: undefined,
|
submissionDeadlineStart: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
log.debug("AssignmentIntroductionView created", props.assignment, props.dueDate);
|
log.debug("AssignmentIntroductionView created", props.assignment);
|
||||||
|
|
||||||
const step = useRouteQuery("step");
|
const step = useRouteQuery("step");
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -40,9 +40,10 @@ const step = useRouteQuery("step");
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3 class="mb-4 mt-8">{{ $t("assignment.dueDateSubmission") }}</h3>
|
<h3 class="mb-4 mt-8">{{ $t("assignment.dueDateSubmission") }}</h3>
|
||||||
<p v-if="props.dueDate?.toString() === 'Invalid Date'" class="text-large">
|
|
||||||
|
<p v-if="submissionDeadlineStart" class="text-large">
|
||||||
{{ $t("assignment.dueDateIntroduction") }}
|
{{ $t("assignment.dueDateIntroduction") }}
|
||||||
<DateEmbedding :single-date="dueDate"></DateEmbedding>
|
<DateEmbedding :single-date="dayjs(submissionDeadlineStart)"></DateEmbedding>
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-large">
|
<p v-else class="text-large">
|
||||||
{{ $t("assignment.dueDateNotSet") }}
|
{{ $t("assignment.dueDateNotSet") }}
|
||||||
|
|
|
||||||
|
|
@ -3,34 +3,36 @@ import DateEmbedding from "@/components/dueDates/DateEmbedding.vue";
|
||||||
import ItButton from "@/components/ui/ItButton.vue";
|
import ItButton from "@/components/ui/ItButton.vue";
|
||||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||||
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||||
import { bustItGetCache } from "@/fetchHelpers";
|
import { bustItGetCache } from "@/fetchHelpers";
|
||||||
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
||||||
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import type { Assignment, AssignmentCompletion, AssignmentTask } from "@/types";
|
import type { Assignment, AssignmentCompletion, AssignmentTask } from "@/types";
|
||||||
import { useMutation } from "@urql/vue";
|
import { useMutation } from "@urql/vue";
|
||||||
import type { Dayjs } from "dayjs";
|
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import eventBus from "@/utils/eventBus";
|
import eventBus from "@/utils/eventBus";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { useCircleStore } from "@/stores/circle";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
assignment: Assignment;
|
assignment: Assignment;
|
||||||
learningContentId: number;
|
learningContentId: string;
|
||||||
assignmentCompletion?: AssignmentCompletion;
|
assignmentCompletion?: AssignmentCompletion;
|
||||||
courseSessionId: number;
|
courseSessionId: string;
|
||||||
dueDate: Dayjs;
|
submissionDeadlineStart?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "editTask", task: AssignmentTask): void;
|
(e: "editTask", task: AssignmentTask): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
const circleStore = useCircleStore();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
|
@ -38,8 +40,15 @@ const state = reactive({
|
||||||
confirmPerson: false,
|
confirmPerson: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const circleExperts = computed(() => {
|
||||||
|
if (circleStore.circle) {
|
||||||
|
return courseSessionDetailResult.filterCircleExperts(circleStore.circle.slug);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
const circleExpert = computed(() => {
|
const circleExpert = computed(() => {
|
||||||
return courseSessionsStore.circleExperts[0];
|
return circleExperts.value[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
const circleExpertName = computed(() => {
|
const circleExpertName = computed(() => {
|
||||||
|
|
@ -74,9 +83,9 @@ const onEditTask = (task: AssignmentTask) => {
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
await upsertAssignmentCompletionMutation.executeMutation({
|
await upsertAssignmentCompletionMutation.executeMutation({
|
||||||
assignmentId: props.assignment.id.toString(),
|
assignmentId: props.assignment.id,
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
learningContentId: props.learningContentId.toString(),
|
learningContentId: props.learningContentId,
|
||||||
completionDataString: JSON.stringify({}),
|
completionDataString: JSON.stringify({}),
|
||||||
completionStatus: "SUBMITTED",
|
completionStatus: "SUBMITTED",
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
|
@ -138,9 +147,11 @@ const onSubmit = async () => {
|
||||||
{{ $t("assignment.showAssessmentDocument") }}
|
{{ $t("assignment.showAssessmentDocument") }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="isCasework" class="pt-6">
|
<p v-if="isCasework && props.submissionDeadlineStart" class="pt-6">
|
||||||
{{ $t("assignment.dueDateSubmission") }}
|
{{ $t("assignment.dueDateSubmission") }}
|
||||||
<DateEmbedding :single-date="dueDate"></DateEmbedding>
|
<DateEmbedding
|
||||||
|
:single-date="dayjs(props.submissionDeadlineStart)"
|
||||||
|
></DateEmbedding>
|
||||||
</p>
|
</p>
|
||||||
<ItButton
|
<ItButton
|
||||||
class="mt-6"
|
class="mt-6"
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ import log from "loglevel";
|
||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
assignmentId: number;
|
assignmentId: string;
|
||||||
learningContentId: number;
|
learningContentId: string;
|
||||||
task: AssignmentTask;
|
task: AssignmentTask;
|
||||||
assignmentCompletion?: AssignmentCompletion;
|
assignmentCompletion?: AssignmentCompletion;
|
||||||
}>();
|
}>();
|
||||||
|
|
@ -33,9 +33,9 @@ const upsertAssignmentCompletionMutation = useMutation(
|
||||||
async function upsertAssignmentCompletion(completion_data: AssignmentCompletionData) {
|
async function upsertAssignmentCompletion(completion_data: AssignmentCompletionData) {
|
||||||
try {
|
try {
|
||||||
await upsertAssignmentCompletionMutation.executeMutation({
|
await upsertAssignmentCompletionMutation.executeMutation({
|
||||||
assignmentId: props.assignmentId.toString(),
|
assignmentId: props.assignmentId,
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
learningContentId: props.learningContentId.toString(),
|
learningContentId: props.learningContentId,
|
||||||
completionDataString: JSON.stringify(completion_data),
|
completionDataString: JSON.stringify(completion_data),
|
||||||
completionStatus: "IN_PROGRESS",
|
completionStatus: "IN_PROGRESS",
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,22 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||||
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
||||||
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
|
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
|
||||||
import AssignmentIntroductionView from "@/pages/learningPath/learningContentPage/assignment/AssignmentIntroductionView.vue";
|
import AssignmentIntroductionView from "@/pages/learningPath/learningContentPage/assignment/AssignmentIntroductionView.vue";
|
||||||
import AssignmentSubmissionView from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue";
|
import AssignmentSubmissionView from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue";
|
||||||
import AssignmentTaskView from "@/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue";
|
import AssignmentTaskView from "@/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue";
|
||||||
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import type {
|
import type {
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentCompletion,
|
AssignmentCompletion,
|
||||||
AssignmentTask,
|
AssignmentTask,
|
||||||
CourseSessionAssignment,
|
|
||||||
CourseSessionUser,
|
|
||||||
LearningContentAssignment,
|
LearningContentAssignment,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import { useMutation, useQuery } from "@urql/vue";
|
import { useMutation, useQuery } from "@urql/vue";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import dayjs from "dayjs";
|
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
import { computed, onMounted, reactive, ref, watchEffect } from "vue";
|
import { computed, onMounted, ref, watchEffect } from "vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import { bustItGetCache } from "@/fetchHelpers";
|
import { bustItGetCache } from "@/fetchHelpers";
|
||||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||||
|
|
@ -30,14 +26,6 @@ const { t } = useTranslation();
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
interface State {
|
|
||||||
courseSessionAssignment: CourseSessionAssignment | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const state: State = reactive({
|
|
||||||
courseSessionAssignment: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
learningContent: LearningContentAssignment;
|
learningContent: LearningContentAssignment;
|
||||||
}>();
|
}>();
|
||||||
|
|
@ -45,17 +33,24 @@ const props = defineProps<{
|
||||||
const queryResult = useQuery({
|
const queryResult = useQuery({
|
||||||
query: ASSIGNMENT_COMPLETION_QUERY,
|
query: ASSIGNMENT_COMPLETION_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
assignmentId: props.learningContent.content_assignment_id.toString(),
|
assignmentId: props.learningContent.content_assignment_id,
|
||||||
learningContentId: props.learningContent.id.toString(),
|
learningContentId: props.learningContent.id,
|
||||||
},
|
},
|
||||||
pause: true,
|
pause: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const upsertAssignmentCompletionMutation = useMutation(
|
const upsertAssignmentCompletionMutation = useMutation(
|
||||||
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
|
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const submissionDeadline = computed(() => {
|
||||||
|
return courseSessionDetailResult.findAssignment(props.learningContent.id)
|
||||||
|
?.submission_deadline;
|
||||||
|
});
|
||||||
|
|
||||||
// FIXME daniel: `useRouteQuery` from usevue is currently the reason that we have to
|
// FIXME daniel: `useRouteQuery` from usevue is currently the reason that we have to
|
||||||
// fix the version of @vueuse/router and @vueuse/core to 10.1.0
|
// fix the version of @vueuse/router and @vueuse/core to 10.1.0
|
||||||
// it fails with version 10.2.0. I have a reminder to check out the situation
|
// it fails with version 10.2.0. I have a reminder to check out the situation
|
||||||
|
|
@ -104,10 +99,6 @@ onMounted(async () => {
|
||||||
props.learningContent
|
props.learningContent
|
||||||
);
|
);
|
||||||
|
|
||||||
state.courseSessionAssignment = useCourseSessionsStore().findCourseSessionAssignment(
|
|
||||||
props.learningContent.id
|
|
||||||
);
|
|
||||||
|
|
||||||
// create initial `AssignmentCompletion` first, so that it exists and we don't
|
// create initial `AssignmentCompletion` first, so that it exists and we don't
|
||||||
// have reactivity problem accessing it.
|
// have reactivity problem accessing it.
|
||||||
await initUpsertAssignmentCompletion();
|
await initUpsertAssignmentCompletion();
|
||||||
|
|
@ -120,9 +111,7 @@ const numPages = computed(() => {
|
||||||
});
|
});
|
||||||
const showPreviousButton = computed(() => stepIndex.value != 0);
|
const showPreviousButton = computed(() => stepIndex.value != 0);
|
||||||
const showNextButton = computed(() => stepIndex.value + 1 < numPages.value);
|
const showNextButton = computed(() => stepIndex.value + 1 < numPages.value);
|
||||||
const dueDate = computed(() =>
|
|
||||||
dayjs(state.courseSessionAssignment?.submission_deadline_start)
|
|
||||||
);
|
|
||||||
const currentTask = computed(() => {
|
const currentTask = computed(() => {
|
||||||
if (stepIndex.value > 0 && stepIndex.value <= numTasks.value) {
|
if (stepIndex.value > 0 && stepIndex.value <= numTasks.value) {
|
||||||
return assignment.value?.tasks[stepIndex.value - 1];
|
return assignment.value?.tasks[stepIndex.value - 1];
|
||||||
|
|
@ -133,9 +122,9 @@ const currentTask = computed(() => {
|
||||||
const initUpsertAssignmentCompletion = async () => {
|
const initUpsertAssignmentCompletion = async () => {
|
||||||
try {
|
try {
|
||||||
await upsertAssignmentCompletionMutation.executeMutation({
|
await upsertAssignmentCompletionMutation.executeMutation({
|
||||||
assignmentId: props.learningContent.content_assignment_id.toString(),
|
assignmentId: props.learningContent.content_assignment_id,
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
learningContentId: props.learningContent.id.toString(),
|
learningContentId: props.learningContent.id,
|
||||||
completionDataString: JSON.stringify({}),
|
completionDataString: JSON.stringify({}),
|
||||||
completionStatus: "IN_PROGRESS",
|
completionStatus: "IN_PROGRESS",
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
|
@ -194,9 +183,9 @@ const subTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const assignmentUser = computed(() => {
|
const assignmentUser = computed(() => {
|
||||||
return courseSession.value.users.find(
|
return (courseSessionDetailResult.courseSessionDetail.value?.users ?? []).find(
|
||||||
(user) => user.user_id === userStore.id
|
(u) => u.user_id === userStore.id
|
||||||
) as CourseSessionUser;
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -204,7 +193,7 @@ const assignmentUser = computed(() => {
|
||||||
<div v-if="queryResult.fetching.value"></div>
|
<div v-if="queryResult.fetching.value"></div>
|
||||||
<div v-else-if="queryResult.error.value">{{ queryResult.error.value }}</div>
|
<div v-else-if="queryResult.error.value">{{ queryResult.error.value }}</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-if="assignment && assignmentCompletion">
|
<div v-if="assignment && assignmentCompletion && assignmentUser">
|
||||||
<div class="flex flex-col lg:flex-row">
|
<div class="flex flex-col lg:flex-row">
|
||||||
<div
|
<div
|
||||||
v-if="assignmentCompletion?.completion_status === 'EVALUATION_SUBMITTED'"
|
v-if="assignmentCompletion?.completion_status === 'EVALUATION_SUBMITTED'"
|
||||||
|
|
@ -239,8 +228,8 @@ const assignmentUser = computed(() => {
|
||||||
<div>
|
<div>
|
||||||
<AssignmentIntroductionView
|
<AssignmentIntroductionView
|
||||||
v-if="stepIndex === 0"
|
v-if="stepIndex === 0"
|
||||||
:due-date="dueDate"
|
|
||||||
:assignment="assignment"
|
:assignment="assignment"
|
||||||
|
:submission-deadline-start="submissionDeadline?.start"
|
||||||
></AssignmentIntroductionView>
|
></AssignmentIntroductionView>
|
||||||
<AssignmentTaskView
|
<AssignmentTaskView
|
||||||
v-else-if="currentTask"
|
v-else-if="currentTask"
|
||||||
|
|
@ -251,11 +240,11 @@ const assignmentUser = computed(() => {
|
||||||
></AssignmentTaskView>
|
></AssignmentTaskView>
|
||||||
<AssignmentSubmissionView
|
<AssignmentSubmissionView
|
||||||
v-else-if="stepIndex + 1 === numPages"
|
v-else-if="stepIndex + 1 === numPages"
|
||||||
:due-date="dueDate"
|
|
||||||
:assignment="assignment"
|
:assignment="assignment"
|
||||||
:assignment-completion="assignmentCompletion"
|
:assignment-completion="assignmentCompletion"
|
||||||
:learning-content-id="props.learningContent.id"
|
:learning-content-id="props.learningContent.id"
|
||||||
:course-session-id="courseSession.id"
|
:course-session-id="courseSession.id"
|
||||||
|
:submission-deadline-start="submissionDeadline?.start"
|
||||||
@edit-task="jumpToTask($event)"
|
@edit-task="jumpToTask($event)"
|
||||||
></AssignmentSubmissionView>
|
></AssignmentSubmissionView>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,12 @@
|
||||||
<it-icon-calendar-light class="w-[60px] grid-in-icon" />
|
<it-icon-calendar-light class="w-[60px] grid-in-icon" />
|
||||||
<h2 class="text-large font-bold grid-in-title">{{ $t("a.Datum") }}</h2>
|
<h2 class="text-large font-bold grid-in-title">{{ $t("a.Datum") }}</h2>
|
||||||
<p class="grid-in-value">
|
<p class="grid-in-value">
|
||||||
{{ formatDueDate(props.attendanceCourse.start, props.attendanceCourse.end) }}
|
{{
|
||||||
|
formatDueDate(
|
||||||
|
props.attendanceCourse.due_date.start,
|
||||||
|
props.attendanceCourse.due_date.end
|
||||||
|
)
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-12 grid grid-cols-icon-card gap-x-4 grid-areas-icon-card">
|
<div class="mb-12 grid grid-cols-icon-card gap-x-4 grid-areas-icon-card">
|
||||||
|
|
@ -24,8 +29,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { formatDueDate } from "@/components/dueDates/dueDatesUtils";
|
import { formatDueDate } from "@/components/dueDates/dueDatesUtils";
|
||||||
import type { CourseSessionAttendanceCourse } from "@/types";
|
import type { CourseSessionAttendanceCourse } from "@/types";
|
||||||
import dayjs from "dayjs";
|
|
||||||
import LocalizedFormat from "dayjs/plugin/localizedFormat";
|
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
|
@ -34,7 +37,6 @@ export interface Props {
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
dayjs.extend(LocalizedFormat);
|
|
||||||
const location = computed(() => props.attendanceCourse.location);
|
const location = computed(() => props.attendanceCourse.location);
|
||||||
const trainer = computed(() => props.attendanceCourse.trainer);
|
const trainer = computed(() => props.attendanceCourse.trainer);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AttendanceCourse from "@/pages/learningPath/learningContentPage/attendanceCourse/AttendanceCourse.vue";
|
import AttendanceCourse from "@/pages/learningPath/learningContentPage/attendanceCourse/AttendanceCourse.vue";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
||||||
import type { LearningContentAttendanceCourse } from "@/types";
|
import type { LearningContentAttendanceCourse } from "@/types";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import LearningContentSimpleLayout from "../layouts/LearningContentSimpleLayout.vue";
|
import LearningContentSimpleLayout from "../layouts/LearningContentSimpleLayout.vue";
|
||||||
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
content: LearningContentAttendanceCourse;
|
content: LearningContentAttendanceCourse;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const courseSessionAttendanceCourse = computed(() => {
|
const courseSessionAttendanceCourse = computed(() => {
|
||||||
return courseSessionsStore.findAttendanceCourse(props.content.id);
|
return courseSessionDetailResult.findAttendanceCourse(props.content.id);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ import * as log from "loglevel";
|
||||||
import { itPost } from "@/fetchHelpers";
|
import { itPost } from "@/fetchHelpers";
|
||||||
import { useQuery } from "@urql/vue";
|
import { useQuery } from "@urql/vue";
|
||||||
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
|
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
||||||
import {
|
import {
|
||||||
|
|
@ -24,18 +23,18 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const courseSessionEdoniqTest = computed(() => {
|
const courseSessionEdoniqTest = computed(() => {
|
||||||
return courseSessionsStore.findCourseSessionEdoniqTest(props.content.id);
|
return courseSessionDetailResult.findEdoniqTest(props.content.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
const queryResult = useQuery({
|
const queryResult = useQuery({
|
||||||
query: ASSIGNMENT_COMPLETION_QUERY,
|
query: ASSIGNMENT_COMPLETION_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
assignmentId: props.content.content_assignment_id.toString(),
|
assignmentId: props.content.content_assignment_id,
|
||||||
learningContentId: props.content.id.toString(),
|
learningContentId: props.content.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -53,7 +52,7 @@ const extendedTimeTest = ref(false);
|
||||||
|
|
||||||
const deadlineInPast = computed(() => {
|
const deadlineInPast = computed(() => {
|
||||||
// with 16 minutes buffer
|
// with 16 minutes buffer
|
||||||
return dayjs(courseSessionEdoniqTest.value?.deadline_start)
|
return dayjs(courseSessionEdoniqTest.value?.deadline.start)
|
||||||
.add(16, "minute")
|
.add(16, "minute")
|
||||||
.isBefore(dayjs());
|
.isBefore(dayjs());
|
||||||
});
|
});
|
||||||
|
|
@ -90,7 +89,7 @@ async function startTest() {
|
||||||
<p class="mt-2 text-lg">
|
<p class="mt-2 text-lg">
|
||||||
{{
|
{{
|
||||||
$t("edoniqTest.submitDateDescription", {
|
$t("edoniqTest.submitDateDescription", {
|
||||||
x: formatDueDate(courseSessionEdoniqTest.deadline_start),
|
x: formatDueDate(courseSessionEdoniqTest.deadline.start),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -158,7 +157,7 @@ async function startTest() {
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ $t("a.Abgabetermin") }}:
|
{{ $t("a.Abgabetermin") }}:
|
||||||
{{ getDateString(dayjs(courseSessionEdoniqTest?.deadline_start)) }}
|
{{ getDateString(dayjs(courseSessionEdoniqTest?.deadline.start)) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,21 @@ import {
|
||||||
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
||||||
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
||||||
import { useCircleStore } from "@/stores/circle";
|
import { useCircleStore } from "@/stores/circle";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
||||||
import type { LearningContentFeedback } from "@/types";
|
import type { LearningContentFeedback } from "@/types";
|
||||||
import { useMutation } from "@urql/vue";
|
import { useMutation } from "@urql/vue";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed, onMounted, reactive, ref } from "vue";
|
import { computed, onMounted, reactive, ref } from "vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
content: LearningContentFeedback;
|
content: LearningContentFeedback;
|
||||||
}>();
|
}>();
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const circleStore = useCircleStore();
|
const circleStore = useCircleStore();
|
||||||
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const stepNo = useRouteQuery("step", "0", { transform: Number, mode: "push" });
|
const stepNo = useRouteQuery("step", "0", { transform: Number, mode: "push" });
|
||||||
|
|
@ -33,6 +33,13 @@ const title = computed(
|
||||||
() => `«${circleStore.circle?.title}»: ${t("feedback.areYouSatisfied")}`
|
() => `«${circleStore.circle?.title}»: ${t("feedback.areYouSatisfied")}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const circleExperts = computed(() => {
|
||||||
|
if (circleStore.circle) {
|
||||||
|
return courseSessionDetailResult.filterCircleExperts(circleStore.circle.slug);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
const stepLabels = [
|
const stepLabels = [
|
||||||
t("general.introduction"),
|
t("general.introduction"),
|
||||||
t("feedback.satisfactionLabel"),
|
t("feedback.satisfactionLabel"),
|
||||||
|
|
@ -185,8 +192,8 @@ function hasStepValidInput(stepNumber: number) {
|
||||||
function mutateFeedback(data: FeedbackData, submit = false) {
|
function mutateFeedback(data: FeedbackData, submit = false) {
|
||||||
log.debug("mutate feedback", feedbackData);
|
log.debug("mutate feedback", feedbackData);
|
||||||
return executeMutation({
|
return executeMutation({
|
||||||
courseSessionId: courseSession.value.id.toString(),
|
courseSessionId: courseSession.value.id,
|
||||||
learningContentId: props.content.id.toString(),
|
learningContentId: props.content.id,
|
||||||
data: data,
|
data: data,
|
||||||
submitted: submit,
|
submitted: submit,
|
||||||
})
|
})
|
||||||
|
|
@ -244,7 +251,7 @@ onMounted(async () => {
|
||||||
<p v-if="stepNo === 0" class="mt-10">
|
<p v-if="stepNo === 0" class="mt-10">
|
||||||
{{
|
{{
|
||||||
$t("feedback.intro", {
|
$t("feedback.intro", {
|
||||||
name: `${courseSessionsStore.circleExperts[0]?.first_name} ${courseSessionsStore.circleExperts[0]?.last_name}`,
|
name: `${circleExperts[0]?.first_name} ${circleExperts[0]?.last_name}`,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -265,10 +272,10 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
<FeedbackCompletition
|
<FeedbackCompletition
|
||||||
v-if="stepNo === 11"
|
v-if="stepNo === 11"
|
||||||
:avatar-url="courseSessionsStore.circleExperts[0].avatar_url"
|
:avatar-url="circleExperts[0].avatar_url"
|
||||||
:title="
|
:title="
|
||||||
$t('feedback.completionTitle', {
|
$t('feedback.completionTitle', {
|
||||||
name: `${courseSessionsStore.circleExperts[0].first_name} ${courseSessionsStore.circleExperts[0].last_name}`,
|
name: `${circleExperts[0].first_name} ${circleExperts[0].last_name}`,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
:description="$t('feedback.completionDescription')"
|
:description="$t('feedback.completionDescription')"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import eventBus from "@/utils/eventBus";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import { computed, onUnmounted } from "vue";
|
import { computed, onUnmounted } from "vue";
|
||||||
import { getPreviousRoute } from "@/router/history";
|
import { getPreviousRoute } from "@/router/history";
|
||||||
|
import { getCompetenceNaviUrl } from "@/utils/utils";
|
||||||
|
|
||||||
log.debug("LearningContent.vue setup");
|
log.debug("LearningContent.vue setup");
|
||||||
|
|
||||||
|
|
@ -127,7 +128,7 @@ onUnmounted(() => {
|
||||||
<div class="mt-6 lg:mt-12">
|
<div class="mt-6 lg:mt-12">
|
||||||
{{ $t("selfEvaluation.progressText") }}
|
{{ $t("selfEvaluation.progressText") }}
|
||||||
<router-link
|
<router-link
|
||||||
:to="courseSession.competence_url"
|
:to="getCompetenceNaviUrl(courseSession.course.slug)"
|
||||||
class="text-primary-500 underline"
|
class="text-primary-500 underline"
|
||||||
>
|
>
|
||||||
{{ $t("selfEvaluation.progressLink") }}
|
{{ $t("selfEvaluation.progressLink") }}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
import { itGet } from "@/fetchHelpers";
|
import { itGet } from "@/fetchHelpers";
|
||||||
import type { LearningPath } from "@/services/learningPath";
|
import type { LearningPath } from "@/services/learningPath";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
|
||||||
import { useUserStore } from "@/stores/user";
|
|
||||||
import type {
|
import type {
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentCompletion,
|
AssignmentCompletion,
|
||||||
|
|
@ -31,23 +28,21 @@ export function calcLearningContentAssignments(learningPath?: LearningPath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadAssignmentCompletionStatusData(
|
export async function loadAssignmentCompletionStatusData(
|
||||||
assignmentId: number,
|
assignmentId: string,
|
||||||
courseSessionId: number,
|
courseSessionId: string,
|
||||||
learningContentId: number
|
learningContentId: string
|
||||||
) {
|
) {
|
||||||
const cockpitStore = useCockpitStore();
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const assignmentCompletionData = (await itGet(
|
const assignmentCompletionData = (await itGet(
|
||||||
`/api/assignment/${assignmentId}/${courseSessionId}/status/`
|
`/api/assignment/${assignmentId}/${courseSessionId}/status/`
|
||||||
)) as UserAssignmentCompletionStatus[];
|
)) as UserAssignmentCompletionStatus[];
|
||||||
|
|
||||||
const courseSessionUsers = await cockpitStore.loadCourseSessionMembers(
|
const members = courseSessionDetailResult.filterMembers();
|
||||||
courseSessionId
|
|
||||||
);
|
|
||||||
|
|
||||||
const gradedUsers: GradedUser[] = [];
|
const gradedUsers: GradedUser[] = [];
|
||||||
const assignmentSubmittedUsers: CourseSessionUser[] = [];
|
const assignmentSubmittedUsers: CourseSessionUser[] = [];
|
||||||
for (const csu of courseSessionUsers) {
|
for (const csu of members) {
|
||||||
const userAssignmentStatus = assignmentCompletionData.find(
|
const userAssignmentStatus = assignmentCompletionData.find(
|
||||||
(s) =>
|
(s) =>
|
||||||
s.assignment_user_id === csu.user_id &&
|
s.assignment_user_id === csu.user_id &&
|
||||||
|
|
@ -70,34 +65,10 @@ export async function loadAssignmentCompletionStatusData(
|
||||||
return {
|
return {
|
||||||
assignmentSubmittedUsers: assignmentSubmittedUsers,
|
assignmentSubmittedUsers: assignmentSubmittedUsers,
|
||||||
gradedUsers: gradedUsers,
|
gradedUsers: gradedUsers,
|
||||||
total: courseSessionUsers.length,
|
total: members.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findAssignmentDetail(assignmentId: number) {
|
|
||||||
const learningPathStore = useLearningPathStore();
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
|
|
||||||
// TODO: filter by selected circle
|
|
||||||
if (!courseSessionsStore.currentCourseSession) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const learningContents = calcLearningContentAssignments(
|
|
||||||
learningPathStore.learningPathForUser(
|
|
||||||
courseSessionsStore.currentCourseSession.course.slug,
|
|
||||||
userStore.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const learningContent = learningContents.find(
|
|
||||||
(lc) => lc.content_assignment_id === assignmentId
|
|
||||||
);
|
|
||||||
|
|
||||||
return courseSessionsStore.findCourseSessionAssignment(learningContent?.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function maxAssignmentPoints(assignment: Assignment) {
|
export function maxAssignmentPoints(assignment: Assignment) {
|
||||||
return sum(assignment.evaluation_tasks.map((task) => task.value.max_points));
|
return sum(assignment.evaluation_tasks.map((task) => task.value.max_points));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ export class Circle implements WagtailCircle {
|
||||||
previousCircle?: Circle;
|
previousCircle?: Circle;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly id: number,
|
public readonly id: string,
|
||||||
public readonly slug: string,
|
public readonly slug: string,
|
||||||
public readonly title: string,
|
public readonly title: string,
|
||||||
public readonly translation_key: string,
|
public readonly translation_key: string,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { itDelete, itFetch, itPost } from "@/fetchHelpers";
|
import { bustItGetCache, itDelete, itFetch, itGetCached, itPost } from "@/fetchHelpers";
|
||||||
import { getCookieValue } from "@/router/guards";
|
import { getCookieValue } from "@/router/guards";
|
||||||
import type { CircleDocument, DocumentUploadData } from "@/types";
|
import type { DocumentUploadData } from "@/types";
|
||||||
|
|
||||||
type FileData = {
|
type FileData = {
|
||||||
fields: Record<string, string>;
|
fields: Record<string, string>;
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function startFileUpload(fileData: DocumentUploadData, courseSessionId: number) {
|
async function startFileUpload(fileData: DocumentUploadData, courseSessionId: string) {
|
||||||
if (fileData === null || fileData.file === null) {
|
if (fileData === null || fileData.file === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -73,8 +73,9 @@ function handleUpload(url: string, options: RequestInit) {
|
||||||
|
|
||||||
export async function uploadCircleDocument(
|
export async function uploadCircleDocument(
|
||||||
data: DocumentUploadData,
|
data: DocumentUploadData,
|
||||||
courseSessionId: number
|
courseSessionId: string,
|
||||||
): Promise<CircleDocument> {
|
bustCacheUrlKey = ""
|
||||||
|
) {
|
||||||
if (data.file === null) {
|
if (data.file === null) {
|
||||||
throw new Error("No file selected");
|
throw new Error("No file selected");
|
||||||
}
|
}
|
||||||
|
|
@ -82,22 +83,25 @@ export async function uploadCircleDocument(
|
||||||
const startData = await startFileUpload(data, courseSessionId);
|
const startData = await startFileUpload(data, courseSessionId);
|
||||||
|
|
||||||
await uploadFile(startData, data.file);
|
await uploadFile(startData, data.file);
|
||||||
const response = await itPost(`/api/core/file/finish/`, {
|
const response = itPost(`/api/core/file/finish/`, {
|
||||||
file_id: startData.file_id,
|
file_id: startData.file_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newDocument: CircleDocument = {
|
if (bustCacheUrlKey) {
|
||||||
id: startData.id,
|
bustItGetCache(bustCacheUrlKey);
|
||||||
name: data.name,
|
}
|
||||||
file_name: data.file.name,
|
|
||||||
url: response.url,
|
|
||||||
course_session: courseSessionId,
|
|
||||||
learning_sequence: data.learningSequence.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Promise.resolve(newDocument);
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCircleDocument(documentId: string) {
|
export async function deleteCircleDocument(documentId: string, bustCacheUrlKey = "") {
|
||||||
return itDelete(`/api/core/document/${documentId}/`);
|
const result = itDelete(`/api/core/document/${documentId}/`);
|
||||||
|
if (bustCacheUrlKey) {
|
||||||
|
bustItGetCache(bustCacheUrlKey);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchCourseSessionDocuments(courseSessionId: string) {
|
||||||
|
return itGetCached(`/api/core/document/list/${courseSessionId}/`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export class LearningPath implements WagtailLearningPath {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly id: number,
|
public readonly id: string,
|
||||||
public readonly slug: string,
|
public readonly slug: string,
|
||||||
public readonly title: string,
|
public readonly title: string,
|
||||||
public readonly translation_key: string,
|
public readonly translation_key: string,
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,11 @@ describe("CourseSession Store", () => {
|
||||||
};
|
};
|
||||||
courseSessions = [
|
courseSessions = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: "1",
|
||||||
created_at: "2021-05-11T10:00:00.000000Z",
|
created_at: "2021-05-11T10:00:00.000000Z",
|
||||||
updated_at: "2023-05-11T10:00:00.000000Z",
|
updated_at: "2023-05-11T10:00:00.000000Z",
|
||||||
course: {
|
course: {
|
||||||
id: 1,
|
id: "1",
|
||||||
title: "Test Course",
|
title: "Test Course",
|
||||||
category_name: "Test Category",
|
category_name: "Test Category",
|
||||||
slug: "test-course",
|
slug: "test-course",
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
import { itGetCached } from "@/fetchHelpers";
|
|
||||||
import type { CourseSessionUser, ExpertSessionUser } from "@/types";
|
|
||||||
import log from "loglevel";
|
|
||||||
|
|
||||||
import { useCircleStore } from "@/stores/circle";
|
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import type { CircleLight, CourseSessionUser, ExpertSessionUser } from "@/types";
|
||||||
|
import log from "loglevel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
type CircleCockpit = CircleLight & {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type CockpitStoreState = {
|
export type CockpitStoreState = {
|
||||||
courseSessionMembers: CourseSessionUser[] | undefined;
|
courseSessionMembers: CourseSessionUser[] | undefined;
|
||||||
circles: {
|
circles: CircleCockpit[] | undefined;
|
||||||
id: string;
|
currentCircle: CircleCockpit | undefined;
|
||||||
name: string;
|
|
||||||
}[];
|
|
||||||
currentCourseSlug: string | undefined;
|
currentCourseSlug: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -22,77 +21,54 @@ export const useCockpitStore = defineStore({
|
||||||
return {
|
return {
|
||||||
courseSessionMembers: undefined,
|
courseSessionMembers: undefined,
|
||||||
circles: [],
|
circles: [],
|
||||||
|
currentCircle: undefined,
|
||||||
currentCourseSlug: undefined,
|
currentCourseSlug: undefined,
|
||||||
} as CockpitStoreState;
|
} as CockpitStoreState;
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async loadCircles(courseSlug: string, courseSessionId: number) {
|
async loadCircles(
|
||||||
log.debug("loadCircles called", courseSlug, courseSessionId);
|
courseSlug: string,
|
||||||
|
currentCourseSessionUser: CourseSessionUser | undefined
|
||||||
|
) {
|
||||||
|
log.debug("loadCircles called", courseSlug);
|
||||||
this.currentCourseSlug = courseSlug;
|
this.currentCourseSlug = courseSlug;
|
||||||
|
|
||||||
const f = await courseCircles(this.currentCourseSlug, courseSessionId);
|
const circles = await courseCircles(
|
||||||
|
this.currentCourseSlug,
|
||||||
|
currentCourseSessionUser
|
||||||
|
);
|
||||||
|
|
||||||
this.circles = f.map((c) => {
|
this.circles = circles.map((c) => {
|
||||||
return {
|
return {
|
||||||
id: c.slug,
|
id: c.id,
|
||||||
|
slug: c.slug,
|
||||||
|
title: c.title,
|
||||||
name: c.title,
|
name: c.title,
|
||||||
};
|
} as const;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.circles.length > 0) {
|
if (this.circles?.length) {
|
||||||
await this.setCurrentCourseCircle(this.circles[0].id);
|
await this.setCurrentCourseCircle(this.circles[0].slug);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setCurrentCourseCircle(circleSlug: string) {
|
async setCurrentCourseCircle(circleSlug: string) {
|
||||||
if (!this.currentCourseSlug) {
|
this.currentCircle = this.circles?.find((c) => c.slug === circleSlug);
|
||||||
throw new Error("currentCourseSlug is undefined");
|
|
||||||
}
|
|
||||||
const circleStore = useCircleStore();
|
|
||||||
await circleStore.loadCircle(this.currentCourseSlug, circleSlug);
|
|
||||||
},
|
},
|
||||||
async setCurrentCourseCircleFromEvent(event: { id: string }) {
|
async setCurrentCourseCircleFromEvent(event: CircleLight) {
|
||||||
await this.setCurrentCourseCircle(event.id);
|
await this.setCurrentCourseCircle(event.slug);
|
||||||
},
|
|
||||||
async loadCourseSessionMembers(courseSessionId: number, reload = false) {
|
|
||||||
log.debug("loadCourseSessionMembers called");
|
|
||||||
const users = (await itGetCached(
|
|
||||||
`/api/course/sessions/${courseSessionId}/users/`,
|
|
||||||
{
|
|
||||||
reload: reload,
|
|
||||||
}
|
|
||||||
)) as CourseSessionUser[];
|
|
||||||
|
|
||||||
this.courseSessionMembers = users.filter((user) => user.role === "MEMBER");
|
|
||||||
return this.courseSessionMembers;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getters: {
|
|
||||||
currentCircle: () => {
|
|
||||||
const circleStore = useCircleStore();
|
|
||||||
return circleStore.circle;
|
|
||||||
},
|
|
||||||
selectedCircle: () => {
|
|
||||||
const circleStore = useCircleStore();
|
|
||||||
return {
|
|
||||||
id: circleStore.circle?.id || 0,
|
|
||||||
name: circleStore.circle?.title || "",
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function courseCircles(courseSlug: string, courseSessionId: number) {
|
async function courseCircles(
|
||||||
|
courseSlug: string,
|
||||||
|
currentCourseSessionUser: CourseSessionUser | undefined
|
||||||
|
) {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const userId = userStore.id;
|
const userId = userStore.id;
|
||||||
|
|
||||||
const users = (await itGetCached(`/api/course/sessions/${courseSessionId}/users/`, {
|
if (currentCourseSessionUser && currentCourseSessionUser.role === "EXPERT") {
|
||||||
reload: false,
|
const expert = currentCourseSessionUser as ExpertSessionUser;
|
||||||
})) as CourseSessionUser[];
|
|
||||||
|
|
||||||
// First check if current user is an expert for this course session
|
|
||||||
const currentUser = users.find((user) => user.user_id === userId);
|
|
||||||
if (currentUser && currentUser.role === "EXPERT") {
|
|
||||||
const expert = currentUser as ExpertSessionUser;
|
|
||||||
return expert.circles;
|
return expert.circles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import type {
|
||||||
CompetenceProfilePage,
|
CompetenceProfilePage,
|
||||||
PerformanceCriteria,
|
PerformanceCriteria,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import i18next from "i18next";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import cloneDeep from "lodash/cloneDeep";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import groupBy from "lodash/groupBy";
|
import groupBy from "lodash/groupBy";
|
||||||
|
|
@ -17,9 +16,6 @@ import { defineStore } from "pinia";
|
||||||
|
|
||||||
export type CompetenceStoreState = {
|
export type CompetenceStoreState = {
|
||||||
competenceProfilePages: Map<string, CompetenceProfilePage>;
|
competenceProfilePages: Map<string, CompetenceProfilePage>;
|
||||||
|
|
||||||
selectedCircle: { id: string; name: string };
|
|
||||||
availableCircles: { id: string; name: string }[];
|
|
||||||
circles: CircleLight[];
|
circles: CircleLight[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -28,8 +24,6 @@ export const useCompetenceStore = defineStore({
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
competenceProfilePages: new Map<string, CompetenceProfilePage>(),
|
competenceProfilePages: new Map<string, CompetenceProfilePage>(),
|
||||||
selectedCircle: { id: "all", name: `Circle: ${i18next.t("Alle")}` },
|
|
||||||
availableCircles: [],
|
|
||||||
circles: [],
|
circles: [],
|
||||||
} as CompetenceStoreState;
|
} as CompetenceStoreState;
|
||||||
},
|
},
|
||||||
|
|
@ -52,13 +46,7 @@ export const useCompetenceStore = defineStore({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
criteriaByCompetence(competence: CompetencePage) {
|
criteriaByCompetence(competence: CompetencePage) {
|
||||||
return competence.children.filter((criteria) => {
|
return competence.children;
|
||||||
if (this.selectedCircle.id != "all") {
|
|
||||||
return criteria.circle.translation_key === this.selectedCircle.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return competence.children;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
competenceProfilePage(userId: string | undefined = undefined) {
|
competenceProfilePage(userId: string | undefined = undefined) {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
|
|
@ -70,7 +58,7 @@ export const useCompetenceStore = defineStore({
|
||||||
},
|
},
|
||||||
flatPerformanceCriteria(
|
flatPerformanceCriteria(
|
||||||
userId: string | undefined = undefined,
|
userId: string | undefined = undefined,
|
||||||
circleId: number | undefined = undefined
|
circleId: string | undefined = undefined
|
||||||
) {
|
) {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
@ -88,12 +76,6 @@ export const useCompetenceStore = defineStore({
|
||||||
["asc"]
|
["asc"]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.selectedCircle.id !== "all") {
|
|
||||||
criteria = criteria.filter(
|
|
||||||
(c) => c.circle.translation_key === this.selectedCircle.id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (circleId) {
|
if (circleId) {
|
||||||
criteria = criteria.filter((c) => circleId === c.circle.id);
|
criteria = criteria.filter((c) => circleId === c.circle.id);
|
||||||
}
|
}
|
||||||
|
|
@ -116,12 +98,7 @@ export const useCompetenceStore = defineStore({
|
||||||
if (competenceProfilePage?.children.length) {
|
if (competenceProfilePage?.children.length) {
|
||||||
return _.orderBy(
|
return _.orderBy(
|
||||||
competenceProfilePage.children.filter((competence) => {
|
competenceProfilePage.children.filter((competence) => {
|
||||||
let criteria = competence.children;
|
const criteria = competence.children;
|
||||||
if (this.selectedCircle.id != "all") {
|
|
||||||
criteria = criteria.filter((criteria) => {
|
|
||||||
return criteria.circle.translation_key === this.selectedCircle.id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return criteria.length > 0;
|
return criteria.length > 0;
|
||||||
}),
|
}),
|
||||||
["competence_id"],
|
["competence_id"],
|
||||||
|
|
@ -159,10 +136,6 @@ export const useCompetenceStore = defineStore({
|
||||||
this.competenceProfilePages.set(userId, cloneDeep(competenceProfilePage));
|
this.competenceProfilePages.set(userId, cloneDeep(competenceProfilePage));
|
||||||
|
|
||||||
this.circles = competenceProfilePage.circles;
|
this.circles = competenceProfilePage.circles;
|
||||||
const circles = competenceProfilePage.circles.map((c: CircleLight) => {
|
|
||||||
return { id: c.translation_key, name: `Circle: ${c.title}` };
|
|
||||||
});
|
|
||||||
this.availableCircles = [{ id: "all", name: "Circle: Alle" }, ...circles];
|
|
||||||
|
|
||||||
await this.parseCompletionData(userId);
|
await this.parseCompletionData(userId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export const useCompletionStore = defineStore({
|
||||||
getters: {},
|
getters: {},
|
||||||
actions: {
|
actions: {
|
||||||
async loadCourseSessionCompletionData(
|
async loadCourseSessionCompletionData(
|
||||||
courseSessionId: number,
|
courseSessionId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
reload = false
|
reload = false
|
||||||
) {
|
) {
|
||||||
|
|
@ -31,7 +31,7 @@ export const useCompletionStore = defineStore({
|
||||||
async markPage(
|
async markPage(
|
||||||
page: BaseCourseWagtailPage,
|
page: BaseCourseWagtailPage,
|
||||||
userId: string | undefined = undefined,
|
userId: string | undefined = undefined,
|
||||||
courseSessionId: number | undefined = undefined
|
courseSessionId: string | undefined = undefined
|
||||||
) {
|
) {
|
||||||
if (!courseSessionId) {
|
if (!courseSessionId) {
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,5 @@
|
||||||
import { itGetCached, itPost } from "@/fetchHelpers";
|
import { itGetCached } from "@/fetchHelpers";
|
||||||
import { deleteCircleDocument } from "@/services/files";
|
import type { CourseSession, DueDate } from "@/types";
|
||||||
import type {
|
|
||||||
CircleDocument,
|
|
||||||
CourseSession,
|
|
||||||
CourseSessionAssignment,
|
|
||||||
CourseSessionAttendanceCourse,
|
|
||||||
CourseSessionEdoniqTest,
|
|
||||||
CourseSessionUser,
|
|
||||||
DueDate,
|
|
||||||
ExpertSessionUser,
|
|
||||||
} from "@/types";
|
|
||||||
import eventBus from "@/utils/eventBus";
|
import eventBus from "@/utils/eventBus";
|
||||||
import { useRouteLookups } from "@/utils/route";
|
import { useRouteLookups } from "@/utils/route";
|
||||||
import { useLocalStorage } from "@vueuse/core";
|
import { useLocalStorage } from "@vueuse/core";
|
||||||
|
|
@ -18,7 +8,6 @@ import uniqBy from "lodash/uniqBy";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { useCircleStore } from "./circle";
|
|
||||||
import { useUserStore } from "./user";
|
import { useUserStore } from "./user";
|
||||||
|
|
||||||
const SELECTED_COURSE_SESSIONS_KEY = "selectedCourseSessionMap";
|
const SELECTED_COURSE_SESSIONS_KEY = "selectedCourseSessionMap";
|
||||||
|
|
@ -39,10 +28,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
// TODO: refactor after implementing of Klassenkonzept
|
// TODO: refactor after implementing of Klassenkonzept
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
allCourseSessions.value.map(async (cs) => {
|
allCourseSessions.value.map(async (cs) => {
|
||||||
const users = (await itGetCached(`/api/course/sessions/${cs.id}/users/`, {
|
|
||||||
reload: reload,
|
|
||||||
})) as CourseSessionUser[];
|
|
||||||
cs.users = users;
|
|
||||||
sortDueDates(cs.due_dates);
|
sortDueDates(cs.due_dates);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -57,7 +42,7 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
|
|
||||||
const selectedCourseSessionMap = useLocalStorage(
|
const selectedCourseSessionMap = useLocalStorage(
|
||||||
SELECTED_COURSE_SESSIONS_KEY,
|
SELECTED_COURSE_SESSIONS_KEY,
|
||||||
new Map<string, number>()
|
new Map<string, string>()
|
||||||
);
|
);
|
||||||
|
|
||||||
const _currentCourseSlug = ref("");
|
const _currentCourseSlug = ref("");
|
||||||
|
|
@ -99,15 +84,15 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
eventBus.emit("switchedCourseSession", courseSession.id);
|
eventBus.emit("switchedCourseSession", courseSession.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCourseSessionById(courseSessionId: number | string) {
|
function getCourseSessionById(courseSessionId: string) {
|
||||||
return allCourseSessions.value.find((cs) => {
|
return allCourseSessions.value.find((cs) => {
|
||||||
return courseSessionId.toString() === cs.id.toString();
|
return courseSessionId === cs.id;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchCourseSessionById(courseSessionId: number | string) {
|
function switchCourseSessionById(courseSessionId: string) {
|
||||||
const courseSession = allCourseSessions.value.find((cs) => {
|
const courseSession = allCourseSessions.value.find((cs) => {
|
||||||
return courseSessionId.toString() === cs.id.toString();
|
return courseSessionId === cs.id;
|
||||||
});
|
});
|
||||||
if (courseSession) {
|
if (courseSession) {
|
||||||
_switchCourseSession(courseSession);
|
_switchCourseSession(courseSession);
|
||||||
|
|
@ -161,64 +146,14 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
return Boolean(isCourseExpert && (inLearningPath() || inCompetenceProfile()));
|
return Boolean(isCourseExpert && (inLearningPath() || inCompetenceProfile()));
|
||||||
});
|
});
|
||||||
|
|
||||||
const circleExperts = computed(() => {
|
function hasCockpit(courseSession: CourseSession) {
|
||||||
const circleStore = useCircleStore();
|
|
||||||
const circleTranslationKey = circleStore.circle?.translation_key;
|
|
||||||
|
|
||||||
if (currentCourseSession.value && circleTranslationKey) {
|
|
||||||
return currentCourseSession.value.users.filter((u) => {
|
|
||||||
if (u.role === "EXPERT") {
|
|
||||||
return (u as ExpertSessionUser).circles
|
|
||||||
.map((c) => c.translation_key)
|
|
||||||
.includes(circleTranslationKey);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}) as ExpertSessionUser[];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
const canUploadCircleDocuments = computed(() => {
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
return (
|
return (
|
||||||
circleExperts.value.filter((expert) => expert.user_id === userStore.id).length > 0
|
userStore.course_session_experts.includes(courseSession.id) ||
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const circleDocuments = computed(() => {
|
|
||||||
const circleStore = useCircleStore();
|
|
||||||
|
|
||||||
return (
|
|
||||||
circleStore.circle?.learningSequences
|
|
||||||
.map((ls) => ({ id: ls.id, title: ls.title, documents: [] }))
|
|
||||||
.map((ls: { id: number; title: string; documents: CircleDocument[] }) => {
|
|
||||||
if (currentCourseSession.value === undefined) {
|
|
||||||
return ls;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const document of currentCourseSession.value.documents) {
|
|
||||||
if (document.learning_sequence === ls.id) {
|
|
||||||
ls.documents.push(document);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ls;
|
|
||||||
})
|
|
||||||
.filter((ls) => ls.documents.length > 0) || []
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function hasCockpit(couseSession: CourseSession) {
|
|
||||||
const userStore = useUserStore();
|
|
||||||
return (
|
|
||||||
userStore.course_session_experts.includes(couseSession.id) ||
|
|
||||||
userStore.is_superuser
|
userStore.is_superuser
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDocument(document: CircleDocument) {
|
|
||||||
currentCourseSession.value?.documents.push(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
function allDueDates() {
|
function allDueDates() {
|
||||||
const allDueDatesReturn: DueDate[] = [];
|
const allDueDatesReturn: DueDate[] = [];
|
||||||
|
|
||||||
|
|
@ -243,56 +178,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startUpload() {
|
|
||||||
log.debug("loadCourseSessionsData called");
|
|
||||||
allCourseSessions.value = await itPost(`/api/core/file/start`, {
|
|
||||||
file_type: "image/png",
|
|
||||||
file_name: "test.png",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeDocument(documentId: string) {
|
|
||||||
await deleteCircleDocument(documentId);
|
|
||||||
|
|
||||||
if (currentCourseSession.value === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentCourseSession.value.documents = currentCourseSession.value?.documents.filter(
|
|
||||||
(d) => d.id !== documentId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findAttendanceCourse(
|
|
||||||
contentId: number
|
|
||||||
): CourseSessionAttendanceCourse | undefined {
|
|
||||||
if (currentCourseSession.value) {
|
|
||||||
return currentCourseSession.value.attendance_courses.find(
|
|
||||||
(attendanceCourse) => attendanceCourse.learning_content_id === contentId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findCourseSessionAssignment(
|
|
||||||
contentId?: number
|
|
||||||
): CourseSessionAssignment | undefined {
|
|
||||||
if (contentId && currentCourseSession.value) {
|
|
||||||
return currentCourseSession.value.assignments.find(
|
|
||||||
(a) => a.learning_content_id === contentId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findCourseSessionEdoniqTest(
|
|
||||||
contentId?: number
|
|
||||||
): CourseSessionEdoniqTest | undefined {
|
|
||||||
if (contentId && currentCourseSession.value) {
|
|
||||||
return currentCourseSession.value.edoniq_tests.find(
|
|
||||||
(a) => a.learning_content_id === contentId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uniqueCourseSessionsByCourse,
|
uniqueCourseSessionsByCourse,
|
||||||
allCurrentCourseSessions,
|
allCurrentCourseSessions,
|
||||||
|
|
@ -302,15 +187,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
hasCockpit,
|
hasCockpit,
|
||||||
hasCourseSessionPreview,
|
hasCourseSessionPreview,
|
||||||
currentCourseSessionHasCockpit,
|
currentCourseSessionHasCockpit,
|
||||||
canUploadCircleDocuments,
|
|
||||||
circleDocuments,
|
|
||||||
circleExperts,
|
|
||||||
addDocument,
|
|
||||||
startUpload,
|
|
||||||
removeDocument,
|
|
||||||
findAttendanceCourse,
|
|
||||||
findCourseSessionAssignment,
|
|
||||||
findCourseSessionEdoniqTest,
|
|
||||||
allDueDates,
|
allDueDates,
|
||||||
|
|
||||||
// use `useCurrentCourseSession` whenever possible
|
// use `useCurrentCourseSession` whenever possible
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export type UserState = {
|
||||||
username: string;
|
username: string;
|
||||||
avatar_url: string;
|
avatar_url: string;
|
||||||
is_superuser: boolean;
|
is_superuser: boolean;
|
||||||
course_session_experts: number[];
|
course_session_experts: string[];
|
||||||
loggedIn: boolean;
|
loggedIn: boolean;
|
||||||
language: AvailableLanguages;
|
language: AvailableLanguages;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export type LoginMethod = "local" | "sso";
|
||||||
export type CourseCompletionStatus = "UNKNOWN" | "FAIL" | "SUCCESS";
|
export type CourseCompletionStatus = "UNKNOWN" | "FAIL" | "SUCCESS";
|
||||||
|
|
||||||
export interface BaseCourseWagtailPage {
|
export interface BaseCourseWagtailPage {
|
||||||
readonly id: number;
|
readonly id: string;
|
||||||
readonly title: string;
|
readonly title: string;
|
||||||
readonly slug: string;
|
readonly slug: string;
|
||||||
readonly content_type: string;
|
readonly content_type: string;
|
||||||
|
|
@ -18,9 +18,9 @@ export interface BaseCourseWagtailPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CircleLight {
|
export interface CircleLight {
|
||||||
readonly id: number;
|
readonly id: string;
|
||||||
|
readonly slug: string;
|
||||||
readonly title: string;
|
readonly title: string;
|
||||||
readonly translation_key: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LearningContent =
|
export type LearningContent =
|
||||||
|
|
@ -52,10 +52,10 @@ export interface LearningContentInterface extends BaseCourseWagtailPage {
|
||||||
|
|
||||||
export interface LearningContentAssignment extends LearningContentInterface {
|
export interface LearningContentAssignment extends LearningContentInterface {
|
||||||
readonly content_type: "learnpath.LearningContentAssignment";
|
readonly content_type: "learnpath.LearningContentAssignment";
|
||||||
readonly content_assignment_id: number;
|
readonly content_assignment_id: string;
|
||||||
readonly assignment_type: AssignmentType;
|
readonly assignment_type: AssignmentType;
|
||||||
readonly competence_certificate?: {
|
readonly competence_certificate?: {
|
||||||
id: number;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
content_type: string;
|
content_type: string;
|
||||||
|
|
@ -104,9 +104,9 @@ export interface LearningContentEdoniqTest extends LearningContentInterface {
|
||||||
readonly content_type: "learnpath.LearningContentEdoniqTest";
|
readonly content_type: "learnpath.LearningContentEdoniqTest";
|
||||||
readonly checkbox_text: string;
|
readonly checkbox_text: string;
|
||||||
readonly has_extended_time_test: boolean;
|
readonly has_extended_time_test: boolean;
|
||||||
readonly content_assignment_id: number;
|
readonly content_assignment_id: string;
|
||||||
readonly competence_certificate?: {
|
readonly competence_certificate?: {
|
||||||
id: number;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
content_type: string;
|
content_type: string;
|
||||||
|
|
@ -191,22 +191,22 @@ export interface CourseCompletion {
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
readonly user: number;
|
readonly user: number;
|
||||||
readonly page_id: number;
|
readonly page_id: string;
|
||||||
readonly page_type: string;
|
readonly page_type: string;
|
||||||
readonly course_session_id: number;
|
readonly course_session_id: string;
|
||||||
completion_status: CourseCompletionStatus;
|
completion_status: CourseCompletionStatus;
|
||||||
additional_json_data: unknown;
|
additional_json_data: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Course {
|
export interface Course {
|
||||||
id: number;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
category_name: string;
|
category_name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CourseCategory {
|
export interface CourseCategory {
|
||||||
id: number;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
general: boolean;
|
general: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -381,7 +381,7 @@ export interface Assignment extends BaseCourseWagtailPage {
|
||||||
readonly evaluation_tasks: AssignmentEvaluationTask[];
|
readonly evaluation_tasks: AssignmentEvaluationTask[];
|
||||||
readonly max_points: number;
|
readonly max_points: number;
|
||||||
readonly competence_certificate?: {
|
readonly competence_certificate?: {
|
||||||
id: number;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
content_type: string;
|
content_type: string;
|
||||||
|
|
@ -452,7 +452,7 @@ export interface CircleExpert {
|
||||||
user_email: string;
|
user_email: string;
|
||||||
user_first_name: string;
|
user_first_name: string;
|
||||||
user_last_name: string;
|
user_last_name: string;
|
||||||
circle_id: number;
|
circle_id: string;
|
||||||
circle_slug: string;
|
circle_slug: string;
|
||||||
circle_translation_key: string;
|
circle_translation_key: string;
|
||||||
}
|
}
|
||||||
|
|
@ -462,89 +462,112 @@ export interface CircleDocument {
|
||||||
name: string;
|
name: string;
|
||||||
file_name: string;
|
file_name: string;
|
||||||
url: string;
|
url: string;
|
||||||
course_session: number;
|
learning_sequence: {
|
||||||
learning_sequence: number;
|
id: string;
|
||||||
|
title: string;
|
||||||
|
circle: CircleLight;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CourseSessionAttendanceCourse {
|
export interface CourseSessionAttendanceCourse {
|
||||||
id: number;
|
id: string;
|
||||||
course_session_id: number;
|
due_date: SimpleDueDate;
|
||||||
learning_content_id: number;
|
|
||||||
start: string;
|
|
||||||
end: string;
|
|
||||||
location: string;
|
location: string;
|
||||||
trainer: string;
|
trainer: string;
|
||||||
due_date_id: number;
|
|
||||||
circle_title: string;
|
circle_title: string;
|
||||||
|
learning_content: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
circle: CircleLight;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CourseSessionAssignment {
|
export interface CourseSessionAssignment {
|
||||||
id: number;
|
id: string;
|
||||||
course_session_id: number;
|
submission_deadline: SimpleDueDate;
|
||||||
learning_content_id: number;
|
evaluation_deadline: SimpleDueDate;
|
||||||
submission_deadline_id: number;
|
learning_content: {
|
||||||
submission_deadline_start: string;
|
id: string;
|
||||||
evaluation_deadline_id: number;
|
content_assignment: {
|
||||||
evaluation_deadline_start: string;
|
id: string;
|
||||||
|
title: string;
|
||||||
|
assignment_type: AssignmentType;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CourseSessionEdoniqTest {
|
export interface CourseSessionEdoniqTest {
|
||||||
id: number;
|
id: number;
|
||||||
course_session_id: number;
|
course_session_id: string;
|
||||||
learning_content_id: number;
|
deadline: SimpleDueDate;
|
||||||
deadline_id: number;
|
learning_content: {
|
||||||
deadline_start: string;
|
id: string;
|
||||||
|
content_assignment: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
assignment_type: AssignmentType;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CourseSession {
|
export interface CourseSession {
|
||||||
id: number;
|
id: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
course: Course;
|
course: Course;
|
||||||
title: string;
|
title: string;
|
||||||
start_date: string;
|
start_date: string;
|
||||||
end_date: string;
|
end_date: string;
|
||||||
learning_path_url: string;
|
|
||||||
cockpit_url: string;
|
|
||||||
competence_url: string;
|
|
||||||
course_url: string;
|
|
||||||
media_library_url: string;
|
|
||||||
attendance_courses: CourseSessionAttendanceCourse[];
|
|
||||||
assignments: CourseSessionAssignment[];
|
|
||||||
edoniq_tests: CourseSessionEdoniqTest[];
|
|
||||||
documents: CircleDocument[];
|
|
||||||
users: CourseSessionUser[];
|
|
||||||
due_dates: DueDate[];
|
due_dates: DueDate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Role = "MEMBER" | "EXPERT" | "TUTOR";
|
export type Role = "MEMBER" | "EXPERT" | "TUTOR";
|
||||||
|
|
||||||
export interface CourseSessionUser {
|
export interface CourseSessionUser {
|
||||||
session_title: string;
|
|
||||||
user_id: string;
|
user_id: string;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
email: string;
|
email: string;
|
||||||
avatar_url: string;
|
avatar_url: string;
|
||||||
role: Role;
|
role: Role;
|
||||||
|
circles: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
translation_key: string;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExpertSessionUser extends CourseSessionUser {
|
export interface ExpertSessionUser extends CourseSessionUser {
|
||||||
role: "EXPERT";
|
role: "EXPERT";
|
||||||
circles: {
|
circles: {
|
||||||
id: number;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
translation_key: string;
|
translation_key: string;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CourseSessionDetail {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
course: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
assignments: CourseSessionAssignment[];
|
||||||
|
attendance_courses: CourseSessionAttendanceCourse[];
|
||||||
|
edoniq_tests: CourseSessionEdoniqTest[];
|
||||||
|
users: CourseSessionUser[];
|
||||||
|
}
|
||||||
|
|
||||||
// document upload
|
// document upload
|
||||||
export interface DocumentUploadData {
|
export interface DocumentUploadData {
|
||||||
file: File | null;
|
file: File | null;
|
||||||
name: string;
|
name: string;
|
||||||
learningSequence: {
|
learningSequence: {
|
||||||
id: number;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -614,8 +637,8 @@ export interface AssignmentCompletion {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpsertUserAssignmentCompletion = {
|
export type UpsertUserAssignmentCompletion = {
|
||||||
assignment_id: number;
|
assignment_id: string;
|
||||||
course_session_id: number;
|
course_session_id: string;
|
||||||
completion_status: AssignmentCompletionStatus;
|
completion_status: AssignmentCompletionStatus;
|
||||||
completion_data: AssignmentCompletionData;
|
completion_data: AssignmentCompletionData;
|
||||||
};
|
};
|
||||||
|
|
@ -632,20 +655,22 @@ export interface UserAssignmentCompletionStatus {
|
||||||
assignment_user_id: string;
|
assignment_user_id: string;
|
||||||
completion_status: AssignmentCompletionStatus;
|
completion_status: AssignmentCompletionStatus;
|
||||||
evaluation_points: number | null;
|
evaluation_points: number | null;
|
||||||
learning_content_page_id: number;
|
learning_content_page_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DueDate = {
|
export type SimpleDueDate = {
|
||||||
id: number;
|
id: string;
|
||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DueDate = SimpleDueDate & {
|
||||||
title: string;
|
title: string;
|
||||||
assignment_type_translation_key: string;
|
assignment_type_translation_key: string;
|
||||||
date_type_translation_key: string;
|
date_type_translation_key: string;
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
url: string;
|
url: string;
|
||||||
url_expert: string;
|
url_expert: string;
|
||||||
course_session: number | null;
|
course_session_id: string;
|
||||||
page: number | null;
|
|
||||||
circle: CircleLight | null;
|
circle: CircleLight | null;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import mitt from "mitt";
|
||||||
export type MittEvents = {
|
export type MittEvents = {
|
||||||
// event needed so that the App components do re-render
|
// event needed so that the App components do re-render
|
||||||
// and reload the current course session
|
// and reload the current course session
|
||||||
switchedCourseSession: number;
|
switchedCourseSession: string;
|
||||||
|
|
||||||
finishedLearningContent: boolean;
|
finishedLearningContent: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,35 @@
|
||||||
import type { AssignmentType, CourseSession } from "@/types";
|
import type { AssignmentType } from "@/types";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
|
|
||||||
export function assertUnreachable(msg: string): never {
|
export function assertUnreachable(msg: string): never {
|
||||||
throw new Error("Didn't expect to get here, " + msg);
|
throw new Error("Didn't expect to get here, " + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCompetenceBaseUrl(courseSession: CourseSession): string {
|
function createCourseUrl(courseSlug: string | undefined, specificSub: string): string {
|
||||||
return courseSession.competence_url.replace(
|
if (!courseSlug) {
|
||||||
// TODO: remove the `competence_url` with url to Navi...
|
return "/";
|
||||||
"/competences",
|
}
|
||||||
""
|
|
||||||
);
|
if (["learn", "media", "competence", "cockpit"].includes(specificSub)) {
|
||||||
|
return `/course/${courseSlug}/${specificSub}`;
|
||||||
|
}
|
||||||
|
return `/course/${courseSlug}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCompetenceNaviUrl(courseSlug: string | undefined): string {
|
||||||
|
return createCourseUrl(courseSlug, "competence");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMediaCenterUrl(courseSlug: string | undefined): string {
|
||||||
|
return createCourseUrl(courseSlug, "media");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLearningPathUrl(courseSlug: string | undefined): string {
|
||||||
|
return createCourseUrl(courseSlug, "learn");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCockpitUrl(courseSlug: string | undefined): string {
|
||||||
|
return createCourseUrl(courseSlug, "cockpit");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAssignmentTypeTitle(assignmentType: AssignmentType): string {
|
export function getAssignmentTypeTitle(assignmentType: AssignmentType): string {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
import {checkNavigationLink, EXPERT_LOGIN, login, PARTICIPANT_LOGIN, visitCoursePage} from "./helpers";
|
import {
|
||||||
|
checkNavigationLink,
|
||||||
const openMobileNavigation = () => {
|
EXPERT_LOGIN,
|
||||||
cy.get('[data-cy="navigation-mobile-menu-button"]').click();
|
login,
|
||||||
}
|
PARTICIPANT_LOGIN,
|
||||||
|
visitCoursePage,
|
||||||
|
} from "./helpers";
|
||||||
|
|
||||||
describe("navigation.cy.js", () => {
|
describe("navigation.cy.js", () => {
|
||||||
|
const openMobileNavigation = () => {
|
||||||
|
cy.get('[data-cy="navigation-mobile-menu-button"]').click();
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.manageCommand("cypress_reset");
|
cy.manageCommand("cypress_reset");
|
||||||
});
|
});
|
||||||
|
|
@ -24,7 +30,9 @@ describe("navigation.cy.js", () => {
|
||||||
checkNavigationLink("navigation-cockpit-link", "cockpit");
|
checkNavigationLink("navigation-cockpit-link", "cockpit");
|
||||||
checkNavigationLink("navigation-preview-link", "learn");
|
checkNavigationLink("navigation-preview-link", "learn");
|
||||||
|
|
||||||
cy.get('[data-cy="navigation-competence-profile-link"]').should("not.exist");
|
cy.get('[data-cy="navigation-competence-profile-link"]').should(
|
||||||
|
"not.exist"
|
||||||
|
);
|
||||||
cy.get('[data-cy="navigation-learning-path-link"]').should("not.exist");
|
cy.get('[data-cy="navigation-learning-path-link"]').should("not.exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -60,10 +68,14 @@ describe("navigation.cy.js", () => {
|
||||||
it("should have correct expert navigation", () => {
|
it("should have correct expert navigation", () => {
|
||||||
cy.get('[data-cy="navigation-mobile-cockpit-link"]').should("exist");
|
cy.get('[data-cy="navigation-mobile-cockpit-link"]').should("exist");
|
||||||
cy.get('[data-cy="navigation-mobile-preview-link"]').should("exist");
|
cy.get('[data-cy="navigation-mobile-preview-link"]').should("exist");
|
||||||
cy.get('[data-cy="navigation-mobile-competence-profile-link"]').should("not.exist");
|
cy.get('[data-cy="navigation-mobile-competence-profile-link"]').should(
|
||||||
cy.get('[data-cy="navigation-mobile-learning-path-link"]').should("not.exist");
|
"not.exist"
|
||||||
})
|
);
|
||||||
})
|
cy.get('[data-cy="navigation-mobile-learning-path-link"]').should(
|
||||||
|
"not.exist"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Participant", () => {
|
describe("Participant", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -73,12 +85,19 @@ describe("navigation.cy.js", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have correct navigation", () => {
|
it("should have correct navigation", () => {
|
||||||
cy.get('[data-cy="navigation-mobile-cockpit-link"]').should("not.exist");
|
cy.get('[data-cy="navigation-mobile-cockpit-link"]').should(
|
||||||
cy.get('[data-cy="navigation-mobile-preview-link"]').should("not.exist");
|
"not.exist"
|
||||||
cy.get('[data-cy="navigation-mobile-competence-profile-link"]').should("exist");
|
);
|
||||||
cy.get('[data-cy="navigation-mobile-learning-path-link"]').should("exist");
|
cy.get('[data-cy="navigation-mobile-preview-link"]').should(
|
||||||
|
"not.exist"
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="navigation-mobile-competence-profile-link"]').should(
|
||||||
|
"exist"
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="navigation-mobile-learning-path-link"]').should(
|
||||||
|
"exist"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
import {checkNavigationLink, login, PARTICIPANT_LOGIN, visitCoursePage} from "./helpers";
|
import {
|
||||||
|
checkNavigationLink,
|
||||||
|
login,
|
||||||
|
PARTICIPANT_LOGIN,
|
||||||
|
visitCoursePage,
|
||||||
|
} from "./helpers";
|
||||||
|
|
||||||
describe("preview.cy.js", () => {
|
describe("preview.cy.js", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.manageCommand("cypress_reset");
|
cy.manageCommand("cypress_reset");
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("Expert / Trainer", () => {
|
describe("Expert / Trainer", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -41,5 +46,5 @@ describe("preview.cy.js", () => {
|
||||||
visitCoursePage("competence");
|
visitCoursePage("competence");
|
||||||
cy.get('[data-cy="course-preview-bar"]').should("not.exist");
|
cy.get('[data-cy="course-preview-bar"]').should("not.exist");
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,12 @@ from vbv_lernwelt.course.views import (
|
||||||
document_direct_upload,
|
document_direct_upload,
|
||||||
document_upload_finish,
|
document_upload_finish,
|
||||||
document_upload_start,
|
document_upload_start,
|
||||||
get_course_session_users,
|
|
||||||
get_course_sessions,
|
get_course_sessions,
|
||||||
mark_course_completion_view,
|
mark_course_completion_view,
|
||||||
request_course_completion,
|
request_course_completion,
|
||||||
request_course_completion_for_user,
|
request_course_completion_for_user,
|
||||||
)
|
)
|
||||||
|
from vbv_lernwelt.course_session.views import get_course_session_documents
|
||||||
from vbv_lernwelt.edoniq_test.views import (
|
from vbv_lernwelt.edoniq_test.views import (
|
||||||
export_students,
|
export_students,
|
||||||
export_students_and_trainers,
|
export_students_and_trainers,
|
||||||
|
|
@ -90,6 +90,10 @@ urlpatterns = [
|
||||||
path('server/documents/', include(wagtaildocs_urls)),
|
path('server/documents/', include(wagtaildocs_urls)),
|
||||||
path('server/pages/', include(wagtail_urls)),
|
path('server/pages/', include(wagtail_urls)),
|
||||||
|
|
||||||
|
# core
|
||||||
|
re_path(r"server/core/icons/$", generate_web_component_icons,
|
||||||
|
name="generate_web_component_icons"),
|
||||||
|
|
||||||
# user management
|
# user management
|
||||||
path("sso/", include("vbv_lernwelt.sso.urls")),
|
path("sso/", include("vbv_lernwelt.sso.urls")),
|
||||||
re_path(r'api/core/me/$', me_user_view, name='me_user_view'),
|
re_path(r'api/core/me/$', me_user_view, name='me_user_view'),
|
||||||
|
|
@ -104,15 +108,11 @@ urlpatterns = [
|
||||||
re_path(r"api/notify/email_notification_settings/$", email_notification_settings,
|
re_path(r"api/notify/email_notification_settings/$", email_notification_settings,
|
||||||
name='email_notification_settings'),
|
name='email_notification_settings'),
|
||||||
|
|
||||||
# core
|
|
||||||
re_path(r"server/core/icons/$", generate_web_component_icons,
|
|
||||||
name="generate_web_component_icons"),
|
|
||||||
|
|
||||||
# course
|
# course
|
||||||
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
||||||
path(r"api/course/sessions/<signed_int:course_session_id>/users/",
|
# path(r"api/course/sessions/<signed_int:course_session_id>/users/",
|
||||||
get_course_session_users,
|
# get_course_session_users,
|
||||||
name="get_course_session_users"),
|
# name="get_course_session_users"),
|
||||||
path(r"api/course/page/<slug_or_id>/", course_page_api_view,
|
path(r"api/course/page/<slug_or_id>/", course_page_api_view,
|
||||||
name="course_page_api_view"),
|
name="course_page_api_view"),
|
||||||
path(r"api/course/completion/mark/", mark_course_completion_view,
|
path(r"api/course/completion/mark/", mark_course_completion_view,
|
||||||
|
|
@ -139,6 +139,9 @@ urlpatterns = [
|
||||||
name='file_upload_finish'),
|
name='file_upload_finish'),
|
||||||
path(r"api/core/document/local/<str:file_id>/", document_direct_upload,
|
path(r"api/core/document/local/<str:file_id>/", document_direct_upload,
|
||||||
name='file_upload_local'),
|
name='file_upload_local'),
|
||||||
|
path(r'api/core/document/list/<str:course_session_id>/',
|
||||||
|
get_course_session_documents,
|
||||||
|
name='get_course_session_documents'),
|
||||||
|
|
||||||
# feedback
|
# feedback
|
||||||
path(r'api/core/feedback/<str:course_session_id>/summary/',
|
path(r'api/core/feedback/<str:course_session_id>/summary/',
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,12 @@ def request_assignment_completion_status(request, assignment_id, course_session_
|
||||||
"evaluation_passed",
|
"evaluation_passed",
|
||||||
"learning_content_page_id",
|
"learning_content_page_id",
|
||||||
)
|
)
|
||||||
return Response(status=200, data=qs)
|
|
||||||
|
# Convert the learning_content_page_id to a string
|
||||||
|
data = list(qs) # Evaluate the queryset
|
||||||
|
for item in data:
|
||||||
|
item["learning_content_page_id"] = str(item["learning_content_page_id"])
|
||||||
|
|
||||||
|
return Response(status=200, data=data)
|
||||||
|
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,4 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
role=CourseSessionUser.Role.EXPERT, user=obj
|
role=CourseSessionUser.Role.EXPERT, user=obj
|
||||||
)
|
)
|
||||||
|
|
||||||
return [csu.course_session.id for csu in qs]
|
return [str(csu.course_session.id) for csu in qs]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from rest_framework import serializers
|
||||||
from rest_framework.throttling import UserRateThrottle
|
from rest_framework.throttling import UserRateThrottle
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -41,6 +42,11 @@ def get_django_content_type(obj):
|
||||||
return obj._meta.app_label + "." + type(obj).__name__
|
return obj._meta.app_label + "." + type(obj).__name__
|
||||||
|
|
||||||
|
|
||||||
|
class StringIDField(serializers.Field):
|
||||||
|
def to_representation(self, value):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
def pretty_print_json(json_string):
|
def pretty_print_json(json_string):
|
||||||
try:
|
try:
|
||||||
parsed = json_string
|
parsed = json_string
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,22 @@
|
||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
from vbv_lernwelt.course.graphql.types import CourseObjectType
|
from vbv_lernwelt.course.graphql.types import CourseObjectType, CourseSessionObjectType
|
||||||
from vbv_lernwelt.course.models import Course
|
from vbv_lernwelt.course.models import Course, CourseSession
|
||||||
from vbv_lernwelt.course.permissions import has_course_access
|
from vbv_lernwelt.course.permissions import has_course_access
|
||||||
|
|
||||||
|
|
||||||
class CourseQuery(graphene.ObjectType):
|
class CourseQuery(graphene.ObjectType):
|
||||||
course = graphene.Field(CourseObjectType, id=graphene.Int())
|
course = graphene.Field(CourseObjectType, id=graphene.ID())
|
||||||
|
course_session = graphene.Field(CourseSessionObjectType, id=graphene.ID())
|
||||||
|
|
||||||
def resolve_course(root, info, id):
|
def resolve_course(root, info, id):
|
||||||
course = Course.objects.get(pk=id)
|
course = Course.objects.get(pk=id)
|
||||||
if has_course_access(info.context.user, course):
|
if has_course_access(info.context.user, course):
|
||||||
return course
|
return course
|
||||||
raise PermissionError("You do not have access to this course")
|
raise PermissionError("You do not have access to this course")
|
||||||
|
|
||||||
|
def resolve_course_session(root, info, id):
|
||||||
|
course_session = CourseSession.objects.get(pk=id)
|
||||||
|
if has_course_access(info.context.user, course_session.course):
|
||||||
|
return course_session
|
||||||
|
raise PermissionError("You do not have access to this course session")
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,30 @@ from typing import Type
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
import structlog
|
import structlog
|
||||||
|
from graphene import ObjectType
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphql import GraphQLError
|
from graphql import GraphQLError
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
from vbv_lernwelt.course.models import Course, CourseBasePage, CoursePage
|
from vbv_lernwelt.course.models import (
|
||||||
|
CircleDocument,
|
||||||
|
Course,
|
||||||
|
CourseBasePage,
|
||||||
|
CoursePage,
|
||||||
|
CourseSession,
|
||||||
|
CourseSessionUser,
|
||||||
|
)
|
||||||
from vbv_lernwelt.course.permissions import has_course_access
|
from vbv_lernwelt.course.permissions import has_course_access
|
||||||
|
from vbv_lernwelt.course_session.graphql.types import (
|
||||||
|
CourseSessionAssignmentObjectType,
|
||||||
|
CourseSessionAttendanceCourseObjectType,
|
||||||
|
CourseSessionEdoniqTestObjectType,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.course_session.models import (
|
||||||
|
CourseSessionAssignment,
|
||||||
|
CourseSessionAttendanceCourse,
|
||||||
|
CourseSessionEdoniqTest,
|
||||||
|
)
|
||||||
from vbv_lernwelt.learnpath.graphql.types import LearningPathObjectType
|
from vbv_lernwelt.learnpath.graphql.types import LearningPathObjectType
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
@ -74,3 +92,113 @@ class CourseObjectType(DjangoObjectType):
|
||||||
|
|
||||||
def resolve_learning_path(self, info):
|
def resolve_learning_path(self, info):
|
||||||
return self.get_learning_path()
|
return self.get_learning_path()
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionUserExpertCircleType(ObjectType):
|
||||||
|
id = graphene.ID()
|
||||||
|
title = graphene.String()
|
||||||
|
slug = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionUserObjectsType(DjangoObjectType):
|
||||||
|
user_id = graphene.UUID()
|
||||||
|
first_name = graphene.String()
|
||||||
|
last_name = graphene.String()
|
||||||
|
email = graphene.String()
|
||||||
|
avatar_url = graphene.String()
|
||||||
|
role = graphene.String()
|
||||||
|
circles = graphene.List(CourseSessionUserExpertCircleType)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CourseSessionUser
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"user_id",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"email",
|
||||||
|
"avatar_url",
|
||||||
|
"role",
|
||||||
|
)
|
||||||
|
|
||||||
|
def resolve_user_id(self, info):
|
||||||
|
return self.user.id
|
||||||
|
|
||||||
|
def resolve_first_name(self, info):
|
||||||
|
return self.user.first_name
|
||||||
|
|
||||||
|
def resolve_last_name(self, info):
|
||||||
|
return self.user.last_name
|
||||||
|
|
||||||
|
def resolve_email(self, info):
|
||||||
|
return self.user.email
|
||||||
|
|
||||||
|
def resolve_avatar_url(self, info):
|
||||||
|
return self.user.avatar_url
|
||||||
|
|
||||||
|
def resolve_role(self, info):
|
||||||
|
return self.role
|
||||||
|
|
||||||
|
def resolve_circles(self, info):
|
||||||
|
return self.expert.all().values("id", "title", "slug", "translation_key")
|
||||||
|
|
||||||
|
|
||||||
|
class CircleDocumentObjectType(DjangoObjectType):
|
||||||
|
file_name = graphene.String()
|
||||||
|
url = graphene.String()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CircleDocument
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"file_name",
|
||||||
|
"url",
|
||||||
|
"course_session",
|
||||||
|
"learning_sequence",
|
||||||
|
]
|
||||||
|
|
||||||
|
def resolve_file_name(self, info):
|
||||||
|
return self.file_name
|
||||||
|
|
||||||
|
def resolve_url(self, info):
|
||||||
|
return self.url
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionObjectType(DjangoObjectType):
|
||||||
|
attendance_courses = graphene.List(CourseSessionAttendanceCourseObjectType)
|
||||||
|
assignments = graphene.List(CourseSessionAssignmentObjectType)
|
||||||
|
edoniq_tests = graphene.List(CourseSessionEdoniqTestObjectType)
|
||||||
|
documents = graphene.List(CircleDocumentObjectType)
|
||||||
|
|
||||||
|
users = graphene.List(CourseSessionUserObjectsType)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CourseSession
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"course",
|
||||||
|
"title",
|
||||||
|
"start_date",
|
||||||
|
"end_date",
|
||||||
|
"users",
|
||||||
|
)
|
||||||
|
|
||||||
|
def resolve_attendance_courses(self, info):
|
||||||
|
return CourseSessionAttendanceCourse.objects.filter(course_session=self)
|
||||||
|
|
||||||
|
def resolve_assignments(self, info):
|
||||||
|
return CourseSessionAssignment.objects.filter(course_session=self)
|
||||||
|
|
||||||
|
def resolve_edoniq_tests(self, info):
|
||||||
|
return CourseSessionEdoniqTest.objects.filter(course_session=self)
|
||||||
|
|
||||||
|
def resolve_documents(self, info):
|
||||||
|
return CircleDocument.objects.filter(
|
||||||
|
course_session=self, file__upload_finished_at__isnull=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def resolve_users(self, info):
|
||||||
|
return CourseSessionUser.objects.filter(course_session_id=self.id).distinct()
|
||||||
|
|
|
||||||
|
|
@ -294,21 +294,6 @@ class CourseSessionUser(models.Model):
|
||||||
]
|
]
|
||||||
ordering = ["user__last_name", "user__first_name", "user__email"]
|
ordering = ["user__last_name", "user__first_name", "user__email"]
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
"session_id": self.course_session.id,
|
|
||||||
"session_title": self.course_session.title,
|
|
||||||
"user_id": self.user.id,
|
|
||||||
"first_name": self.user.first_name,
|
|
||||||
"last_name": self.user.last_name,
|
|
||||||
"email": self.user.email,
|
|
||||||
"avatar_url": self.user.avatar_url,
|
|
||||||
"role": self.role,
|
|
||||||
"circles": self.expert.all().values(
|
|
||||||
"id", "title", "slug", "translation_key"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CircleDocument(models.Model):
|
class CircleDocument(models.Model):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
|
@ -322,6 +307,9 @@ class CircleDocument(models.Model):
|
||||||
"learnpath.LearningSequence", on_delete=models.CASCADE
|
"learnpath.LearningSequence", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_circle(self):
|
||||||
|
return self.learning_sequence.get_circle()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self) -> str:
|
def url(self) -> str:
|
||||||
return self.file.url
|
return self.file.url
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,12 @@ from vbv_lernwelt.core.serializer_helpers import (
|
||||||
get_it_serializer_class,
|
get_it_serializer_class,
|
||||||
ItWagtailBaseSerializer,
|
ItWagtailBaseSerializer,
|
||||||
)
|
)
|
||||||
|
from vbv_lernwelt.core.utils import StringIDField
|
||||||
|
|
||||||
|
|
||||||
class CourseBaseSerializer(ItWagtailBaseSerializer):
|
class CourseBaseSerializer(ItWagtailBaseSerializer):
|
||||||
|
id = StringIDField()
|
||||||
|
content_assignment_id = StringIDField()
|
||||||
course = SerializerMethodField()
|
course = SerializerMethodField()
|
||||||
course_category = SerializerMethodField()
|
course_category = SerializerMethodField()
|
||||||
circles = SerializerMethodField()
|
circles = SerializerMethodField()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.utils import StringIDField
|
||||||
from vbv_lernwelt.course.models import (
|
from vbv_lernwelt.course.models import (
|
||||||
CircleDocument,
|
CircleDocument,
|
||||||
Course,
|
Course,
|
||||||
|
|
@ -7,21 +8,13 @@ from vbv_lernwelt.course.models import (
|
||||||
CourseCompletion,
|
CourseCompletion,
|
||||||
CourseSession,
|
CourseSession,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course_session.models import (
|
|
||||||
CourseSessionAssignment,
|
|
||||||
CourseSessionAttendanceCourse,
|
|
||||||
CourseSessionEdoniqTest,
|
|
||||||
)
|
|
||||||
from vbv_lernwelt.course_session.serializers import (
|
|
||||||
CourseSessionAssignmentSerializer,
|
|
||||||
CourseSessionAttendanceCourseSerializer,
|
|
||||||
CourseSessionEdoniqTestSerializer,
|
|
||||||
)
|
|
||||||
from vbv_lernwelt.duedate.models import DueDate
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
||||||
|
|
||||||
|
|
||||||
class CourseSerializer(serializers.ModelSerializer):
|
class CourseSerializer(serializers.ModelSerializer):
|
||||||
|
id = StringIDField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Course
|
model = Course
|
||||||
fields = ["id", "title", "category_name", "slug"]
|
fields = ["id", "title", "category_name", "slug"]
|
||||||
|
|
@ -38,6 +31,9 @@ class CourseCategorySerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class CourseCompletionSerializer(serializers.ModelSerializer):
|
class CourseCompletionSerializer(serializers.ModelSerializer):
|
||||||
|
page_id = StringIDField()
|
||||||
|
course_session_id = StringIDField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CourseCompletion
|
model = CourseCompletion
|
||||||
fields = [
|
fields = [
|
||||||
|
|
@ -54,56 +50,17 @@ class CourseCompletionSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class CourseSessionSerializer(serializers.ModelSerializer):
|
class CourseSessionSerializer(serializers.ModelSerializer):
|
||||||
|
id = StringIDField()
|
||||||
|
|
||||||
course = serializers.SerializerMethodField()
|
course = serializers.SerializerMethodField()
|
||||||
course_url = serializers.SerializerMethodField()
|
# course_url = serializers.SerializerMethodField()
|
||||||
learning_path_url = serializers.SerializerMethodField()
|
|
||||||
cockpit_url = serializers.SerializerMethodField()
|
|
||||||
competence_url = serializers.SerializerMethodField()
|
|
||||||
media_library_url = serializers.SerializerMethodField()
|
|
||||||
documents = serializers.SerializerMethodField()
|
|
||||||
attendance_courses = serializers.SerializerMethodField()
|
|
||||||
assignments = serializers.SerializerMethodField()
|
|
||||||
edoniq_tests = serializers.SerializerMethodField()
|
|
||||||
due_dates = serializers.SerializerMethodField()
|
due_dates = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_course(self, obj):
|
def get_course(self, obj):
|
||||||
return CourseSerializer(obj.course).data
|
return CourseSerializer(obj.course).data
|
||||||
|
|
||||||
def get_course_url(self, obj):
|
# def get_course_url(self, obj):
|
||||||
return obj.course.get_course_url()
|
# return obj.course.get_course_url()
|
||||||
|
|
||||||
def get_learning_path_url(self, obj):
|
|
||||||
return obj.course.get_learning_path_url()
|
|
||||||
|
|
||||||
def get_cockpit_url(self, obj):
|
|
||||||
return obj.course.get_cockpit_url()
|
|
||||||
|
|
||||||
def get_media_library_url(self, obj):
|
|
||||||
return obj.course.get_media_library_url()
|
|
||||||
|
|
||||||
def get_competence_url(self, obj):
|
|
||||||
return obj.course.get_competence_url()
|
|
||||||
|
|
||||||
def get_documents(self, obj):
|
|
||||||
documents = CircleDocument.objects.filter(
|
|
||||||
course_session=obj, file__upload_finished_at__isnull=False
|
|
||||||
)
|
|
||||||
return CircleDocumentSerializer(documents, many=True).data
|
|
||||||
|
|
||||||
def get_attendance_courses(self, obj):
|
|
||||||
return CourseSessionAttendanceCourseSerializer(
|
|
||||||
CourseSessionAttendanceCourse.objects.filter(course_session=obj), many=True
|
|
||||||
).data
|
|
||||||
|
|
||||||
def get_assignments(self, obj):
|
|
||||||
return CourseSessionAssignmentSerializer(
|
|
||||||
CourseSessionAssignment.objects.filter(course_session=obj), many=True
|
|
||||||
).data
|
|
||||||
|
|
||||||
def get_edoniq_tests(self, obj):
|
|
||||||
return CourseSessionEdoniqTestSerializer(
|
|
||||||
CourseSessionEdoniqTest.objects.filter(course_session=obj), many=True
|
|
||||||
).data
|
|
||||||
|
|
||||||
def get_due_dates(self, obj):
|
def get_due_dates(self, obj):
|
||||||
due_dates = DueDate.objects.filter(course_session=obj)
|
due_dates = DueDate.objects.filter(course_session=obj)
|
||||||
|
|
@ -119,21 +76,13 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
||||||
"title",
|
"title",
|
||||||
"start_date",
|
"start_date",
|
||||||
"end_date",
|
"end_date",
|
||||||
"additional_json_data",
|
|
||||||
"attendance_courses",
|
|
||||||
"assignments",
|
|
||||||
"edoniq_tests",
|
|
||||||
"learning_path_url",
|
|
||||||
"cockpit_url",
|
|
||||||
"competence_url",
|
|
||||||
"media_library_url",
|
|
||||||
"course_url",
|
|
||||||
"documents",
|
|
||||||
"due_dates",
|
"due_dates",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CircleDocumentSerializer(serializers.ModelSerializer):
|
class CircleDocumentSerializer(serializers.ModelSerializer):
|
||||||
|
learning_sequence = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircleDocument
|
model = CircleDocument
|
||||||
fields = [
|
fields = [
|
||||||
|
|
@ -145,6 +94,20 @@ class CircleDocumentSerializer(serializers.ModelSerializer):
|
||||||
"learning_sequence",
|
"learning_sequence",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_learning_sequence(self, obj):
|
||||||
|
ls = obj.learning_sequence
|
||||||
|
circle = ls.get_circle()
|
||||||
|
return {
|
||||||
|
"title": ls.title,
|
||||||
|
"id": str(ls.id),
|
||||||
|
"slug": ls.slug,
|
||||||
|
"circle": {
|
||||||
|
"title": circle.title,
|
||||||
|
"id": str(circle.id),
|
||||||
|
"slug": circle.slug,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DocumentUploadStartInputSerializer(serializers.Serializer):
|
class DocumentUploadStartInputSerializer(serializers.Serializer):
|
||||||
file_name = serializers.CharField()
|
file_name = serializers.CharField()
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ class CourseCompletionApiTestCase(APITestCase):
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(len(response_json), 1)
|
self.assertEqual(len(response_json), 1)
|
||||||
self.assertEqual(response_json[0]["page_id"], learning_content.id)
|
self.assertEqual(response_json[0]["page_id"], str(learning_content.id))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response_json[0]["page_type"], "learnpath.LearningContentPlaceholder"
|
response_json[0]["page_type"], "learnpath.LearningContentPlaceholder"
|
||||||
)
|
)
|
||||||
|
|
@ -67,7 +67,7 @@ class CourseCompletionApiTestCase(APITestCase):
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(len(response_json), 1)
|
self.assertEqual(len(response_json), 1)
|
||||||
self.assertEqual(response_json[0]["page_id"], learning_content.id)
|
self.assertEqual(response_json[0]["page_id"], str(learning_content.id))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response_json[0]["page_type"], "learnpath.LearningContentPlaceholder"
|
response_json[0]["page_type"], "learnpath.LearningContentPlaceholder"
|
||||||
)
|
)
|
||||||
|
|
@ -86,7 +86,7 @@ class CourseCompletionApiTestCase(APITestCase):
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(len(response_json), 1)
|
self.assertEqual(len(response_json), 1)
|
||||||
self.assertEqual(response_json[0]["page_id"], learning_content.id)
|
self.assertEqual(response_json[0]["page_id"], str(learning_content.id))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response_json[0]["page_type"], "learnpath.LearningContentPlaceholder"
|
response_json[0]["page_type"], "learnpath.LearningContentPlaceholder"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class CourseCompletionApiTestCase(APITestCase):
|
||||||
self.assertEqual(len(response.json()), 1)
|
self.assertEqual(len(response.json()), 1)
|
||||||
|
|
||||||
print(json.dumps(response.json(), indent=4))
|
print(json.dumps(response.json(), indent=4))
|
||||||
self.assertEqual(response.json()[0]["id"], self.course_session.id)
|
self.assertEqual(response.json()[0]["id"], str(self.course_session.id))
|
||||||
|
|
||||||
def test_api_superUser_canAccessEveryCourseSession(self):
|
def test_api_superUser_canAccessEveryCourseSession(self):
|
||||||
self.client.login(username="admin", password="test")
|
self.client.login(username="admin", password="test")
|
||||||
|
|
@ -50,4 +50,4 @@ class CourseCompletionApiTestCase(APITestCase):
|
||||||
self.assertEqual(len(response.json()), 1)
|
self.assertEqual(len(response.json()), 1)
|
||||||
|
|
||||||
print(json.dumps(response.json(), indent=4))
|
print(json.dumps(response.json(), indent=4))
|
||||||
self.assertEqual(response.json()[0]["id"], self.course_session.id)
|
self.assertEqual(response.json()[0]["id"], str(self.course_session.id))
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ from rest_framework.response import Response
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
|
|
||||||
from vbv_lernwelt.core.utils import get_django_content_type
|
from vbv_lernwelt.core.utils import get_django_content_type
|
||||||
|
|
||||||
from vbv_lernwelt.course.models import (
|
from vbv_lernwelt.course.models import (
|
||||||
CircleDocument,
|
CircleDocument,
|
||||||
CourseCompletion,
|
CourseCompletion,
|
||||||
|
|
@ -135,7 +134,9 @@ def mark_course_completion_view(request):
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
def get_course_sessions(request):
|
def get_course_sessions(request):
|
||||||
try:
|
try:
|
||||||
course_sessions = course_sessions_for_user_qs(request.user)
|
course_sessions = course_sessions_for_user_qs(request.user).prefetch_related(
|
||||||
|
"course"
|
||||||
|
)
|
||||||
return Response(
|
return Response(
|
||||||
status=200, data=CourseSessionSerializer(course_sessions, many=True).data
|
status=200, data=CourseSessionSerializer(course_sessions, many=True).data
|
||||||
)
|
)
|
||||||
|
|
@ -146,20 +147,6 @@ def get_course_sessions(request):
|
||||||
return Response({"error": str(e)}, status=404)
|
return Response({"error": str(e)}, status=404)
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
|
||||||
def get_course_session_users(request, course_session_id):
|
|
||||||
try:
|
|
||||||
qs = CourseSessionUser.objects.filter(course_session_id=course_session_id)
|
|
||||||
|
|
||||||
user_data = [csu.to_dict() for csu in qs]
|
|
||||||
return Response(status=200, data=user_data)
|
|
||||||
except PermissionDenied as e:
|
|
||||||
raise e
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e)
|
|
||||||
return Response({"error": str(e)}, status=404)
|
|
||||||
|
|
||||||
|
|
||||||
@api_view(["POST"])
|
@api_view(["POST"])
|
||||||
def document_upload_start(request):
|
def document_upload_start(request):
|
||||||
serializer = DocumentUploadStartInputSerializer(data=request.data)
|
serializer = DocumentUploadStartInputSerializer(data=request.data)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ import structlog
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
from vbv_lernwelt.course.permissions import has_course_access
|
from vbv_lernwelt.course.permissions import has_course_access
|
||||||
from vbv_lernwelt.course_session.graphql.types import CourseSessionAttendanceCourseType
|
from vbv_lernwelt.course_session.graphql.types import (
|
||||||
|
CourseSessionAttendanceCourseObjectType,
|
||||||
|
)
|
||||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||||
from vbv_lernwelt.course_session.services.attendance import (
|
from vbv_lernwelt.course_session.services.attendance import (
|
||||||
AttendanceUserStatus,
|
AttendanceUserStatus,
|
||||||
|
|
@ -21,7 +23,9 @@ class AttendanceUserInputType(graphene.InputObjectType):
|
||||||
|
|
||||||
|
|
||||||
class AttendanceCourseUserMutation(graphene.Mutation):
|
class AttendanceCourseUserMutation(graphene.Mutation):
|
||||||
course_session_attendance_course = graphene.Field(CourseSessionAttendanceCourseType)
|
course_session_attendance_course = graphene.Field(
|
||||||
|
CourseSessionAttendanceCourseObjectType
|
||||||
|
)
|
||||||
|
|
||||||
class Input:
|
class Input:
|
||||||
id = graphene.ID(required=True)
|
id = graphene.ID(required=True)
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
from vbv_lernwelt.course.models import CourseSession
|
from vbv_lernwelt.course.models import CourseSession
|
||||||
from vbv_lernwelt.course.permissions import has_course_access, is_course_session_expert
|
from vbv_lernwelt.course.permissions import has_course_access, is_course_session_expert
|
||||||
from vbv_lernwelt.course_session.graphql.types import CourseSessionAttendanceCourseType
|
from vbv_lernwelt.course_session.graphql.types import (
|
||||||
|
CourseSessionAttendanceCourseObjectType,
|
||||||
|
)
|
||||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||||
|
|
||||||
|
|
||||||
class CourseSessionQuery(object):
|
class CourseSessionQuery(object):
|
||||||
course_session_attendance_course = graphene.Field(
|
course_session_attendance_course = graphene.Field(
|
||||||
CourseSessionAttendanceCourseType,
|
CourseSessionAttendanceCourseObjectType,
|
||||||
id=graphene.ID(required=True),
|
id=graphene.ID(required=True),
|
||||||
assignment_user_id=graphene.ID(required=False),
|
assignment_user_id=graphene.ID(required=False),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,22 @@
|
||||||
import graphene
|
import graphene
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
|
|
||||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
from vbv_lernwelt.course.permissions import is_course_session_expert
|
||||||
|
from vbv_lernwelt.course_session.models import (
|
||||||
|
CourseSessionAssignment,
|
||||||
|
CourseSessionAttendanceCourse,
|
||||||
|
CourseSessionEdoniqTest,
|
||||||
|
)
|
||||||
from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus
|
from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus
|
||||||
|
from vbv_lernwelt.duedate.graphql.types import DueDateObjectType
|
||||||
|
from vbv_lernwelt.learnpath.graphql.types import (
|
||||||
|
LearningContentAssignmentObjectType,
|
||||||
|
LearningContentAttendanceCourseObjectType,
|
||||||
|
LearningContentEdoniqTestObjectType,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AttendanceUserType(graphene.ObjectType):
|
class AttendanceUserObjectType(graphene.ObjectType):
|
||||||
user_id = graphene.UUID(required=True)
|
user_id = graphene.UUID(required=True)
|
||||||
status = graphene.Field(
|
status = graphene.Field(
|
||||||
graphene.Enum.from_enum(AttendanceUserStatus), required=True
|
graphene.Enum.from_enum(AttendanceUserStatus), required=True
|
||||||
|
|
@ -15,35 +26,61 @@ class AttendanceUserType(graphene.ObjectType):
|
||||||
email = graphene.String()
|
email = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
class CourseSessionAttendanceCourseType(DjangoObjectType):
|
class CourseSessionAttendanceCourseObjectType(DjangoObjectType):
|
||||||
course_session_id = graphene.ID(source="course_session_id")
|
course_session_id = graphene.ID(source="course_session_id")
|
||||||
learning_content_id = graphene.ID(source="learning_content_id")
|
learning_content_id = graphene.ID(source="learning_content_id")
|
||||||
due_date_id = graphene.ID(source="due_date_id")
|
|
||||||
end = graphene.DateTime()
|
|
||||||
start = graphene.DateTime()
|
|
||||||
attendance_user_list = graphene.List(
|
attendance_user_list = graphene.List(
|
||||||
AttendanceUserType, source="attendance_user_list"
|
AttendanceUserObjectType, source="attendance_user_list"
|
||||||
)
|
)
|
||||||
|
due_date = graphene.Field(DueDateObjectType)
|
||||||
|
learning_content = graphene.Field(LearningContentAttendanceCourseObjectType)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CourseSessionAttendanceCourse
|
model = CourseSessionAttendanceCourse
|
||||||
fields = (
|
fields = (
|
||||||
"id",
|
"id",
|
||||||
"course_session_id",
|
"course_session_id",
|
||||||
"learning_content_id",
|
"learning_content",
|
||||||
"due_date_id",
|
|
||||||
"location",
|
"location",
|
||||||
"trainer",
|
"trainer",
|
||||||
"start",
|
"due_date",
|
||||||
"end",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def resolve_start(self, info):
|
def resolve_attendance_user_list(self, info):
|
||||||
if self.due_date is None:
|
if is_course_session_expert(info.context.user, self.course_session_id):
|
||||||
return None
|
return self.attendance_user_list
|
||||||
return self.due_date.start
|
return []
|
||||||
|
|
||||||
def resolve_end(self, info):
|
|
||||||
if self.due_date is None:
|
class CourseSessionAssignmentObjectType(DjangoObjectType):
|
||||||
return None
|
course_session_id = graphene.ID(source="course_session_id")
|
||||||
return self.due_date.end
|
learning_content_id = graphene.ID(source="learning_content_id")
|
||||||
|
submission_deadline = graphene.Field(DueDateObjectType)
|
||||||
|
evaluation_deadline = graphene.Field(DueDateObjectType)
|
||||||
|
learning_content = graphene.Field(LearningContentAssignmentObjectType)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CourseSessionAssignment
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"course_session_id",
|
||||||
|
"learning_content",
|
||||||
|
"submission_deadline",
|
||||||
|
"evaluation_deadline",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionEdoniqTestObjectType(DjangoObjectType):
|
||||||
|
course_session_id = graphene.ID(source="course_session_id")
|
||||||
|
learning_content_id = graphene.ID(source="learning_content_id")
|
||||||
|
deadline = graphene.Field(DueDateObjectType)
|
||||||
|
learning_content = graphene.Field(LearningContentEdoniqTestObjectType)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CourseSessionEdoniqTest
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"course_session_id",
|
||||||
|
"learning_content",
|
||||||
|
"deadline",
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,82 +1 @@
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from vbv_lernwelt.course_session.models import (
|
|
||||||
CourseSessionAssignment,
|
|
||||||
CourseSessionAttendanceCourse,
|
|
||||||
CourseSessionEdoniqTest,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer):
|
|
||||||
start = serializers.SerializerMethodField()
|
|
||||||
end = serializers.SerializerMethodField()
|
|
||||||
circle_title = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = CourseSessionAttendanceCourse
|
|
||||||
fields = [
|
|
||||||
"id",
|
|
||||||
"course_session",
|
|
||||||
"learning_content_id",
|
|
||||||
"due_date_id",
|
|
||||||
"location",
|
|
||||||
"trainer",
|
|
||||||
"start",
|
|
||||||
"end",
|
|
||||||
"circle_title",
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_start(self, obj):
|
|
||||||
return obj.due_date.start
|
|
||||||
|
|
||||||
def get_end(self, obj):
|
|
||||||
return obj.due_date.end
|
|
||||||
|
|
||||||
def get_circle_title(self, obj):
|
|
||||||
circle = obj.get_circle()
|
|
||||||
if circle:
|
|
||||||
return circle.title
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
class CourseSessionAssignmentSerializer(serializers.ModelSerializer):
|
|
||||||
submission_deadline_start = serializers.SerializerMethodField()
|
|
||||||
evaluation_deadline_start = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = CourseSessionAssignment
|
|
||||||
fields = [
|
|
||||||
"id",
|
|
||||||
"course_session_id",
|
|
||||||
"learning_content_id",
|
|
||||||
"submission_deadline_id",
|
|
||||||
"submission_deadline_start",
|
|
||||||
"evaluation_deadline_id",
|
|
||||||
"evaluation_deadline_start",
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_evaluation_deadline_start(self, obj):
|
|
||||||
if obj.evaluation_deadline:
|
|
||||||
return obj.evaluation_deadline.start
|
|
||||||
|
|
||||||
def get_submission_deadline_start(self, obj):
|
|
||||||
if obj.submission_deadline:
|
|
||||||
return obj.submission_deadline.start
|
|
||||||
|
|
||||||
|
|
||||||
class CourseSessionEdoniqTestSerializer(serializers.ModelSerializer):
|
|
||||||
deadline_start = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = CourseSessionEdoniqTest
|
|
||||||
fields = [
|
|
||||||
"id",
|
|
||||||
"course_session_id",
|
|
||||||
"learning_content_id",
|
|
||||||
"deadline_id",
|
|
||||||
"deadline_start",
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_deadline_start(self, obj):
|
|
||||||
if obj.deadline:
|
|
||||||
return obj.deadline.start
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,21 @@
|
||||||
from django.shortcuts import render
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
# Create your views here.
|
from vbv_lernwelt.course.models import CircleDocument
|
||||||
|
from vbv_lernwelt.course.permissions import has_course_session_access
|
||||||
|
from vbv_lernwelt.course.serializers import CircleDocumentSerializer
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["GET"])
|
||||||
|
def get_course_session_documents(request, course_session_id):
|
||||||
|
if not has_course_session_access(request.user, course_session_id):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
circle_documents = CircleDocument.objects.filter(
|
||||||
|
course_session_id=course_session_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
status=200, data=CircleDocumentSerializer(circle_documents, many=True).data
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
from graphene_django import DjangoObjectType
|
||||||
|
|
||||||
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
|
|
||||||
|
|
||||||
|
class DueDateObjectType(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = DueDate
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"start",
|
||||||
|
"end",
|
||||||
|
"manual_override_fields",
|
||||||
|
"title",
|
||||||
|
"assignment_type_translation_key",
|
||||||
|
"date_type_translation_key",
|
||||||
|
"subtitle",
|
||||||
|
"url",
|
||||||
|
"course_session",
|
||||||
|
"page",
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.2.20 on 2023-10-10 13:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("duedate", "0005_auto_20230925_1648"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="circle_data",
|
||||||
|
field=models.JSONField(blank=True, default=dict),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 3.2.20 on 2023-10-10 13:05
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.migrations import RunPython
|
||||||
|
|
||||||
|
|
||||||
|
def trigger_due_date_save(apps, schema_editor):
|
||||||
|
# need to load concrete model, so that wagtail page has `specific` instance method...
|
||||||
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
|
|
||||||
|
for due_date in DueDate.objects.all():
|
||||||
|
# trigger save to prefetch circle data
|
||||||
|
due_date.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("duedate", "0006_duedate_circle_data"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [RunPython(trigger_due_date_save)]
|
||||||
|
|
@ -60,6 +60,7 @@ class DueDate(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
page = models.ForeignKey(Page, on_delete=models.SET_NULL, null=True, blank=True)
|
page = models.ForeignKey(Page, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
circle_data = models.JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["start", "end"]
|
ordering = ["start", "end"]
|
||||||
|
|
@ -80,6 +81,21 @@ class DueDate(models.Model):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
circle = self.get_circle()
|
||||||
|
# prefetch circle data, because loading it dynamically is too slow
|
||||||
|
if circle:
|
||||||
|
self.circle_data = {
|
||||||
|
"id": str(circle.id),
|
||||||
|
"title": circle.title,
|
||||||
|
"slug": circle.slug,
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
# noop
|
||||||
|
pass
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_subtitle(self):
|
def display_subtitle(self):
|
||||||
result = ""
|
result = ""
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.utils import StringIDField
|
||||||
from vbv_lernwelt.duedate.models import DueDate
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
|
|
||||||
|
|
||||||
class DueDateSerializer(serializers.ModelSerializer):
|
class DueDateSerializer(serializers.ModelSerializer):
|
||||||
|
id = StringIDField()
|
||||||
|
course_session_id = serializers.SerializerMethodField()
|
||||||
circle = serializers.SerializerMethodField()
|
circle = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DueDate
|
model = DueDate
|
||||||
fields = [
|
fields = [
|
||||||
|
"id",
|
||||||
"start",
|
"start",
|
||||||
"end",
|
"end",
|
||||||
"manual_override_fields",
|
"manual_override_fields",
|
||||||
|
|
@ -18,18 +22,18 @@ class DueDateSerializer(serializers.ModelSerializer):
|
||||||
"subtitle",
|
"subtitle",
|
||||||
"url",
|
"url",
|
||||||
"url_expert",
|
"url_expert",
|
||||||
"course_session",
|
"course_session_id",
|
||||||
"page",
|
|
||||||
"circle",
|
"circle",
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_circle(self, obj):
|
def get_course_session_id(self, obj):
|
||||||
circle = obj.get_circle()
|
return str(obj.course_session.id)
|
||||||
|
|
||||||
if circle:
|
def get_circle(self, obj):
|
||||||
|
if obj.circle_data:
|
||||||
return {
|
return {
|
||||||
"id": circle.id,
|
"id": obj.circle_data.get("id"),
|
||||||
"title": circle.title,
|
"title": obj.circle_data.get("title"),
|
||||||
"translation_key": circle.translation_key,
|
"slug": obj.circle_data.get("slug"),
|
||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ from vbv_lernwelt.learnpath.graphql.types import (
|
||||||
LearningContentAssignmentObjectType,
|
LearningContentAssignmentObjectType,
|
||||||
LearningContentAttendanceCourseObjectType,
|
LearningContentAttendanceCourseObjectType,
|
||||||
LearningContentDocumentListObjectType,
|
LearningContentDocumentListObjectType,
|
||||||
|
LearningContentEdoniqTestObjectType,
|
||||||
LearningContentFeedbackObjectType,
|
LearningContentFeedbackObjectType,
|
||||||
LearningContentLearningModuleObjectType,
|
LearningContentLearningModuleObjectType,
|
||||||
LearningContentMediaLibraryObjectType,
|
LearningContentMediaLibraryObjectType,
|
||||||
LearningContentPlaceholderObjectType,
|
LearningContentPlaceholderObjectType,
|
||||||
LearningContentRichTextObjectType,
|
LearningContentRichTextObjectType,
|
||||||
LearningContentTestObjectType,
|
|
||||||
LearningContentVideoObjectType,
|
LearningContentVideoObjectType,
|
||||||
LearningPathObjectType,
|
LearningPathObjectType,
|
||||||
)
|
)
|
||||||
|
|
@ -58,7 +58,7 @@ class LearningPathQuery:
|
||||||
)
|
)
|
||||||
learning_content_placeholder = graphene.Field(LearningContentPlaceholderObjectType)
|
learning_content_placeholder = graphene.Field(LearningContentPlaceholderObjectType)
|
||||||
learning_content_rich_text = graphene.Field(LearningContentRichTextObjectType)
|
learning_content_rich_text = graphene.Field(LearningContentRichTextObjectType)
|
||||||
learning_content_test = graphene.Field(LearningContentTestObjectType)
|
learning_content_test = graphene.Field(LearningContentEdoniqTestObjectType)
|
||||||
learning_content_video = graphene.Field(LearningContentVideoObjectType)
|
learning_content_video = graphene.Field(LearningContentVideoObjectType)
|
||||||
learning_content_document_list = graphene.Field(
|
learning_content_document_list = graphene.Field(
|
||||||
LearningContentDocumentListObjectType
|
LearningContentDocumentListObjectType
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class LearningContentInterface(CoursePageInterface):
|
||||||
elif isinstance(instance, LearningContentRichText):
|
elif isinstance(instance, LearningContentRichText):
|
||||||
return LearningContentRichTextObjectType
|
return LearningContentRichTextObjectType
|
||||||
elif isinstance(instance, LearningContentEdoniqTest):
|
elif isinstance(instance, LearningContentEdoniqTest):
|
||||||
return LearningContentTestObjectType
|
return LearningContentEdoniqTestObjectType
|
||||||
elif isinstance(instance, LearningContentVideo):
|
elif isinstance(instance, LearningContentVideo):
|
||||||
return LearningContentVideoObjectType
|
return LearningContentVideoObjectType
|
||||||
elif isinstance(instance, LearningContentDocumentList):
|
elif isinstance(instance, LearningContentDocumentList):
|
||||||
|
|
@ -102,7 +102,7 @@ class LearningContentMediaLibraryObjectType(DjangoObjectType):
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
|
|
||||||
class LearningContentTestObjectType(DjangoObjectType):
|
class LearningContentEdoniqTestObjectType(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = LearningContentEdoniqTest
|
model = LearningContentEdoniqTest
|
||||||
interfaces = (LearningContentInterface,)
|
interfaces = (LearningContentInterface,)
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ class LearningContentEdoniqTestSerializer(
|
||||||
try:
|
try:
|
||||||
cert = obj.content_assignment.competence_certificate
|
cert = obj.content_assignment.competence_certificate
|
||||||
return {
|
return {
|
||||||
"id": cert.id,
|
"id": str(cert.id),
|
||||||
"title": cert.title,
|
"title": cert.title,
|
||||||
"slug": cert.slug,
|
"slug": cert.slug,
|
||||||
"content_type": get_django_content_type(cert),
|
"content_type": get_django_content_type(cert),
|
||||||
|
|
@ -89,7 +89,7 @@ class LearningContentAssignmentSerializer(
|
||||||
try:
|
try:
|
||||||
cert = obj.content_assignment.competence_certificate
|
cert = obj.content_assignment.competence_certificate
|
||||||
return {
|
return {
|
||||||
"id": cert.id,
|
"id": str(cert.id),
|
||||||
"title": cert.title,
|
"title": cert.title,
|
||||||
"slug": cert.slug,
|
"slug": cert.slug,
|
||||||
"content_type": get_django_content_type(cert),
|
"content_type": get_django_content_type(cert),
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@ from django.db import migrations
|
||||||
def init_user_notification_emails(apps=None, schema_editor=None):
|
def init_user_notification_emails(apps=None, schema_editor=None):
|
||||||
User = apps.get_model("core", "User")
|
User = apps.get_model("core", "User")
|
||||||
for u in User.objects.all():
|
for u in User.objects.all():
|
||||||
u.additional_json_data["email_notification_categories"] = ["NOTIFICATION"]
|
u.additional_json_data["email_notification_categories"] = [
|
||||||
|
"INFORMATION",
|
||||||
|
"USER_INTERACTION",
|
||||||
|
]
|
||||||
u.save()
|
u.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue