vbv/client/src/services/circle.ts

254 lines
7.5 KiB
TypeScript

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);
}
}
}