/* ========================================================================== 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) => `
${esc(item.Name || "free day")} ${esc(countdownLabel(item.DateFrom))} · ${esc(fmtDateShort(item.DateFrom))}
` ) .join("") : `
no upcoming free days
`; const lessonsHtml = renderDashboardLessons(lessons, isToday); const html = `
student profile ${esc(me.Type || "synergia")}
${esc(me.FirstName || "")} ${esc(me.LastName || "")}
class ${esc(me.Class || "—")} · tutor ${esc(personName(me.Tutor) || "—")}
school
${esc(school.Name || "—")}
address
${esc(schoolAddress(school))}
year
${esc(fmtDate(me.SchoolYearStarts))} → ${esc(fmtDate(me.SchoolYearEnds))}
semester
ends ${esc(fmtDate(me.SchoolYearMiddles))}
lucky numberdaily draw
${lucky === null || lucky === undefined ? "–" : esc(lucky)}
${lucky === null || lucky === undefined ? "not drawn yet" : "today's number"}
behavioursemester 1
${behaviour ? esc(titleCase(behaviour.Grade)) : "—"}
${behaviour ? esc(personName(behaviour.Teacher) || "—") : "no grade yet"}
counters click to open a section
${isToday ? "today" : "next school day"} · ${esc(dayName)} ${esc(fmtDate(dayDate))}${isToday ? "" : " · coming up"}
${lessonsHtml}
upcoming breaks ${upcomingFree.length} ahead
${freeRows}
`; $("#view").innerHTML = html; } function renderDashboardLessons(lessons, isToday) { if (!lessons || !lessons.length) { return `
no lessons
`; } 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 `
${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 `
${esc(shortTime(lesson.HourFrom) || "—")} ${marker} ${esc(lesson.Subject || "lesson")} ${esc(lesson.Classroom || "")}${lesson.isCancelled ? " [cancelled]" : ""}
`; }) .join("")}
`; }