Phase 2+5: lab submissions and student lab flow

- progress: submissions table+V4; SubmissionService (deadline-gated submit,
  unlimited resubmits, per-student history, zip download); files on disk
  data/submissions/{student}/{lab}/{seq}/
- student API: POST submit (multipart), GET me submissions, download; admin
  GET lab submissions + admin download (zip pack)
- progress enrich(): labId in MeAssignment; web DTO aligned
- web: /student dashboard (avg/best/worst cards), /student/labs cards,
  /student/labs/{id}: condition md, variant block, submit form, history;
  'Лабораторные' link in nav for students
- admin: submissions column on lab assignment table + download link
- fixed long-standing template bug: broken @{} expression (missing }) in
  admin/lab and student/lab
This commit is contained in:
2026-09-12 00:20:46 +03:00
parent 7818f459bd
commit a3be71db27
21 changed files with 628 additions and 49 deletions

View File

@@ -0,0 +1,26 @@
<!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>
<div class="grid grid-2">
<div th:each="c : ${cards}" class="card">
<div class="card-body">
<h2 class="section-title">
<a th:href="@{/student/labs/{id}(id=${c.id})}" th:text="|Лабораторная работа ${c.number}. ${c.title}|">ЛР</a>
</h2>
<div class="flex flex-wrap gap-2 text-sm mt-2">
<span th:if="${c.variantNumber}" class="badge badge-warn">Вариант <span class="font-mono" th:text="${c.variantNumber}"></span></span>
<span th:if="${c.submissionSeq}" class="badge badge-ok">сдано (попытка <span th:text="${c.submissionSeq}"></span>)</span>
<span th:unless="${c.submissionSeq}" class="badge badge-warn">не сдано</span>
<span th:if="${c.grade}" class="badge badge-ok">оценка <span th:text="${c.grade}"></span></span>
</div>
<p class="text-sm text-muted mt-2" th:if="${c.deadlineMsk}" th:text="|Срок сдачи: ${c.deadlineMsk} МСК|">—</p>
</div>
</div>
<div th:if="${#lists.isEmpty(cards)}" class="card card-body text-muted">Опубликованных лабораторных пока нет.</div>
</div>
</div>
</body>
</html>