feat(web): кабинет преподавателя и управление преподавателями
This commit is contained in:
@@ -75,10 +75,14 @@
|
||||
<h2 class="section-title">Преподаватели</h2>
|
||||
<div class="card card-flush">
|
||||
<table>
|
||||
<thead><tr><th>Аккаунт</th><th>Назначен</th><th data-nosort></th></tr></thead>
|
||||
<thead><tr><th>Преподаватель</th><th>Назначен</th><th data-nosort></th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="teacher : ${activeTeachers}">
|
||||
<td class="font-mono" th:text="${teacher.accountId}">—</td>
|
||||
<td>
|
||||
<span class="font-medium"
|
||||
th:text="${teacherNames[teacher.accountId] ?: teacher.accountId}">—</span>
|
||||
<div class="text-sm text-muted font-mono" th:text="${teacher.accountId}">—</div>
|
||||
</td>
|
||||
<td class="text-muted" th:text="${teacher.assignedAt}">—</td>
|
||||
<td class="text-center">
|
||||
<form class="inline-form"
|
||||
@@ -98,11 +102,27 @@
|
||||
|
||||
<form class="card card-body form-stack mt-4" th:action="@{/admin/courses/{id}/teachers(id=${course.id})}"
|
||||
method="post">
|
||||
<div><label class="label" for="teacherAccount">Идентификатор аккаунта преподавателя</label>
|
||||
<input class="input font-mono" id="teacherAccount" name="accountId" required
|
||||
placeholder="UUID аккаунта"></div>
|
||||
<p class="hint">UUID можно скопировать на странице «Пользователи».</p>
|
||||
<button class="btn btn-primary" type="submit">Назначить</button>
|
||||
<div><label class="label" for="teacherAccount">Преподаватель</label>
|
||||
<select class="input" id="teacherAccount" name="accountId" required
|
||||
th:disabled="${#lists.isEmpty(availableTeachers)}">
|
||||
<option value="">— выберите преподавателя —</option>
|
||||
<option th:each="t : ${availableTeachers}" th:value="${t.id}"
|
||||
th:text="${#strings.isEmpty(t.name) ? t.login : t.name + ' (' + t.login + ')'}">—</option>
|
||||
</select>
|
||||
<p class="hint" th:if="${#lists.isEmpty(teachers)}">
|
||||
Преподавателей ещё нет.
|
||||
<a th:href="@{/admin/teachers}">Создайте первого на странице «Преподаватели»</a>.
|
||||
</p>
|
||||
<p class="hint" th:if="${!#lists.isEmpty(teachers) and #lists.isEmpty(availableTeachers)}">
|
||||
Все преподаватели уже назначены на курс.
|
||||
</p>
|
||||
<p class="hint" th:if="${!#lists.isEmpty(availableTeachers)}">
|
||||
Нет нужного?
|
||||
<a th:href="@{/admin/teachers}">Создать преподавателя на странице «Преподаватели»</a>.
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit"
|
||||
th:disabled="${#lists.isEmpty(availableTeachers)}">Назначить</button>
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
70
web/src/main/resources/templates/admin/teachers.html
Normal file
70
web/src/main/resources/templates/admin/teachers.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru" xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/base :: layout(~{::title}, ~{::#content})}">
|
||||
<head><title>Преподаватели — LMS</title></head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<h1 class="page-title">Преподаватели</h1>
|
||||
|
||||
<form class="card card-body form-stack mb-6" th:action="@{/admin/teachers}" method="post">
|
||||
<h2 class="section-title m-0">Новый преподаватель</h2>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="label" for="teacherLogin">Логин</label>
|
||||
<input class="input" id="teacherLogin" name="login" autocomplete="off"
|
||||
placeholder="ivanov" required minlength="3">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="label" for="teacherPassword">Пароль (необязательно)</label>
|
||||
<input class="input" id="teacherPassword" name="password" type="password"
|
||||
autocomplete="new-password" placeholder="Оставьте пустым — сгенерируем">
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint">
|
||||
Логин — минимум 3 символа. Если пароль не указан, сервис сгенерирует его.
|
||||
Одноразовый пароль будет показан один раз после создания — передайте его преподавателю.
|
||||
</p>
|
||||
<button class="btn btn-primary" type="submit">Создать преподавателя</button>
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
</form>
|
||||
|
||||
<div class="card card-flush">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Преподаватель</th>
|
||||
<th>Логин</th>
|
||||
<th>Статус</th>
|
||||
<th class="text-right" data-nosort>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="t : ${teachers}">
|
||||
<td>
|
||||
<span class="font-medium" th:text="${#strings.isEmpty(t.name) ? t.login : t.name}">—</span>
|
||||
<div class="text-sm text-muted font-mono" th:text="${t.id}">—</div>
|
||||
</td>
|
||||
<td class="font-mono" th:text="${t.login}">—</td>
|
||||
<td>
|
||||
<span class="badge" th:classappend="${t.status == 'ACTIVE' ? 'badge-ok' : 'badge-warn'}"
|
||||
th:text="${t.status == 'ACTIVE' ? 'активен' : t.status}">—</span>
|
||||
<span class="badge badge-warn" th:if="${t.mustChangePassword}">временный пароль</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<form class="inline-form"
|
||||
th:action="@{/admin/teachers/{id}/reset-password(id=${t.id})}"
|
||||
method="post" data-confirm="Сбросить пароль преподавателя? Старый пароль перестанет действовать.">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="login" th:value="${t.login}">
|
||||
<button class="btn btn-sm btn-secondary" type="submit">Сбросить пароль</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(teachers)}">
|
||||
<td colspan="4" class="empty">Преподавателей пока нет. Создайте первого в форме выше.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -51,13 +51,16 @@
|
||||
<li class="nav-item" th:if="${currentUser != null and currentUser.admin}"><a class="nav-link" th:href="@{/admin/groups}">Группы</a></li>
|
||||
<li class="nav-item" th:if="${currentUser != null and currentUser.admin}"><a class="nav-link" th:href="@{/admin/courses}">Курсы</a></li>
|
||||
<li class="nav-item" th:if="${currentUser != null and currentUser.admin}"><a class="nav-link" th:href="@{/admin/users}">Пользователи</a></li>
|
||||
<li class="nav-item" th:if="${currentUser != null and currentUser.admin}"><a class="nav-link" th:href="@{/admin/teachers}">Преподаватели</a></li>
|
||||
<li class="nav-item" th:if="${currentUser != null and currentUser.admin}"><a class="nav-link" th:href="@{/admin/labs}">Лабораторные</a></li>
|
||||
<li class="nav-item" th:if="${currentUser != null and !currentUser.admin}"><a class="nav-link" th:href="@{/student/labs}">Лабораторные</a></li>
|
||||
<li class="nav-item" th:if="${currentUser != null and currentUser.teacher}"><a class="nav-link" th:href="@{/teacher}">Мои курсы</a></li>
|
||||
<li class="nav-item" th:if="${currentUser != null and currentUser.student}"><a class="nav-link" th:href="@{/student/labs}">Лабораторные</a></li>
|
||||
<li class="nav-item" th:if="${currentUser != null}"><a class="nav-link" th:href="@{/change-password}">Смена пароля</a></li>
|
||||
</ul>
|
||||
<div class="navbar-user d-flex flex-column flex-md-row align-items-start align-items-md-center gap-2">
|
||||
<a th:href="@{/admin}" th:if="${currentUser != null and currentUser.admin}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
|
||||
<a th:href="@{/student}" th:if="${currentUser != null and !currentUser.admin}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
|
||||
<a th:href="@{/teacher}" th:if="${currentUser != null and currentUser.teacher}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
|
||||
<a th:href="@{/student}" th:if="${currentUser != null and currentUser.student}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<button type="button" class="btn btn-secondary btn-sm theme-btn" aria-label="Сменить тему" title="Сменить тему">
|
||||
<i class="bi bi-circle-half"></i>
|
||||
@@ -97,6 +100,13 @@
|
||||
</span>
|
||||
<button class="toast-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div th:if="${teacherPassword}" class="toast-item toast-warning" data-toast>
|
||||
<span class="toast-text">
|
||||
Одноразовый пароль<span th:if="${teacherLogin}" th:text="| для ${teacherLogin}|"> для login</span>:
|
||||
<code class="code font-mono" th:text="${teacherPassword}">password</code>
|
||||
</span>
|
||||
<button class="toast-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<dialog id="modal-confirm" class="dlg">
|
||||
<div class="dlg-header">
|
||||
|
||||
92
web/src/main/resources/templates/teacher/course.html
Normal file
92
web/src/main/resources/templates/teacher/course.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru" xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/base :: layout(~{::title}, ~{::#content})}">
|
||||
<head><title th:text="${course.title} + ' — LMS'">Курс</title></head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<a th:href="@{/teacher}" class="nav-link">← Мои курсы</a>
|
||||
|
||||
<div class="page-head mb-2">
|
||||
<div>
|
||||
<h1 class="page-title" th:text="|Курс ${course.courseNo}. ${course.title}|">Курс</h1>
|
||||
<div class="meta">
|
||||
<span class="badge"
|
||||
th:classappend="${course.status == 'ACTIVE' ? 'badge-ok' : 'badge-warn'}"
|
||||
th:text="${course.status == 'ACTIVE' ? 'активен' : 'в архиве'}">—</span>
|
||||
<span th:text="|Групп: ${#lists.size(groups)}|">Групп: 0</span>
|
||||
· <span th:text="|Лабораторных: ${#lists.size(labs)}|">Лабораторных: 0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mt-4">
|
||||
<div class="col-lg-6">
|
||||
<h2 class="section-title">Группы курса</h2>
|
||||
<div class="card card-flush">
|
||||
<table>
|
||||
<thead><tr><th>Группа</th><th>Добавлена</th></tr></thead>
|
||||
<tbody>
|
||||
<tr th:each="group : ${groups}">
|
||||
<td>
|
||||
<a th:if="${currentUser.admin}"
|
||||
th:href="@{/admin/groups/{id}(id=${group.groupId})}"
|
||||
class="font-mono"
|
||||
th:text="${group.groupId}">—</a>
|
||||
<span th:unless="${currentUser.admin}" class="font-mono"
|
||||
th:text="${group.groupId}">—</span>
|
||||
</td>
|
||||
<td class="text-muted" th:text="${group.addedAt}">—</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(groups)}">
|
||||
<td colspan="2" class="empty">К курсу не привязано ни одной группы.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="hint mt-2">Состав групп и привязку к курсу изменяет администратор.</p>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<h2 class="section-title">Сведения о курсе</h2>
|
||||
<div class="card card-body">
|
||||
<p class="text-sm">Создан: <span th:text="${course.createdAt}">—</span></p>
|
||||
<p class="text-sm">Обновлён: <span th:text="${course.updatedAt}">—</span></p>
|
||||
<p class="text-sm" th:if="${!#lists.isEmpty(course.teachers)}">
|
||||
Преподавателей на курсе: <span th:text="${#lists.size(course.teachers)}">0</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-title mt-8 mb-2">Лабораторные курса</h2>
|
||||
<div class="card card-flush">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>№</th><th>Название</th><th>Статус</th><th>Вариантов</th><th>Срок сдачи</th><th>Защита</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="lab : ${labs}">
|
||||
<td th:text="${lab.number}">1</td>
|
||||
<td>
|
||||
<a th:if="${currentUser.admin}"
|
||||
th:href="@{/admin/labs/{id}(id=${lab.id})}"
|
||||
th:text="${lab.title}">—</a>
|
||||
<span th:unless="${currentUser.admin}" th:text="${lab.title}">—</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge"
|
||||
th:classappend="${lab.status == 'published' ? 'badge-ok' : 'badge-warn'}"
|
||||
th:text="${lab.status == 'published' ? 'Опубликована' : (lab.status == 'archived' ? 'В архиве' : 'Черновик')}">—</span>
|
||||
</td>
|
||||
<td th:text="${lab.variantCount}">0</td>
|
||||
<td class="text-muted" th:text="${lab.deadlineMsk ?: '—'}">—</td>
|
||||
<td class="text-muted" th:text="${lab.defenseMsk ?: '—'}">—</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(labs)}">
|
||||
<td colspan="6" class="empty">В курсе пока нет лабораторных.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
43
web/src/main/resources/templates/teacher/dashboard.html
Normal file
43
web/src/main/resources/templates/teacher/dashboard.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru" xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/base :: layout(~{::title}, ~{::#content})}">
|
||||
<head><title>Мои курсы — LMS</title></head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<h1 class="page-title">Здравствуйте, <span th:text="${currentUser.name}">преподаватель</span></h1>
|
||||
<p class="meta">Курсы, на которые вы назначены.</p>
|
||||
|
||||
<div class="row row-cols-1 row-cols-lg-2 g-4 mt-4">
|
||||
<div th:each="course : ${courses}" class="col">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h2 class="section-title">
|
||||
<a th:href="@{/teacher/courses/{id}(id=${course.id})}"
|
||||
th:text="|Курс ${course.courseNo}. ${course.title}|">Курс</a>
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-2 text-sm mt-2">
|
||||
<span class="badge"
|
||||
th:classappend="${course.status == 'ACTIVE' ? 'badge-ok' : 'badge-warn'}"
|
||||
th:text="${course.status == 'ACTIVE' ? 'активен' : 'в архиве'}">—</span>
|
||||
<span class="badge" th:text="|групп: ${course.groupCount}|">групп: 0</span>
|
||||
<span class="badge" th:text="|лабораторных: ${course.labCount}|">лабораторных: 0</span>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<a class="btn btn-sm btn-secondary"
|
||||
th:href="@{/teacher/courses/{id}(id=${course.id})}">Открыть курс</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:if="${#lists.isEmpty(courses)}" class="col">
|
||||
<div class="card card-body text-muted">
|
||||
Вам пока не назначено ни одного курса. Обратитесь к администратору.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 mt-8">
|
||||
<a class="btn btn-secondary" th:href="@{/learn}">Учебные материалы</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user