init here
This commit is contained in:
406
frontend/views/teacher.js
Normal file
406
frontend/views/teacher.js
Normal file
@@ -0,0 +1,406 @@
|
||||
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));
|
||||
});
|
||||
Reference in New Issue
Block a user