203 lines
13 KiB
HTML
203 lines
13 KiB
HTML
<!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="page-head mb-2">
|
||
<div>
|
||
<h1 class="page-title" th:text="|ЛР ${lab.number}. ${lab.title}|">ЛР 1</h1>
|
||
<div class="meta mb-2">
|
||
<span class="badge"
|
||
th:classappend="${lab.status == 'published'} ? 'badge-ok' : 'badge-warn'"
|
||
th:text="${lab.status == 'published'} ? 'Опубликована' : (${lab.status == 'archived'} ? 'В архиве' : 'Черновик')">Черновик</span>
|
||
<form class="inline-form" th:action="@{/admin/labs/{id}/toggle-status(id=${lab.id})}"
|
||
method="post"
|
||
data-confirm="Сменить статус лабораторной? Опубликованную увидят студенты, черновик — только вы.">
|
||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||
<button class="btn btn-sm" type="submit"
|
||
th:text="${lab.status == 'published'} ? 'Скрыть' : 'Опубликовать'">Опубликовать
|
||
</button>
|
||
</form>
|
||
</div>
|
||
<span th:if="${deadlineMsk != null}">Срок: <span th:text="${deadlineMsk} + ' МСК'"></span></span>
|
||
<span th:if="${defenseMsk != null}"> · Защита: <span th:text="${defenseMsk} + ' МСК'"></span></span>
|
||
· Максимум: <span th:text="${lab.pointsMax}"></span> б.
|
||
<span th:if="${totalVariants > 0}"> · Вариантов занято: <span th:text="${occupiedVariants}"></span> из <span
|
||
th:text="${totalVariants}"></span></span>
|
||
<span th:if="${submissionCount > 0}"> · Сдач: <span th:text="${submissionCount}"></span></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<h2 class="section-title m-0">Условие лабораторной</h2>
|
||
<button class="mini-btn" type="button" data-action="lab-edit"
|
||
title="Редактировать параметры лабораторной" aria-label="Редактировать параметры лабораторной">
|
||
<span class="cell-ico ico ico-pencil" aria-hidden="true"></span>
|
||
</button>
|
||
</div>
|
||
<div class="card card-body md-body mb-6" id="lab-description-view" th:utext="${descriptionHtml}"></div>
|
||
<form id="lab-edit-form" hidden class="card card-body form-stack mb-6" method="post"
|
||
th:action="@{/admin/labs/{id}/edit(id=${lab.id})}">
|
||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||
<div class="grid grid-2">
|
||
<div><label class="label" for="l-title">Название</label>
|
||
<input class="input" id="l-title" name="title" th:value="${lab.title}" required></div>
|
||
<div><label class="label" for="l-points">Максимум баллов</label>
|
||
<input class="input" id="l-points" name="pointsMax" type="number" step="0.1" min="0"
|
||
th:value="${lab.pointsMax}" required></div>
|
||
</div>
|
||
<div class="grid grid-2">
|
||
<div><label class="label" for="l-deadline">Срок сдачи (время — МСК)</label>
|
||
<input class="input" id="l-deadline" name="deadlineAt" type="datetime-local"
|
||
th:value="${deadlineLocal}"></div>
|
||
<div><label class="label" for="l-defense">Дата защиты (время — МСК)</label>
|
||
<input class="input" id="l-defense" name="defenseAt" type="datetime-local" th:value="${defenseLocal}">
|
||
</div>
|
||
</div>
|
||
<div><label class="label" for="l-status">Статус</label>
|
||
<select class="input" id="l-status" name="status">
|
||
<option value="draft" th:selected="${lab.status == 'draft'}">Черновик</option>
|
||
<option value="published" th:selected="${lab.status == 'published'}">Опубликована</option>
|
||
<option value="archived" th:selected="${lab.status == 'archived'}">В архиве</option>
|
||
</select></div>
|
||
<div><label class="label" for="l-desc">Условие (markdown)</label>
|
||
<textarea class="input" id="l-desc" name="descriptionMd" rows="14"
|
||
th:text="${lab.descriptionMd}"></textarea></div>
|
||
<div class="flex justify-end gap-2">
|
||
<button class="btn btn-secondary" type="button" data-action="lab-edit-cancel">Отмена</button>
|
||
<button class="btn btn-primary" type="submit">Сохранить</button>
|
||
</div>
|
||
</form>
|
||
|
||
<h2 class="section-title">Уровни и варианты</h2>
|
||
<div th:if="${#lists.isEmpty(lab.levels)}" class="card card-body text-muted">
|
||
Уровней пока нет — добавьте их при создании лабораторной или импорте.
|
||
</div>
|
||
|
||
<div th:each="level : ${lab.levels}" class="card mb-4">
|
||
<div class="card-body">
|
||
<div class="flex flex-wrap items-center justify-between gap-2 mb-4">
|
||
<h3 class="section-title m-0" th:text="${level.title}">Уровень</h3>
|
||
<div class="flex items-center gap-2">
|
||
<button class="mini-btn" type="button" th:attr="data-modal=|modal-level-rename-${level.id}|"
|
||
title="Переименовать уровень" aria-label="Переименовать уровень">
|
||
<span class="cell-ico ico ico-pencil" aria-hidden="true"></span>
|
||
</button>
|
||
<form class="inline-form"
|
||
th:action="@{/admin/labs/{labId}/levels/{levelId}/delete(labId=${lab.id}, levelId=${level.id})}"
|
||
method="post" data-confirm="Удалить уровень? Варианты на уровне должны быть пустыми.">
|
||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||
<button class="mini-btn danger" type="submit" title="Удалить уровень"
|
||
aria-label="Удалить уровень">
|
||
<span class="cell-ico ico ico-trash" aria-hidden="true"></span>
|
||
</button>
|
||
</form>
|
||
<button class="btn btn-sm" type="button" th:attr="data-modal=|modal-variant-add-${level.id}|">+
|
||
Добавить
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div th:each="v : ${level.variants}" class="variant-block mb-4">
|
||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||
<div class="variant-name">Вариант <b th:text="${v.number}">—</b><span th:if="${v.title}"
|
||
class="text-muted"
|
||
th:text="' · ' + ${v.title}"></span>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button class="mini-btn" type="button" th:attr="data-modal=|modal-variant-edit-${v.id}|"
|
||
title="Редактировать вариант" aria-label="Редактировать вариант">
|
||
<span class="cell-ico ico ico-pencil" aria-hidden="true"></span>
|
||
</button>
|
||
<form class="inline-form"
|
||
th:action="@{/admin/labs/{labId}/variants/{variantId}/delete(labId=${lab.id}, variantId=${v.id})}"
|
||
method="post" data-confirm="Удалить вариант вместе с его условием?">
|
||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||
<button class="mini-btn danger" type="submit" title="Удалить вариант"
|
||
aria-label="Удалить вариант">
|
||
<span class="cell-ico ico ico-trash" aria-hidden="true"></span>
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<div class="md-body mt-2" th:utext="${variantHtml[v.id]}"></div>
|
||
</div>
|
||
<p th:if="${#lists.isEmpty(level.variants)}" class="text-sm text-muted m-0">Вариантов пока нет.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div th:each="level : ${lab.levels}" th:remove="tag">
|
||
<dialog th:id="|modal-level-rename-${level.id}|" class="modal">
|
||
<div class="modal-header">
|
||
<h2 class="modal-title">Переименовать уровень</h2>
|
||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form class="form-stack" th:action="@{/admin/labs/{labId}/levels/save(labId=${lab.id})}" method="post">
|
||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||
<input type="hidden" name="levelId" th:value="${level.id}">
|
||
<div><label class="label" for="|lvl-title-${level.id}|">Название</label>
|
||
<input class="input" th:id="|lvl-title-${level.id}|" name="title" th:value="${level.title}"
|
||
required></div>
|
||
<button class="btn btn-primary" type="submit">Сохранить</button>
|
||
</form>
|
||
</div>
|
||
</dialog>
|
||
<dialog th:id="|modal-variant-add-${level.id}|" class="modal">
|
||
<div class="modal-header">
|
||
<h2 class="modal-title" th:text="|Новый вариант — ${level.title}|">Новый вариант</h2>
|
||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form class="form-stack" th:action="@{/admin/labs/{labId}/variants/save(labId=${lab.id})}"
|
||
method="post">
|
||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||
<input type="hidden" name="labLevelId" th:value="${level.id}">
|
||
<div class="grid grid-2">
|
||
<div><label class="label" for="|va-number-${level.id}|">Номер</label>
|
||
<input class="input" th:id="|va-number-${level.id}|" name="number" required></div>
|
||
<div><label class="label" for="|va-title-${level.id}|">Название</label>
|
||
<input class="input" th:id="|va-title-${level.id}|" name="title"></div>
|
||
</div>
|
||
<div><label class="label" for="|va-statement-${level.id}|">Условие (markdown)</label>
|
||
<textarea class="input" th:id="|va-statement-${level.id}|" name="statementMd"
|
||
rows="8"></textarea></div>
|
||
<button class="btn btn-primary" type="submit">Создать</button>
|
||
</form>
|
||
</div>
|
||
</dialog>
|
||
<th:block th:each="v : ${level.variants}">
|
||
<dialog th:id="|modal-variant-edit-${v.id}|" class="modal">
|
||
<div class="modal-header">
|
||
<h2 class="modal-title" th:text="|Вариант ${v.number}|">Вариант</h2>
|
||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form class="form-stack" th:action="@{/admin/labs/{labId}/variants/save(labId=${lab.id})}"
|
||
method="post">
|
||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||
<input type="hidden" name="variantId" th:value="${v.id}">
|
||
<input type="hidden" name="labLevelId" th:value="${level.id}">
|
||
<div class="grid grid-2">
|
||
<div><label class="label" for="|ve-number-${v.id}|">Номер</label>
|
||
<input class="input" th:id="|ve-number-${v.id}|" name="number" th:value="${v.number}"
|
||
required></div>
|
||
<div><label class="label" for="|ve-title-${v.id}|">Название</label>
|
||
<input class="input" th:id="|ve-title-${v.id}|" name="title" th:value="${v.title}">
|
||
</div>
|
||
</div>
|
||
<div><label class="label" for="|ve-statement-${v.id}|">Условие (markdown)</label>
|
||
<textarea class="input" th:id="|ve-statement-${v.id}|" name="statementMd" rows="10"
|
||
th:text="${v.statementMd}"></textarea></div>
|
||
<button class="btn btn-primary" type="submit">Сохранить</button>
|
||
</form>
|
||
</div>
|
||
</dialog>
|
||
</th:block>
|
||
</div>
|
||
<script th:src="@{/js/admin-lab.js(v='13')}" defer></script>
|
||
</div>
|
||
</body>
|
||
</html>
|