Crank dat!
This commit is contained in:
17 files changed
+6624
No files matched your search
@@ -0,0 +1,182 @@
|
||||
/* ==========================================================================
|
||||
librus-tui frontend - timetable view
|
||||
========================================================================== */
|
||||
|
||||
"use strict";
|
||||
|
||||
async function renderTimetable(token) {
|
||||
const data = await api(`/api/timetable?week=${state.timetableWeek}`);
|
||||
if (token !== state.loadToken) return;
|
||||
|
||||
const timetable = data.Timetable || {};
|
||||
const weekStart = parseDate(data.weekStart) || mondayOfCurrentWeek(state.timetableWeek);
|
||||
const weekEnd = parseDate(data.weekEnd) || addDays(weekStart, 6);
|
||||
const weekStartIso = isoDate(weekStart);
|
||||
const weekEndIso = isoDate(weekEnd);
|
||||
|
||||
const columns = WEEK_ORDER.filter(
|
||||
(day, index) => index < 5 || (timetable[day] || []).length > 0
|
||||
);
|
||||
|
||||
const numericLessons = [];
|
||||
let hasExtra = false;
|
||||
for (const day of columns) {
|
||||
for (const entry of timetable[day] || []) {
|
||||
const raw = String(entry.Lesson ?? "");
|
||||
if (/^\d+$/.test(raw)) {
|
||||
numericLessons.push(Number(raw));
|
||||
} else {
|
||||
hasExtra = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
const maxLesson = Math.max(8, ...numericLessons);
|
||||
const rows = [];
|
||||
for (let number = 1; number <= maxLesson; number += 1) rows.push(String(number));
|
||||
if (hasExtra) rows.push("extra");
|
||||
|
||||
const grid = {};
|
||||
for (const day of columns) {
|
||||
grid[day] = new Map();
|
||||
for (const entry of timetable[day] || []) {
|
||||
const raw = String(entry.Lesson ?? "");
|
||||
const key = /^\d+$/.test(raw) ? raw : "extra";
|
||||
if (!grid[day].has(key)) grid[day].set(key, []);
|
||||
grid[day].get(key).push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
const today = isoDate();
|
||||
const weekLabel =
|
||||
state.timetableWeek === 0
|
||||
? "current week"
|
||||
: state.timetableWeek === 1
|
||||
? "next week"
|
||||
: `week +${state.timetableWeek}`;
|
||||
|
||||
const header = columns
|
||||
.map((day) => {
|
||||
const index = WEEK_ORDER.indexOf(day);
|
||||
const date = addDays(weekStart, index);
|
||||
const dateIso = isoDate(date);
|
||||
return `
|
||||
<div class="tt-head ${dateIso === today ? "today" : ""}">
|
||||
<span>${esc(day.slice(0, 3))}</span>
|
||||
<span class="date">${esc(fmtDate(dateIso, false))}</span>
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const body = rows
|
||||
.map((row) => {
|
||||
let timeLabel = "";
|
||||
if (row !== "extra") {
|
||||
for (const day of columns) {
|
||||
const entry = (grid[day].get(row) || [])[0];
|
||||
if (entry && entry.HourFrom) {
|
||||
timeLabel = shortTime(entry.HourFrom);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const day of columns) {
|
||||
const entry = (grid[day].get("extra") || [])[0];
|
||||
if (entry && entry.HourFrom) {
|
||||
timeLabel = shortTime(entry.HourFrom);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hourCell = `
|
||||
<div class="tt-hour">
|
||||
<b>${row === "extra" ? "+" : esc(row)}</b>
|
||||
<span>${esc(timeLabel)}</span>
|
||||
</div>`;
|
||||
|
||||
const cells = columns
|
||||
.map((day) => {
|
||||
const entries = grid[day].get(row) || [];
|
||||
if (!entries.length) {
|
||||
return `<div class="tt-cell"><div class="tt-free">·</div></div>`;
|
||||
}
|
||||
return `<div class="tt-cell">${entries.map(renderTimetableEntry).join("")}</div>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
return hourCell + cells;
|
||||
})
|
||||
.join("");
|
||||
|
||||
$("#view").innerHTML = `
|
||||
<div class="view-inner">
|
||||
<div class="toolbar">
|
||||
<button class="btn btn-sm" type="button" data-action="tt-prev" ${state.timetableWeek <= 0 ? "disabled" : ""}>[ < prev ]</button>
|
||||
<button class="btn btn-sm" type="button" data-action="tt-next" ${state.timetableWeek >= 3 ? "disabled" : ""}>[ next > ]</button>
|
||||
<span><b>${esc(weekLabel)}</b></span>
|
||||
<span class="dim">${esc(fmtDate(weekStartIso))} → ${esc(fmtDate(weekEndIso))}</span>
|
||||
<span class="grow"></span>
|
||||
<span class="dim"><kbd>[</kbd> <kbd>]</kbd> switch week</span>
|
||||
</div>
|
||||
<div class="timetable-scroll">
|
||||
<div class="timetable" style="grid-template-columns: 60px repeat(${columns.length}, minmax(150px, 1fr))">
|
||||
<div class="tt-hour corner">lesson</div>
|
||||
${header}
|
||||
${body}
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar" style="margin-top:10px">
|
||||
<span class="dim">
|
||||
<span class="tag tag-amber">SUB</span> substitution ·
|
||||
<span class="tag tag-red">CNL</span> cancelled ·
|
||||
<span class="tag tag-cyan">EXTRA</span> additional activity
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderTimetableEntry(entry) {
|
||||
const flags = [];
|
||||
if (entry.isSubstitution) flags.push(`<span class="tt-flag sub">SUB</span>`);
|
||||
if (entry.isCancelled) flags.push(`<span class="tt-flag cancel">CNL</span>`);
|
||||
if (entry.isAdditional) flags.push(`<span class="tt-flag extra">EXTRA</span>`);
|
||||
|
||||
const teacher = personName(entry.Teacher);
|
||||
const title = [
|
||||
entry.Subject,
|
||||
timeRange(entry.HourFrom, entry.HourTo),
|
||||
entry.Classroom ? `room ${entry.Classroom}` : "",
|
||||
teacher,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
|
||||
return `
|
||||
<div class="tt-lesson ${entry.isCancelled ? "cancelled" : ""}" title="${esc(title)}">
|
||||
<span class="tt-subject">${esc(entry.Subject || "lesson")}</span>
|
||||
<span class="tt-meta">${esc(timeRange(entry.HourFrom, entry.HourTo))}</span>
|
||||
<span class="tt-meta"><span class="tt-room">${esc(entry.Classroom || "?")}</span>${teacher ? ` · ${esc(teacher)}` : ""}</span>
|
||||
${flags.length ? `<span class="tt-flags">${flags.join("")}</span>` : ""}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function mondayOfCurrentWeek(week = 0) {
|
||||
const today = new Date();
|
||||
const monday = addDays(today, -((today.getDay() + 6) % 7) + week * 7);
|
||||
monday.setHours(0, 0, 0, 0);
|
||||
return monday;
|
||||
}
|
||||
|
||||
function addDays(date, days) {
|
||||
const copy = new Date(date.getTime());
|
||||
copy.setDate(copy.getDate() + days);
|
||||
return copy;
|
||||
}
|
||||
|
||||
function changeTimetableWeek(delta) {
|
||||
const next = Math.min(3, Math.max(0, state.timetableWeek + delta));
|
||||
if (next === state.timetableWeek) return;
|
||||
state.timetableWeek = next;
|
||||
navigate("timetable");
|
||||
}
|
||||
Reference in new issue
Block a user