finished backend
This commit is contained in:
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user