finished backend

This commit is contained in:
2026-04-17 21:30:46 +02:00
parent 8d4c54fdef
commit 6889cf15bf
5 changed files with 952 additions and 49 deletions
+216
View File
@@ -0,0 +1,216 @@
export const formatData = (rawData) => {
const weekdays = [
"Sonntag",
"Montag",
"Dienstag",
"Mittwoch",
"Donnerstag",
"Freitag",
"Samstag",
];
const MERGE_GAP_MINUTES = 15;
function formatTime(time) {
if (time === null || time === undefined) return "";
const n = Number(time);
if (!Number.isFinite(n)) return "";
const str = String(time).padStart(4, "0");
return str.slice(0, 2) + ":" + str.slice(2);
}
function formatDate(date) {
if (date === null || date === undefined) return "";
const str = String(date);
if (str.length < 8) return "";
return str.slice(6, 8) + "." + str.slice(4, 6) + "." + str.slice(0, 4);
}
function getWeekday(date) {
if (date === null || date === undefined) return "";
const str = String(date);
if (str.length < 8) return "";
const d = new Date(str.slice(0, 4), str.slice(4, 6) - 1, str.slice(6, 8));
if (Number.isNaN(d.getTime())) return "";
return weekdays[d.getDay()];
}
function parseTimeToMinutes(timeStr) {
if (!timeStr || timeStr === "") return null;
const [h, m] = String(timeStr).split(":");
const hh = Number(h);
const mm = Number(m);
if (!Number.isFinite(hh) || !Number.isFinite(mm)) return null;
if (hh < 0 || hh > 23 || mm < 0 || mm > 59) return null;
return hh * 60 + mm;
}
function dateKeyFromFormatted(dateStr) {
if (!dateStr || dateStr === "") return null;
const [dd, mm, yyyy] = String(dateStr).split(".");
const d = Number(dd);
const m = Number(mm);
const y = Number(yyyy);
if (!Number.isFinite(d) || !Number.isFinite(m) || !Number.isFinite(y)) {
return null;
}
return y * 10000 + m * 100 + d;
}
const items = rawData?.result ?? [];
const lessons = [];
for (const item of items) {
if (item?.activityType !== "Unterricht") continue;
const subject = item?.su?.[0]?.name ?? "";
const normalizedSubject = String(subject).trimStart();
const isSz = normalizedSubject.startsWith("SZ");
const cancelled =
String(item?.code ?? "")
.trim()
.toLowerCase() === "cancelled";
lessons.push({
room: item?.ro?.[0]?.name ?? "",
teacher: item?.te?.[0]?.name ?? "",
subject,
startTime: formatTime(item?.startTime),
endTime: formatTime(item?.endTime),
weekday: getWeekday(item?.date),
date: formatDate(item?.date),
cancelled,
// internal fields for grouping/sorting
_dateRaw: Number(item?.date) || 0,
_startTimeRaw: Number(item?.startTime) || 0,
_endTimeRaw: Number(item?.endTime) || 0,
_isSz: isSz,
});
}
lessons.sort((a, b) => {
if (a._dateRaw !== b._dateRaw) return a._dateRaw - b._dateRaw;
if (a._startTimeRaw !== b._startTimeRaw)
return a._startTimeRaw - b._startTimeRaw;
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.
const groups = new Map();
for (const lesson of lessons) {
const key = `${lesson._dateRaw}-${lesson._startTimeRaw}`;
const group = groups.get(key);
if (group) group.push(lesson);
else groups.set(key, [lesson]);
}
const timetable = [];
for (const group of groups.values()) {
const hasSz = group.some((l) => l._isSz);
if (!hasSz) {
for (const lesson of group) {
const { _dateRaw, _startTimeRaw, _endTimeRaw, _isSz, ...publicLesson } =
lesson;
timetable.push(publicLesson);
}
continue;
}
// Normalize: if any SZ* exists, show exactly one SZ block for that slot.
const base = group.find((l) => l._isSz) ?? group[0];
const maxEndRaw = group.reduce(
(max, l) => (l._endTimeRaw > max ? l._endTimeRaw : max),
0,
);
const cancelled = group.every((l) => l.cancelled);
timetable.push({
room: "-",
teacher: "-",
subject: "SZ",
startTime: base.startTime,
endTime: maxEndRaw ? formatTime(maxEndRaw) : base.endTime,
weekday: base.weekday,
date: base.date,
cancelled,
});
}
// Merge double lessons (same subject/teacher/room/day) that are consecutive
// (allows a short gap, e.g. breaks).
const bucketKey = (e) =>
`${e.date}|${e.weekday}|${e.subject}|${e.teacher}|${e.room}`;
const buckets = new Map();
for (const entry of timetable) {
const key = bucketKey(entry);
const arr = buckets.get(key);
if (arr) arr.push(entry);
else buckets.set(key, [entry]);
}
const merged = [];
for (const arr of buckets.values()) {
arr.sort((a, b) => {
const as = parseTimeToMinutes(a.startTime) ?? 0;
const bs = parseTimeToMinutes(b.startTime) ?? 0;
if (as !== bs) return as - bs;
const ae = parseTimeToMinutes(a.endTime) ?? 0;
const be = parseTimeToMinutes(b.endTime) ?? 0;
return ae - be;
});
const mergedArr = [];
for (const entry of arr) {
const prev = mergedArr[mergedArr.length - 1];
if (!prev) {
mergedArr.push(entry);
continue;
}
const prevEnd = parseTimeToMinutes(prev.endTime);
const currStart = parseTimeToMinutes(entry.startTime);
const currEnd = parseTimeToMinutes(entry.endTime);
const canMergeTimes =
prevEnd !== null &&
currStart !== null &&
currEnd !== null &&
currStart >= prevEnd &&
currStart - prevEnd <= MERGE_GAP_MINUTES;
if (canMergeTimes) {
prev.endTime = entry.endTime;
prev.cancelled = Boolean(prev.cancelled) || Boolean(entry.cancelled);
continue;
}
mergedArr.push(entry);
}
merged.push(...mergedArr);
}
merged.sort((a, b) => {
const ad = dateKeyFromFormatted(a.date) ?? 0;
const bd = dateKeyFromFormatted(b.date) ?? 0;
if (ad !== bd) return ad - bd;
const as = parseTimeToMinutes(a.startTime) ?? 0;
const bs = parseTimeToMinutes(b.startTime) ?? 0;
if (as !== bs) return as - bs;
// stable-ish: keep similar subjects grouped
const subj = String(a.subject).localeCompare(String(b.subject));
if (subj !== 0) return subj;
const room = String(a.room).localeCompare(String(b.room));
if (room !== 0) return room;
return String(a.teacher).localeCompare(String(b.teacher));
});
return merged;
};