217 lines
6.3 KiB
JavaScript
217 lines
6.3 KiB
JavaScript
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;
|
||
};
|