Files
srab/frontend/views/teacher.js
2025-11-26 21:32:41 +03:00

407 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { route } from "../router.js";
import { Api } from "../kek.js";
import { el, formToJSON, $, fmtDateInput, loadSession } from "../utils.js";
function needSession() {
const s = loadSession();
if (!s) location.hash = "#/auth/login";
return s;
}
function sectionTabs(active) {
const tabs = document.createElement("div");
tabs.className = "tabs";
const items = [
["#/dashboard", "Сводная"],
["#/teacher/classes", "Классы"],
["#/teacher/students", "Ученики"],
["#/teacher/lessons", "Уроки"],
];
for (const [href, title] of items) {
const a = document.createElement("a");
a.href = href;
a.textContent = title;
if (href.endsWith(active)) a.classList.add("active");
tabs.appendChild(a);
}
return tabs;
}
function classesView(session) {
const wrap = el("div", { class: "grid-two" });
const formCard = el("div", { class: "card" }, [
el("div", { class: "card-header" }, "Создать класс"),
el("div", { class: "card-body" }, [
(() => {
const form = el("form", { class: "form-grid" });
form.innerHTML = `
<div class="input"><label>Номер</label><input name="номер" type="number" required min="1" max="11"></div>
<div class="input"><label>Буква</label><input name="буква" maxlength="2" required></div>
<div class="actions"><button class="btn">Создать</button></div>
<div class="small">Ограничение: однобуквенное обозначение параллели.</div>
<div id="cls-err" class="error hidden"></div>`;
form.addEventListener("submit", async (e) => {
e.preventDefault();
const payload = formToJSON(form);
try {
await Api.createClass(session, payload);
await refresh();
form.reset();
} catch (err) {
const box = form.querySelector("#cls-err");
box.classList.remove("hidden");
box.textContent = err.message || "Ошибка создания класса";
}
});
return form;
})(),
]),
]);
const listCard = el("div", { class: "card" }, [
el("div", { class: "card-header" }, "Ваши классы"),
el("div", { class: "card-body" }, [
(() => {
const box = el("div", {
class: "form-grid",
style: "margin-bottom:8px",
});
box.innerHTML = `
<div class="input"><label>ID ученика (для добавления/удаления)</label><input id="st-to-add" type="number" min="1"></div>
<div class="small">Выберите класс в таблице и используйте кнопки «Добавить ученика»/«Убрать ученика»</div>`;
return box;
})(),
el("table", { class: "table", id: "class-table" }, [
el(
"thead",
{},
el("tr", {}, [
el("th", {}, "ID"),
el("th", {}, "Класс"),
el("th", {}, "Создатель"),
el("th", {}, ""),
])
),
el("tbody"),
]),
]),
]);
wrap.appendChild(formCard);
wrap.appendChild(listCard);
async function refresh() {
const { data } = await Api.listClasses(session);
const tbody = listCard.querySelector("tbody");
tbody.innerHTML = "";
for (const c of data?.["классы"] || []) {
const tr = el("tr");
tr.appendChild(el("td", {}, String(c["идентификатор"])));
tr.appendChild(
el("td", {}, `${c["номер"] || ""}${c["буква"] ? "-" + c["буква"] : ""}`)
);
tr.appendChild(el("td", {}, String(c["создатель"] ?? "")));
const actions = el("td");
const btnAdd = el("button", { class: "btn" }, "Добавить ученика");
btnAdd.addEventListener("click", async () => {
const sid = Number($("#st-to-add", listCard)?.value || 0);
if (!sid) return alert("Укажите ID ученика");
try {
await Api.addStudentToClass(session, c["идентификатор"], sid);
await refresh();
} catch (e) {
alert(e.message || "Не удалось добавить ученика");
}
});
const btnRm = el("button", { class: "btn secondary" }, "Убрать ученика");
btnRm.addEventListener("click", async () => {
const sid = Number($("#st-to-add", listCard)?.value || 0);
if (!sid) return alert("Укажите ID ученика");
try {
await Api.removeStudentFromClass(session, c["идентификатор"], sid);
await refresh();
} catch (e) {
alert(e.message || "Не удалось удалить ученика");
}
});
const btnDel = el("button", { class: "btn danger" }, "Удалить класс");
btnDel.addEventListener("click", async () => {
if (!confirm("Удалить класс?")) return;
await Api.deleteClass(session, c["идентификатор"]);
await refresh();
});
actions.appendChild(btnAdd);
actions.appendChild(btnRm);
actions.appendChild(btnDel);
tr.appendChild(actions);
tbody.appendChild(tr);
}
}
refresh().catch(console.error);
return wrap;
}
function studentsView(session) {
const wrap = el("div", { class: "grid-two" });
const formCard = el("div", { class: "card" }, [
el("div", { class: "card-header" }, "Создать ученика"),
el("div", { class: "card-body" }, [
(() => {
const form = el("form", { class: "form-grid" });
form.innerHTML = `
<div class="input"><label>Имя</label><input name="имя" required></div>
<div class="input"><label>Фамилия</label><input name="фамилия" required></div>
<div class="input"><label>Отчество</label><input name="отчество" required></div>
<div class="input"><label>СНИЛС</label><input name="снилс" required></div>
<div class="input"><label>Паспорт</label><input name="паспорт" required></div>
<div class="input"><label>Пароль</label><input type="password" name="пароль" required></div>
<div class="input"><label>Повтор пароля</label><input type="password" name="повтор пароля" required></div>
<div class="actions"><button class="btn">Создать</button></div>
<div id="st-err" class="error hidden"></div>`;
form.addEventListener("submit", async (e) => {
e.preventDefault();
const payload = formToJSON(form);
try {
await Api.createStudent(session, payload);
await refresh();
form.reset();
} catch (err) {
const box = form.querySelector("#st-err");
box.classList.remove("hidden");
box.textContent = err.message || "Ошибка создания ученика";
}
});
return form;
})(),
]),
]);
const listCard = el("div", { class: "card" }, [
el("div", { class: "card-header" }, "Ученики"),
el("div", { class: "card-body" }, [
el("table", { class: "table", id: "st-table" }, [
el(
"thead",
{},
el("tr", {}, [
el("th", {}, "ID"),
el("th", {}, "ФИО"),
el("th", {}, "Имя пользователя"),
el("th", {}, ""),
])
),
el("tbody"),
]),
]),
]);
wrap.appendChild(formCard);
wrap.appendChild(listCard);
async function refresh() {
const { data } = await Api.listStudents(session);
const tbody = listCard.querySelector("tbody");
tbody.innerHTML = "";
for (const s of data?.["ученики"] || []) {
const tr = el("tr");
tr.appendChild(el("td", {}, String(s["идентификатор"])));
tr.appendChild(
el(
"td",
{},
`${s["фамилия"] || ""} ${s["имя"] || ""} ${
s["отчество"] || ""
}`.trim()
)
);
tr.appendChild(el("td", {}, s["имя пользователя"] || ""));
const actions = el("td");
const btnDel = el("button", { class: "btn danger" }, "Удалить");
btnDel.addEventListener("click", async () => {
if (!confirm("Удалить ученика?")) return;
await Api.deleteStudent(session, s["идентификатор"]);
await refresh();
});
actions.appendChild(btnDel);
tr.appendChild(actions);
tbody.appendChild(tr);
}
}
refresh().catch(console.error);
return wrap;
}
function lessonsView(session) {
const wrap = el("div", { class: "grid-two" });
const formCard = el("div", { class: "card" }, [
el("div", { class: "card-header" }, "Создать урок"),
el("div", { class: "card-body" }, [
(() => {
const form = el("form", { class: "form-grid" });
form.innerHTML = `
<div class="input"><label>ID класса</label><input name="classId" required type="number" min="1"></div>
<div class="input"><label>Дата</label><input name="дата" type="date" value="${fmtDateInput()}" required></div>
<div class="input"><label>Тема</label><input name="тема" required></div>
<div class="input" style="grid-column: 1/-1"><label>Домашнее задание</label><textarea name="домашка" rows="3"></textarea></div>
<div class="actions"><button class="btn">Создать</button></div>
<div id="lsn-err" class="error hidden"></div>`;
form.addEventListener("submit", async (e) => {
e.preventDefault();
const f = new FormData(form);
const classId = Number(f.get("classId"));
const payload = {
дата: f.get("дата"),
тема: f.get("тема"),
домашка: f.get("домашка"),
};
try {
await Api.createLesson(session, classId, payload);
await refresh();
} catch (err) {
const box = form.querySelector("#lsn-err");
box.classList.remove("hidden");
box.textContent = err.message || "Ошибка создания урока";
}
});
return form;
})(),
]),
]);
const listCard = el("div", { class: "card" }, [
el("div", { class: "card-header" }, "Уроки класса"),
el("div", { class: "card-body" }, [
(() => {
const filter = el("div", {
class: "form-grid",
style: "margin-bottom:8px",
});
filter.innerHTML = `
<div class="input"><label>ID класса</label><input id="flt-class" type="number" min="1"></div>
<div class="input"><label>Дата</label><input id="flt-date" type="date"></div>
<div class="actions" style="align-self:end"><button id="btn-load" class="btn" type="button">Загрузить</button></div>`;
return filter;
})(),
el("table", { class: "table", id: "lsn-table" }, [
el(
"thead",
{},
el("tr", {}, [
el("th", {}, "ID"),
el("th", {}, "Класс"),
el("th", {}, "Дата"),
el("th", {}, "Тема"),
el("th", {}, "Д/З"),
el("th", {}, ""),
])
),
el("tbody"),
]),
]),
]);
wrap.appendChild(formCard);
wrap.appendChild(listCard);
async function refresh() {
const classId = Number($("#flt-class", listCard)?.value || 0);
const date = $("#flt-date", listCard)?.value || undefined;
if (!classId) return;
const { data } = await Api.listLessons(session, classId, date);
const tbody = listCard.querySelector("tbody");
tbody.innerHTML = "";
for (const l of data?.["уроки"] || []) {
const tr = el("tr");
tr.appendChild(el("td", {}, String(l["идентификатор"])));
tr.appendChild(el("td", {}, String(l["идентификатор класса"])));
tr.appendChild(el("td", {}, l["дата"] || ""));
tr.appendChild(el("td", {}, l["название"] || ""));
tr.appendChild(el("td", {}, l["домашнее задание"] || ""));
const actions = el("td");
const btnDel = el("button", { class: "btn danger" }, "Удалить");
btnDel.addEventListener("click", async () => {
if (!confirm("Удалить урок?")) return;
await Api.deleteLessonById(
session,
l["идентификатор класса"],
l["идентификатор"]
);
await refresh();
});
actions.appendChild(btnDel);
tr.appendChild(actions);
tbody.appendChild(tr);
}
}
$("#btn-load", listCard)?.addEventListener("click", refresh);
return wrap;
}
function dashboardView() {
const card = el("div", { class: "card" }, [
el("div", { class: "card-header" }, "Сводная"),
el("div", { class: "card-body" }, [
el(
"p",
{},
"Добро пожаловать! Используйте боковое меню, чтобы работать с данными."
),
el(
"p",
{ class: "small" },
`Стоят пассажиры в аэропорту, посадочный досмотр проходят. Дошла очередь до мужика с чемоданом.
— Пожалуйста, приготовьте чемодан к осмотру.
Не могу.
— Почему?
А у меня там бипки!
А что это?
Ну, пропатчите сервис на тривиле — покажу!
Служба безопасности шутку не поняла, вызвала наряд полиции:
— Гражданин, открывайте чемодан.
Но я не могу!
— Почему не можете?
— Потому что у меня там бипки!
— «Бипки»? Что это?
— Так реализуйте гарбадж коллектор мне, и я покажу!
Отобрали чемодан, а открыть никто не может. Повезли мужика в СИЗО. В камере сидельцы расспрашивают:
— Братуха, за что тебя?
— Да чемодан отказался открывать.
А что там?
— Да бипки там.
— Какие еще «бипки»?
Ну документацию обновите мне, тогда и расскажу.
И вот угодил мужик в лазарет, с побоями да синяками по всему телу, весь перебинтован, еле дышит. Следователи вызвали группу специалистов для вскрытия чемодана. Час, два, три пыхтели. Кое-как разворотили, смотрят — а там бипки.`
),
]),
]);
return card;
}
route("/dashboard", async ({ view }) => {
const s = needSession();
view.appendChild(sectionTabs("dashboard"));
view.appendChild(dashboardView(s));
});
route("/teacher/classes", async ({ view }) => {
const s = needSession();
view.appendChild(sectionTabs("classes"));
view.appendChild(classesView(s));
});
route("/teacher/students", async ({ view }) => {
const s = needSession();
view.appendChild(sectionTabs("students"));
view.appendChild(studentsView(s));
});
route("/teacher/lessons", async ({ view }) => {
const s = needSession();
view.appendChild(sectionTabs("lessons"));
view.appendChild(lessonsView(s));
});