Files
LMS/web/src/main/resources/templates/admin/lab.html
Vladislav Kopnin feed600d43 Lab assignment management UI and student variant view
- progress-service: POST /api/v1/assign (manual variant), GET /api/v1/assignments?labId=
  (enriched assignments), fixed enrich() to return real variant number/title/statement
- gateway: routes for /api/v1/assign and /api/v1/levels (exact paths)
- web: lab detail page shows students grouped with level and variant selects,
  group filter, distribute/redistribute buttons, source badge (auto/manual)
- web: student dashboard renders variant statement via libs/markdown (spoilers,
  code highlight, sanitized)
- academic-service: unquote YAML front-matter values (variant numbers were quoted)
- academic-service: LabLevelDetail.id for level references
2026-09-10 23:25:36 +03:00

99 lines
5.8 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru" xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/base :: layout(~{::title}, ~{::#content})}">
<head><title th:text="${lab.title} + ' — LMS'">Лабораторная</title></head>
<body>
<div id="content">
<a th:href="@{/admin/labs}" class="nav-link">← Все лабораторные</a>
<div class="flex flex-wrap items-center justify-between gap-4 mt-4 mb-6">
<div>
<h1 class="page-title" th:text="|${lab.number}. ${lab.title}|">ЛР 1</h1>
<p class="text-sm text-muted">Статус: <span class="badge" th:classappend="${lab.status == 'published'} ? 'badge-ok' : 'badge-warn'" th:text="${lab.status}">—</span></p>
</div>
<div class="flex gap-2">
<form th:action="@{/admin/labs/{id}/distribute(id=${lab.id})}" method="post">
<button class="btn btn-primary" type="submit" name="force" value="false">Распределить</button>
</form>
<form th:action="@{/admin/labs/{id}/distribute(id=${lab.id})}" method="post">
<button class="btn btn-secondary" type="submit" name="force" value="true">Перераспределить</button>
</form>
</div>
</div>
<div th:if="${notice}" class="alert alert-success" th:text="${notice}"></div>
<div th:if="${error}" class="alert alert-error" th:text="${error}"></div>
<h2 class="section-title">Уровни вариантов</h2>
<div class="card card-flush mt-2 mb-6">
<table>
<thead><tr><th>Код</th><th>Название</th><th>Вариантов</th></tr></thead>
<tbody>
<tr th:each="level : ${lab.levels}">
<td class="font-mono" th:text="${level.code}">—</td>
<td th:text="${level.title}">—</td>
<td th:text="${#lists.size(level.variants)}">0</td>
</tr>
<tr th:if="${#lists.isEmpty(lab.levels)}"><td colspan="3" class="empty">Уровни не найдены.</td></tr>
</tbody>
</table>
</div>
<div class="flex items-center justify-between gap-4 mb-4">
<h2 class="section-title">Студенты и варианты</h2>
<label class="text-sm text-muted">Группа:
<select id="group-filter" class="input filter-select" onchange="filterGroups(this.value)">
<option value="">Все</option>
<option th:each="g : ${groups}" th:value="${g.groupName}" th:text="${g.groupName}">—</option>
</select>
</label>
</div>
<p class="text-sm text-muted mb-4">Уровень по умолчанию — самый простой. Селекты позволяют выдать вариант посложнее или назначить вариант вручную.</p>
<div class="group-section" th:each="g : ${groups}" th:data-group="${g.groupName}">
<h3 class="group-title" th:text="${g.groupName}">Группа</h3>
<div class="card card-flush mb-6">
<table>
<thead><tr><th>Студент</th><th>Логин</th><th>Уровень</th><th>Вариант</th><th>Источник</th></tr></thead>
<tbody>
<tr th:each="s : ${g.students}">
<td th:text="${s.fullName}">—</td>
<td class="font-mono" th:text="${s.studentNo}">—</td>
<td>
<form class="inline-form" th:action="@{/admin/labs/{labId}/set-level(labId=${lab.id})}" method="post">
<input type="hidden" name="studentId" th:value="${s.studentId}">
<select class="input input-sm" name="labLevelId">
<option th:each="level : ${lab.levels}" th:value="${level.id}" th:text="${level.title}"
th:selected="${level.id == s.levelId}">—</option>
</select>
<button class="btn btn-secondary btn-sm" type="submit">OK</button>
</form>
</td>
<td>
<form class="inline-form" th:action="@{/admin/labs/{labId}/assign(labId=${lab.id})}" method="post">
<input type="hidden" name="studentId" th:value="${s.studentId}">
<select class="input input-sm" name="variantId">
<optgroup th:each="level : ${lab.levels}" th:label="${level.title}">
<option th:each="v : ${level.variants}" th:value="${v.id}"
th:text="|${v.number} — ${v.title}|"
th:selected="${v.id == s.variantId}">—</option>
</optgroup>
</select>
<button class="btn btn-secondary btn-sm" type="submit">OK</button>
</form>
</td>
<td><span th:if="${s.variantNumber != null}" class="font-mono" th:text="${s.variantNumber}">—</span></td>
<td><span class="badge" th:classappend="${s.source == 'вручную'} ? 'badge-warn' : 'badge-ok'" th:text="${s.source}">—</span></td>
</tr>
<tr th:if="${#lists.isEmpty(g.students)}"><td colspan="6" class="empty">В группе нет студентов.</td></tr>
</tbody>
</table>
</div>
</div>
<script>
function filterGroups(name) {
document.querySelectorAll('.group-section').forEach(function (el) {
el.classList.toggle('hidden', !!name && el.getAttribute('data-group') !== name);
});
}
</script>
</div>
</body>
</html>