/* ==========================================================================
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 = `
`;
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)}
`;
})
.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"
? ``
: "";
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 },
]);
}