Load CompletionData
This commit is contained in:
parent
627e4f6873
commit
8621d4af07
|
|
@ -11,7 +11,7 @@
|
|||
</template>
|
||||
<template #center>
|
||||
<div class="flex w-full justify-between">
|
||||
<div>Circle: {{ feedbacks.circle.title }}</div>
|
||||
<div>{{ $t("a.Circle") }}: {{ feedbacks.circle.title }}</div>
|
||||
<div>{{ $t("feedback.sentByUsers", { count: feedbacks.count }) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -34,7 +34,7 @@ import ItRow from "@/components/ui/ItRow.vue";
|
|||
import { itGet } from "@/fetchHelpers";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
import type { Circle } from "@/services/circle";
|
||||
import type { OldCircle } from "@/services/oldCircle";
|
||||
|
||||
interface FeedbackSummary {
|
||||
circle_id: string;
|
||||
|
|
@ -42,12 +42,12 @@ interface FeedbackSummary {
|
|||
}
|
||||
|
||||
interface FeedbackDisplaySummary extends FeedbackSummary {
|
||||
circle: Circle;
|
||||
circle: OldCircle;
|
||||
}
|
||||
|
||||
function makeSummary(
|
||||
feedbackData: FeedbackSummary[],
|
||||
circles: Circle[],
|
||||
circles: OldCircle[],
|
||||
selectedCircles: string[]
|
||||
) {
|
||||
const summary: FeedbackDisplaySummary[] = circles
|
||||
|
|
@ -64,7 +64,7 @@ function makeSummary(
|
|||
|
||||
const props = defineProps<{
|
||||
selctedCircles: string[];
|
||||
circles: Circle[];
|
||||
circles: OldCircle[];
|
||||
courseSessionId: string;
|
||||
url: string;
|
||||
}>();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
import { COURSE_SESSION_DETAIL_QUERY, LEARNING_PATH_QUERY } from "@/graphql/queries";
|
||||
import { circleFlatChildren } from "@/services/circle";
|
||||
import { useCompletionStore } from "@/stores/completion";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { CourseSession, CourseSessionDetail, LearningPathType } from "@/types";
|
||||
import type {
|
||||
CourseCompletion,
|
||||
CourseCompletionStatus,
|
||||
CourseSession,
|
||||
CourseSessionDetail,
|
||||
LearningContentWithCompletion,
|
||||
LearningPathType,
|
||||
LearningUnitPerformanceCriteria,
|
||||
} from "@/types";
|
||||
import { useQuery } from "@urql/vue";
|
||||
import log from "loglevel";
|
||||
import type { ComputedRef } from "vue";
|
||||
|
|
@ -150,3 +160,71 @@ export function useLearningPathQuery(courseSlug: string) {
|
|||
|
||||
return { ...queryResult, learningPath, circles, findCircle };
|
||||
}
|
||||
|
||||
export function useLearningPathWithCompletion(
|
||||
courseSlug?: string,
|
||||
courseSessionId?: string,
|
||||
userId?: string
|
||||
) {
|
||||
if (!courseSlug) {
|
||||
courseSlug = useCurrentCourseSession().value.course.slug;
|
||||
}
|
||||
if (!userId) {
|
||||
userId = useUserStore().id;
|
||||
}
|
||||
if (!courseSessionId) {
|
||||
courseSessionId = useCurrentCourseSession().value.id;
|
||||
}
|
||||
|
||||
const lpQueryResult = useLearningPathQuery(courseSlug);
|
||||
const completionStore = useCompletionStore();
|
||||
|
||||
function updateCompletionData() {
|
||||
if (userId && courseSessionId) {
|
||||
completionStore
|
||||
.loadCourseSessionCompletionData(courseSessionId, userId)
|
||||
.then(_parseCompletionData);
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (lpQueryResult.data.value) {
|
||||
// load completion data when learning path data is loaded
|
||||
updateCompletionData();
|
||||
}
|
||||
});
|
||||
|
||||
function _parseCompletionData(completionData: CourseCompletion[]) {
|
||||
if (lpQueryResult.circles.value) {
|
||||
lpQueryResult.circles.value.forEach((circle) => {
|
||||
circleFlatChildren(circle).forEach((lc) => {
|
||||
const pageIndex = completionData.findIndex((e) => {
|
||||
return e.page_id === lc.id;
|
||||
});
|
||||
if (pageIndex >= 0) {
|
||||
lc.completion_status = completionData[pageIndex].completion_status;
|
||||
} else {
|
||||
lc.completion_status = "UNKNOWN";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function markCompletion(
|
||||
page: LearningContentWithCompletion | LearningUnitPerformanceCriteria,
|
||||
completion_status: CourseCompletionStatus = "SUCCESS"
|
||||
) {
|
||||
if (userId && courseSessionId) {
|
||||
page.completion_status = completion_status;
|
||||
const completionData = await completionStore.markPage(
|
||||
page,
|
||||
userId,
|
||||
courseSessionId
|
||||
);
|
||||
_parseCompletionData(completionData);
|
||||
}
|
||||
}
|
||||
|
||||
return { ...lpQueryResult, updateCompletionData, markCompletion };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const documents = {
|
|||
"\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 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 ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n learning_contents {\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 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,
|
||||
};
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ export function graphql(source: "\n query courseSessionDetail($courseSessionId:
|
|||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query learningPathQuery($slug: String!) {\n learning_path(slug: $slug) {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n learning_contents {\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"): (typeof documents)["\n query learningPathQuery($slug: String!) {\n learning_path(slug: $slug) {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n learning_contents {\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"];
|
||||
export function graphql(source: "\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"): (typeof documents)["\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"];
|
||||
/**
|
||||
* 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
|
|
@ -93,6 +93,7 @@ type LearningUnitObjectType implements CoursePageInterface {
|
|||
circle: CircleObjectType
|
||||
course: CourseObjectType
|
||||
learning_contents: [LearningContentInterface!]!
|
||||
performance_criteria: [PerformanceCriteriaObjectType!]!
|
||||
evaluate_url: String!
|
||||
}
|
||||
|
||||
|
|
@ -107,8 +108,22 @@ interface LearningContentInterface {
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
}
|
||||
|
||||
type PerformanceCriteriaObjectType implements CoursePageInterface {
|
||||
competence_id: String!
|
||||
id: ID!
|
||||
title: String!
|
||||
slug: String!
|
||||
content_type: String!
|
||||
live: Boolean!
|
||||
translation_key: String!
|
||||
frontend_url: String!
|
||||
circle: CircleObjectType
|
||||
course: CourseObjectType
|
||||
}
|
||||
|
||||
type TopicObjectType implements CoursePageInterface {
|
||||
|
|
@ -136,8 +151,9 @@ type LearningContentMediaLibraryObjectType implements CoursePageInterface & Lear
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
}
|
||||
|
||||
type LearningContentAssignmentObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
|
|
@ -153,9 +169,10 @@ type LearningContentAssignmentObjectType implements CoursePageInterface & Learni
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
competence_certificate: CompetenceCertificateObjectType!
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
competence_certificate: CompetenceCertificateObjectType
|
||||
}
|
||||
|
||||
type AssignmentObjectType implements CoursePageInterface {
|
||||
|
|
@ -330,8 +347,9 @@ type LearningContentAttendanceCourseObjectType implements CoursePageInterface &
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
}
|
||||
|
||||
type DueDateObjectType {
|
||||
|
|
@ -411,9 +429,10 @@ type LearningContentEdoniqTestObjectType implements CoursePageInterface & Learni
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
competence_certificate: CompetenceCertificateObjectType!
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
competence_certificate: CompetenceCertificateObjectType
|
||||
}
|
||||
|
||||
type CourseSessionUserObjectsType {
|
||||
|
|
@ -504,8 +523,9 @@ type LearningContentFeedbackObjectType implements CoursePageInterface & Learning
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
}
|
||||
|
||||
type LearningContentLearningModuleObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
|
|
@ -519,8 +539,9 @@ type LearningContentLearningModuleObjectType implements CoursePageInterface & Le
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
}
|
||||
|
||||
type LearningContentPlaceholderObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
|
|
@ -534,8 +555,9 @@ type LearningContentPlaceholderObjectType implements CoursePageInterface & Learn
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
}
|
||||
|
||||
type LearningContentRichTextObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
|
|
@ -549,8 +571,9 @@ type LearningContentRichTextObjectType implements CoursePageInterface & Learning
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
}
|
||||
|
||||
type LearningContentVideoObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
|
|
@ -564,8 +587,9 @@ type LearningContentVideoObjectType implements CoursePageInterface & LearningCon
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
}
|
||||
|
||||
type LearningContentDocumentListObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
|
|
@ -579,8 +603,9 @@ type LearningContentDocumentListObjectType implements CoursePageInterface & Lear
|
|||
circle: CircleObjectType!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String
|
||||
content: String
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
}
|
||||
|
||||
type CompetenceCertificateListObjectType implements CoursePageInterface {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export const LearningSequenceObjectType = "LearningSequenceObjectType";
|
|||
export const LearningUnitObjectType = "LearningUnitObjectType";
|
||||
export const LearnpathLearningContentAssignmentAssignmentTypeChoices = "LearnpathLearningContentAssignmentAssignmentTypeChoices";
|
||||
export const Mutation = "Mutation";
|
||||
export const PerformanceCriteriaObjectType = "PerformanceCriteriaObjectType";
|
||||
export const Query = "Query";
|
||||
export const SendFeedbackMutation = "SendFeedbackMutation";
|
||||
export const String = "String";
|
||||
|
|
|
|||
|
|
@ -199,6 +199,8 @@ export const LEARNING_PATH_QUERY = graphql(`
|
|||
is_visible
|
||||
...CoursePageFields
|
||||
circles {
|
||||
description
|
||||
goals
|
||||
...CoursePageFields
|
||||
learning_sequences {
|
||||
icon
|
||||
|
|
@ -206,7 +208,11 @@ export const LEARNING_PATH_QUERY = graphql(`
|
|||
learning_units {
|
||||
evaluate_url
|
||||
...CoursePageFields
|
||||
performance_criteria {
|
||||
...CoursePageFields
|
||||
}
|
||||
learning_contents {
|
||||
can_user_self_toggle_course_completion
|
||||
...CoursePageFields
|
||||
... on LearningContentAssignmentObjectType {
|
||||
assignment_type
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ onMounted(async () => {
|
|||
{{ learningContentAssignment.title }}
|
||||
</h2>
|
||||
<div class="pt-1 underline">
|
||||
Circle «{{ learningContentAssignment.parentCircle.title }}»
|
||||
{{ $t("a.Circle") }} «{{ learningContentAssignment.parentCircle.title }}»
|
||||
</div>
|
||||
<div v-if="assignmentDetail">
|
||||
<span v-if="assignmentDetail.submission_deadline?.start">
|
||||
|
|
|
|||
|
|
@ -147,7 +147,9 @@ const getIconName = (lc: LearningContent) => {
|
|||
</div>
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-bold flex items-center gap-2">{{ submittable.title }}</h3>
|
||||
<p class="text-gray-800">Circle «{{ submittable.circleName }}»</p>
|
||||
<p class="text-gray-800">
|
||||
{{ $t("a.Circle") }} «{{ submittable.circleName }}»
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<AssignmentSubmissionProgress
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { showIcon } from "@/pages/learningPath/circlePage/learningSequenceUtils";
|
||||
import { useCircleStore } from "@/stores/circle";
|
||||
import type { DefaultArcObject } from "d3";
|
||||
import * as d3 from "d3";
|
||||
import pick from "lodash/pick";
|
||||
|
|
@ -9,26 +8,23 @@ import { computed, onMounted } from "vue";
|
|||
|
||||
// @ts-ignore
|
||||
import colors from "@/colors.json";
|
||||
import type { LearningSequence } from "@/types";
|
||||
|
||||
const circleStore = useCircleStore();
|
||||
import type { CircleType, LearningSequence } from "@/types";
|
||||
import {
|
||||
allFinishedInLearningSequence,
|
||||
someFinishedInLearningSequence,
|
||||
} from "@/services/circle";
|
||||
|
||||
function someFinished(learningSequence: LearningSequence) {
|
||||
if (circleStore.circle) {
|
||||
return circleStore.circle.someFinishedInLearningSequence(
|
||||
learningSequence.translation_key
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const props = defineProps<{
|
||||
circle: CircleType;
|
||||
}>();
|
||||
|
||||
function allFinished(learningSequence: LearningSequence) {
|
||||
if (circleStore.circle) {
|
||||
return circleStore.circle.allFinishedInLearningSequence(
|
||||
learningSequence.translation_key
|
||||
);
|
||||
}
|
||||
return false;
|
||||
return allFinishedInLearningSequence(learningSequence);
|
||||
}
|
||||
|
||||
function someFinished(learningSequence: LearningSequence) {
|
||||
return someFinishedInLearningSequence(learningSequence);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
@ -48,14 +44,14 @@ interface CirclePie extends d3.PieArcDatum<number> {
|
|||
}
|
||||
|
||||
const pieData = computed(() => {
|
||||
const circle = circleStore.circle;
|
||||
const circle = props.circle;
|
||||
|
||||
if (circle) {
|
||||
const pieWeights = new Array(Math.max(circle.learningSequences.length, 1)).fill(1);
|
||||
const pieWeights = new Array(Math.max(circle.learning_sequences.length, 1)).fill(1);
|
||||
const pieGenerator = d3.pie();
|
||||
const angles = pieGenerator(pieWeights);
|
||||
let result = angles.map((angle) => {
|
||||
const thisLearningSequence = circle.learningSequences[angle.index];
|
||||
const thisLearningSequence = circle.learning_sequences[angle.index];
|
||||
|
||||
// Rotate the cirlce by PI (180 degrees) normally 0 = 12'o clock, now start at 6 o clock
|
||||
angle.startAngle += Math.PI;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||
import type { Circle } from "@/services/circle";
|
||||
import type { CircleType } from "@/types";
|
||||
|
||||
defineProps<{
|
||||
circle: Circle | undefined;
|
||||
circle: CircleType;
|
||||
show: boolean;
|
||||
}>();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ import { useRoute } from "vue-router";
|
|||
import CircleDiagram from "./CircleDiagram.vue";
|
||||
import CircleOverview from "./CircleOverview.vue";
|
||||
import DocumentSection from "./DocumentSection.vue";
|
||||
import { useCourseSessionDetailQuery, useLearningPathQuery } from "@/composables";
|
||||
import {
|
||||
useCourseSessionDetailQuery,
|
||||
useLearningPathWithCompletion,
|
||||
} from "@/composables";
|
||||
import { stringifyParse } from "@/utils/utils";
|
||||
import { useCircleStore } from "@/stores/circle";
|
||||
import LearningSequence from "@/pages/learningPath/circlePage/LearningSequence.vue";
|
||||
|
|
@ -29,6 +32,12 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
|
||||
log.debug("CirclePage created", stringifyParse(props));
|
||||
|
||||
const lpQueryResult = useLearningPathWithCompletion(props.courseSlug);
|
||||
|
||||
const circle = computed(() => {
|
||||
return lpQueryResult.findCircle(props.circleSlug);
|
||||
});
|
||||
|
||||
const circleExperts = computed(() => {
|
||||
if (circle.value) {
|
||||
return courseSessionDetailResult.filterCircleExperts(circle.value.slug);
|
||||
|
|
@ -51,12 +60,6 @@ const showDuration = computed(() => {
|
|||
return false;
|
||||
});
|
||||
|
||||
const lpQueryResult = useLearningPathQuery(props.courseSlug);
|
||||
|
||||
const circle = computed(() => {
|
||||
return lpQueryResult.findCircle(props.circleSlug);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => circle.value,
|
||||
() => {
|
||||
|
|
@ -102,7 +105,7 @@ watch(
|
|||
<div v-if="circle">
|
||||
<Teleport to="body">
|
||||
<CircleOverview
|
||||
:circle="circleStore.circle"
|
||||
:circle="circle"
|
||||
:show="circleStore.page === 'OVERVIEW'"
|
||||
@closemodal="circleStore.page = 'INDEX'"
|
||||
/>
|
||||
|
|
@ -151,7 +154,7 @@ watch(
|
|||
</div>
|
||||
|
||||
<div class="mt-8 w-full">
|
||||
<CircleDiagram></CircleDiagram>
|
||||
<CircleDiagram v-if="circle" :circle="circle"></CircleDiagram>
|
||||
</div>
|
||||
<div v-if="!props.readonly" class="mt-4 border-t-2 lg:hidden">
|
||||
<div
|
||||
|
|
@ -221,6 +224,7 @@ watch(
|
|||
:key="learningSequence.id"
|
||||
>
|
||||
<LearningSequence
|
||||
:course-slug="props.courseSlug"
|
||||
:learning-sequence="learningSequence"
|
||||
:readonly="props.readonly"
|
||||
></LearningSequence>
|
||||
|
|
|
|||
|
|
@ -3,19 +3,28 @@ import LearningContentBadge from "@/pages/learningPath/LearningContentTypeBadge.
|
|||
import { showIcon } from "@/pages/learningPath/circlePage/learningSequenceUtils";
|
||||
import { useCircleStore } from "@/stores/circle";
|
||||
import type {
|
||||
CourseCompletionStatus,
|
||||
LearningContent,
|
||||
LearningContentAssignment,
|
||||
LearningContentEdoniqTest,
|
||||
LearningContentWithCompletion,
|
||||
LearningSequence,
|
||||
} from "@/types";
|
||||
import { computed } from "vue";
|
||||
import { humanizeDuration } from "../../../utils/humanizeDuration";
|
||||
import {
|
||||
itCheckboxDefaultIconCheckedTailwindClass,
|
||||
itCheckboxDefaultIconUncheckedTailwindClass,
|
||||
} from "@/constants";
|
||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||
import {
|
||||
allFinishedInLearningSequence,
|
||||
calcSelfEvaluationStatus,
|
||||
someFinishedInLearningSequence,
|
||||
} from "@/services/circle";
|
||||
import { useLearningPathWithCompletion } from "@/composables";
|
||||
|
||||
type Props = {
|
||||
courseSlug: string;
|
||||
learningSequence: LearningSequence;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
|
@ -26,30 +35,28 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
|
||||
const circleStore = useCircleStore();
|
||||
|
||||
function toggleCompleted(learningContent: LearningContent) {
|
||||
// let completionStatus: CourseCompletionStatus = "SUCCESS";
|
||||
// if (learningContent.completion_status === "SUCCESS") {
|
||||
// completionStatus = "FAIL";
|
||||
// }
|
||||
// circleStore.markCompletion(learningContent, completionStatus);
|
||||
const lpQueryResult = useLearningPathWithCompletion(props.courseSlug);
|
||||
|
||||
function toggleCompleted(learningContent: LearningContentWithCompletion) {
|
||||
let completionStatus: CourseCompletionStatus = "SUCCESS";
|
||||
if (learningContent.completion_status === "SUCCESS") {
|
||||
completionStatus = "FAIL";
|
||||
}
|
||||
lpQueryResult.markCompletion(learningContent, completionStatus);
|
||||
}
|
||||
|
||||
const someFinished = computed(() => {
|
||||
// if (props.learningSequence && circleStore.circle) {
|
||||
// return circleStore.circle.someFinishedInLearningSequence(
|
||||
// props.learningSequence.translation_key
|
||||
// );
|
||||
// }
|
||||
if (props.learningSequence) {
|
||||
return someFinishedInLearningSequence(props.learningSequence);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const allFinished = computed(() => {
|
||||
// if (props.learningSequence && circleStore.circle) {
|
||||
// return circleStore.circle.allFinishedInLearningSequence(
|
||||
// props.learningSequence.translation_key
|
||||
// );
|
||||
// }
|
||||
if (props.learningSequence) {
|
||||
return allFinishedInLearningSequence(props.learningSequence);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
|
@ -78,13 +85,13 @@ const continueTranslationKeyTuple = computed(() => {
|
|||
|
||||
const learningSequenceBorderClass = computed(() => {
|
||||
let result: string[] = [];
|
||||
if (props.learningSequence && circleStore.circle) {
|
||||
if (props.learningSequence) {
|
||||
if (allFinished.value) {
|
||||
result = ["border-l-4", "border-l-green-500"];
|
||||
} else if (someFinished.value) {
|
||||
result = ["border-l-4", "border-l-sky-500"];
|
||||
} else {
|
||||
result = ["border-l-gray-500"];
|
||||
result = ["border-l", "border-l-gray-500"];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -125,9 +132,9 @@ function checkboxIconUncheckedTailwindClass(lc: LearningContent) {
|
|||
<h3 class="text-large font-semibold">
|
||||
{{ learningSequence.title }}
|
||||
</h3>
|
||||
<div v-if="learningSequence.minutes > 0">
|
||||
{{ humanizeDuration(learningSequence.minutes) }}
|
||||
</div>
|
||||
<!-- <div v-if="learningSequence.minutes > 0">-->
|
||||
<!-- {{ humanizeDuration(learningSequence.minutes) }}-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
|
||||
<ol class="border bg-white px-4 lg:px-6" :class="learningSequenceBorderClass">
|
||||
|
|
@ -144,9 +151,9 @@ function checkboxIconUncheckedTailwindClass(lc: LearningContent) {
|
|||
<div class="font-semibold">
|
||||
{{ learningUnit.title }}
|
||||
</div>
|
||||
<div v-if="learningUnit.minutes > 0" class="whitespace-nowrap">
|
||||
{{ humanizeDuration(learningUnit.minutes) }}
|
||||
</div>
|
||||
<!-- <div v-if="learningUnit.minutes > 0" class="whitespace-nowrap">-->
|
||||
<!-- {{ humanizeDuration(learningUnit.minutes) }}-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
<ol>
|
||||
<li
|
||||
|
|
@ -254,31 +261,31 @@ function checkboxIconUncheckedTailwindClass(lc: LearningContent) {
|
|||
</li>
|
||||
</ol>
|
||||
|
||||
<!-- <div-->
|
||||
<!-- v-if="learningUnit.children.length"-->
|
||||
<!-- :class="{ 'cursor-pointer': !props.readonly }"-->
|
||||
<!-- :data-cy="`${learningUnit.slug}`"-->
|
||||
<!-- @click="!props.readonly && circleStore.openSelfEvaluation(learningUnit)"-->
|
||||
<!-- >-->
|
||||
<!-- <div-->
|
||||
<!-- v-if="circleStore.calcSelfEvaluationStatus(learningUnit) === 'SUCCESS'"-->
|
||||
<!-- class="self-evaluation-success flex items-center gap-4 pb-6"-->
|
||||
<!-- >-->
|
||||
<!-- <it-icon-smiley-happy class="mr-4 h-8 w-8 flex-none" data-cy="success" />-->
|
||||
<!-- <div>{{ $t("selfEvaluation.selfEvaluationYes") }}</div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div-->
|
||||
<!-- v-else-if="circleStore.calcSelfEvaluationStatus(learningUnit) === 'FAIL'"-->
|
||||
<!-- class="self-evaluation-fail flex items-center gap-4 pb-6"-->
|
||||
<!-- >-->
|
||||
<!-- <it-icon-smiley-thinking class="mr-4 h-8 w-8 flex-none" data-cy="fail" />-->
|
||||
<!-- <div>{{ $t("selfEvaluation.selfEvaluationNo") }}</div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-else class="self-evaluation-unknown flex items-center gap-4 pb-6">-->
|
||||
<!-- <it-icon-smiley-neutral class="mr-4 h-8 w-8 flex-none" data-cy="unknown" />-->
|
||||
<!-- <div>{{ $t("a.Selbsteinschätzung") }}</div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<div
|
||||
v-if="learningUnit.performance_criteria.length"
|
||||
:class="{ 'cursor-pointer': !props.readonly }"
|
||||
:data-cy="`${learningUnit.slug}`"
|
||||
@click="!props.readonly && circleStore.openSelfEvaluation(learningUnit)"
|
||||
>
|
||||
<div
|
||||
v-if="calcSelfEvaluationStatus(learningUnit) === 'SUCCESS'"
|
||||
class="self-evaluation-success flex items-center gap-4 pb-6"
|
||||
>
|
||||
<it-icon-smiley-happy class="mr-4 h-8 w-8 flex-none" data-cy="success" />
|
||||
<div>{{ $t("selfEvaluation.selfEvaluationYes") }}</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="calcSelfEvaluationStatus(learningUnit) === 'FAIL'"
|
||||
class="self-evaluation-fail flex items-center gap-4 pb-6"
|
||||
>
|
||||
<it-icon-smiley-thinking class="mr-4 h-8 w-8 flex-none" data-cy="fail" />
|
||||
<div>{{ $t("selfEvaluation.selfEvaluationNo") }}</div>
|
||||
</div>
|
||||
<div v-else class="self-evaluation-unknown flex items-center gap-4 pb-6">
|
||||
<it-icon-smiley-neutral class="mr-4 h-8 w-8 flex-none" data-cy="unknown" />
|
||||
<div>{{ $t("a.Selbsteinschätzung") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr v-if="!learningUnit.last" class="-mx-4 text-gray-500" />
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
||||
import LearningPathContinueButton from "@/pages/learningPath/learningPathPage/LearningPathContinueButton.vue";
|
||||
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
||||
import type { Circle } from "@/services/circle";
|
||||
import type { OldCircle } from "@/services/oldCircle";
|
||||
import type { LearningPath } from "@/services/learningPath";
|
||||
import type { Topic } from "@/types";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
learningPath: LearningPath | undefined;
|
||||
circle: Circle;
|
||||
circle: OldCircle;
|
||||
topic: Topic;
|
||||
isFirstCircle: boolean;
|
||||
isLastCircle: boolean;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
||||
import LearningPathContinueButton from "@/pages/learningPath/learningPathPage/LearningPathContinueButton.vue";
|
||||
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
||||
import type { Circle } from "@/services/circle";
|
||||
import type { OldCircle } from "@/services/oldCircle";
|
||||
import type { LearningPath } from "@/services/learningPath";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
learningPath: LearningPath | undefined;
|
||||
circle: Circle;
|
||||
circle: OldCircle;
|
||||
isCurrentCircle: boolean;
|
||||
}>();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import LearningPathCircleListTile from "@/pages/learningPath/learningPathPage/LearningPathCircleListTile.vue";
|
||||
import type { Circle } from "@/services/circle";
|
||||
import type { OldCircle } from "@/services/oldCircle";
|
||||
import type { LearningPath } from "@/services/learningPath";
|
||||
import { computed } from "vue";
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ const props = defineProps<{
|
|||
|
||||
const topics = computed(() => props.learningPath?.topics ?? []);
|
||||
|
||||
const isCurrentCircle = (circle: Circle) =>
|
||||
const isCurrentCircle = (circle: OldCircle) =>
|
||||
props.learningPath?.nextLearningContent?.parentCircle === circle;
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import LearningPathCircleColumn from "@/pages/learningPath/learningPathPage/LearningPathCircleColumn.vue";
|
||||
import LearningPathScrollButton from "@/pages/learningPath/learningPathPage/LearningPathScrollButton.vue";
|
||||
import type { Circle } from "@/services/circle";
|
||||
import type { OldCircle } from "@/services/oldCircle";
|
||||
import type { LearningPath } from "@/services/learningPath";
|
||||
import { useScroll } from "@vueuse/core";
|
||||
import { ref } from "vue";
|
||||
|
|
@ -25,7 +25,7 @@ const isLastCircle = (topicIndex: number, circleIndex: number, numCircles: numbe
|
|||
topicIndex === (props.learningPath?.topics ?? []).length - 1 &&
|
||||
circleIndex === numCircles - 1;
|
||||
|
||||
const isCurrentCircle = (circle: Circle) =>
|
||||
const isCurrentCircle = (circle: OldCircle) =>
|
||||
props.learningPath?.nextLearningContent?.parentCircle === circle;
|
||||
|
||||
const scrollRight = () => scrollLearnPathDiagram(scrollIncrement);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import type {
|
|||
CircleSectorData,
|
||||
CircleSectorProgress,
|
||||
} from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
||||
import type { Circle } from "@/services/circle";
|
||||
import type { OldCircle } from "@/services/oldCircle";
|
||||
|
||||
export function calculateCircleSectorData(circle: Circle): CircleSectorData[] {
|
||||
export function calculateCircleSectorData(circle: OldCircle): CircleSectorData[] {
|
||||
return circle.learningSequences.map((ls) => {
|
||||
let progress: CircleSectorProgress = "none";
|
||||
if (circle.allFinishedInLearningSequence(ls.translation_key)) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { WagtailCircle } from "@/types";
|
||||
import { describe, it } from "vitest";
|
||||
import { Circle } from "../circle";
|
||||
import { OldCircle } from "../oldCircle";
|
||||
import data from "./learning_path_json.json";
|
||||
|
||||
describe("Circle.parseJson", () => {
|
||||
|
|
@ -8,7 +8,7 @@ describe("Circle.parseJson", () => {
|
|||
const cirleData = data.children.find(
|
||||
(c) => c.slug === "test-lehrgang-lp-circle-fahrzeug"
|
||||
) as unknown as WagtailCircle;
|
||||
const circle = Circle.fromJson(cirleData, undefined);
|
||||
const circle = OldCircle.fromJson(cirleData, undefined);
|
||||
expect(circle.learningSequences.length).toBe(3);
|
||||
expect(circle.flatLearningContents.length).toBe(9);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,268 +1,70 @@
|
|||
import type { LearningPath } from "@/services/learningPath";
|
||||
import type {
|
||||
CircleChild,
|
||||
CircleGoal,
|
||||
CircleJobSituation,
|
||||
CourseCompletion,
|
||||
LearningContent,
|
||||
LearningContentInterface,
|
||||
CircleType,
|
||||
CourseCompletionStatus,
|
||||
LearningSequence,
|
||||
LearningUnit,
|
||||
LearningUnitPerformanceCriteria,
|
||||
WagtailCircle,
|
||||
} from "@/types";
|
||||
import groupBy from "lodash/groupBy";
|
||||
import partition from "lodash/partition";
|
||||
import values from "lodash/values";
|
||||
import log from "loglevel";
|
||||
|
||||
function isLearningContentType(object: any): object is LearningContent {
|
||||
return (
|
||||
object?.content_type === "learnpath.LearningContentAssignment" ||
|
||||
object?.content_type === "learnpath.LearningContentAttendanceCourse" ||
|
||||
object?.content_type === "learnpath.LearningContentDocumentList" ||
|
||||
object?.content_type === "learnpath.LearningContentFeedback" ||
|
||||
object?.content_type === "learnpath.LearningContentLearningModule" ||
|
||||
object?.content_type === "learnpath.LearningContentMediaLibrary" ||
|
||||
object?.content_type === "learnpath.LearningContentPlaceholder" ||
|
||||
object?.content_type === "learnpath.LearningContentRichText" ||
|
||||
object?.content_type === "learnpath.LearningContentEdoniqTest" ||
|
||||
object?.content_type === "learnpath.LearningContentVideo"
|
||||
);
|
||||
}
|
||||
|
||||
export function parseLearningSequences(
|
||||
circle: Circle,
|
||||
children: CircleChild[]
|
||||
): LearningSequence[] {
|
||||
let learningSequence: LearningSequence | undefined;
|
||||
let learningUnit: LearningUnit | undefined;
|
||||
let learningContent: LearningContent | undefined;
|
||||
let previousLearningContent: LearningContent | undefined;
|
||||
const result: LearningSequence[] = [];
|
||||
|
||||
children.forEach((child) => {
|
||||
if (child.content_type === "learnpath.LearningSequence") {
|
||||
if (learningSequence) {
|
||||
if (learningUnit) {
|
||||
learningUnit.last = true;
|
||||
}
|
||||
}
|
||||
learningSequence = Object.assign(child, { learningUnits: [] });
|
||||
result.push(learningSequence);
|
||||
} else if (child.content_type === "learnpath.LearningUnit") {
|
||||
if (!learningSequence) {
|
||||
throw new Error("LearningUnit found before LearningSequence");
|
||||
}
|
||||
|
||||
learningUnit = Object.assign(child, {
|
||||
learningContents: [],
|
||||
parentLearningSequence: learningSequence,
|
||||
parentCircle: circle,
|
||||
children: child.children.map((c) => {
|
||||
c.parentLearningUnit = learningUnit;
|
||||
c.parentLearningSequence = learningSequence;
|
||||
return c;
|
||||
export function circleFlatChildren(circle: CircleType) {
|
||||
return [
|
||||
...circleFlatLearningContents(circle),
|
||||
...circleFlatLearningUnits(circle).flatMap((lu) => {
|
||||
return lu.performance_criteria;
|
||||
}),
|
||||
});
|
||||
learningSequence.learningUnits.push(learningUnit);
|
||||
} else if (isLearningContentType(child)) {
|
||||
if (!learningUnit) {
|
||||
throw new Error(`LearningContent found before LearningUnit ${child.slug}`);
|
||||
}
|
||||
previousLearningContent = learningContent;
|
||||
|
||||
learningContent = Object.assign(child, {
|
||||
parentCircle: circle,
|
||||
parentLearningSequence: learningSequence,
|
||||
parentLearningUnit: learningUnit,
|
||||
previousLearningContent: previousLearningContent,
|
||||
});
|
||||
|
||||
if (previousLearningContent) {
|
||||
previousLearningContent.nextLearningContent = learningContent;
|
||||
}
|
||||
|
||||
learningUnit.learningContents.push(child);
|
||||
} else {
|
||||
log.error("Unknown CircleChild found...", child);
|
||||
throw new Error("Unknown CircleChild found...");
|
||||
}
|
||||
});
|
||||
|
||||
if (learningUnit) {
|
||||
learningUnit.last = true;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Finished with LearningContent but there is no LearningSequence and LearningUnit"
|
||||
);
|
||||
}
|
||||
|
||||
// sum minutes
|
||||
result.forEach((learningSequence) => {
|
||||
learningSequence.minutes = 0;
|
||||
learningSequence.learningUnits.forEach((learningUnit) => {
|
||||
learningUnit.minutes = 0;
|
||||
learningUnit.learningContents.forEach((learningContent) => {
|
||||
learningUnit.minutes += learningContent.minutes;
|
||||
});
|
||||
learningSequence.minutes += learningUnit.minutes;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
];
|
||||
}
|
||||
|
||||
export class Circle implements WagtailCircle {
|
||||
readonly content_type = "learnpath.Circle";
|
||||
readonly learningSequences: LearningSequence[];
|
||||
export function circleFlatLearningContents(circle: CircleType) {
|
||||
return circleFlatLearningUnits(circle).flatMap((lu) => {
|
||||
return lu.learning_contents;
|
||||
});
|
||||
}
|
||||
|
||||
nextCircle?: Circle;
|
||||
previousCircle?: Circle;
|
||||
export function circleFlatLearningUnits(circle: CircleType) {
|
||||
return circle.learning_sequences.flatMap((ls) => {
|
||||
return ls.learning_units;
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
public readonly slug: string,
|
||||
public readonly title: string,
|
||||
public readonly translation_key: string,
|
||||
public readonly frontend_url: string,
|
||||
public readonly description: string,
|
||||
public readonly children: CircleChild[],
|
||||
public readonly goal_description: string,
|
||||
public readonly goals: CircleGoal[],
|
||||
public readonly job_situation_description: string,
|
||||
public readonly job_situations: CircleJobSituation[],
|
||||
public readonly parentLearningPath?: LearningPath
|
||||
export function learningSequenceFlatChildren(ls: LearningSequence) {
|
||||
return [
|
||||
...ls.learning_units.flatMap((lu) => {
|
||||
return lu.learning_contents;
|
||||
}),
|
||||
...ls.learning_units.flatMap((lu) => {
|
||||
return lu.performance_criteria;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
export function someFinishedInLearningSequence(ls: LearningSequence) {
|
||||
return learningSequenceFlatChildren(ls).some((lc) => {
|
||||
return lc.completion_status === "SUCCESS";
|
||||
});
|
||||
}
|
||||
|
||||
export function allFinishedInLearningSequence(ls: LearningSequence) {
|
||||
return learningSequenceFlatChildren(ls).every((lc) => {
|
||||
return lc.completion_status === "SUCCESS";
|
||||
});
|
||||
}
|
||||
|
||||
export function calcSelfEvaluationStatus(
|
||||
learningUnit: LearningUnit
|
||||
): CourseCompletionStatus {
|
||||
if (learningUnit.performance_criteria.length > 0) {
|
||||
if (
|
||||
learningUnit.performance_criteria.every((q) => q.completion_status === "SUCCESS")
|
||||
) {
|
||||
this.learningSequences = parseLearningSequences(this, this.children);
|
||||
return "SUCCESS";
|
||||
}
|
||||
|
||||
public static fromJson(
|
||||
wagtailCircle: WagtailCircle,
|
||||
learningPath?: LearningPath
|
||||
): Circle {
|
||||
// TODO add error checking when the data does not conform to the schema
|
||||
return new Circle(
|
||||
wagtailCircle.id,
|
||||
wagtailCircle.slug,
|
||||
wagtailCircle.title,
|
||||
wagtailCircle.translation_key,
|
||||
wagtailCircle.frontend_url,
|
||||
wagtailCircle.description,
|
||||
wagtailCircle.children,
|
||||
wagtailCircle.goal_description,
|
||||
wagtailCircle.goals,
|
||||
wagtailCircle.job_situation_description,
|
||||
wagtailCircle.job_situations,
|
||||
learningPath
|
||||
);
|
||||
}
|
||||
|
||||
public get flatChildren(): (
|
||||
| LearningContentInterface
|
||||
| LearningUnitPerformanceCriteria
|
||||
)[] {
|
||||
const result: (LearningContentInterface | LearningUnitPerformanceCriteria)[] = [];
|
||||
this.learningSequences.forEach((learningSequence) => {
|
||||
learningSequence.learningUnits.forEach((learningUnit) => {
|
||||
learningUnit.children.forEach((performanceCriteria) => {
|
||||
result.push(performanceCriteria);
|
||||
});
|
||||
learningUnit.learningContents.forEach((learningContent) => {
|
||||
result.push(learningContent);
|
||||
});
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public get flatLearningContents(): LearningContent[] {
|
||||
const result: LearningContent[] = [];
|
||||
this.learningSequences.forEach((learningSequence) => {
|
||||
learningSequence.learningUnits.forEach((learningUnit) => {
|
||||
learningUnit.learningContents.forEach((learningContent) => {
|
||||
result.push(learningContent);
|
||||
});
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public get flatLearningUnits(): LearningUnit[] {
|
||||
const result: LearningUnit[] = [];
|
||||
this.learningSequences.forEach((learningSequence) => {
|
||||
learningSequence.learningUnits.forEach((learningUnit) => {
|
||||
result.push(learningUnit);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public someFinishedInLearningSequence(translationKey: string): boolean {
|
||||
if (translationKey) {
|
||||
return (
|
||||
this.flatChildren.filter((lc) => {
|
||||
return (
|
||||
lc.completion_status === "SUCCESS" &&
|
||||
lc.parentLearningSequence?.translation_key === translationKey
|
||||
);
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public allFinishedInLearningSequence(translationKey: string): boolean {
|
||||
if (translationKey) {
|
||||
const [performanceCriteria, learningContents] = partition(
|
||||
this.flatChildren.filter(
|
||||
(lc) => lc.parentLearningSequence?.translation_key === translationKey
|
||||
),
|
||||
function (child) {
|
||||
return child.content_type === "competence.PerformanceCriteria";
|
||||
}
|
||||
);
|
||||
|
||||
const groupedPerformanceCriteria = values(
|
||||
groupBy(performanceCriteria, (pc) => pc.parentLearningUnit?.id)
|
||||
);
|
||||
|
||||
return (
|
||||
learningContents.every((lc) => lc.completion_status === "SUCCESS") &&
|
||||
(groupedPerformanceCriteria.length === 0 ||
|
||||
groupedPerformanceCriteria.every((group) =>
|
||||
group.every(
|
||||
(pc) =>
|
||||
pc.completion_status === "SUCCESS" || pc.completion_status === "FAIL"
|
||||
if (
|
||||
learningUnit.performance_criteria.every(
|
||||
(q) => q.completion_status === "FAIL" || q.completion_status === "SUCCESS"
|
||||
)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public isComplete(): boolean {
|
||||
return this.learningSequences.every((ls) =>
|
||||
this.allFinishedInLearningSequence(ls.translation_key)
|
||||
);
|
||||
}
|
||||
|
||||
public parseCompletionData(completionData: CourseCompletion[]) {
|
||||
this.flatChildren.forEach((page) => {
|
||||
const pageIndex = completionData.findIndex((e) => {
|
||||
return e.page_id === page.id;
|
||||
});
|
||||
if (pageIndex >= 0) {
|
||||
page.completion_status = completionData[pageIndex].completion_status;
|
||||
} else {
|
||||
page.completion_status = "UNKNOWN";
|
||||
}
|
||||
});
|
||||
|
||||
if (this.parentLearningPath) {
|
||||
this.parentLearningPath.calcNextLearningContent(completionData);
|
||||
) {
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import orderBy from "lodash/orderBy";
|
||||
|
||||
import { Circle } from "@/services/circle";
|
||||
import { OldCircle } from "@/services/oldCircle";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useLearningPathStore } from "@/stores/learningPath";
|
||||
import type {
|
||||
|
|
@ -12,6 +12,8 @@ import type {
|
|||
WagtailLearningPath,
|
||||
} from "@/types";
|
||||
|
||||
// FIXME: remove
|
||||
|
||||
export interface ContinueData {
|
||||
url: string;
|
||||
has_no_progress: boolean;
|
||||
|
|
@ -35,7 +37,7 @@ function getLastCompleted(courseSlug: string, completionData: CourseCompletion[]
|
|||
export class LearningPath implements WagtailLearningPath {
|
||||
readonly content_type = "learnpath.LearningPath";
|
||||
public topics: Topic[];
|
||||
public circles: Circle[];
|
||||
public circles: OldCircle[];
|
||||
public nextLearningContent?: LearningContentInterface;
|
||||
|
||||
public static fromJson(
|
||||
|
|
@ -81,7 +83,7 @@ export class LearningPath implements WagtailLearningPath {
|
|||
topic = Object.assign(page, { circles: [] });
|
||||
}
|
||||
if (page.content_type === "learnpath.Circle") {
|
||||
const circle = Circle.fromJson(page, this);
|
||||
const circle = OldCircle.fromJson(page, this);
|
||||
if (completionData && completionData.length > 0) {
|
||||
circle.parseCompletionData(completionData);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,264 @@
|
|||
import type { LearningPath } from "@/services/learningPath";
|
||||
import type {
|
||||
CircleChild,
|
||||
CircleGoal,
|
||||
CircleJobSituation,
|
||||
CourseCompletion,
|
||||
LearningContent,
|
||||
LearningContentInterface,
|
||||
LearningSequence,
|
||||
LearningUnit,
|
||||
LearningUnitPerformanceCriteria,
|
||||
WagtailCircle,
|
||||
} from "@/types";
|
||||
import groupBy from "lodash/groupBy";
|
||||
import partition from "lodash/partition";
|
||||
import values from "lodash/values";
|
||||
import log from "loglevel";
|
||||
|
||||
// FIXME: remove
|
||||
|
||||
function isLearningContentType(object: any): object is LearningContent {
|
||||
return (
|
||||
object?.content_type === "learnpath.LearningContentAssignment" ||
|
||||
object?.content_type === "learnpath.LearningContentAttendanceCourse" ||
|
||||
object?.content_type === "learnpath.LearningContentDocumentList" ||
|
||||
object?.content_type === "learnpath.LearningContentFeedback" ||
|
||||
object?.content_type === "learnpath.LearningContentLearningModule" ||
|
||||
object?.content_type === "learnpath.LearningContentMediaLibrary" ||
|
||||
object?.content_type === "learnpath.LearningContentPlaceholder" ||
|
||||
object?.content_type === "learnpath.LearningContentRichText" ||
|
||||
object?.content_type === "learnpath.LearningContentEdoniqTest" ||
|
||||
object?.content_type === "learnpath.LearningContentVideo"
|
||||
);
|
||||
}
|
||||
|
||||
export function parseLearningSequences(
|
||||
circle: OldCircle,
|
||||
children: CircleChild[]
|
||||
): LearningSequence[] {
|
||||
let learningSequence: LearningSequence | undefined;
|
||||
let learningUnit: LearningUnit | undefined;
|
||||
let learningContent: LearningContent | undefined;
|
||||
let previousLearningContent: LearningContent | undefined;
|
||||
const result: LearningSequence[] = [];
|
||||
|
||||
children.forEach((child) => {
|
||||
if (child.content_type === "learnpath.LearningSequence") {
|
||||
if (learningSequence) {
|
||||
if (learningUnit) {
|
||||
learningUnit.last = true;
|
||||
}
|
||||
}
|
||||
learningSequence = Object.assign(child, { learningUnits: [] });
|
||||
result.push(learningSequence);
|
||||
} else if (child.content_type === "learnpath.LearningUnit") {
|
||||
if (!learningSequence) {
|
||||
throw new Error("LearningUnit found before LearningSequence");
|
||||
}
|
||||
|
||||
learningUnit = Object.assign(child, {
|
||||
learningContents: [],
|
||||
parentLearningSequence: learningSequence,
|
||||
parentCircle: circle,
|
||||
children: child.children.map((c) => {
|
||||
c.parentLearningUnit = learningUnit;
|
||||
c.parentLearningSequence = learningSequence;
|
||||
return c;
|
||||
}),
|
||||
});
|
||||
learningSequence.learningUnits.push(learningUnit);
|
||||
} else if (isLearningContentType(child)) {
|
||||
if (!learningUnit) {
|
||||
throw new Error(`LearningContent found before LearningUnit ${child.slug}`);
|
||||
}
|
||||
previousLearningContent = learningContent;
|
||||
|
||||
learningContent = Object.assign(child, {
|
||||
parentCircle: circle,
|
||||
parentLearningSequence: learningSequence,
|
||||
parentLearningUnit: learningUnit,
|
||||
previousLearningContent: previousLearningContent,
|
||||
});
|
||||
|
||||
if (previousLearningContent) {
|
||||
previousLearningContent.nextLearningContent = learningContent;
|
||||
}
|
||||
|
||||
learningUnit.learningContents.push(child);
|
||||
} else {
|
||||
log.error("Unknown CircleChild found...", child);
|
||||
throw new Error("Unknown CircleChild found...");
|
||||
}
|
||||
});
|
||||
|
||||
if (learningUnit) {
|
||||
learningUnit.last = true;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Finished with LearningContent but there is no LearningSequence and LearningUnit"
|
||||
);
|
||||
}
|
||||
|
||||
// sum minutes
|
||||
result.forEach((learningSequence) => {
|
||||
learningSequence.minutes = 0;
|
||||
learningSequence.learningUnits.forEach((learningUnit) => {
|
||||
learningUnit.minutes = 0;
|
||||
learningUnit.learningContents.forEach((learningContent) => {
|
||||
learningUnit.minutes += learningContent.minutes;
|
||||
});
|
||||
learningSequence.minutes += learningUnit.minutes;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export class OldCircle implements WagtailCircle {
|
||||
readonly content_type = "learnpath.Circle";
|
||||
readonly learningSequences: LearningSequence[];
|
||||
|
||||
nextCircle?: OldCircle;
|
||||
previousCircle?: OldCircle;
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
public readonly slug: string,
|
||||
public readonly title: string,
|
||||
public readonly translation_key: string,
|
||||
public readonly frontend_url: string,
|
||||
public readonly description: string,
|
||||
public readonly children: CircleChild[],
|
||||
public readonly goal_description: string,
|
||||
public readonly goals: CircleGoal[],
|
||||
public readonly job_situation_description: string,
|
||||
public readonly job_situations: CircleJobSituation[],
|
||||
public readonly parentLearningPath?: LearningPath
|
||||
) {
|
||||
this.learningSequences = parseLearningSequences(this, this.children);
|
||||
}
|
||||
|
||||
public static fromJson(
|
||||
wagtailCircle: WagtailCircle,
|
||||
learningPath?: LearningPath
|
||||
): OldCircle {
|
||||
// TODO add error checking when the data does not conform to the schema
|
||||
return new OldCircle(
|
||||
wagtailCircle.id,
|
||||
wagtailCircle.slug,
|
||||
wagtailCircle.title,
|
||||
wagtailCircle.translation_key,
|
||||
wagtailCircle.frontend_url,
|
||||
wagtailCircle.description,
|
||||
wagtailCircle.children,
|
||||
wagtailCircle.goal_description,
|
||||
wagtailCircle.goals,
|
||||
wagtailCircle.job_situation_description,
|
||||
wagtailCircle.job_situations,
|
||||
learningPath
|
||||
);
|
||||
}
|
||||
|
||||
public get flatChildren(): (
|
||||
| LearningContentInterface
|
||||
| LearningUnitPerformanceCriteria
|
||||
)[] {
|
||||
const result: (LearningContentInterface | LearningUnitPerformanceCriteria)[] = [];
|
||||
this.learningSequences.forEach((learningSequence) => {
|
||||
learningSequence.learningUnits.forEach((learningUnit) => {
|
||||
learningUnit.children.forEach((performanceCriteria) => {
|
||||
result.push(performanceCriteria);
|
||||
});
|
||||
learningUnit.learningContents.forEach((learningContent) => {
|
||||
result.push(learningContent);
|
||||
});
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public get flatLearningContents(): LearningContent[] {
|
||||
const result: LearningContent[] = [];
|
||||
this.learningSequences.forEach((learningSequence) => {
|
||||
learningSequence.learningUnits.forEach((learningUnit) => {
|
||||
learningUnit.learningContents.forEach((learningContent) => {
|
||||
result.push(learningContent);
|
||||
});
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public get flatLearningUnits(): LearningUnit[] {
|
||||
const result: LearningUnit[] = [];
|
||||
this.learningSequences.forEach((learningSequence) => {
|
||||
learningSequence.learningUnits.forEach((learningUnit) => {
|
||||
result.push(learningUnit);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public someFinishedInLearningSequence(translationKey: string): boolean {
|
||||
if (translationKey) {
|
||||
return (
|
||||
this.flatChildren.filter((lc) => {
|
||||
return (
|
||||
lc.completion_status === "SUCCESS" &&
|
||||
lc.parentLearningSequence?.translation_key === translationKey
|
||||
);
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public allFinishedInLearningSequence(translationKey: string): boolean {
|
||||
if (translationKey) {
|
||||
const [performanceCriteria, learningContents] = partition(
|
||||
this.flatChildren.filter(
|
||||
(lc) => lc.parentLearningSequence?.translation_key === translationKey
|
||||
),
|
||||
function (child) {
|
||||
return child.content_type === "competence.PerformanceCriteria";
|
||||
}
|
||||
);
|
||||
|
||||
const groupedPerformanceCriteria = values(
|
||||
groupBy(performanceCriteria, (pc) => pc.parentLearningUnit?.id)
|
||||
);
|
||||
|
||||
return (
|
||||
learningContents.every((lc) => lc.completion_status === "SUCCESS") &&
|
||||
(groupedPerformanceCriteria.length === 0 ||
|
||||
groupedPerformanceCriteria.every((group) =>
|
||||
group.every(
|
||||
(pc) =>
|
||||
pc.completion_status === "SUCCESS" || pc.completion_status === "FAIL"
|
||||
)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public parseCompletionData(completionData: CourseCompletion[]) {
|
||||
this.flatChildren.forEach((page) => {
|
||||
const pageIndex = completionData.findIndex((e) => {
|
||||
return e.page_id === page.id;
|
||||
});
|
||||
if (pageIndex >= 0) {
|
||||
page.completion_status = completionData[pageIndex].completion_status;
|
||||
} else {
|
||||
page.completion_status = "UNKNOWN";
|
||||
}
|
||||
});
|
||||
|
||||
if (this.parentLearningPath) {
|
||||
this.parentLearningPath.calcNextLearningContent(completionData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Circle } from "@/services/circle";
|
||||
import type { OldCircle } from "@/services/oldCircle";
|
||||
import { useCompletionStore } from "@/stores/completion";
|
||||
import { useLearningPathStore } from "@/stores/learningPath";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
|
@ -14,7 +14,7 @@ import { defineStore } from "pinia";
|
|||
import type { RouteLocationNormalized } from "vue-router";
|
||||
|
||||
export type CircleStoreState = {
|
||||
circle: Circle | undefined;
|
||||
circle: OldCircle | undefined;
|
||||
page: "INDEX" | "OVERVIEW";
|
||||
};
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ export const useCircleStore = defineStore({
|
|||
courseSlug: string,
|
||||
circleSlug: string,
|
||||
userId: string | undefined = undefined
|
||||
): Promise<Circle> {
|
||||
): Promise<OldCircle> {
|
||||
if (!userId) {
|
||||
const userStore = useUserStore();
|
||||
userId = userStore.id;
|
||||
|
|
@ -158,21 +158,6 @@ export const useCircleStore = defineStore({
|
|||
});
|
||||
}
|
||||
},
|
||||
calcSelfEvaluationStatus(learningUnit: LearningUnit): CourseCompletionStatus {
|
||||
if (learningUnit.children.length > 0) {
|
||||
if (learningUnit.children.every((q) => q.completion_status === "SUCCESS")) {
|
||||
return "SUCCESS";
|
||||
}
|
||||
if (
|
||||
learningUnit.children.every(
|
||||
(q) => q.completion_status === "FAIL" || q.completion_status === "SUCCESS"
|
||||
)
|
||||
) {
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
return "UNKNOWN";
|
||||
},
|
||||
continueFromLearningContent(
|
||||
currentLearningContent: LearningContentInterface,
|
||||
returnRoute?: RouteLocationNormalized
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { bustItGetCache, itGetCached, itPost } from "@/fetchHelpers";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { BaseCourseWagtailPage, CourseCompletion } from "@/types";
|
||||
import type {
|
||||
CourseCompletion,
|
||||
LearningContentWithCompletion,
|
||||
LearningUnitPerformanceCriteria,
|
||||
} from "@/types";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useCompletionStore = defineStore({
|
||||
|
|
@ -29,7 +33,7 @@ export const useCompletionStore = defineStore({
|
|||
return userCompletionData || [];
|
||||
},
|
||||
async markPage(
|
||||
page: BaseCourseWagtailPage,
|
||||
page: LearningContentWithCompletion | LearningUnitPerformanceCriteria,
|
||||
userId: string | undefined = undefined,
|
||||
courseSessionId: string | undefined = undefined
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import log from "loglevel";
|
|||
import { defineStore } from "pinia";
|
||||
import { computed, reactive } from "vue";
|
||||
|
||||
// FIXME: remove
|
||||
export type LearningPathStoreState = {
|
||||
learningPaths: Map<string, LearningPath>;
|
||||
page: "INDEX" | "OVERVIEW";
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import type {
|
|||
LearningPathObjectType,
|
||||
LearningSequenceObjectType,
|
||||
LearningUnitObjectType,
|
||||
PerformanceCriteriaObjectType,
|
||||
TopicObjectType,
|
||||
} from "@/gql/graphql";
|
||||
import type { Component } from "vue";
|
||||
|
|
@ -26,6 +27,11 @@ export type LoginMethod = "local" | "sso";
|
|||
|
||||
export type CourseCompletionStatus = "UNKNOWN" | "FAIL" | "SUCCESS";
|
||||
|
||||
export type Completable = {
|
||||
completion_status?: CourseCompletionStatus;
|
||||
completion_status_updated_at?: string;
|
||||
};
|
||||
|
||||
export interface BaseCourseWagtailPage {
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
|
|
@ -33,8 +39,6 @@ export interface BaseCourseWagtailPage {
|
|||
readonly content_type: string;
|
||||
readonly translation_key: string;
|
||||
readonly frontend_url: string;
|
||||
completion_status?: CourseCompletionStatus;
|
||||
completion_status_updated_at?: string;
|
||||
}
|
||||
|
||||
export interface CircleLight {
|
||||
|
|
@ -97,14 +101,17 @@ export type LearningContent =
|
|||
| LearningContentRichText
|
||||
| LearningContentVideo;
|
||||
|
||||
export type LearningContentWithCompletion = LearningContent & Completable;
|
||||
|
||||
export type LearningContentContentType = LearningContent["content_type"];
|
||||
|
||||
export type LearningUnit = Omit<
|
||||
LearningUnitObjectType,
|
||||
"content_type" | "learning_contents"
|
||||
"content_type" | "learning_contents" | "performance_criteria"
|
||||
> & {
|
||||
content_type: "learnpath.LearningUnit";
|
||||
learning_contents: LearningContent[];
|
||||
learning_contents: LearningContentWithCompletion[];
|
||||
performance_criteria: LearningUnitPerformanceCriteria[];
|
||||
};
|
||||
|
||||
export type LearningSequence = Omit<
|
||||
|
|
@ -136,12 +143,13 @@ export type LearningPathType = Omit<
|
|||
topics: TopicType[];
|
||||
};
|
||||
|
||||
export interface LearningUnitPerformanceCriteria extends BaseCourseWagtailPage {
|
||||
export type LearningUnitPerformanceCriteria = Omit<
|
||||
PerformanceCriteriaObjectType,
|
||||
"content_type"
|
||||
> &
|
||||
Completable & {
|
||||
readonly content_type: "competence.PerformanceCriteria";
|
||||
readonly competence_id: string;
|
||||
parentLearningSequence?: LearningSequence;
|
||||
parentLearningUnit?: LearningUnit;
|
||||
}
|
||||
};
|
||||
|
||||
export interface CourseCompletion {
|
||||
readonly id: string;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from vbv_lernwelt.assignment.graphql.types import AssignmentObjectType
|
|||
from vbv_lernwelt.competence.models import (
|
||||
CompetenceCertificate,
|
||||
CompetenceCertificateList,
|
||||
PerformanceCriteria,
|
||||
)
|
||||
from vbv_lernwelt.course.graphql.interfaces import CoursePageInterface
|
||||
|
||||
|
|
@ -34,3 +35,10 @@ class CompetenceCertificateListObjectType(DjangoObjectType):
|
|||
|
||||
def resolve_competence_certificates(self, info):
|
||||
return CompetenceCertificate.objects.child_of(self)
|
||||
|
||||
|
||||
class PerformanceCriteriaObjectType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = PerformanceCriteria
|
||||
interfaces = (CoursePageInterface,)
|
||||
fields = ["competence_id"]
|
||||
|
|
|
|||
|
|
@ -141,6 +141,9 @@ class PerformanceCriteria(CourseBasePage):
|
|||
FieldPanel("learning_unit"),
|
||||
]
|
||||
|
||||
def get_frontend_url(self):
|
||||
return ""
|
||||
|
||||
def save(self, clean=True, user=None, log_action=False, **kwargs):
|
||||
profile_parent = (
|
||||
self.get_ancestors().exact_type(ActionCompetenceListPage).last()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from rest_framework.test import APITestCase
|
|||
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||
from vbv_lernwelt.course.models import CourseSession
|
||||
from vbv_lernwelt.feedback.factories import FeedbackResponseFactory
|
||||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||
from vbv_lernwelt.learnpath.models import Circle
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ logger = structlog.get_logger(__name__)
|
|||
|
||||
class LearningContentInterface(CoursePageInterface):
|
||||
minutes = graphene.Int()
|
||||
description = graphene.String()
|
||||
content = graphene.String()
|
||||
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
|
||||
)
|
||||
|
|
@ -186,6 +187,12 @@ class LearningUnitObjectType(DjangoObjectType):
|
|||
learning_contents = graphene.List(
|
||||
graphene.NonNull(LearningContentInterface), required=True
|
||||
)
|
||||
performance_criteria = graphene.List(
|
||||
graphene.NonNull(
|
||||
"vbv_lernwelt.competence.graphql.types.PerformanceCriteriaObjectType"
|
||||
),
|
||||
required=True,
|
||||
)
|
||||
evaluate_url = graphene.String(required=True)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -197,6 +204,10 @@ class LearningUnitObjectType(DjangoObjectType):
|
|||
def resolve_evaluate_url(root: LearningUnit, info, **kwargs):
|
||||
return root.get_evaluate_url()
|
||||
|
||||
@staticmethod
|
||||
def resolve_performance_criteria(root: LearningUnit, info, **kwargs):
|
||||
return root.performancecriteria_set.all()
|
||||
|
||||
@staticmethod
|
||||
def resolve_learning_contents(root: LearningUnit, info, **kwargs):
|
||||
siblings = None
|
||||
|
|
|
|||
Loading…
Reference in New Issue