Improve course session loading
This commit is contained in:
parent
bb50cc60e9
commit
778dde12d7
|
|
@ -2,7 +2,7 @@
|
|||
import { useTranslation } from "i18next-vue";
|
||||
import { useRouteLookups } from "@/utils/route";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { getCompetenceBaseUrl } from "@/utils/utils";
|
||||
import { getCompetenceNaviUrl, getLearningPathUrl } from "@/utils/utils";
|
||||
|
||||
const { inCompetenceProfile, inLearningPath } = useRouteLookups();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
|
@ -24,7 +24,7 @@ const { t } = useTranslation();
|
|||
<div class="flex space-x-8">
|
||||
<router-link
|
||||
data-cy="preview-learn-path-link"
|
||||
:to="courseSession.learning_path_url"
|
||||
:to="getLearningPathUrl(courseSession)"
|
||||
class="preview-nav-item"
|
||||
:class="{ 'preview-nav-item--active': inLearningPath() }"
|
||||
>
|
||||
|
|
@ -33,7 +33,7 @@ const { t } = useTranslation();
|
|||
|
||||
<router-link
|
||||
data-cy="preview-competence-profile-link"
|
||||
:to="getCompetenceBaseUrl(courseSession)"
|
||||
:to="getCompetenceNaviUrl(courseSession)"
|
||||
class="preview-nav-item"
|
||||
:class="{ 'preview-nav-item--active': inCompetenceProfile() }"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
|||
import { computed, onMounted, reactive } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import CoursePreviewBar from "@/components/header/CoursePreviewBar.vue";
|
||||
import { getCompetenceBaseUrl } from "@/utils/utils";
|
||||
import {
|
||||
getCompetenceNaviUrl,
|
||||
getLearningPathUrl,
|
||||
getMediaCenterUrl,
|
||||
} from "@/utils/utils";
|
||||
|
||||
log.debug("MainNavigationBar created");
|
||||
|
||||
|
|
@ -71,7 +75,7 @@ onMounted(() => {
|
|||
v-if="userStore.loggedIn"
|
||||
:show="state.showMobileNavigationMenu"
|
||||
:course-session="courseSessionsStore.currentCourseSession"
|
||||
:media-url="courseSessionsStore.currentCourseSession?.media_library_url"
|
||||
:media-url="getMediaCenterUrl(courseSessionsStore.currentCourseSession)"
|
||||
:user="userStore"
|
||||
@closemodal="state.showMobileNavigationMenu = false"
|
||||
@logout="userStore.handleLogout()"
|
||||
|
|
@ -138,7 +142,7 @@ onMounted(() => {
|
|||
|
||||
<router-link
|
||||
data-cy="navigation-preview-link"
|
||||
:to="courseSessionsStore.currentCourseSession.learning_path_url"
|
||||
:to="getLearningPathUrl(courseSessionsStore.currentCourseSession)"
|
||||
target="_blank"
|
||||
class="nav-item"
|
||||
>
|
||||
|
|
@ -151,7 +155,7 @@ onMounted(() => {
|
|||
<template v-else>
|
||||
<router-link
|
||||
data-cy="navigation-learning-path-link"
|
||||
:to="courseSessionsStore.currentCourseSession.learning_path_url"
|
||||
:to="getLearningPathUrl(courseSessionsStore.currentCourseSession)"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inLearningPath() }"
|
||||
>
|
||||
|
|
@ -161,7 +165,7 @@ onMounted(() => {
|
|||
<router-link
|
||||
data-cy="navigation-competence-profile-link"
|
||||
:to="
|
||||
getCompetenceBaseUrl(courseSessionsStore.currentCourseSession)
|
||||
getCompetenceNaviUrl(courseSessionsStore.currentCourseSession)
|
||||
"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
||||
|
|
@ -176,7 +180,7 @@ onMounted(() => {
|
|||
<div class="flex items-stretch justify-start space-x-8">
|
||||
<router-link
|
||||
v-if="inCourse() && courseSessionsStore.currentCourseSession"
|
||||
:to="courseSessionsStore.currentCourseSession.media_library_url"
|
||||
:to="getMediaCenterUrl(courseSessionsStore.currentCourseSession)"
|
||||
data-cy="medialibrary-link"
|
||||
class="nav-item-no-mobile"
|
||||
:class="{ 'nav-item--active': inMediaLibrary() }"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@ import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|||
import type { UserState } from "@/stores/user";
|
||||
import type { CourseSession } from "@/types";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getCompetenceBaseUrl } from "@/utils/utils";
|
||||
import {
|
||||
getCompetenceNaviUrl,
|
||||
getLearningPathUrl,
|
||||
getMediaCenterUrl,
|
||||
} from "@/utils/utils";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
|
@ -68,7 +72,7 @@ const courseSessionsStore = useCourseSessionsStore();
|
|||
<li class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-preview-link"
|
||||
@click="clickLink(courseSession.learning_path_url)"
|
||||
@click="clickLink(getLearningPathUrl(courseSession))"
|
||||
>
|
||||
{{ $t("a.VorschauTeilnehmer") }}
|
||||
</button>
|
||||
|
|
@ -78,7 +82,7 @@ const courseSessionsStore = useCourseSessionsStore();
|
|||
<li class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-learning-path-link"
|
||||
@click="clickLink(courseSession.learning_path_url)"
|
||||
@click="clickLink(getLearningPathUrl(courseSession))"
|
||||
>
|
||||
{{ $t("general.learningPath") }}
|
||||
</button>
|
||||
|
|
@ -86,7 +90,7 @@ const courseSessionsStore = useCourseSessionsStore();
|
|||
<li class="mb-6">
|
||||
<button
|
||||
data-cy="navigation-mobile-competence-profile-link"
|
||||
@click="clickLink(getCompetenceBaseUrl(courseSession))"
|
||||
@click="clickLink(getCompetenceNaviUrl(courseSession))"
|
||||
>
|
||||
{{ $t("competences.title") }}
|
||||
</button>
|
||||
|
|
@ -95,7 +99,7 @@ const courseSessionsStore = useCourseSessionsStore();
|
|||
<li class="mb-6">
|
||||
<button
|
||||
data-cy="medialibrary-link"
|
||||
@click="clickLink(`${courseSession?.media_library_url}`)"
|
||||
@click="clickLink(getMediaCenterUrl(courseSession))"
|
||||
>
|
||||
{{ $t("a.Mediathek") }}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
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 type { ComputedRef } from "vue";
|
||||
import { computed } from "vue";
|
||||
|
|
@ -28,3 +32,52 @@ export function useCurrentCourseSession() {
|
|||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function useCourseSessionDetailQuery(courSessionId?: string | number) {
|
||||
if (!courSessionId) {
|
||||
courSessionId = useCurrentCourseSession().value.id;
|
||||
}
|
||||
const queryResult = useQuery({
|
||||
query: COURSE_SESSION_DETAIL_QUERY,
|
||||
variables: {
|
||||
courseSessionId: courSessionId.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
const courseSessionDetail = computed(() => {
|
||||
return queryResult.data.value?.course_session as CourseSessionDetail | undefined;
|
||||
});
|
||||
|
||||
function findAssignmentDetail(assignmentId: string) {
|
||||
return (courseSessionDetail.value?.assignments ?? []).find((a) => {
|
||||
return a.learning_content?.content_assignment?.id === assignmentId;
|
||||
});
|
||||
}
|
||||
|
||||
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";
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...queryResult,
|
||||
courseSessionDetail,
|
||||
findAssignmentDetail,
|
||||
findUser,
|
||||
findCurrentUser,
|
||||
filterMembers,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ type Query {
|
|||
learning_content_learning_module: LearningContentLearningModuleObjectType
|
||||
learning_content_placeholder: LearningContentPlaceholderObjectType
|
||||
learning_content_rich_text: LearningContentRichTextObjectType
|
||||
learning_content_test: LearningContentTestObjectType
|
||||
learning_content_test: LearningContentEdoniqTestObjectType
|
||||
learning_content_video: LearningContentVideoObjectType
|
||||
learning_content_document_list: LearningContentDocumentListObjectType
|
||||
course_session_attendance_course(id: ID!, assignment_user_id: ID): CourseSessionAttendanceCourseType
|
||||
course(id: Int): CourseObjectType
|
||||
course_session_attendance_course(id: ID!, assignment_user_id: ID): CourseSessionAttendanceCourseObjectType
|
||||
course(id: ID): CourseObjectType
|
||||
course_session(id: ID): CourseSessionObjectType
|
||||
competence_certificate(id: ID, slug: String): CompetenceCertificateObjectType
|
||||
competence_certificate_list(id: ID, slug: String, course_id: ID, course_slug: String): CompetenceCertificateListObjectType
|
||||
assignment(id: ID, slug: String): AssignmentObjectType
|
||||
|
|
@ -307,12 +308,155 @@ type AssignmentCompletionObjectType {
|
|||
edoniq_extended_time_flag: Boolean!
|
||||
assignment_user: UserType!
|
||||
assignment: AssignmentObjectType!
|
||||
course_session: CourseSessionObjectType!
|
||||
completion_status: AssignmentAssignmentCompletionCompletionStatusChoices!
|
||||
completion_data: GenericScalar
|
||||
additional_json_data: JSONString!
|
||||
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]
|
||||
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"""
|
||||
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
|
||||
course_session_id: ID
|
||||
learning_content_id: ID
|
||||
deadline: DueDateObjectType
|
||||
}
|
||||
|
||||
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 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."""
|
||||
enum AssignmentAssignmentCompletionCompletionStatusChoices {
|
||||
"""IN_PROGRESS"""
|
||||
|
|
@ -361,21 +505,6 @@ enum LearnpathLearningContentAssignmentAssignmentTypeChoices {
|
|||
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 {
|
||||
id: ID
|
||||
title: String
|
||||
|
|
@ -436,22 +565,6 @@ type LearningContentRichTextObjectType implements LearningContentInterface {
|
|||
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 {
|
||||
id: ID
|
||||
title: String
|
||||
|
|
@ -482,32 +595,6 @@ type LearningContentDocumentListObjectType implements LearningContentInterface {
|
|||
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 {
|
||||
id: ID
|
||||
path: String!
|
||||
|
|
@ -577,7 +664,7 @@ type ErrorType {
|
|||
}
|
||||
|
||||
type AttendanceCourseUserMutation {
|
||||
course_session_attendance_course: CourseSessionAttendanceCourseType
|
||||
course_session_attendance_course: CourseSessionAttendanceCourseObjectType
|
||||
}
|
||||
|
||||
input AttendanceUserInputType {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ export const AssignmentCompletionStatus = "AssignmentCompletionStatus";
|
|||
export const AssignmentObjectType = "AssignmentObjectType";
|
||||
export const AttendanceCourseUserMutation = "AttendanceCourseUserMutation";
|
||||
export const AttendanceUserInputType = "AttendanceUserInputType";
|
||||
export const AttendanceUserObjectType = "AttendanceUserObjectType";
|
||||
export const AttendanceUserStatus = "AttendanceUserStatus";
|
||||
export const AttendanceUserType = "AttendanceUserType";
|
||||
export const Boolean = "Boolean";
|
||||
export const CircleObjectType = "CircleObjectType";
|
||||
export const CompetenceCertificateListObjectType = "CompetenceCertificateListObjectType";
|
||||
|
|
@ -15,8 +15,15 @@ export const CompetenceCertificateObjectType = "CompetenceCertificateObjectType"
|
|||
export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
|
||||
export const CourseObjectType = "CourseObjectType";
|
||||
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 DueDateObjectType = "DueDateObjectType";
|
||||
export const ErrorType = "ErrorType";
|
||||
export const FeedbackResponseObjectType = "FeedbackResponseObjectType";
|
||||
export const Float = "Float";
|
||||
|
|
@ -28,13 +35,13 @@ export const JSONString = "JSONString";
|
|||
export const LearningContentAssignmentObjectType = "LearningContentAssignmentObjectType";
|
||||
export const LearningContentAttendanceCourseObjectType = "LearningContentAttendanceCourseObjectType";
|
||||
export const LearningContentDocumentListObjectType = "LearningContentDocumentListObjectType";
|
||||
export const LearningContentEdoniqTestObjectType = "LearningContentEdoniqTestObjectType";
|
||||
export const LearningContentFeedbackObjectType = "LearningContentFeedbackObjectType";
|
||||
export const LearningContentInterface = "LearningContentInterface";
|
||||
export const LearningContentLearningModuleObjectType = "LearningContentLearningModuleObjectType";
|
||||
export const LearningContentMediaLibraryObjectType = "LearningContentMediaLibraryObjectType";
|
||||
export const LearningContentPlaceholderObjectType = "LearningContentPlaceholderObjectType";
|
||||
export const LearningContentRichTextObjectType = "LearningContentRichTextObjectType";
|
||||
export const LearningContentTestObjectType = "LearningContentTestObjectType";
|
||||
export const LearningContentVideoObjectType = "LearningContentVideoObjectType";
|
||||
export const LearningPathObjectType = "LearningPathObjectType";
|
||||
export const LearningSequenceObjectType = "LearningSequenceObjectType";
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const graphqlClient = new Client({
|
|||
cacheExchange({
|
||||
schema: schema,
|
||||
keys: {
|
||||
AttendanceUserType: (data) => data?.user_id?.toString() ?? null,
|
||||
AttendanceUserObjectType: (data) => data?.user_id?.toString() ?? null,
|
||||
},
|
||||
updates: {
|
||||
Mutation: {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export const ASSIGNMENT_COMPLETION_QUERY = graphql(`
|
|||
`);
|
||||
|
||||
export const COURSE_QUERY = graphql(`
|
||||
query courseQuery($courseId: Int!) {
|
||||
query courseQuery($courseId: ID!) {
|
||||
course(id: $courseId) {
|
||||
id
|
||||
slug
|
||||
|
|
@ -122,3 +122,76 @@ 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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
assignments {
|
||||
id
|
||||
submission_deadline {
|
||||
id
|
||||
start
|
||||
}
|
||||
evaluation_deadline {
|
||||
id
|
||||
start
|
||||
}
|
||||
learning_content {
|
||||
id
|
||||
content_assignment {
|
||||
id
|
||||
title
|
||||
assignment_type
|
||||
}
|
||||
}
|
||||
}
|
||||
edoniq_tests {
|
||||
id
|
||||
deadline {
|
||||
id
|
||||
start
|
||||
end
|
||||
}
|
||||
learning_content {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useUserStore } from "@/stores/user";
|
|||
import type { CourseSession } from "@/types";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted } from "vue";
|
||||
import { getLearningPathUrl } from "@/utils/utils";
|
||||
|
||||
log.debug("DashboardPage created");
|
||||
|
||||
|
|
@ -20,9 +21,9 @@ const allDueDates = courseSessionsStore.allDueDates();
|
|||
const getNextStepLink = (courseSession: CourseSession) => {
|
||||
return computed(() => {
|
||||
if (courseSessionsStore.hasCockpit(courseSession)) {
|
||||
return courseSession.cockpit_url;
|
||||
return `${courseSession.course_url}/cockpit`;
|
||||
}
|
||||
return courseSession.learning_path_url;
|
||||
return getLearningPathUrl(courseSession);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from "loglevel";
|
||||
import { onMounted } from "vue";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("TestCourseSessionComposablePage mounted");
|
||||
});
|
||||
|
||||
// const courseSession = useCurrentCourseSession();
|
||||
const queryResult = useCourseSessionDetailQuery("-1");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>Hello World</h1>
|
||||
|
||||
<pre>{{ queryResult.courseSessionDetail }}</pre>
|
||||
</template>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
import { useCompetenceStore } from "@/stores/competence";
|
||||
import { useLearningPathStore } from "@/stores/learningPath";
|
||||
|
|
@ -16,13 +16,14 @@ const props = defineProps<{
|
|||
const cockpitStore = useCockpitStore();
|
||||
const competenceStore = useCompetenceStore();
|
||||
const learningPathStore = useLearningPathStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("CockpitParentPage mounted", props.courseSlug);
|
||||
|
||||
try {
|
||||
const members = await cockpitStore.loadCourseSessionMembers(courseSession.value.id);
|
||||
const members = courseSessionDetailResult.filterMembers();
|
||||
members.forEach((csu) => {
|
||||
competenceStore.loadCompetenceProfilePage(
|
||||
props.courseSlug + "-competencenavi-competences",
|
||||
|
|
@ -35,7 +36,10 @@ onMounted(async () => {
|
|||
props.courseSlug + "-lp",
|
||||
useUserStore().id
|
||||
);
|
||||
await cockpitStore.loadCircles(props.courseSlug, courseSession.value.id);
|
||||
await cockpitStore.loadCircles(
|
||||
props.courseSlug,
|
||||
courseSessionDetailResult.findCurrentUser()
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import CirclePage from "@/pages/learningPath/circlePage/CirclePage.vue";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
import * as log from "loglevel";
|
||||
import { computed, onMounted } from "vue";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
|
||||
const props = defineProps<{
|
||||
userId: string;
|
||||
|
|
@ -12,14 +12,14 @@ const props = defineProps<{
|
|||
|
||||
log.debug("CockpitUserCirclePage created", props.userId, props.circleSlug);
|
||||
|
||||
const cockpitStore = useCockpitStore();
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("CockpitUserCirclePage mounted");
|
||||
});
|
||||
|
||||
const { findUser } = useCourseSessionDetailQuery();
|
||||
|
||||
const user = computed(() => {
|
||||
return cockpitStore.courseSessionMembers?.find((csu) => csu.user_id === props.userId);
|
||||
return findUser(props.userId);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { computed, onMounted } from "vue";
|
|||
|
||||
import CompetenceDetail from "@/pages/competence/ActionCompetenceDetail.vue";
|
||||
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
|
||||
const props = defineProps<{
|
||||
userId: string;
|
||||
|
|
@ -27,8 +28,10 @@ const learningPath = computed(() => {
|
|||
return learningPathStore.learningPathForUser(props.courseSlug, props.userId);
|
||||
});
|
||||
|
||||
const { findUser } = useCourseSessionDetailQuery();
|
||||
|
||||
const user = computed(() => {
|
||||
return cockpitStore.courseSessionMembers?.find((csu) => csu.user_id === props.userId);
|
||||
return findUser(props.userId);
|
||||
});
|
||||
|
||||
function setActiveClasses(isActive: boolean) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useCurrentCourseSession, useCourseSessionDetailQuery } from "@/composables";
|
||||
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
|
||||
import EvaluationContainer from "@/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue";
|
||||
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
||||
import type {
|
||||
Assignment,
|
||||
AssignmentCompletion,
|
||||
CourseSessionAssignment,
|
||||
CourseSessionUser,
|
||||
} from "@/types";
|
||||
import type { Assignment, AssignmentCompletion } from "@/types";
|
||||
import { useQuery } from "@urql/vue";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted, reactive } from "vue";
|
||||
import { computed, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getPreviousRoute } from "@/router/history";
|
||||
import { getAssignmentTypeTitle } from "@/utils/utils";
|
||||
import { getAssignmentTypeTitle } from "../../../utils/utils";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
|
|
@ -24,16 +19,6 @@ const props = defineProps<{
|
|||
|
||||
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 router = useRouter();
|
||||
|
||||
|
|
@ -49,12 +34,11 @@ const queryResult = useQuery({
|
|||
|
||||
onMounted(async () => {
|
||||
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();
|
||||
|
||||
function close() {
|
||||
|
|
@ -103,10 +87,7 @@ const assignment = computed(
|
|||
<it-icon-close></it-icon-close>
|
||||
</button>
|
||||
</header>
|
||||
<div
|
||||
v-if="assignment && assignmentCompletion && state.assignmentUser"
|
||||
class="relative"
|
||||
>
|
||||
<div v-if="assignment && assignmentCompletion && assignmentUser" class="relative">
|
||||
<div class="md:h-content flex flex-col md:flex-row">
|
||||
<div
|
||||
class="bg-white md:h-full md:overflow-y-auto"
|
||||
|
|
@ -118,12 +99,12 @@ const assignment = computed(
|
|||
|
||||
<div class="my-6 flex items-center">
|
||||
<img
|
||||
:src="state.assignmentUser?.avatar_url"
|
||||
:src="assignmentUser?.avatar_url"
|
||||
class="mr-4 h-11 w-11 rounded-full"
|
||||
/>
|
||||
<div class="font-bold">
|
||||
{{ state.assignmentUser?.first_name }}
|
||||
{{ state.assignmentUser?.last_name }}
|
||||
{{ assignmentUser?.first_name }}
|
||||
{{ assignmentUser?.last_name }}
|
||||
</div>
|
||||
</div>
|
||||
<AssignmentSubmissionResponses
|
||||
|
|
@ -139,7 +120,7 @@ const assignment = computed(
|
|||
>
|
||||
<EvaluationContainer
|
||||
:assignment-completion="assignmentCompletion"
|
||||
:assignment-user="state.assignmentUser"
|
||||
:assignment-user="assignmentUser"
|
||||
:assignment="assignment"
|
||||
@close="close()"
|
||||
></EvaluationContainer>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import EvaluationIntro from "@/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue";
|
||||
import EvaluationSummary from "@/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue";
|
||||
import EvaluationTask from "@/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue";
|
||||
import { findAssignmentDetail } from "@/services/assignmentService";
|
||||
import type {
|
||||
Assignment,
|
||||
AssignmentCompletion,
|
||||
|
|
@ -14,6 +13,7 @@ import dayjs from "dayjs";
|
|||
import { findIndex } from "lodash";
|
||||
import * as log from "loglevel";
|
||||
import { computed, onMounted } from "vue";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
|
||||
const props = defineProps<{
|
||||
assignmentUser: CourseSessionUser;
|
||||
|
|
@ -58,10 +58,14 @@ function editTask(task: AssignmentEvaluationTask) {
|
|||
stepIndex.value = taskIndex + 1;
|
||||
}
|
||||
|
||||
const assignmentDetail = computed(() => findAssignmentDetail(props.assignment.id));
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const assignmentDetail = computed(() => {
|
||||
return courseSessionDetailResult.findAssignmentDetail(props.assignment.id.toString());
|
||||
});
|
||||
|
||||
const dueDate = computed(() =>
|
||||
dayjs(assignmentDetail.value?.evaluation_deadline_start)
|
||||
dayjs(assignmentDetail.value?.evaluation_deadline.start)
|
||||
);
|
||||
|
||||
const inEvaluationTask = computed(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
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 {
|
||||
maxAssignmentPoints,
|
||||
|
|
@ -77,13 +77,13 @@ const userPoints = computed(() =>
|
|||
userAssignmentPoints(props.assignment, props.assignmentCompletion)
|
||||
);
|
||||
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
const evaluationUser = computed(() => {
|
||||
if (props.assignmentCompletion.evaluation_user) {
|
||||
return (courseSession.value.users ?? []).find(
|
||||
(user) => user.user_id === props.assignmentCompletion.evaluation_user
|
||||
) as CourseSessionUser;
|
||||
return courseSessionDetailResult.findUser(
|
||||
props.assignmentCompletion.evaluation_user
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,20 +2,17 @@
|
|||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||
import type { StatusCount } from "@/components/ui/ItProgress.vue";
|
||||
import type { GradedUser } from "@/services/assignmentService";
|
||||
import {
|
||||
findAssignmentDetail,
|
||||
loadAssignmentCompletionStatusData,
|
||||
} from "@/services/assignmentService";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
import { loadAssignmentCompletionStatusData } from "@/services/assignmentService";
|
||||
import type {
|
||||
CourseSession,
|
||||
CourseSessionUser,
|
||||
LearningContentAssignment,
|
||||
} from "@/types";
|
||||
import dayjs from "dayjs";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted, reactive } from "vue";
|
||||
import AssignmentSubmissionProgress from "@/pages/cockpit/cockpitPage/AssignmentSubmissionProgress.vue";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
import { formatDueDate } from "../../../components/dueDates/dueDatesUtils";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSession: CourseSession;
|
||||
|
|
@ -27,7 +24,7 @@ log.debug(
|
|||
props.learningContentAssignment.content_assignment_id
|
||||
);
|
||||
|
||||
const cockpitStore = useCockpitStore();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const state = reactive({
|
||||
progressStatusCount: {} as StatusCount,
|
||||
|
|
@ -35,6 +32,12 @@ const state = reactive({
|
|||
assignmentSubmittedUsers: [] as CourseSessionUser[],
|
||||
});
|
||||
|
||||
const assignmentDetail = computed(() => {
|
||||
return courseSessionDetailResult.findAssignmentDetail(
|
||||
props.learningContentAssignment.content_assignment_id.toString()
|
||||
);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const { gradedUsers, assignmentSubmittedUsers } =
|
||||
await loadAssignmentCompletionStatusData(
|
||||
|
|
@ -45,10 +48,6 @@ onMounted(async () => {
|
|||
state.gradedUsers = gradedUsers;
|
||||
state.assignmentSubmittedUsers = assignmentSubmittedUsers;
|
||||
});
|
||||
|
||||
const assignmentDetail = computed(() =>
|
||||
findAssignmentDetail(props.learningContentAssignment.content_assignment_id)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -62,14 +61,12 @@ const assignmentDetail = computed(() =>
|
|||
<div v-if="assignmentDetail">
|
||||
<span>
|
||||
{{ $t("Abgabetermin Ergebnisse:") }}
|
||||
{{ dayjs(assignmentDetail.submission_deadline_start).format("DD.MM.YYYY") }}
|
||||
{{ formatDueDate(assignmentDetail.submission_deadline.start) }}
|
||||
</span>
|
||||
<template v-if="assignmentDetail.evaluation_deadline_start">
|
||||
<template v-if="assignmentDetail.evaluation_deadline.start">
|
||||
<br />
|
||||
<span v-if="assignmentDetail.evaluation_deadline_start">
|
||||
{{ $t("Freigabetermin Bewertungen:") }}
|
||||
{{ dayjs(assignmentDetail.evaluation_deadline_start).format("DD.MM.YYYY") }}
|
||||
</span>
|
||||
{{ $t("Freigabetermin Bewertungen:") }}
|
||||
{{ formatDueDate(assignmentDetail.evaluation_deadline.start) }}
|
||||
</template>
|
||||
</div>
|
||||
<div v-else>
|
||||
|
|
@ -85,11 +82,11 @@ const assignmentDetail = computed(() =>
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="cockpitStore.courseSessionMembers?.length" class="mt-6">
|
||||
<div v-if="courseSessionDetailResult.filterMembers().length" class="mt-6">
|
||||
<ul>
|
||||
<ItPersonRow
|
||||
v-for="csu in cockpitStore.courseSessionMembers"
|
||||
:key="csu.user_id + csu.session_title"
|
||||
v-for="csu in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
:avatar-url="csu.avatar_url"
|
||||
:data-cy="csu.last_name"
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@
|
|||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||
import type { AttendanceUserStatus } from "@/gql/graphql";
|
||||
import { ATTENDANCE_CHECK_MUTATION } from "@/graphql/mutations";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
import type { DropdownSelectable } from "@/types";
|
||||
import { useMutation } from "@urql/vue";
|
||||
import dayjs from "dayjs";
|
||||
|
|
@ -16,9 +15,9 @@ import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
|
|||
import { graphqlClient } from "@/graphql/client";
|
||||
|
||||
const { t } = useTranslation();
|
||||
const cockpitStore = useCockpitStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const attendanceMutation = useMutation(ATTENDANCE_CHECK_MUTATION);
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const attendanceCourses = computed(() => {
|
||||
return courseSession.value.attendance_courses;
|
||||
|
|
@ -167,8 +166,8 @@ watch(
|
|||
|
||||
<div class="mt-4 flex flex-col bg-white p-6">
|
||||
<div
|
||||
v-for="(csu, index) in cockpitStore.courseSessionMembers"
|
||||
:key="csu.user_id + csu.session_title"
|
||||
v-for="(csu, index) in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
>
|
||||
<ItPersonRow
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.v
|
|||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||
import type { LearningPath } from "@/services/learningPath";
|
||||
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useCurrentCourseSession, useCourseSessionDetailQuery } from "@/composables";
|
||||
import SubmissionsOverview from "@/pages/cockpit/cockpitPage/SubmissionsOverview.vue";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
import { useCompetenceStore } from "@/stores/competence";
|
||||
|
|
@ -24,6 +24,7 @@ const competenceStore = useCompetenceStore();
|
|||
const learningPathStore = useLearningPathStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
function userCountStatusForCircle(userId: string) {
|
||||
if (!cockpitStore.currentCircle) return { FAIL: 0, SUCCESS: 0, UNKNOWN: 0 };
|
||||
|
|
@ -126,12 +127,15 @@ function userCountStatusForCircle(userId: string) {
|
|||
></SubmissionsOverview>
|
||||
<div class="pt-4">
|
||||
<!-- progress -->
|
||||
<div v-if="cockpitStore.courseSessionMembers" class="bg-white p-6">
|
||||
<div
|
||||
v-if="courseSessionDetailResult.filterMembers().length > 0"
|
||||
class="bg-white p-6"
|
||||
>
|
||||
<h1 class="heading-3 mb-5">{{ $t("cockpit.progress") }}</h1>
|
||||
<ul>
|
||||
<ItPersonRow
|
||||
v-for="csu in cockpitStore.courseSessionMembers"
|
||||
:key="csu.user_id + csu.session_title"
|
||||
v-for="csu in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
:avatar-url="csu.avatar_url"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import log from "loglevel";
|
|||
import { computed, onMounted, ref } from "vue";
|
||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||
import { itGet } from "@/fetchHelpers";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSession: CourseSession;
|
||||
|
|
@ -13,12 +13,11 @@ const props = defineProps<{
|
|||
|
||||
log.debug("FeedbackSubmissionProgress created");
|
||||
|
||||
const cockpitStore = useCockpitStore();
|
||||
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
const completeFeedbacks = ref(0);
|
||||
|
||||
const numFeedbacks = computed(() => {
|
||||
return cockpitStore.courseSessionMembers?.length ?? 0;
|
||||
return courseSessionDetailResult.filterMembers().length;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
2
|
||||
<script setup lang="ts">
|
||||
import AssignmentSubmissionProgress from "@/pages/cockpit/cockpitPage/AssignmentSubmissionProgress.vue";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
import { useLearningPathStore } from "@/stores/learningPath";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type {
|
||||
|
|
@ -14,6 +13,7 @@ import { computed } from "vue";
|
|||
import { useTranslation } from "i18next-vue";
|
||||
import FeedbackSubmissionProgress from "@/pages/cockpit/cockpitPage/FeedbackSubmissionProgress.vue";
|
||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
|
||||
interface Submittable {
|
||||
id: number;
|
||||
|
|
@ -33,8 +33,9 @@ const props = defineProps<{
|
|||
log.debug("SubmissionsOverview created", props.courseSession.id);
|
||||
|
||||
const userStore = useUserStore();
|
||||
const cockpitStore = useCockpitStore();
|
||||
const learningPathStore = useLearningPathStore();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const submittables = computed(() => {
|
||||
|
|
@ -128,7 +129,10 @@ const getIconName = (lc: LearningContent) => {
|
|||
|
||||
<template>
|
||||
<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
|
||||
v-for="submittable in submittables"
|
||||
:key="submittable.id"
|
||||
|
|
|
|||
|
|
@ -98,6 +98,11 @@ const router = createRouter({
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/test-composable",
|
||||
component: () => import("../pages/TestCourseSessionComposablePage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/learn",
|
||||
component: () =>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
import { itGet } from "@/fetchHelpers";
|
||||
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 {
|
||||
Assignment,
|
||||
AssignmentCompletion,
|
||||
|
|
@ -35,19 +32,17 @@ export async function loadAssignmentCompletionStatusData(
|
|||
courseSessionId: number,
|
||||
learningContentId: number
|
||||
) {
|
||||
const cockpitStore = useCockpitStore();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const assignmentCompletionData = (await itGet(
|
||||
`/api/assignment/${assignmentId}/${courseSessionId}/status/`
|
||||
)) as UserAssignmentCompletionStatus[];
|
||||
|
||||
const courseSessionUsers = await cockpitStore.loadCourseSessionMembers(
|
||||
courseSessionId
|
||||
);
|
||||
const members = courseSessionDetailResult.filterMembers();
|
||||
|
||||
const gradedUsers: GradedUser[] = [];
|
||||
const assignmentSubmittedUsers: CourseSessionUser[] = [];
|
||||
for (const csu of courseSessionUsers) {
|
||||
for (const csu of members) {
|
||||
const userAssignmentStatus = assignmentCompletionData.find(
|
||||
(s) =>
|
||||
s.assignment_user_id === csu.user_id &&
|
||||
|
|
@ -70,34 +65,10 @@ export async function loadAssignmentCompletionStatusData(
|
|||
return {
|
||||
assignmentSubmittedUsers: assignmentSubmittedUsers,
|
||||
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) {
|
||||
return sum(assignment.evaluation_tasks.map((task) => task.value.max_points));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import log from "loglevel";
|
|||
import { useCircleStore } from "@/stores/circle";
|
||||
import { useLearningPathStore } from "@/stores/learningPath";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { CourseSessionUser, ExpertSessionUser } from "@/types";
|
||||
import log from "loglevel";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export type CockpitStoreState = {
|
||||
|
|
@ -26,13 +28,16 @@ export const useCockpitStore = defineStore({
|
|||
} as CockpitStoreState;
|
||||
},
|
||||
actions: {
|
||||
async loadCircles(courseSlug: string, courseSessionId: number) {
|
||||
async loadCircles(
|
||||
courseSlug: string,
|
||||
currentCourseSessionUser: CourseSessionUser | undefined
|
||||
) {
|
||||
log.debug("loadCircles called", courseSlug, courseSessionId);
|
||||
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 {
|
||||
id: c.slug,
|
||||
name: c.title,
|
||||
|
|
@ -53,18 +58,6 @@ export const useCockpitStore = defineStore({
|
|||
async setCurrentCourseCircleFromEvent(event: { id: string }) {
|
||||
await this.setCurrentCourseCircle(event.id);
|
||||
},
|
||||
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: () => {
|
||||
|
|
@ -81,18 +74,15 @@ export const useCockpitStore = defineStore({
|
|||
},
|
||||
});
|
||||
|
||||
async function courseCircles(courseSlug: string, courseSessionId: number) {
|
||||
async function courseCircles(
|
||||
courseSlug: string,
|
||||
currentCourseSessionUser: CourseSessionUser | undefined
|
||||
) {
|
||||
const userStore = useUserStore();
|
||||
const userId = userStore.id;
|
||||
|
||||
const users = (await itGetCached(`/api/course/sessions/${courseSessionId}/users/`, {
|
||||
reload: false,
|
||||
})) 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;
|
||||
if (currentCourseSessionUser && currentCourseSessionUser.role === "EXPERT") {
|
||||
const expert = currentCourseSessionUser as ExpertSessionUser;
|
||||
return expert.circles;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
import { itGetCached, itPost } from "@/fetchHelpers";
|
||||
import { deleteCircleDocument } from "@/services/files";
|
||||
import type {
|
||||
CircleDocument,
|
||||
CourseSession,
|
||||
CourseSessionAssignment,
|
||||
CourseSessionAttendanceCourse,
|
||||
CourseSessionEdoniqTest,
|
||||
CourseSessionUser,
|
||||
DueDate,
|
||||
ExpertSessionUser,
|
||||
} from "@/types";
|
||||
import type { CircleDocument, CourseSession, DueDate } from "@/types";
|
||||
import eventBus from "@/utils/eventBus";
|
||||
import { useRouteLookups } from "@/utils/route";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
|
|
@ -18,7 +9,6 @@ import uniqBy from "lodash/uniqBy";
|
|||
import log from "loglevel";
|
||||
import { defineStore } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
import { useCircleStore } from "./circle";
|
||||
import { useUserStore } from "./user";
|
||||
|
||||
const SELECTED_COURSE_SESSIONS_KEY = "selectedCourseSessionMap";
|
||||
|
|
@ -39,10 +29,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
// TODO: refactor after implementing of Klassenkonzept
|
||||
await Promise.all(
|
||||
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);
|
||||
})
|
||||
);
|
||||
|
|
@ -161,56 +147,56 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
return Boolean(isCourseExpert && (inLearningPath() || inCompetenceProfile()));
|
||||
});
|
||||
|
||||
const circleExperts = computed(() => {
|
||||
const circleStore = useCircleStore();
|
||||
const circleTranslationKey = circleStore.circle?.translation_key;
|
||||
// const circleExperts = computed(() => {
|
||||
// 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 [];
|
||||
// });
|
||||
|
||||
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();
|
||||
// return (
|
||||
// circleExperts.value.filter((expert) => expert.user_id === userStore.id).length > 0
|
||||
// );
|
||||
// });
|
||||
|
||||
const canUploadCircleDocuments = computed(() => {
|
||||
// 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(courseSession: CourseSession) {
|
||||
const userStore = useUserStore();
|
||||
return (
|
||||
circleExperts.value.filter((expert) => expert.user_id === userStore.id).length > 0
|
||||
);
|
||||
});
|
||||
|
||||
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.course_session_experts.includes(courseSession.id) ||
|
||||
userStore.is_superuser
|
||||
);
|
||||
}
|
||||
|
|
@ -263,35 +249,35 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
);
|
||||
}
|
||||
|
||||
function findAttendanceCourse(
|
||||
contentId: number
|
||||
): CourseSessionAttendanceCourse | undefined {
|
||||
if (currentCourseSession.value) {
|
||||
return currentCourseSession.value.attendance_courses.find(
|
||||
(attendanceCourse) => attendanceCourse.learning_content_id === contentId
|
||||
);
|
||||
}
|
||||
}
|
||||
// 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
|
||||
);
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
uniqueCourseSessionsByCourse,
|
||||
|
|
@ -302,15 +288,9 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
hasCockpit,
|
||||
hasCourseSessionPreview,
|
||||
currentCourseSessionHasCockpit,
|
||||
canUploadCircleDocuments,
|
||||
circleDocuments,
|
||||
circleExperts,
|
||||
addDocument,
|
||||
startUpload,
|
||||
removeDocument,
|
||||
findAttendanceCourse,
|
||||
findCourseSessionAssignment,
|
||||
findCourseSessionEdoniqTest,
|
||||
allDueDates,
|
||||
|
||||
// use `useCurrentCourseSession` whenever possible
|
||||
|
|
|
|||
|
|
@ -479,13 +479,23 @@ export interface CourseSessionAttendanceCourse {
|
|||
}
|
||||
|
||||
export interface CourseSessionAssignment {
|
||||
id: number;
|
||||
course_session_id: number;
|
||||
learning_content_id: number;
|
||||
submission_deadline_id: number;
|
||||
submission_deadline_start: string;
|
||||
evaluation_deadline_id: number;
|
||||
evaluation_deadline_start: string;
|
||||
id: string;
|
||||
submission_deadline: {
|
||||
id: string;
|
||||
start: string;
|
||||
};
|
||||
evaluation_deadline: {
|
||||
id: string;
|
||||
start: string;
|
||||
};
|
||||
learning_content: {
|
||||
id: string;
|
||||
content_assignment: {
|
||||
id: string;
|
||||
title: string;
|
||||
assignment_type: AssignmentType;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface CourseSessionEdoniqTest {
|
||||
|
|
@ -504,29 +514,34 @@ export interface CourseSession {
|
|||
title: string;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
learning_path_url: string;
|
||||
cockpit_url: string;
|
||||
competence_url: 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[];
|
||||
// media_library_url: string;
|
||||
// attendance_courses: CourseSessionAttendanceCourse[];
|
||||
// assignments: CourseSessionAssignment[];
|
||||
// edoniq_tests: CourseSessionEdoniqTest[];
|
||||
// documents: CircleDocument[];
|
||||
// users: CourseSessionUser[];
|
||||
due_dates: DueDate[];
|
||||
}
|
||||
|
||||
export type Role = "MEMBER" | "EXPERT" | "TUTOR";
|
||||
|
||||
export interface CourseSessionUser {
|
||||
session_title: string;
|
||||
user_id: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
avatar_url: string;
|
||||
role: Role;
|
||||
circles: {
|
||||
id: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
translation_key: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface ExpertSessionUser extends CourseSessionUser {
|
||||
|
|
@ -539,6 +554,17 @@ export interface ExpertSessionUser extends CourseSessionUser {
|
|||
}[];
|
||||
}
|
||||
|
||||
export interface CourseSessionDetail {
|
||||
id: string;
|
||||
title: string;
|
||||
course: {
|
||||
id: string;
|
||||
title: string;
|
||||
};
|
||||
assignments: CourseSessionAssignment[];
|
||||
users: CourseSessionUser[];
|
||||
}
|
||||
|
||||
// document upload
|
||||
export interface DocumentUploadData {
|
||||
file: File | null;
|
||||
|
|
|
|||
|
|
@ -5,12 +5,30 @@ export function assertUnreachable(msg: string): never {
|
|||
throw new Error("Didn't expect to get here, " + msg);
|
||||
}
|
||||
|
||||
export function getCompetenceBaseUrl(courseSession: CourseSession): string {
|
||||
return courseSession.competence_url.replace(
|
||||
// TODO: remove the `competence_url` with url to Navi...
|
||||
"/competences",
|
||||
""
|
||||
);
|
||||
function createCourseUrl(
|
||||
courseSession: CourseSession | undefined,
|
||||
specificSub: string
|
||||
): string {
|
||||
if (!courseSession) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (["learn", "media", "competence"].includes(specificSub)) {
|
||||
return `${courseSession.course_url}/${specificSub}`;
|
||||
}
|
||||
return courseSession.course_url;
|
||||
}
|
||||
|
||||
export function getCompetenceNaviUrl(courseSession: CourseSession | undefined): string {
|
||||
return createCourseUrl(courseSession, "competence");
|
||||
}
|
||||
|
||||
export function getMediaCenterUrl(courseSession: CourseSession | undefined): string {
|
||||
return createCourseUrl(courseSession, "media");
|
||||
}
|
||||
|
||||
export function getLearningPathUrl(courseSession: CourseSession | undefined): string {
|
||||
return createCourseUrl(courseSession, "learn");
|
||||
}
|
||||
|
||||
export function getAssignmentTypeTitle(assignmentType: AssignmentType): string {
|
||||
|
|
|
|||
|
|
@ -110,9 +110,9 @@ urlpatterns = [
|
|||
|
||||
# course
|
||||
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
||||
path(r"api/course/sessions/<signed_int:course_session_id>/users/",
|
||||
get_course_session_users,
|
||||
name="get_course_session_users"),
|
||||
# path(r"api/course/sessions/<signed_int:course_session_id>/users/",
|
||||
# get_course_session_users,
|
||||
# name="get_course_session_users"),
|
||||
path(r"api/course/page/<slug_or_id>/", course_page_api_view,
|
||||
name="course_page_api_view"),
|
||||
path(r"api/course/completion/mark/", mark_course_completion_view,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,22 @@
|
|||
import graphene
|
||||
|
||||
from vbv_lernwelt.course.graphql.types import CourseObjectType
|
||||
from vbv_lernwelt.course.models import Course
|
||||
from vbv_lernwelt.course.graphql.types import CourseObjectType, CourseSessionObjectType
|
||||
from vbv_lernwelt.course.models import Course, CourseSession
|
||||
from vbv_lernwelt.course.permissions import has_course_access
|
||||
|
||||
|
||||
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):
|
||||
course = Course.objects.get(pk=id)
|
||||
if has_course_access(info.context.user, course):
|
||||
return 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,24 @@ from typing import Type
|
|||
|
||||
import graphene
|
||||
import structlog
|
||||
from graphene import ObjectType
|
||||
from graphene_django import DjangoObjectType
|
||||
from graphql import GraphQLError
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from vbv_lernwelt.course.models import Course, CourseBasePage, CoursePage
|
||||
from vbv_lernwelt.course.models import Course, CourseBasePage, CoursePage, \
|
||||
CourseSession, CourseSessionUser
|
||||
from vbv_lernwelt.course.permissions import has_course_access
|
||||
from vbv_lernwelt.course_session.graphql.types import (
|
||||
CourseSessionAttendanceCourseObjectType,
|
||||
CourseSessionAssignmentObjectType,
|
||||
CourseSessionEdoniqTestObjectType,
|
||||
)
|
||||
from vbv_lernwelt.course_session.models import (
|
||||
CourseSessionAttendanceCourse,
|
||||
CourseSessionAssignment,
|
||||
CourseSessionEdoniqTest,
|
||||
)
|
||||
from vbv_lernwelt.learnpath.graphql.types import LearningPathObjectType
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
|
@ -74,3 +86,89 @@ class CourseObjectType(DjangoObjectType):
|
|||
|
||||
def resolve_learning_path(self, info):
|
||||
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 CourseSessionObjectType(DjangoObjectType):
|
||||
attendance_courses = graphene.List(CourseSessionAttendanceCourseObjectType)
|
||||
assignments = graphene.List(CourseSessionAssignmentObjectType)
|
||||
edoniq_tests = graphene.List(CourseSessionEdoniqTestObjectType)
|
||||
users = graphene.List(CourseSessionUserObjectsType)
|
||||
|
||||
class Meta:
|
||||
model = CourseSession
|
||||
fields = (
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"course",
|
||||
"title",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"attendance_courses",
|
||||
"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_test(self, info):
|
||||
return CourseSessionEdoniqTest.objects.filter(course_session=self)
|
||||
|
||||
def resolve_users(self, info):
|
||||
return CourseSessionUser.objects.filter(
|
||||
course_session_id=self.id
|
||||
).distinct()
|
||||
|
|
|
|||
|
|
@ -56,14 +56,15 @@ class CourseCompletionSerializer(serializers.ModelSerializer):
|
|||
class CourseSessionSerializer(serializers.ModelSerializer):
|
||||
course = 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()
|
||||
# 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()
|
||||
|
||||
def get_course(self, obj):
|
||||
|
|
@ -119,16 +120,16 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
|||
"title",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"additional_json_data",
|
||||
"attendance_courses",
|
||||
"assignments",
|
||||
"edoniq_tests",
|
||||
"learning_path_url",
|
||||
"cockpit_url",
|
||||
"competence_url",
|
||||
"media_library_url",
|
||||
# "additional_json_data",
|
||||
# "attendance_courses",
|
||||
# "assignments",
|
||||
# "edoniq_tests",
|
||||
# "learning_path_url",
|
||||
# "cockpit_url",
|
||||
# "competence_url",
|
||||
# "media_library_url",
|
||||
"course_url",
|
||||
"documents",
|
||||
# "documents",
|
||||
"due_dates",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from rest_framework.response import Response
|
|||
from wagtail.models import Page
|
||||
|
||||
from vbv_lernwelt.core.utils import get_django_content_type
|
||||
|
||||
from vbv_lernwelt.course.models import (
|
||||
CircleDocument,
|
||||
CourseCompletion,
|
||||
|
|
@ -135,7 +134,9 @@ def mark_course_completion_view(request):
|
|||
@api_view(["GET"])
|
||||
def get_course_sessions(request):
|
||||
try:
|
||||
course_sessions = course_sessions_for_user_qs(request.user)
|
||||
course_sessions = course_sessions_for_user_qs(request.user).prefetch_related(
|
||||
"course"
|
||||
)
|
||||
return Response(
|
||||
status=200, data=CourseSessionSerializer(course_sessions, many=True).data
|
||||
)
|
||||
|
|
@ -149,7 +150,9 @@ def get_course_sessions(request):
|
|||
@api_view(["GET"])
|
||||
def get_course_session_users(request, course_session_id):
|
||||
try:
|
||||
qs = CourseSessionUser.objects.filter(course_session_id=course_session_id)
|
||||
qs = CourseSessionUser.objects.filter(
|
||||
course_session_id=course_session_id
|
||||
).distinct()
|
||||
|
||||
user_data = [csu.to_dict() for csu in qs]
|
||||
return Response(status=200, data=user_data)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ import structlog
|
|||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
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.services.attendance import (
|
||||
AttendanceUserStatus,
|
||||
|
|
@ -21,7 +23,9 @@ class AttendanceUserInputType(graphene.InputObjectType):
|
|||
|
||||
|
||||
class AttendanceCourseUserMutation(graphene.Mutation):
|
||||
course_session_attendance_course = graphene.Field(CourseSessionAttendanceCourseType)
|
||||
course_session_attendance_course = graphene.Field(
|
||||
CourseSessionAttendanceCourseObjectType
|
||||
)
|
||||
|
||||
class Input:
|
||||
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.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
|
||||
|
||||
|
||||
class CourseSessionQuery(object):
|
||||
course_session_attendance_course = graphene.Field(
|
||||
CourseSessionAttendanceCourseType,
|
||||
CourseSessionAttendanceCourseObjectType,
|
||||
id=graphene.ID(required=True),
|
||||
assignment_user_id=graphene.ID(required=False),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
import graphene
|
||||
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 (
|
||||
CourseSessionAttendanceCourse,
|
||||
CourseSessionAssignment,
|
||||
)
|
||||
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)
|
||||
status = graphene.Field(
|
||||
graphene.Enum.from_enum(AttendanceUserStatus), required=True
|
||||
|
|
@ -15,15 +25,14 @@ class AttendanceUserType(graphene.ObjectType):
|
|||
email = graphene.String()
|
||||
|
||||
|
||||
class CourseSessionAttendanceCourseType(DjangoObjectType):
|
||||
class CourseSessionAttendanceCourseObjectType(DjangoObjectType):
|
||||
course_session_id = graphene.ID(source="course_session_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(
|
||||
AttendanceUserType, source="attendance_user_list"
|
||||
AttendanceUserObjectType, source="attendance_user_list"
|
||||
)
|
||||
due_date = graphene.Field(DueDateObjectType)
|
||||
learning_content = graphene.Field(LearningContentAttendanceCourseObjectType)
|
||||
|
||||
class Meta:
|
||||
model = CourseSessionAttendanceCourse
|
||||
|
|
@ -31,19 +40,49 @@ class CourseSessionAttendanceCourseType(DjangoObjectType):
|
|||
"id",
|
||||
"course_session_id",
|
||||
"learning_content_id",
|
||||
"due_date_id",
|
||||
"learning_content",
|
||||
"location",
|
||||
"trainer",
|
||||
"start",
|
||||
"end",
|
||||
"due_date",
|
||||
)
|
||||
|
||||
def resolve_start(self, info):
|
||||
if self.due_date is None:
|
||||
return None
|
||||
return self.due_date.start
|
||||
def resolve_attendance_user_list(self, info):
|
||||
if is_course_session_expert(info.context.user, self.course_session_id):
|
||||
return self.attendance_user_list
|
||||
return []
|
||||
|
||||
def resolve_end(self, info):
|
||||
if self.due_date is None:
|
||||
return None
|
||||
return self.due_date.end
|
||||
|
||||
class CourseSessionAssignmentObjectType(DjangoObjectType):
|
||||
course_session_id = graphene.ID(source="course_session_id")
|
||||
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_id",
|
||||
"submission_deadline",
|
||||
"evaluation_deadline",
|
||||
"learning_content",
|
||||
)
|
||||
|
||||
|
||||
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 = CourseSessionAssignment
|
||||
fields = (
|
||||
"id",
|
||||
"course_session_id",
|
||||
"learning_content_id",
|
||||
"deadline",
|
||||
"learning_content",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
@ -4,7 +4,7 @@ from vbv_lernwelt.duedate.models import DueDate
|
|||
|
||||
|
||||
class DueDateSerializer(serializers.ModelSerializer):
|
||||
circle = serializers.SerializerMethodField()
|
||||
# circle = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = DueDate
|
||||
|
|
@ -20,7 +20,7 @@ class DueDateSerializer(serializers.ModelSerializer):
|
|||
"url_expert",
|
||||
"course_session",
|
||||
"page",
|
||||
"circle",
|
||||
# "circle",
|
||||
]
|
||||
|
||||
def get_circle(self, obj):
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from vbv_lernwelt.learnpath.graphql.types import (
|
|||
LearningContentMediaLibraryObjectType,
|
||||
LearningContentPlaceholderObjectType,
|
||||
LearningContentRichTextObjectType,
|
||||
LearningContentTestObjectType,
|
||||
LearningContentEdoniqTestObjectType,
|
||||
LearningContentVideoObjectType,
|
||||
LearningPathObjectType,
|
||||
)
|
||||
|
|
@ -58,7 +58,7 @@ class LearningPathQuery:
|
|||
)
|
||||
learning_content_placeholder = graphene.Field(LearningContentPlaceholderObjectType)
|
||||
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_document_list = graphene.Field(
|
||||
LearningContentDocumentListObjectType
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class LearningContentInterface(CoursePageInterface):
|
|||
elif isinstance(instance, LearningContentRichText):
|
||||
return LearningContentRichTextObjectType
|
||||
elif isinstance(instance, LearningContentEdoniqTest):
|
||||
return LearningContentTestObjectType
|
||||
return LearningContentEdoniqTestObjectType
|
||||
elif isinstance(instance, LearningContentVideo):
|
||||
return LearningContentVideoObjectType
|
||||
elif isinstance(instance, LearningContentDocumentList):
|
||||
|
|
@ -102,7 +102,7 @@ class LearningContentMediaLibraryObjectType(DjangoObjectType):
|
|||
fields = []
|
||||
|
||||
|
||||
class LearningContentTestObjectType(DjangoObjectType):
|
||||
class LearningContentEdoniqTestObjectType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = LearningContentEdoniqTest
|
||||
interfaces = (LearningContentInterface,)
|
||||
|
|
|
|||
Loading…
Reference in New Issue