- teacher/admin course pages, content, gradebook, profile, downloads - courseNo -> UUID только на границе REST API - обновлены e2e, шаблоны, тесты и docs - LDD-UI-002; baseline 59 claims / 385 тестов
135 lines
7.8 KiB
HTML
135 lines
7.8 KiB
HTML
<!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 class="mt-2 flex flex-wrap gap-2">
|
||
<a class="btn btn-sm btn-primary" th:href="@{/teacher/courses/{no}/content(no=${course.courseNo})}">Материалы курса</a>
|
||
<a class="btn btn-sm btn-secondary" th:href="@{/learn/course/{no}(no=${course.courseNo})}">Учебные материалы курса</a>
|
||
</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>
|
||
<th th:unless="${currentUser.admin}" data-nosort></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="${groupNames[group.groupId] ?: group.groupId}">—</a>
|
||
<a th:unless="${currentUser.admin}"
|
||
th:href="@{/teacher/courses/{no}/groups/{groupId}(no=${course.courseNo}, groupId=${group.groupId})}"
|
||
th:text="${groupNames[group.groupId] ?: group.groupId}">—</a>
|
||
</td>
|
||
<td class="text-muted" th:text="${group.addedAt}">—</td>
|
||
<td th:unless="${currentUser.admin}" class="text-center">
|
||
<div class="flex flex-wrap items-center justify-center gap-2">
|
||
<a class="btn btn-sm btn-secondary"
|
||
th:href="@{/admin/groups/{groupId}(groupId=${group.groupId}, courseNo=${course.courseNo})}">Ведомость</a>
|
||
<a class="btn btn-sm btn-secondary"
|
||
th:href="@{/teacher/courses/{no}/groups/{groupId}(no=${course.courseNo}, groupId=${group.groupId})}">Студенты</a>
|
||
<form class="inline-form"
|
||
th:action="@{/teacher/courses/{no}/groups/detach(no=${course.courseNo})}"
|
||
method="post" data-confirm="Отвязать группу от курса? Ведомость и данные группы останутся в системе.">
|
||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||
<input type="hidden" name="groupId" th:value="${group.groupId}">
|
||
<button class="btn btn-sm btn-danger" type="submit">Отвязать</button>
|
||
</form>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr th:if="${#lists.isEmpty(groups)}">
|
||
<td th:attr="colspan=${currentUser.admin ? 2 : 3}" class="empty">К курсу не привязано ни одной группы.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<form class="card card-body form-stack mt-4" th:unless="${currentUser.admin}"
|
||
th:action="@{/teacher/courses/{no}/groups/attach(no=${course.courseNo})}" method="post">
|
||
<div><label class="label" for="courseGroup">Привязать группу</label>
|
||
<select class="input" id="courseGroup" name="groupId" required
|
||
th:disabled="${#lists.isEmpty(availableGroups)}">
|
||
<option value="">— выберите группу —</option>
|
||
<option th:each="group : ${availableGroups}" th:value="${group.id}"
|
||
th:text="${group.name} + (${group.status == 'ACTIVE'} ? '' : ' (архив)')">—</option>
|
||
</select>
|
||
<p class="hint" th:if="${#lists.isEmpty(availableGroups)}">Все доступные группы уже привязаны.</p>
|
||
</div>
|
||
<button class="btn btn-primary" type="submit"
|
||
th:disabled="${#lists.isEmpty(availableGroups)}">Привязать</button>
|
||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||
</form>
|
||
<p class="hint mt-2" th:if="${currentUser.admin}">Состав групп и привязку к курсу изменяет администратор.</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>
|