mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-16 08:27:07 +00:00
214 lines
5.8 KiB
TypeScript
214 lines
5.8 KiB
TypeScript
export interface OverviewSubject {
|
|
code: string;
|
|
programme: number;
|
|
metaclass: number;
|
|
title: string;
|
|
}
|
|
|
|
function isActiveTermFlag(active: unknown): boolean {
|
|
return active === 1 || active === true;
|
|
}
|
|
|
|
export function normalizeOverviewSubject(raw: unknown): OverviewSubject | null {
|
|
if (!raw || typeof raw !== "object") return null;
|
|
|
|
const subject = raw as Record<string, unknown>;
|
|
const programme = Number(subject.programme ?? subject.programmeID);
|
|
const metaclass = Number(subject.metaclass ?? subject.metaclassID);
|
|
if (!programme || !metaclass || Number.isNaN(programme) || Number.isNaN(metaclass)) {
|
|
return null;
|
|
}
|
|
|
|
const code = String(subject.code ?? subject.subject ?? "").trim();
|
|
if (!code) return null;
|
|
|
|
return {
|
|
code,
|
|
programme,
|
|
metaclass,
|
|
title: String(subject.title ?? subject.description ?? code),
|
|
};
|
|
}
|
|
|
|
/** Subjects from the active programme-year folder(s) in `/seqta/student/load/subjects`. */
|
|
export function activeSubjectsFromLearnPayload(payload: unknown): OverviewSubject[] {
|
|
if (!Array.isArray(payload)) return [];
|
|
|
|
const subjects: OverviewSubject[] = [];
|
|
const seen = new Set<string>();
|
|
|
|
for (const folder of payload) {
|
|
if (!folder || typeof folder !== "object") continue;
|
|
const term = folder as { active?: unknown; subjects?: unknown[] };
|
|
if (!isActiveTermFlag(term.active) || !Array.isArray(term.subjects)) continue;
|
|
|
|
for (const raw of term.subjects) {
|
|
const subject = normalizeOverviewSubject(raw);
|
|
if (!subject) continue;
|
|
const key = `${subject.programme}-${subject.metaclass}`;
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
subjects.push(subject);
|
|
}
|
|
}
|
|
|
|
return subjects;
|
|
}
|
|
|
|
export function activeSubjectsFromEngageChild(child: {
|
|
terms?: { active?: number; subjects?: unknown[] }[];
|
|
}): OverviewSubject[] {
|
|
const subjects: OverviewSubject[] = [];
|
|
const seen = new Set<string>();
|
|
|
|
for (const term of child.terms ?? []) {
|
|
if (term.active !== 1) continue;
|
|
for (const raw of term.subjects ?? []) {
|
|
const subject = normalizeOverviewSubject(raw);
|
|
if (!subject) continue;
|
|
const key = `${subject.programme}-${subject.metaclass}`;
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
subjects.push(subject);
|
|
}
|
|
}
|
|
|
|
return subjects;
|
|
}
|
|
|
|
export function assessmentBelongsToActiveSubjects(
|
|
assessment: Record<string, unknown>,
|
|
activeSubjects: OverviewSubject[],
|
|
): boolean {
|
|
if (!activeSubjects.length) return false;
|
|
|
|
const programme = Number(
|
|
assessment.programmeID ?? assessment.programme,
|
|
);
|
|
const metaclass = Number(
|
|
assessment.metaclassID ?? assessment.metaclass,
|
|
);
|
|
|
|
if (programme && metaclass && !Number.isNaN(programme) && !Number.isNaN(metaclass)) {
|
|
return activeSubjects.some(
|
|
(subject) =>
|
|
subject.programme === programme && subject.metaclass === metaclass,
|
|
);
|
|
}
|
|
|
|
const code = String(assessment.code ?? assessment.subject ?? "").trim();
|
|
if (!code) return false;
|
|
return activeSubjects.some((subject) => subject.code === code);
|
|
}
|
|
|
|
export function filterAssessmentsForActiveSubjects<T extends Record<string, unknown>>(
|
|
assessments: T[],
|
|
activeSubjects: OverviewSubject[],
|
|
): T[] {
|
|
return assessments.filter((assessment) =>
|
|
assessmentBelongsToActiveSubjects(assessment, activeSubjects),
|
|
);
|
|
}
|
|
|
|
export function formatDate(dateStr: string, submitted?: boolean): string {
|
|
const d = new Date(dateStr);
|
|
const now = new Date();
|
|
const diffTime = d.getTime() - now.getTime();
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffDays < 0 && !submitted) {
|
|
const overdueDays = Math.abs(diffDays);
|
|
if (overdueDays === 1) return "1 day overdue";
|
|
return `${overdueDays} days overdue`;
|
|
}
|
|
|
|
if (diffDays === 0) return "Today";
|
|
|
|
if (diffDays === 1) return "Tomorrow";
|
|
|
|
if (diffDays <= 7) {
|
|
const weekdayName = d.toLocaleDateString(undefined, { weekday: "long" });
|
|
|
|
return diffDays < 0 ? `Last ${weekdayName}` : weekdayName;
|
|
}
|
|
|
|
return d.toLocaleDateString(undefined, {
|
|
weekday: "short",
|
|
month: "short",
|
|
day: "numeric",
|
|
year: d.getFullYear() !== now.getFullYear() ? "numeric" : undefined,
|
|
});
|
|
}
|
|
|
|
export function determineStatus(item: any): string {
|
|
if (
|
|
item.status === "MARKS_RELEASED" ||
|
|
item.grade ||
|
|
(item.percentage !== undefined && item.percentage !== null) ||
|
|
(item.achieved !== undefined && item.achieved !== null)
|
|
) {
|
|
return "MARKS_RELEASED";
|
|
}
|
|
|
|
const completedKey = "betterseqta-completed-assessments";
|
|
const completed = JSON.parse(localStorage.getItem(completedKey) || "[]");
|
|
if (completed.includes(item.id)) {
|
|
return "MARKS_RELEASED";
|
|
}
|
|
|
|
if (item.submitted) {
|
|
return "SUBMITTED";
|
|
}
|
|
|
|
const now = new Date();
|
|
const due = new Date(item.due);
|
|
|
|
const diffTime = due.getTime() - now.getTime();
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffDays < 0) {
|
|
return "OVERDUE";
|
|
}
|
|
|
|
if (diffDays <= 7) {
|
|
return "DUE_SOON";
|
|
}
|
|
|
|
return "UPCOMING";
|
|
}
|
|
|
|
export function getGradeValue(assessment: any): number | null {
|
|
if (
|
|
assessment.results?.percentage !== undefined &&
|
|
assessment.results.percentage !== null
|
|
) {
|
|
return assessment.results.percentage;
|
|
}
|
|
|
|
if (assessment.percentage !== undefined && assessment.percentage !== null) {
|
|
return assessment.percentage;
|
|
}
|
|
|
|
if (
|
|
assessment.achieved !== undefined &&
|
|
assessment.outOf !== undefined &&
|
|
assessment.achieved !== null &&
|
|
assessment.outOf !== null &&
|
|
assessment.outOf > 0
|
|
) {
|
|
return (assessment.achieved / assessment.outOf) * 100;
|
|
}
|
|
|
|
if (
|
|
assessment.results?.achieved !== undefined &&
|
|
assessment.results?.outOf !== undefined &&
|
|
assessment.results.achieved !== null &&
|
|
assessment.results.outOf !== null &&
|
|
assessment.results.outOf > 0
|
|
) {
|
|
return (assessment.results.achieved / assessment.results.outOf) * 100;
|
|
}
|
|
|
|
return null;
|
|
}
|