import type { LearningPath } from "@/services/learningPath"; import type { CircleChild, CircleGoal, CircleJobSituation, CourseCompletion, CourseCompletionStatus, CourseWagtailPage, LearningContent, LearningSequence, LearningUnit, LearningUnitPerformanceCriteria, } from "@/types"; function _createEmptyLearningUnit( parentLearningSequence: LearningSequence ): LearningUnit { return { id: 0, title: "", slug: "", translation_key: "", type: "learnpath.LearningUnit", frontend_url: "", learningContents: [], minutes: 0, parentLearningSequence: parentLearningSequence, children: [], last: true, completion_status: "unknown", evaluate_url: "", }; } 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.type === "learnpath.LearningSequence") { if (learningSequence) { if (learningUnit) { learningUnit.last = true; learningSequence.learningUnits.push(learningUnit); } result.push(learningSequence); } learningSequence = Object.assign(child, { learningUnits: [] }); // initialize empty learning unit if there will not come a learning unit next learningUnit = _createEmptyLearningUnit(learningSequence); } else if (child.type === "learnpath.LearningUnit") { if (!learningSequence) { throw new Error("LearningUnit found before LearningSequence"); } if (learningUnit && learningUnit.learningContents.length) { learningSequence.learningUnits.push(learningUnit); } learningUnit = Object.assign(child, { learningContents: [], parentLearningSequence: learningSequence, parentCircle: circle, children: child.children.map((c) => { c.parentLearningUnit = learningUnit; c.parentLearningSequence = learningSequence; return c; }), }); } else if (child.type === "learnpath.LearningContent") { if (!learningUnit) { throw new Error("LearningContent found before LearningUnit"); } previousLearningContent = learningContent; learningContent = Object.assign(child, { parentCircle: circle, parentLearningSequence: learningSequence, parentLearningUnit: learningUnit, previousLearningContent: previousLearningContent, }); if (previousLearningContent) { previousLearningContent.nextLearningContent = learningContent; } learningUnit.learningContents.push(child); } }); if (learningUnit && learningSequence) { // TypeScript does not get it here... learningUnit.last = true; (learningSequence as LearningSequence).learningUnits.push(learningUnit); result.push(learningSequence); } 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 CourseWagtailPage { readonly type = "learnpath.Circle"; readonly learningSequences: LearningSequence[]; completion_status: CourseCompletionStatus = "unknown"; nextCircle?: Circle; previousCircle?: Circle; constructor( public readonly id: number, public readonly slug: string, public readonly title: string, public readonly translation_key: string, public readonly frontend_url: string, public readonly description: string, public children: CircleChild[], public goal_description: string, public goals: CircleGoal[], public job_situation_description: string, public job_situations: CircleJobSituation[], public readonly parentLearningPath?: LearningPath ) { this.learningSequences = parseLearningSequences(this, this.children); } public static fromJson(json: any, learningPath?: LearningPath): Circle { // TODO add error checking when the data does not conform to the schema return new Circle( json.id, json.slug, json.title, json.translation_key, json.frontend_url, json.description, json.children, json.goal_description, json.goals, json.job_situation_description, json.job_situations, learningPath ); } public get flatChildren(): (LearningContent | LearningUnitPerformanceCriteria)[] { const result: (LearningContent | 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 finishedContents = this.flatChildren.filter((lc) => { return ( lc.completion_status === "success" && lc.parentLearningSequence?.translation_key === translationKey ); }).length; const totalContents = this.flatChildren.filter((lc) => { return lc.parentLearningSequence?.translation_key === translationKey; }).length; return finishedContents === totalContents; } return false; } public parseCompletionData(completionData: CourseCompletion[]) { this.flatChildren.forEach((page) => { const pageIndex = completionData.findIndex((e) => { return e.page_key === page.translation_key; }); if (pageIndex >= 0) { page.completion_status = completionData[pageIndex].completion_status; } else { page.completion_status = "unknown"; } }); if (this.parentLearningPath) { this.parentLearningPath.calcNextLearningContent(completionData); } } }