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
This commit is contained in:
2026-09-10 23:25:36 +03:00
parent 0fdb9faab6
commit feed600d43
17 changed files with 408 additions and 67 deletions

View File

@@ -5,23 +5,25 @@
<div id="content">
<h1 class="page-title">Здравствуйте, <span th:text="${currentUser.name}">студент</span></h1>
<p class="text-sm text-muted mb-6">Ваши лабораторные работы</p>
<div th:if="${#lists.isEmpty(assignments)}" class="card card-body text-muted">
Пока нет назначенных лабораторных. Ожидайте, пока преподаватель распределит варианты.
</div>
<div class="card card-flush" th:unless="${#lists.isEmpty(assignments)}">
<table>
<thead><tr><th>№</th><th>Название</th><th>Вариант</th><th>Срок сдачи</th><th>Дата защиты</th><th>Статус</th></tr></thead>
<tbody>
<tr th:each="a : ${assignments}">
<td th:text="${a.labNumber}">1</td>
<td th:text="${a.labTitle}">—</td>
<td th:text="${a.variantNumber}">—</td>
<td class="text-sm text-muted" th:text="${a.deadlineAt}">—</td>
<td class="text-sm text-muted" th:text="${a.defenseAt}">—</td>
<td><span class="badge badge-warn">назначено</span></td>
</tr>
</tbody>
</table>
<div th:each="a : ${assignments}" class="card mb-6">
<div class="card-body">
<div class="flex flex-wrap items-center justify-between gap-4">
<h2 class="section-title" th:text="|ЛР ${a.labNumber}. ${a.labTitle}|">ЛР</h2>
<div class="flex gap-2 items-center">
<span class="badge badge-ok">Вариант <span class="font-mono" th:text="${a.variantNumber}">—</span></span>
<span th:if="${a.levelTitle}" class="badge badge-warn" th:text="${a.levelTitle}">—</span>
</div>
</div>
<p class="text-sm text-muted" th:if="${a.variantTitle}" th:text="${a.variantTitle}">—</p>
<p class="text-sm text-muted" th:if="${a.deadlineAt}" th:text="|Срок сдачи: ${a.deadlineAt}|">—</p>
<p class="text-sm text-muted" th:if="${a.defenseAt}" th:text="|Защита: ${a.defenseAt}|">—</p>
<div class="md-body mt-4" th:utext="${a.statementHtml}"></div>
</div>
</div>
</div>
</body>