// 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" ? `` : ""}${flash.message ? `

${esc(flash.message)}

` : ""}`; const tabs = () => `
${[["classes", "כיתות"], ["edit", editing ? "עריכת כיתה" : "כיתה חדשה"], ["students", "תלמידים"], ["curriculum", "תכנית לימודים"], ["subjects", "מקצועות"]].map(([id, 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 = ``; } 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 `

תכנית לימודים

התאריך שעד אליו כל שיעור אמור להסתיים. תלמיד שלא סיים שיעור אחרי התאריך רואה אותו מסומן בפיגור. אפשר להשאיר תאריך ריק.

${[...new Set(lessons.map(l => l.subject?.id || "math"))].map(sid => { const mine = lessons.filter(l => (l.subject?.id || "math") === sid); return `

${esc(mine[0].subject?.name || "מתמטיקה")}

`; }).join("")}

`; } 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 `

${c ? `עריכת ${esc(c.name)}` : "כיתה חדשה"}

ה־AI כבוי עד שבית הספר מאשר שהמשפחות מסכימות. רק אחרי זה מפעילים אותו.

מקצועות שהכיתה לומדת ${subjects.map(s => ``).join("")}

הכיתה לומדת את כל השיעורים שפורסמו במקצועות שנבחרו. הסרת מקצוע לא מוחקת את ההתקדמות של התלמידים.

${flash.error ? `` : ""}
`; } 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 => ``).join("") : ``}
תלמידכיתות
${esc(s.email)}${s.classes.map(esc).join(" · ")}
עדיין לא נוספו תלמידים לכיתות.
`; } export async function show(me, view) { draw = view; tab = "classes"; editing = null; await render(); }