Crank dat!
This commit is contained in:
17 files changed
+6624
No files matched your search
@@ -0,0 +1,220 @@
|
||||
/* ==========================================================================
|
||||
librus-tui frontend - dashboard view
|
||||
========================================================================== */
|
||||
|
||||
"use strict";
|
||||
|
||||
async function renderDashboard(token) {
|
||||
const [bootstrap, notifications, timetable, freedays] = await Promise.all([
|
||||
api("/api/bootstrap"),
|
||||
api("/api/notifications").catch(() => null),
|
||||
api("/api/timetable?week=0").catch(() => null),
|
||||
api("/api/freedays").catch(() => null),
|
||||
]);
|
||||
|
||||
if (token !== state.loadToken) return;
|
||||
|
||||
applyUserChrome(bootstrap);
|
||||
|
||||
const me = bootstrap.me || {};
|
||||
const school = bootstrap.school || {};
|
||||
const behaviour = bootstrap.behaviour;
|
||||
const lucky = bootstrap.luckyNumber;
|
||||
const notif = notifications || {};
|
||||
|
||||
const now = new Date();
|
||||
const todayName = DAY_NAMES[now.getDay()];
|
||||
const todayIndex = (now.getDay() + 6) % 7; // Monday = 0
|
||||
const todayIso = isoDate(now);
|
||||
const week = (timetable && timetable.Timetable) || {};
|
||||
|
||||
let dayName = todayName;
|
||||
let lessons = week[todayName] || [];
|
||||
let dayDate = (lessons[0] && lessons[0].Date) || todayIso;
|
||||
|
||||
if (!lessons.length) {
|
||||
// Prefer the next day that still belongs to the current week.
|
||||
for (let offset = 1; offset < 7 - todayIndex; offset += 1) {
|
||||
const candidate = WEEK_ORDER[todayIndex + offset];
|
||||
const candidateLessons = week[candidate] || [];
|
||||
if (candidateLessons.length) {
|
||||
dayName = candidate;
|
||||
lessons = candidateLessons;
|
||||
dayDate = candidateLessons[0].Date || todayIso;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!lessons.length) {
|
||||
// Weekends: peek at next week so the panel is not empty.
|
||||
try {
|
||||
const nextWeek = await api("/api/timetable?week=1");
|
||||
if (token !== state.loadToken) return;
|
||||
const nextData = (nextWeek && nextWeek.Timetable) || {};
|
||||
const firstDay = WEEK_ORDER.find((day) => (nextData[day] || []).length);
|
||||
if (firstDay) {
|
||||
dayName = firstDay;
|
||||
lessons = nextData[firstDay];
|
||||
dayDate = (lessons[0] && lessons[0].Date) || todayIso;
|
||||
}
|
||||
} catch (error) {
|
||||
/* dashboard works without a preview */
|
||||
}
|
||||
}
|
||||
|
||||
const isToday = dayDate === todayIso;
|
||||
const schoolFree = ((freedays && freedays.school) || [])
|
||||
.filter((item) => item && item.DateFrom)
|
||||
.slice()
|
||||
.sort((a, b) => (parseDate(a.DateFrom) || 0) - (parseDate(b.DateFrom) || 0));
|
||||
|
||||
const upcomingFree = schoolFree
|
||||
.filter((item) => (daysUntil(item.DateFrom) ?? -1) >= 0)
|
||||
.slice(0, 4);
|
||||
|
||||
const freeRows = upcomingFree.length
|
||||
? upcomingFree
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="freeday-line" title="${esc(fmtDate(item.DateFrom))} → ${esc(fmtDate(item.DateTo))}">
|
||||
<span class="name">${esc(item.Name || "free day")}</span>
|
||||
<span class="countdown ${countdownClass(item.DateFrom)}">${esc(countdownLabel(item.DateFrom))} · ${esc(fmtDateShort(item.DateFrom))}</span>
|
||||
</div>`
|
||||
)
|
||||
.join("")
|
||||
: `<div class="empty">no upcoming free days</div>`;
|
||||
|
||||
const lessonsHtml = renderDashboardLessons(lessons, isToday);
|
||||
|
||||
const html = `
|
||||
<div class="view-inner">
|
||||
<div class="dash-grid">
|
||||
<section class="panel span-2 identity-panel">
|
||||
<div class="panel-title">
|
||||
<span>student profile</span>
|
||||
<span class="title-note">${esc(me.Type || "synergia")}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="identity-name">${esc(me.FirstName || "")} ${esc(me.LastName || "")}</div>
|
||||
<div class="identity-sub">
|
||||
<span class="class-chip">class ${esc(me.Class || "—")}</span>
|
||||
<span class="dim"> · tutor ${esc(personName(me.Tutor) || "—")}</span>
|
||||
</div>
|
||||
<dl class="kv">
|
||||
<dt>school</dt><dd>${esc(school.Name || "—")}</dd>
|
||||
<dt>address</dt><dd>${esc(schoolAddress(school))}</dd>
|
||||
<dt>year</dt><dd>${esc(fmtDate(me.SchoolYearStarts))} → ${esc(fmtDate(me.SchoolYearEnds))}</dd>
|
||||
<dt>semester</dt><dd>ends ${esc(fmtDate(me.SchoolYearMiddles))}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-title"><span>lucky number</span><span class="title-note">daily draw</span></div>
|
||||
<div class="lucky">
|
||||
<div class="lucky-number">${lucky === null || lucky === undefined ? "–" : esc(lucky)}</div>
|
||||
<div class="lucky-label">${lucky === null || lucky === undefined ? "not drawn yet" : "today's number"}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel behaviour-panel">
|
||||
<div class="panel-title"><span>behaviour</span><span class="title-note">semester 1</span></div>
|
||||
<div class="behaviour">
|
||||
<div class="behaviour-grade">${behaviour ? esc(titleCase(behaviour.Grade)) : "—"}</div>
|
||||
<div class="behaviour-teacher">${behaviour ? esc(personName(behaviour.Teacher) || "—") : "no grade yet"}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel counters-panel">
|
||||
<div class="panel-title">
|
||||
<span>counters</span>
|
||||
<span class="title-note">click to open a section</span>
|
||||
</div>
|
||||
<div class="stat-grid">
|
||||
<button class="stat" type="button" data-view="grades">
|
||||
<span class="stat-value">${notif.grades ?? "—"}</span>
|
||||
<span class="stat-label">grades</span>
|
||||
</button>
|
||||
<button class="stat" type="button" data-view="homework">
|
||||
<span class="stat-value">${notif.exams ?? "—"}</span>
|
||||
<span class="stat-label">homework</span>
|
||||
</button>
|
||||
<button class="stat warn" type="button" data-view="attendance">
|
||||
<span class="stat-value">${notif.absences ?? "—"}</span>
|
||||
<span class="stat-label">absences</span>
|
||||
</button>
|
||||
<button class="stat" type="button" data-view="freedays">
|
||||
<span class="stat-value">${notif.conferences ?? "—"}</span>
|
||||
<span class="stat-label">conferences</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel span-2 today-panel">
|
||||
<div class="panel-title">
|
||||
<span>${isToday ? "today" : "next school day"} · ${esc(dayName)}</span>
|
||||
<span class="title-note">${esc(fmtDate(dayDate))}${isToday ? "" : " · coming up"}</span>
|
||||
</div>
|
||||
<div class="panel-body tight">${lessonsHtml}</div>
|
||||
</section>
|
||||
|
||||
<section class="panel freedays-panel">
|
||||
<div class="panel-title">
|
||||
<span>upcoming breaks</span>
|
||||
<span class="title-note">${upcomingFree.length} ahead</span>
|
||||
</div>
|
||||
<div class="panel-body tight">${freeRows}</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$("#view").innerHTML = html;
|
||||
}
|
||||
|
||||
function renderDashboardLessons(lessons, isToday) {
|
||||
if (!lessons || !lessons.length) {
|
||||
return `<div class="empty">no lessons</div>`;
|
||||
}
|
||||
|
||||
const sorted = lessons
|
||||
.slice()
|
||||
.sort((a, b) => (timeToMinutes(a.HourFrom) ?? 9999) - (timeToMinutes(b.HourFrom) ?? 9999));
|
||||
|
||||
const now = new Date();
|
||||
const nowMinutes = now.getHours() * 60 + now.getMinutes();
|
||||
let currentIndex = -1;
|
||||
let nextIndex = -1;
|
||||
|
||||
if (isToday) {
|
||||
for (let index = 0; index < sorted.length; index += 1) {
|
||||
const lesson = sorted[index];
|
||||
const start = timeToMinutes(lesson.HourFrom);
|
||||
const end = timeToMinutes(lesson.HourTo);
|
||||
if (start !== null && end !== null && nowMinutes >= start && nowMinutes <= end) {
|
||||
currentIndex = index;
|
||||
} else if (nextIndex === -1 && start !== null && start > nowMinutes && !lesson.isCancelled) {
|
||||
nextIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return `<div class="lesson-list">${sorted
|
||||
.map((lesson, index) => {
|
||||
const classes = ["lesson-line"];
|
||||
if (index === currentIndex) classes.push("now");
|
||||
if (index === nextIndex) classes.push("next");
|
||||
if (lesson.isCancelled) classes.push("cancelled");
|
||||
const marker = index === currentIndex ? ">" : index === nextIndex ? "»" : "·";
|
||||
const room = [lesson.Classroom, personName(lesson.Teacher)].filter(Boolean).join(" · ");
|
||||
return `
|
||||
<div class="${classes.join(" ")}" title="${esc(room)}">
|
||||
<span class="time">${esc(shortTime(lesson.HourFrom) || "—")}</span>
|
||||
<span class="marker">${marker}</span>
|
||||
<span class="subject">${esc(lesson.Subject || "lesson")}</span>
|
||||
<span class="room">${esc(lesson.Classroom || "")}${lesson.isCancelled ? " [cancelled]" : ""}</span>
|
||||
</div>`;
|
||||
})
|
||||
.join("")}</div>`;
|
||||
}
|
||||
Reference in new issue
Block a user