/* ========================================================================== librus-tui frontend - grades view ========================================================================== */ "use strict"; async function renderGrades(token) { const data = await api("/api/grades"); if (token !== state.loadToken) return; const subjects = data.subjects || {}; const sorted = Object.entries(subjects).sort((a, b) => String(a[0]).localeCompare(String(b[0]), "pl") ); state.gradesFlat = []; state.gradeGroups = sorted.map(([subject, grades]) => { const items = (grades || []).map((grade) => { const item = { ...grade, _subject: subject, _index: state.gradesFlat.length }; state.gradesFlat.push(item); return item; }); return { subject, grades: items }; }); const overall = averageLabel(state.gradesFlat); const gradeCount = state.gradesFlat.length; $("#view").innerHTML = `
filter average ${esc(overall)} ${gradeCount} grades ${state.gradeGroups.length} subjects
5 / 6 4 3 1 / 2 dashed chip = final · outlined = semester
`; const input = $("#grade-filter"); if (input) { input.value = state.gradeFilter; input.addEventListener("input", () => { state.gradeFilter = input.value; renderGradesList(); }); } renderGradesList(); } function gradeWeightLabel(weight) { const raw = String(weight ?? "").toLowerCase(); if (!raw || raw === "none" || raw === "null") return ""; return `w${raw}`; } function renderGradesList() { const container = $("#grades-list"); if (!container) return; const query = state.gradeFilter.trim().toLowerCase(); const groups = state.gradeGroups.filter((group) => { if (!query) return true; if (group.subject.toLowerCase().includes(query)) return true; return group.grades.some((grade) => `${grade.Category || ""} ${grade.Grade || ""}`.toLowerCase().includes(query) ); }); if (!groups.length) { container.innerHTML = `
no matching grades
`; return; } container.innerHTML = groups .map((group) => { const average = averageLabel(group.grades); const chips = group.grades.length ? group.grades .map((grade) => { const weight = gradeWeightLabel(grade.Weight); const classes = ["grade-chip", gradeClass(grade.Grade)]; if (grade.isFinal) classes.push("final"); if (grade.isSemester) classes.push("semester"); const label = `${group.subject} · ${grade.Grade} · ${grade.Category || "grade"} · ${fmtDate(grade.AddDate)}`; return ` `; }) .join("") : `— no grades yet —`; return `
${esc(group.subject)} avg ${esc(average)}
${chips}
`; }) .join(""); } function focusGradeFilter() { const input = $("#grade-filter"); if (input) { input.focus(); input.select(); } } function openGradeDetail(index) { const grade = state.gradesFlat[index]; if (!grade) return; const flags = []; if (grade.isFinal) flags.push("final"); if (grade.isSemester) flags.push("semester"); if (grade.Weight && String(grade.Weight).toLowerCase() !== "none") { flags.push(`weight ${grade.Weight}`); } const comment = grade.Comment && grade.Comment !== "No comment" ? `
${esc(grade.Comment)}
` : ""; const body = `
subject
${esc(grade._subject)}
grade
${esc(grade.Grade)}
category
${esc(grade.Category || "—")}
weight
${esc(grade.Weight ?? "—")}
added
${esc(fmtDate(grade.AddDate))}
teacher
${esc(personName(grade.AddedBy) || "—")}
semester
${esc(grade.Semester ?? "—")}${flags.length ? ` · ${esc(flags.join(" · "))}` : ""}
${comment} `; openModal(`grade ${grade.Grade} · ${grade._subject}`, body, [ { label: "[ close ]", close: true }, ]); }