init here
This commit is contained in:
86
frontend/views/auth.js
Normal file
86
frontend/views/auth.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import { route } from "../router.js";
|
||||
import { Api } from "../kek.js";
|
||||
import {
|
||||
saveSession,
|
||||
setTopUser,
|
||||
setLogoutVisible,
|
||||
formToJSON,
|
||||
} from "../utils.js";
|
||||
|
||||
function loginView() {
|
||||
const card = document.createElement("div");
|
||||
card.className = "card";
|
||||
card.innerHTML = `
|
||||
<div class="card-header">Вход</div>
|
||||
<div class="card-body">
|
||||
<form id="login-form" class="form-grid" autocomplete="on">
|
||||
<div class="input"><label>Имя пользователя</label><input name="username" required /></div>
|
||||
<div class="input"><label>Пароль</label><input name="password" type="password" required /></div>
|
||||
<div class="actions">
|
||||
<button class="btn" type="submit">Войти</button>
|
||||
<a class="btn secondary" href="#/auth/register">Регистрация учителя</a>
|
||||
</div>
|
||||
<div id="login-error" class="error hidden"></div>
|
||||
</form>
|
||||
</div>`;
|
||||
const form = card.querySelector("#login-form");
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const { username, password } = Object.fromEntries(new FormData(form));
|
||||
try {
|
||||
const { session } = await Api.login({ username, password });
|
||||
saveSession(session);
|
||||
setTopUser(username);
|
||||
setLogoutVisible(true);
|
||||
location.hash = "#/dashboard";
|
||||
} catch (err) {
|
||||
const box = card.querySelector("#login-error");
|
||||
box.classList.remove("hidden");
|
||||
box.textContent = err.message || "Не удалось войти";
|
||||
}
|
||||
});
|
||||
return card;
|
||||
}
|
||||
|
||||
function registerView() {
|
||||
const card = document.createElement("div");
|
||||
card.className = "card";
|
||||
card.innerHTML = `
|
||||
<div class="card-header">Регистрация учителя</div>
|
||||
<div class="card-body">
|
||||
<form id="reg-form" class="form-grid">
|
||||
<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" type="submit">Создать</button></div>
|
||||
<div id="reg-msg" class="notice hidden"></div>
|
||||
<div id="reg-err" class="error hidden"></div>
|
||||
</form>
|
||||
</div>`;
|
||||
card.querySelector("#reg-form").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const payload = formToJSON(e.target);
|
||||
try {
|
||||
await Api.registerTeacher(payload);
|
||||
const msg = card.querySelector("#reg-msg");
|
||||
msg.classList.remove("hidden");
|
||||
msg.textContent = "Учитель создан. Теперь можно войти.";
|
||||
card.querySelector("#reg-err").classList.add("hidden");
|
||||
} catch (err) {
|
||||
const errBox = card.querySelector("#reg-err");
|
||||
errBox.classList.remove("hidden");
|
||||
errBox.textContent = err.message || "Ошибка регистрации";
|
||||
}
|
||||
});
|
||||
return card;
|
||||
}
|
||||
|
||||
route("/auth/login", async ({ view }) => {
|
||||
view.appendChild(loginView());
|
||||
});
|
||||
route("/auth/register", async ({ view }) => {
|
||||
view.appendChild(registerView());
|
||||
});
|
||||
98
frontend/views/student.js
Normal file
98
frontend/views/student.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { route } from "../router.js";
|
||||
import { Api } from "../kek.js";
|
||||
import { el, $, loadSession } from "../utils.js";
|
||||
|
||||
function needSession() {
|
||||
const s = loadSession();
|
||||
if (!s) location.hash = "#/auth/login";
|
||||
return s;
|
||||
}
|
||||
|
||||
function studentDashboard(session) {
|
||||
const card = el("div", { class: "card" }, [
|
||||
el("div", { class: "card-header" }, "Дневник ученика"),
|
||||
el("div", { class: "card-body" }, [
|
||||
(() => {
|
||||
const box = el("div", { class: "form-grid" });
|
||||
box.innerHTML = `
|
||||
<div class="input"><label>ID класса</label><input id="st-class-id" type="number" min="1"></div>
|
||||
<div class="input"><label>Дата</label><input id="st-date" type="date"></div>
|
||||
<div class="actions" style="align-self:end"><button id="st-load" class="btn" type="button">Показать</button></div>`;
|
||||
return box;
|
||||
})(),
|
||||
el("table", { class: "table", id: "st-lessons" }, [
|
||||
el(
|
||||
"thead",
|
||||
{},
|
||||
el("tr", {}, [
|
||||
el("th", {}, "Дата"),
|
||||
el("th", {}, "Предмет/тема"),
|
||||
el("th", {}, "Домашнее задание"),
|
||||
])
|
||||
),
|
||||
el("tbody"),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
|
||||
async function refresh() {
|
||||
const classId = Number($("#st-class-id", card)?.value || 0);
|
||||
const date = $("#st-date", card)?.value || undefined;
|
||||
if (!classId) return;
|
||||
const { data } = await Api.listLessons(session, classId, date);
|
||||
const tbody = card.querySelector("tbody");
|
||||
tbody.innerHTML = "";
|
||||
for (const l of data?.["уроки"] || []) {
|
||||
const tr = el("tr");
|
||||
tr.appendChild(el("td", {}, l["дата"] || ""));
|
||||
tr.appendChild(el("td", {}, l["название"] || ""));
|
||||
tr.appendChild(el("td", {}, l["домашнее задание"] || ""));
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
card.querySelector("#st-load")?.addEventListener("click", refresh);
|
||||
return card;
|
||||
}
|
||||
|
||||
function changePasswordView(session) {
|
||||
const card = 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="password" type="password" required></div>
|
||||
<div class="actions"><button class="btn">Изменить</button></div>
|
||||
<div id="pw-msg" class="notice hidden"></div>
|
||||
<div id="pw-err" class="error hidden"></div>`;
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const newPassword = new FormData(form).get("password");
|
||||
try {
|
||||
await Api.changePassword(session, newPassword);
|
||||
const msg = form.querySelector("#pw-msg");
|
||||
msg.classList.remove("hidden");
|
||||
msg.textContent = "Пароль изменён";
|
||||
form.querySelector("#pw-err").classList.add("hidden");
|
||||
} catch (err) {
|
||||
const box = form.querySelector("#pw-err");
|
||||
box.classList.remove("hidden");
|
||||
box.textContent = err.message || "Не удалось изменить пароль";
|
||||
}
|
||||
});
|
||||
return form;
|
||||
})(),
|
||||
]),
|
||||
]);
|
||||
return card;
|
||||
}
|
||||
|
||||
route("/student/dashboard", async ({ view }) => {
|
||||
const s = needSession();
|
||||
view.appendChild(studentDashboard(s));
|
||||
});
|
||||
route("/student/password", async ({ view }) => {
|
||||
const s = needSession();
|
||||
view.appendChild(changePasswordView(s));
|
||||
});
|
||||
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