simplified code and added documentation
This commit is contained in:
+60
-4
@@ -1,3 +1,30 @@
|
||||
/**
|
||||
* Formats the raw WebUntis JSON-RPC timetable response into a compact list of lessons.
|
||||
*
|
||||
* Output schema (per entry):
|
||||
* - room: string
|
||||
* - teacher: string
|
||||
* - subject: string
|
||||
* - startTime: "HH:MM" | "–"
|
||||
* - endTime: "HH:MM" | "–"
|
||||
* - weekday: German weekday name | "–"
|
||||
* - date: "DD.MM.YYYY" | "–"
|
||||
* - cancelled: boolean
|
||||
*
|
||||
* Business rules implemented here:
|
||||
* 1) SZ normalization:
|
||||
* If multiple lessons share the same day + same start time and at least one subject starts
|
||||
* with "SZ" (e.g. "SZ1", "SZ10"), the whole slot becomes exactly ONE entry with:
|
||||
* subject="SZ", room="-", teacher="-".
|
||||
* 2) Cancelled flag:
|
||||
* If the raw item contains code="cancelled", cancelled=true, otherwise cancelled=false.
|
||||
* 3) Double-lesson merging:
|
||||
* Consecutive lessons with same (date, weekday, subject, teacher, room) are merged into one
|
||||
* block by extending endTime. A small gap (break) up to MERGE_GAP_MINUTES is allowed.
|
||||
*
|
||||
* @param {any} rawData WebUntis JSON-RPC response (expects rawData.result to be an array)
|
||||
* @returns {Array<{room:string,teacher:string,subject:string,startTime:string,endTime:string,weekday:string,date:string,cancelled:boolean}>}
|
||||
*/
|
||||
export const formatData = (rawData) => {
|
||||
const weekdays = [
|
||||
"Sonntag",
|
||||
@@ -9,8 +36,16 @@ export const formatData = (rawData) => {
|
||||
"Samstag",
|
||||
];
|
||||
|
||||
/**
|
||||
* Allowed gap (in minutes) between two blocks to still merge them into a double lesson.
|
||||
* Example: 15:30 -> 15:35 (5 min break) will still merge.
|
||||
*/
|
||||
const MERGE_GAP_MINUTES = 15;
|
||||
|
||||
/**
|
||||
* WebUntis times are typically numbers like 800, 855, 1435.
|
||||
* Converts to "HH:MM". Returns "–" for missing/invalid values.
|
||||
*/
|
||||
function formatTime(time) {
|
||||
if (time === null || time === undefined) return "–";
|
||||
const n = Number(time);
|
||||
@@ -19,6 +54,10 @@ export const formatData = (rawData) => {
|
||||
return str.slice(0, 2) + ":" + str.slice(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* WebUntis dates are typically numbers like 20260417.
|
||||
* Converts to "DD.MM.YYYY". Returns "–" for missing/invalid values.
|
||||
*/
|
||||
function formatDate(date) {
|
||||
if (date === null || date === undefined) return "–";
|
||||
const str = String(date);
|
||||
@@ -26,6 +65,10 @@ export const formatData = (rawData) => {
|
||||
return str.slice(6, 8) + "." + str.slice(4, 6) + "." + str.slice(0, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns German weekday name for a WebUntis date (YYYYMMDD).
|
||||
* Returns "–" for missing/invalid values.
|
||||
*/
|
||||
function getWeekday(date) {
|
||||
if (date === null || date === undefined) return "–";
|
||||
const str = String(date);
|
||||
@@ -35,6 +78,10 @@ export const formatData = (rawData) => {
|
||||
return weekdays[d.getDay()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses "HH:MM" into minutes since midnight.
|
||||
* Returns null for "–" or invalid values.
|
||||
*/
|
||||
function parseTimeToMinutes(timeStr) {
|
||||
if (!timeStr || timeStr === "–") return null;
|
||||
const [h, m] = String(timeStr).split(":");
|
||||
@@ -45,6 +92,10 @@ export const formatData = (rawData) => {
|
||||
return hh * 60 + mm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a formatted date "DD.MM.YYYY" to a sortable numeric key YYYYMMDD.
|
||||
* Returns null for "–" or invalid values.
|
||||
*/
|
||||
function dateKeyFromFormatted(dateStr) {
|
||||
if (!dateStr || dateStr === "–") return null;
|
||||
const [dd, mm, yyyy] = String(dateStr).split(".");
|
||||
@@ -57,16 +108,19 @@ export const formatData = (rawData) => {
|
||||
return y * 10000 + m * 100 + d;
|
||||
}
|
||||
|
||||
/** @type {any[]} */
|
||||
const items = rawData?.result ?? [];
|
||||
const lessons = [];
|
||||
|
||||
for (const item of items) {
|
||||
if (item?.activityType !== "Unterricht") continue;
|
||||
|
||||
// Subject name is typically in item.su[0].name.
|
||||
const subject = item?.su?.[0]?.name ?? "–";
|
||||
const normalizedSubject = String(subject).trimStart();
|
||||
const isSz = normalizedSubject.startsWith("SZ");
|
||||
|
||||
// WebUntis uses item.code === "cancelled" for cancelled lessons.
|
||||
const cancelled =
|
||||
String(item?.code ?? "")
|
||||
.trim()
|
||||
@@ -97,8 +151,8 @@ export const formatData = (rawData) => {
|
||||
return a._endTimeRaw - b._endTimeRaw;
|
||||
});
|
||||
|
||||
// Group by same day + same start time.
|
||||
// If any subject in a group starts with "SZ", collapse the whole group into the SZ lesson.
|
||||
// 1) Group by same day + same start time.
|
||||
// If any subject in a group starts with "SZ", collapse the whole group into ONE SZ entry.
|
||||
const groups = new Map();
|
||||
for (const lesson of lessons) {
|
||||
const key = `${lesson._dateRaw}-${lesson._startTimeRaw}`;
|
||||
@@ -126,6 +180,8 @@ export const formatData = (rawData) => {
|
||||
(max, l) => (l._endTimeRaw > max ? l._endTimeRaw : max),
|
||||
0,
|
||||
);
|
||||
// SZ cancelled only if ALL underlying entries are cancelled.
|
||||
// If at least one is not cancelled, the SZ block is considered not cancelled.
|
||||
const cancelled = group.every((l) => l.cancelled);
|
||||
|
||||
timetable.push({
|
||||
@@ -140,8 +196,8 @@ export const formatData = (rawData) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Merge double lessons (same subject/teacher/room/day) that are consecutive
|
||||
// (allows a short gap, e.g. breaks).
|
||||
// 2) Merge double lessons: group by (date, weekday, subject, teacher, room)
|
||||
// and merge consecutive time blocks within each group.
|
||||
const bucketKey = (e) =>
|
||||
`${e.date}|${e.weekday}|${e.subject}|${e.teacher}|${e.room}`;
|
||||
|
||||
|
||||
@@ -163,10 +163,6 @@ app.get("/api/get-timetable/", async (req, res) => {
|
||||
console.error("formatData failed", formatError);
|
||||
res.status(500).json({ error: "Failed to format timetable data" });
|
||||
}
|
||||
|
||||
// respond to client
|
||||
res.json(timetableResponse.data?.result ?? null);
|
||||
return;
|
||||
} catch (error) {
|
||||
const status = error?.response?.status;
|
||||
const data = error?.response?.data;
|
||||
|
||||
Reference in New Issue
Block a user