Show Diagram for users in cockpit

This commit is contained in:
Daniel Egger 2023-10-12 21:37:57 +02:00
parent 61dfdfda9d
commit a6cf4ad128
24 changed files with 184 additions and 264 deletions

View File

@ -2,29 +2,37 @@
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
import { computed } from "vue";
import type { LearningPathType } from "@/types";
import { flatCircles } from "@/composables";
import { useLearningPathWithCompletion } from "@/composables";
export type DiagramType = "horizontal" | "horizontalSmall" | "singleSmall";
export interface Props {
diagramType?: DiagramType;
learningPath: LearningPathType;
showCircleSlugs?: string[];
courseSlug: string;
courseSessionId: string;
userId?: string;
diagramType?: DiagramType;
}
const props = withDefaults(defineProps<Props>(), {
diagramType: "horizontal",
showCircleSlugs: undefined,
userId: undefined,
});
const lpQueryResult = useLearningPathWithCompletion(
props.courseSlug,
props.userId,
props.courseSessionId
);
const circles = computed(() => {
if (props.showCircleSlugs?.length) {
return flatCircles(props.learningPath).filter(
return (lpQueryResult.circles.value ?? []).filter(
(c) => props.showCircleSlugs?.includes(c.slug) ?? true
);
}
return flatCircles(props.learningPath);
return lpQueryResult.circles.value ?? [];
});
const wrapperClasses = computed(() => {

View File

@ -1,27 +0,0 @@
<script setup lang="ts">
import * as log from "loglevel";
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
import { useLearningPathWithCompletion } from "@/composables";
log.debug("LearningPathDiagramSmall created");
const props = defineProps<{
courseSlug: string;
courseSessionId: string;
}>();
const lpQueryResult = useLearningPathWithCompletion(
props.courseSlug,
props.courseSessionId
);
</script>
<template>
<LearningPathDiagram
v-if="lpQueryResult.learningPath.value"
:learning-path="lpQueryResult.learningPath.value"
diagram-type="horizontalSmall"
></LearningPathDiagram>
</template>
<style scoped></style>

View File

@ -148,6 +148,18 @@ export function useLearningPath(courseSlug: string) {
resultPromise.then((result) => {
learningPath.value = result.data?.learning_path as LearningPathType;
// attach circle information to learning contents
if (learningPath.value) {
flatCircles(learningPath.value).forEach((circle) => {
circleFlatChildren(circle).forEach((lc) => {
lc.circle = {
id: circle.id,
slug: circle.slug,
title: circle.title,
};
});
});
}
});
const circles = computed(() => {
@ -163,13 +175,23 @@ export function useLearningPath(courseSlug: string) {
});
}
return { resultPromise, learningPath, circles, findCircle };
function findLearningContent(learningContentId: string) {
return (circles.value ?? [])
.flatMap((c) => {
return circleFlatLearningContents(c);
})
.find((lc) => {
return lc.id === learningContentId;
});
}
return { resultPromise, learningPath, circles, findCircle, findLearningContent };
}
export function useLearningPathWithCompletion(
courseSlug?: string,
courseSessionId?: string,
userId?: string
userId?: string,
courseSessionId?: string
) {
if (!courseSlug) {
courseSlug = useCurrentCourseSession().value.course.slug;

View File

@ -18,7 +18,7 @@ const documents = {
"\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 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 task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
"\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n ...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 ...CoursePageFields\n circle {\n id\n title\n slug\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 query learningPathQuery($slug: String!) {\n learning_path(slug: $slug) {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n competence_certificate {\n ...CoursePageFields\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.LearningPathQueryDocument,
"\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,
@ -61,7 +61,7 @@ export function graphql(source: "\n query assignmentCompletionQuery(\n $assi
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n ...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 ...CoursePageFields\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 ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,5 @@
type Query {
learning_path(id: ID, slug: String, course_id: ID, course_slug: String): LearningPathObjectType
circle(id: ID, slug: String): CircleObjectType
learning_content_media_library: LearningContentMediaLibraryObjectType
learning_content_assignment: LearningContentAssignmentObjectType
learning_content_attendance_course: LearningContentAttendanceCourseObjectType
@ -28,7 +27,6 @@ type LearningPathObjectType implements CoursePageInterface {
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType
course: CourseObjectType
topics: [TopicObjectType!]!
}
@ -41,10 +39,29 @@ interface CoursePageInterface {
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType
course: CourseObjectType
}
type CourseObjectType {
id: ID!
title: String!
category_name: String!
slug: String!
}
type TopicObjectType implements CoursePageInterface {
is_visible: Boolean!
id: ID!
title: String!
slug: String!
content_type: String!
live: Boolean!
translation_key: String!
frontend_url: String!
course: CourseObjectType
circles: [CircleObjectType!]!
}
type CircleObjectType implements CoursePageInterface {
description: String!
goals: String!
@ -55,18 +72,10 @@ type CircleObjectType implements CoursePageInterface {
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType
course: CourseObjectType
learning_sequences: [LearningSequenceObjectType!]!
}
type CourseObjectType {
id: ID!
title: String!
category_name: String!
slug: String!
}
type LearningSequenceObjectType implements CoursePageInterface {
icon: String!
id: ID!
@ -76,7 +85,6 @@ type LearningSequenceObjectType implements CoursePageInterface {
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType
course: CourseObjectType
learning_units: [LearningUnitObjectType!]!
}
@ -90,7 +98,6 @@ type LearningUnitObjectType implements CoursePageInterface {
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType
course: CourseObjectType
learning_contents: [LearningContentInterface!]!
performance_criteria: [PerformanceCriteriaObjectType!]!
@ -105,12 +112,18 @@ interface LearningContentInterface {
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
}
type CircleLightObjectType {
id: ID!
title: String!
slug: String!
}
type PerformanceCriteriaObjectType implements CoursePageInterface {
@ -122,24 +135,9 @@ type PerformanceCriteriaObjectType implements CoursePageInterface {
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType
course: CourseObjectType
}
type TopicObjectType implements CoursePageInterface {
is_visible: Boolean!
id: ID!
title: String!
slug: String!
content_type: String!
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType
course: CourseObjectType
circles: [CircleObjectType!]!
}
type LearningContentMediaLibraryObjectType implements CoursePageInterface & LearningContentInterface {
id: ID!
title: String!
@ -148,12 +146,12 @@ type LearningContentMediaLibraryObjectType implements CoursePageInterface & Lear
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
}
type LearningContentAssignmentObjectType implements CoursePageInterface & LearningContentInterface {
@ -166,12 +164,12 @@ type LearningContentAssignmentObjectType implements CoursePageInterface & Learni
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
competence_certificate: CompetenceCertificateObjectType
}
@ -202,7 +200,6 @@ type AssignmentObjectType implements CoursePageInterface {
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType
course: CourseObjectType
tasks: JSONStreamField
evaluation_tasks: JSONStreamField
@ -238,7 +235,6 @@ type CompetenceCertificateObjectType implements CoursePageInterface {
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType
course: CourseObjectType
assignments: [AssignmentObjectType!]!
}
@ -344,12 +340,12 @@ type LearningContentAttendanceCourseObjectType implements CoursePageInterface &
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
}
type DueDateObjectType {
@ -426,12 +422,12 @@ type LearningContentEdoniqTestObjectType implements CoursePageInterface & Learni
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
competence_certificate: CompetenceCertificateObjectType
}
@ -520,12 +516,12 @@ type LearningContentFeedbackObjectType implements CoursePageInterface & Learning
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
}
type LearningContentLearningModuleObjectType implements CoursePageInterface & LearningContentInterface {
@ -536,12 +532,12 @@ type LearningContentLearningModuleObjectType implements CoursePageInterface & Le
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
}
type LearningContentPlaceholderObjectType implements CoursePageInterface & LearningContentInterface {
@ -552,12 +548,12 @@ type LearningContentPlaceholderObjectType implements CoursePageInterface & Learn
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
}
type LearningContentRichTextObjectType implements CoursePageInterface & LearningContentInterface {
@ -568,12 +564,12 @@ type LearningContentRichTextObjectType implements CoursePageInterface & Learning
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
}
type LearningContentVideoObjectType implements CoursePageInterface & LearningContentInterface {
@ -584,12 +580,12 @@ type LearningContentVideoObjectType implements CoursePageInterface & LearningCon
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
}
type LearningContentDocumentListObjectType implements CoursePageInterface & LearningContentInterface {
@ -600,12 +596,12 @@ type LearningContentDocumentListObjectType implements CoursePageInterface & Lear
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType!
course: CourseObjectType
minutes: Int
description: String!
content_url: String!
can_user_self_toggle_course_completion: Boolean!
circle: CircleLightObjectType
}
type CompetenceCertificateListObjectType implements CoursePageInterface {
@ -616,7 +612,6 @@ type CompetenceCertificateListObjectType implements CoursePageInterface {
live: Boolean!
translation_key: String!
frontend_url: String!
circle: CircleObjectType
course: CourseObjectType
competence_certificates: [CompetenceCertificateObjectType!]!
}

View File

@ -9,6 +9,7 @@ export const AttendanceUserInputType = "AttendanceUserInputType";
export const AttendanceUserObjectType = "AttendanceUserObjectType";
export const AttendanceUserStatus = "AttendanceUserStatus";
export const Boolean = "Boolean";
export const CircleLightObjectType = "CircleLightObjectType";
export const CircleObjectType = "CircleObjectType";
export const CompetenceCertificateListObjectType = "CompetenceCertificateListObjectType";
export const CompetenceCertificateObjectType = "CompetenceCertificateObjectType";

View File

@ -97,7 +97,9 @@ export const COMPETENCE_NAVI_CERTIFICATE_QUERY = graphql(`
learning_content {
...CoursePageFields
circle {
...CoursePageFields
id
title
slug
}
}
}

View File

@ -1,17 +1,16 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from "vue";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useLearningPathStore } from "@/stores/learningPath";
import { useTranslation } from "i18next-vue";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import type { DueDate } from "@/types";
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
import { useLearningPath } from "@/composables";
const { t } = useTranslation();
const UNFILTERED = Number.MAX_SAFE_INTEGER.toString();
const courseSessionsStore = useCourseSessionsStore();
const learningPathStore = useLearningPathStore();
type Item = {
id: string;
@ -61,16 +60,15 @@ const circles = ref<Item[]>([initialItemCircle]);
const selectedCircle = ref<Item>(circles.value[0]);
async function loadCircleValues() {
const data = await learningPathStore.loadLearningPath(
`${selectedCourse.value.slug}-lp`,
undefined,
false,
false
);
if (data) {
if (selectedCourse.value) {
const learningPathQuery = useLearningPath(selectedCourse.value.slug);
await learningPathQuery.resultPromise;
circles.value = [
initialItemCircle,
...data.circles.map((circle) => ({ id: circle.id, name: circle.title })),
...(learningPathQuery.circles.value ?? []).map((circle) => ({
id: circle.id,
name: circle.title,
})),
];
} else {
circles.value = [initialItemCircle];

View File

@ -1,12 +1,12 @@
<script setup lang="ts">
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
import LearningPathDiagramSmall from "@/components/learningPath/LearningPathDiagramSmall.vue";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useUserStore } from "@/stores/user";
import type { CourseSession } from "@/types";
import log from "loglevel";
import { computed, onMounted } from "vue";
import { getCockpitUrl, getLearningPathUrl } from "@/utils/utils";
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
log.debug("DashboardPage created");
@ -47,11 +47,12 @@ const getNextStepLink = (courseSession: CourseSession) => {
<div class="bg-white p-6 md:h-full">
<h3 class="mb-4">{{ courseSession.course.title }}</h3>
<div>
<LearningPathDiagramSmall
<LearningPathDiagram
class="mb-4"
:course-slug="courseSession.course.slug"
:course-session-id="courseSession.id"
></LearningPathDiagramSmall>
diagram-type="horizontalSmall"
></LearningPathDiagram>
</div>
<div>
<router-link

View File

@ -2,8 +2,6 @@
import { useCourseSessionDetailQuery } from "@/composables";
import { useCockpitStore } from "@/stores/cockpit";
import { useCompetenceStore } from "@/stores/competence";
import { useLearningPathStore } from "@/stores/learningPath";
import { useUserStore } from "@/stores/user";
import * as log from "loglevel";
import { onMounted } from "vue";
@ -15,8 +13,6 @@ const props = defineProps<{
const cockpitStore = useCockpitStore();
const competenceStore = useCompetenceStore();
const learningPathStore = useLearningPathStore();
const courseSessionDetailResult = useCourseSessionDetailQuery();
onMounted(async () => {
@ -30,13 +26,7 @@ onMounted(async () => {
props.courseSlug + "-competencenavi-competences",
csu.user_id
);
learningPathStore.loadLearningPath(props.courseSlug + "-lp", csu.user_id);
});
await learningPathStore.loadLearningPath(
props.courseSlug + "-lp",
useUserStore().id
);
await cockpitStore.loadCircles(
props.courseSlug,
courseSessionDetailResult.findCurrentUser()

View File

@ -1,12 +1,14 @@
<script setup lang="ts">
import { useCompetenceStore } from "@/stores/competence";
import { useLearningPathStore } from "@/stores/learningPath";
import * as log from "loglevel";
import { computed, onMounted } from "vue";
import CompetenceDetail from "@/pages/competence/ActionCompetenceDetail.vue";
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
import { useCourseSessionDetailQuery } from "@/composables";
import {
useCourseSessionDetailQuery,
useLearningPathWithCompletion,
} from "@/composables";
const props = defineProps<{
userId: string;
@ -16,14 +18,15 @@ const props = defineProps<{
log.debug("CockpitUserProfilePage created", props.userId);
const competenceStore = useCompetenceStore();
const learningPathStore = useLearningPathStore();
onMounted(async () => {
log.debug("CockpitUserProfilePage mounted");
});
const lpQueryResult = useLearningPathWithCompletion(props.courseSlug, props.userId);
const learningPath = computed(() => {
return learningPathStore.learningPathForUser(props.courseSlug, props.userId);
return lpQueryResult.learningPath.value;
});
const { findUser } = useCourseSessionDetailQuery();

View File

@ -13,16 +13,14 @@ import { computed, onMounted, reactive } from "vue";
import AssignmentSubmissionProgress from "@/pages/cockpit/cockpitPage/AssignmentSubmissionProgress.vue";
import { useCourseSessionDetailQuery } from "@/composables";
import { formatDueDate } from "../../../components/dueDates/dueDatesUtils";
import { stringifyParse } from "@/utils/utils";
const props = defineProps<{
courseSession: CourseSession;
learningContentAssignment: LearningContentAssignment;
}>();
log.debug(
"AssignmentDetails created",
props.learningContentAssignment.content_assignment_id
);
log.debug("AssignmentDetails created", stringifyParse(props));
const courseSessionDetailResult = useCourseSessionDetailQuery();
@ -39,7 +37,7 @@ const assignmentDetail = computed(() => {
onMounted(async () => {
const { gradedUsers, assignmentSubmittedUsers } =
await loadAssignmentCompletionStatusData(
props.learningContentAssignment.content_assignment_id,
props.learningContentAssignment.content_assignment.id,
props.courseSession.id,
props.learningContentAssignment.id
);
@ -54,17 +52,17 @@ onMounted(async () => {
{{ learningContentAssignment.title }}
</h2>
<div class="pt-1 underline">
{{ $t("a.Circle") }} «{{ learningContentAssignment.parentCircle.title }}»
{{ $t("a.Circle") }} «{{ learningContentAssignment.circle?.title }}»
</div>
<div v-if="assignmentDetail">
<span v-if="assignmentDetail.submission_deadline?.start">
{{ $t("Abgabetermin Ergebnisse:") }}
{{ formatDueDate(assignmentDetail.submission_deadline.start) }}
{{ formatDueDate(assignmentDetail.submission_deadline?.start) }}
</span>
<template v-if="assignmentDetail.evaluation_deadline?.start">
<br />
{{ $t("Freigabetermin Bewertungen:") }}
{{ formatDueDate(assignmentDetail.evaluation_deadline.start) }}
{{ formatDueDate(assignmentDetail.evaluation_deadline?.start) }}
</template>
</div>
<div v-else>
@ -131,7 +129,7 @@ onMounted(async () => {
<template #link>
<router-link
v-if="state.assignmentSubmittedUsers.includes(csu)"
:to="`/course/${props.courseSession.course.slug}/cockpit/assignment/${learningContentAssignment.content_assignment_id}/${csu.user_id}`"
:to="`/course/${props.courseSession.course.slug}/cockpit/assignment/${learningContentAssignment.content_assignment.id}/${csu.user_id}`"
class="link lg:w-full lg:text-right"
data-cy="show-results"
>

View File

@ -1,11 +1,9 @@
<script setup lang="ts">
import { useCurrentCourseSession } from "@/composables";
import { useCurrentCourseSession, useLearningPath } from "@/composables";
import AssignmentDetails from "@/pages/cockpit/assignmentsPage/AssignmentDetails.vue";
import * as log from "loglevel";
import { computed, onMounted } from "vue";
import { useUserStore } from "@/stores/user";
import { useLearningPathStore } from "@/stores/learningPath";
import { calcLearningContentAssignments } from "@/services/assignmentService";
import type { LearningContentAssignment } from "@/types";
const props = defineProps<{
courseSlug: string;
@ -15,17 +13,15 @@ const props = defineProps<{
log.debug("AssignmentsPage created", props.courseSlug);
const courseSession = useCurrentCourseSession();
const userStore = useUserStore();
const learningPathStore = useLearningPathStore();
onMounted(async () => {
log.debug("AssignmentsPage mounted");
});
const lpQueryResult = useLearningPath(props.courseSlug);
const learningContentAssignment = computed(() => {
return calcLearningContentAssignments(
learningPathStore.learningPathForUser(courseSession.value.course.slug, userStore.id)
).filter((lc) => lc.id === props.assignmentId)[0];
return lpQueryResult.findLearningContent(props.assignmentId);
});
</script>
@ -46,7 +42,7 @@ const learningContentAssignment = computed(() => {
<AssignmentDetails
v-if="learningContentAssignment"
:course-session="courseSession"
:learning-content-assignment="learningContentAssignment"
:learning-content-assignment="learningContentAssignment as LearningContentAssignment"
/>
</div>
</main>

View File

@ -32,7 +32,7 @@ const presenceCoursesDropdownOptions = computed(() => {
({
id: attendanceCourse.id,
name: `${t("Präsenzkurs")} ${
attendanceCourse.learning_content.circle.title
attendanceCourse.learning_content.circle?.title
} ${dayjs(attendanceCourse.due_date?.start).format("DD.MM.YYYY")}`,
} as DropdownSelectable)
);

View File

@ -9,6 +9,7 @@ import type {
} from "@/types";
import log from "loglevel";
import { onMounted, reactive } from "vue";
import { stringifyParse } from "@/utils/utils";
const props = defineProps<{
courseSession: CourseSession;
@ -16,10 +17,7 @@ const props = defineProps<{
showTitle: boolean;
}>();
log.debug(
"AssignmentSubmissionProgress created",
props.learningContentAssignment.content_assignment_id
);
log.debug("AssignmentSubmissionProgress created", stringifyParse(props));
const state = reactive({
statusByUser: [] as {
@ -34,7 +32,7 @@ const state = reactive({
onMounted(async () => {
const { assignmentSubmittedUsers, gradedUsers, total } =
await loadAssignmentCompletionStatusData(
props.learningContentAssignment.content_assignment_id,
props.learningContentAssignment.content_assignment.id,
props.courseSession.id,
props.learningContentAssignment.id
);

View File

@ -1,13 +1,11 @@
<script setup lang="ts">
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
import type { LearningPath } from "@/services/learningPath";
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
import SubmissionsOverview from "@/pages/cockpit/cockpitPage/SubmissionsOverview.vue";
import { useCockpitStore } from "@/stores/cockpit";
import { useCompetenceStore } from "@/stores/competence";
import { useLearningPathStore } from "@/stores/learningPath";
import log from "loglevel";
import CockpitDates from "@/pages/cockpit/cockpitPage/CockpitDates.vue";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
@ -20,7 +18,6 @@ log.debug("CockpitIndexPage created", props.courseSlug);
const cockpitStore = useCockpitStore();
const competenceStore = useCompetenceStore();
const learningPathStore = useLearningPathStore();
const courseSession = useCurrentCourseSession();
const courseSessionDetailResult = useCourseSessionDetailQuery();
@ -143,18 +140,9 @@ function userCountStatusForCircle(userId: string) {
class="mt-2 flex w-full flex-col items-center justify-between lg:mt-0 lg:flex-row"
>
<LearningPathDiagram
v-if="
learningPathStore.learningPathForUser(
props.courseSlug,
csu.user_id
)
"
:learning-path="
learningPathStore.learningPathForUser(
props.courseSlug,
csu.user_id
) as LearningPath
"
:course-session-id="courseSession.id"
:course-slug="props.courseSlug"
:user-id="csu.user_id"
:show-circle-slugs="[cockpitStore.currentCircle.slug]"
diagram-type="singleSmall"
class="mr-4"

View File

@ -1,8 +1,6 @@
2
<script setup lang="ts">
import AssignmentSubmissionProgress from "@/pages/cockpit/cockpitPage/AssignmentSubmissionProgress.vue";
import { useLearningPathStore } from "@/stores/learningPath";
import { useUserStore } from "@/stores/user";
import type {
CourseSession,
LearningContent,
@ -13,11 +11,16 @@ 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";
import {
useCourseSessionDetailQuery,
useLearningPathWithCompletion,
} from "@/composables";
import { circleFlatLearningContents } from "@/services/circle";
interface Submittable {
id: string;
circleName: string;
circleId: string;
frontendUrl: string;
title: string;
showDetailsText: string;
@ -32,24 +35,20 @@ const props = defineProps<{
log.debug("SubmissionsOverview created", props.courseSession.id);
const userStore = useUserStore();
const learningPathStore = useLearningPathStore();
const courseSessionDetailResult = useCourseSessionDetailQuery();
const { t } = useTranslation();
const lpQueryResult = useLearningPathWithCompletion();
const submittables = computed(() => {
const learningPath = learningPathStore.learningPathForUser(
props.courseSession.course.slug,
userStore.id
);
if (!learningPath) {
if (!lpQueryResult.circles.value?.length) {
return [];
}
return learningPath.circles
return lpQueryResult.circles.value
.filter((circle) => props.selectedCircle == circle.id)
.flatMap((circle) => {
const learningContents = circle.flatLearningContents.filter(
const learningContents = circleFlatLearningContents(circle).filter(
(lc) =>
lc.content_type === "learnpath.LearningContentAssignment" ||
lc.content_type === "learnpath.LearningContentFeedback"
@ -59,10 +58,11 @@ const submittables = computed(() => {
return {
id: lc.id,
circleName: circle.title,
circleId: circle.id,
frontendUrl: lc.frontend_url,
title: getLearningContentType(lc),
showDetailsText: getShowDetailsText(lc),
detailsLink: getDetailsLink(lc),
detailsLink: getDetailsLink(lc, circle.id),
content: lc,
};
});
@ -103,9 +103,9 @@ const getShowDetailsText = (lc: LearningContent) => {
return t("Feedback anschauen");
};
const getDetailsLink = (lc: LearningContent) => {
const getDetailsLink = (lc: LearningContent, circleId: string) => {
if (isFeedback(lc)) {
return `cockpit/feedback/${lc.parentCircle.id}`;
return `cockpit/feedback/${circleId}`;
}
return `cockpit/assignment/${lc.id}`;
};
@ -162,7 +162,7 @@ const getIconName = (lc: LearningContent) => {
<FeedbackSubmissionProgress
v-if="isFeedback(submittable.content)"
:course-session="props.courseSession"
:circle-id="submittable.content.parentCircle.id"
:circle-id="submittable.circleId"
class="grow pr-8"
></FeedbackSubmissionProgress>
<div class="flex items-center lg:w-1/4 lg:justify-end">

View File

@ -32,7 +32,10 @@ const props = withDefaults(defineProps<Props>(), {
log.debug("CirclePage created", stringifyParse(props));
const lpQueryResult = useLearningPathWithCompletion(props.courseSlug);
const lpQueryResult = useLearningPathWithCompletion(
props.courseSlug,
props.profileUser?.user_id
);
const circle = computed(() => {
return lpQueryResult.findCircle(props.circleSlug);

View File

@ -1,11 +1,9 @@
import { useCourseSessionDetailQuery } from "@/composables";
import { itGet } from "@/fetchHelpers";
import type { LearningPath } from "@/services/learningPath";
import type {
Assignment,
AssignmentCompletion,
CourseSessionUser,
LearningContentAssignment,
UserAssignmentCompletionStatus,
} from "@/types";
import { sum } from "d3";
@ -16,17 +14,6 @@ export interface GradedUser {
points: number;
}
export function calcLearningContentAssignments(learningPath?: LearningPath) {
// TODO: filter by circle
if (!learningPath) return [];
return learningPath.circles.flatMap((circle) => {
return circle.flatLearningContents.filter(
(lc) => lc.content_type === "learnpath.LearningContentAssignment"
) as LearningContentAssignment[];
});
}
export async function loadAssignmentCompletionStatusData(
assignmentId: string,
courseSessionId: string,

View File

@ -149,6 +149,7 @@ export type LearningUnitPerformanceCriteria = Omit<
> &
Completable & {
readonly content_type: "competence.PerformanceCriteria";
circle?: CircleLight;
};
export interface CourseCompletion {

View File

@ -12,7 +12,6 @@ class CoursePageInterface(graphene.Interface):
live = graphene.Boolean(required=True)
translation_key = graphene.String(required=True)
frontend_url = graphene.String(required=True)
circle = graphene.Field("vbv_lernwelt.learnpath.graphql.types.CircleObjectType")
course = graphene.Field("vbv_lernwelt.course.graphql.types.CourseObjectType")
def resolve_frontend_url(self, info):
@ -21,11 +20,5 @@ class CoursePageInterface(graphene.Interface):
def resolve_content_type(self, info):
return get_django_content_type(self)
def resolve_circle(self, info):
circle = self.get_ancestors().exact_type(Circle).first()
if circle:
return circle.specific
return None
def resolve_course(self, info):
return self.get_course()

View File

@ -2,7 +2,6 @@ import graphene
from vbv_lernwelt.course.graphql.types import resolve_course_page
from vbv_lernwelt.learnpath.graphql.types import (
CircleObjectType,
LearningContentAssignmentObjectType,
LearningContentAttendanceCourseObjectType,
LearningContentDocumentListObjectType,
@ -15,7 +14,7 @@ from vbv_lernwelt.learnpath.graphql.types import (
LearningContentVideoObjectType,
LearningPathObjectType,
)
from vbv_lernwelt.learnpath.models import Circle, LearningPath
from vbv_lernwelt.learnpath.models import LearningPath
class LearningPathQuery:
@ -26,10 +25,6 @@ class LearningPathQuery:
course_id=graphene.ID(),
course_slug=graphene.String(),
)
circle = graphene.Field(CircleObjectType, id=graphene.ID(), slug=graphene.String())
def resolve_circle(root, info, id=None, slug=None):
return resolve_course_page(Circle, root, info, id=id, slug=slug)
def resolve_learning_path(
root, info, id=None, slug=None, course_id=None, course_slug=None

View File

@ -25,14 +25,22 @@ from vbv_lernwelt.learnpath.models import (
logger = structlog.get_logger(__name__)
class CircleLightObjectType(graphene.ObjectType):
id = graphene.ID(required=True)
title = graphene.String(required=True)
slug = graphene.String(required=True)
class LearningContentInterface(CoursePageInterface):
minutes = graphene.Int()
description = graphene.String(required=True)
content_url = graphene.String(required=True)
can_user_self_toggle_course_completion = graphene.Boolean(required=True)
circle = graphene.Field(
"vbv_lernwelt.learnpath.graphql.types.CircleObjectType", required=True
)
circle = graphene.Field(CircleLightObjectType)
@staticmethod
def resolve_circle(root, info):
return root.get_parent_circle()
@classmethod
def resolve_type(cls, instance, info):