// The manager's screens: every class (make, change, delete), every student, the curriculum and the subjects. A manager sees no learning data.
import { api } from "./api.js";
import { esc } from "./math.js";
import * as subjectsTab from "./subjects_tab.js";
let draw, tab = "classes", editing = null, draft = null, flash = { message: "", error: "" }, lessonId = null;
// draft: what was typed in the editor when a save was refused, so the form keeps it and the person can fix one thing
const note = () => `${flash.error && tab !== "edit" ? `
${esc(flash.error)}
` : ""}${flash.message ? `${esc(flash.message)}
` : ""}`;
const tabs = () => `
${[["classes", "כיתות"], ["edit", editing ? "עריכת כיתה" : "כיתה חדשה"], ["students", "תלמידים"], ["curriculum", "תכנית לימודים"], ["subjects", "מקצועות"]].map(([id, label]) =>
`${label} `).join("")}
`;
async function render() {
let body, failed = false;
try { // a tab whose data cannot be read says so, instead of the click doing nothing
if (tab === "edit") subjects = (await api("/api/subjects")).subjects;
body = tab === "classes" ? await classesView() : tab === "students" ? await studentsView() : tab === "curriculum" ? await curriculumView()
: tab === "subjects" ? await subjectsTab.view() : tab === "lesson" ? `
` : editorView();
} catch (e) {
failed = true;
body = `לא הצלחנו לפתוח את הלשונית: ${esc(e.message)}
`;
}
draw(`מנהל כיתות ${tabs()}${note()}${body} `);
document.getElementById("mgr-tabs").addEventListener("click", event => {
const next = event.target.closest("[data-tab]")?.dataset.tab;
if (!next) return;
if (next === "edit" && tab !== "edit") editing = null;
draft = null;
go(next);
});
if (failed) { flash = { message: "", error: "" }; return; }
if (tab === "classes") wireClasses();
if (tab === "curriculum") wireCurriculum();
const openLesson = id => { lessonId = id; go("lesson"); };
if (tab === "subjects") subjectsTab.wire(go, (message, error) => { flash = { message, error }; }, openLesson);
if (tab === "lesson") (await import("./lesson_editor.js")).open(document.getElementById("lesson-slot"), lessonId, () => go("subjects"));
if (tab === "edit") wireEditor();
if (tab === "edit" && flash.error) { // the form is long: bring the reason into view, next to the buttons that were pressed
const reason = document.getElementById("form-error");
reason.scrollIntoView({ block: "center" });
reason.focus();
}
flash = { message: "", error: "" };
}
/** The curriculum: the date by which each lesson should be completed, one for the whole school. */
async function curriculumView() {
const lessons = await api("/api/curriculum");
return ``;
}
function wireCurriculum() {
document.getElementById("curriculum-form").addEventListener("submit", async event => {
event.preventDefault();
const dates = Object.fromEntries([...document.querySelectorAll("#curriculum-form [data-lesson]")].map(i => [i.dataset.lesson, i.value || null]));
try { await api("/api/curriculum", { method: "PUT", body: { dates } }); flash = { message: "התכנית נשמרה.", error: "" }; }
catch (e) { flash = { message: "", error: e.message }; }
go("curriculum");
});
}
const go = next => { tab = next; return render(); };
async function classesView() {
const classes = await api("/api/classes");
return `+ כיתה חדשה
${classes.length} כיתות
${classes.length ? `${classes.map(c => `
${esc(c.name)} עריכה מחיקה
מורה: ${esc(c.teacher_email || "עוד לא נבחר")}
${c.students} תלמידים · AI: ${c.ai_enabled ? "פועל" : "כבוי"} · ${c.ai_daily_checks} בדיקות ביום
`).join("")}
`
: `עוד אין כיתות. פתחו כיתה ראשונה והוסיפו את התלמידים.
`}`;
}
function wireClasses() {
document.getElementById("new-class").addEventListener("click", () => { editing = null; draft = null; go("edit"); });
document.getElementById("classes")?.addEventListener("click", async event => {
const edit = event.target.closest("[data-edit]")?.dataset.edit, del = event.target.closest("[data-delete]")?.dataset.delete;
if (edit) { editing = await api(`/api/classes/${edit}`); draft = null; return go("edit"); }
if (del) {
const card = event.target.closest("article");
if (!confirm(`למחוק את הכיתה "${card.querySelector("h3").textContent}"? ההתקדמות של התלמידים נשמרת.`)) return;
try { await api(`/api/classes/${del}`, { method: "DELETE" }); flash.message = "הכיתה נמחקה."; }
catch (e) { flash.error = e.message; }
go("classes");
}
});
}
let subjects = []; // every subject, for the class editor's choice (loaded when the editor opens)
function editorView() {
const c = editing, d = draft; // a refused save keeps what was typed
const value = (typed, saved, fallback = "") => d ? (typed ?? fallback) : (saved ?? fallback);
const studies = d ? d.subjects : (c?.subjects || ["math"]);
return ``;
}
function wireEditor() {
document.getElementById("c-cancel").addEventListener("click", () => { editing = null; draft = null; go("classes"); });
document.getElementById("class-form").addEventListener("submit", async event => {
event.preventDefault();
const body = {
name: document.getElementById("c-name").value,
teacher_email: document.getElementById("c-teacher").value.trim() || null,
students: document.getElementById("c-students").value,
ai_enabled: document.getElementById("c-ai").checked,
ai_daily_checks: Number(document.getElementById("c-budget").value),
subjects: [...document.querySelectorAll("#c-subjects input:checked")].map(i => i.value),
};
try {
const saved = editing ? await api(`/api/classes/${editing.id}`, { method: "PUT", body }) : await api("/api/classes", { method: "POST", body });
flash.message = `${editing ? "השינויים נשמרו" : "הכיתה נפתחה"}: ${saved.name}.${saved.invalid?.length ? ` לא נראה כמו מייל: ${saved.invalid.join(", ")}` : ""}`;
editing = null; draft = null;
go("classes");
} catch (e) { draft = body; flash.error = e.message; go("edit"); }
});
}
async function studentsView() {
const students = await api("/api/students");
return `תלמיד כיתות
${students.length ? students.map(s => `${esc(s.email)} ${s.classes.map(esc).join(" · ")} `).join("")
: `עדיין לא נוספו תלמידים לכיתות. `}
`;
}
export async function show(me, view) {
draw = view;
tab = "classes"; editing = null;
await render();
}