266 lines
8.2 KiB
TypeScript
266 lines
8.2 KiB
TypeScript
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";
|
|
|
|
function isLearningContentType(object: any): object is LearningContent {
|
|
return (
|
|
object?.content_type === "learnpath.LearningContentAssignment" ||
|
|
object?.content_type === "learnpath.LearningContentAttendanceDay" ||
|
|
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.LearningContentTest" ||
|
|
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;
|
|
}),
|
|
});
|
|
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 {
|
|
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[];
|
|
|
|
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 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
|
|
): 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"
|
|
)
|
|
))
|
|
);
|
|
}
|
|
|
|
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_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);
|
|
}
|
|
}
|
|
}
|