407 lines
16 KiB
JavaScript
407 lines
16 KiB
JavaScript
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));
|
||
});
|