- tablekit builds a filter row under the headers for every th with data-filter; distinct cell values become a select (label from data-filter-label), or a text substring filter with data-filter="text" - filters combine with the global table search; placeholder rows (colspan) are not filtered away; filter row follows the lab-focus column hiding - enabled on: matrix lab columns (by variant), defense table (ЛР, status with readable values), students (Основа, Пароль), lab assignment lists (Уровень, Источник, Сдача), labs list (Статус)
105 lines
7.3 KiB
HTML
105 lines
7.3 KiB
HTML
<!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">
|
|
<div class="flex items-center justify-between gap-4 mb-6">
|
|
<h1 class="page-title">Лабораторные</h1>
|
|
<div class="flex gap-2">
|
|
<input class="input input-sm table-search" data-target="labs-table"
|
|
placeholder="Поиск по названию…" aria-label="Поиск по лабораторным">
|
|
<button class="btn btn-secondary" type="button" onclick="openModal('modal-import-lab')">Импорт</button>
|
|
<button class="btn btn-primary" type="button" onclick="openModal('modal-create-lab')">+ Создать</button>
|
|
</div>
|
|
</div>
|
|
<p class="text-sm text-muted mb-4">Статус <b>draft</b> — черновик (редактирование, студентам не видна).
|
|
<b>published</b> — опубликована (доступна студентам, видны распределённые варианты). Нажмите на бейдж статуса, чтобы переключить.</p>
|
|
<div th:if="${notice}" class="alert alert-success" th:text="${notice}"></div>
|
|
<div th:if="${error}" class="alert alert-error" th:text="${error}"></div>
|
|
|
|
<div class="card card-flush">
|
|
<table id="labs-table" class="tablekit">
|
|
<thead><tr><th class="th-sortable is-asc" title="Номер — по возрастанию">№</th><th>Название</th><th>Вариантов</th><th data-filter>Статус</th><th data-nosort></th></tr></thead>
|
|
<tbody>
|
|
<tr th:each="lab : ${labs}">
|
|
<td th:text="${lab.number}">1</td>
|
|
<td><a th:href="@{/admin/labs/{id}(id=${lab.id})}" th:text="${lab.title}">—</a></td>
|
|
<td th:text="${lab.variantCount}">0</td>
|
|
<td th:attr="data-filter-value=${lab.status}">
|
|
<form class="inline-form" th:action="@{/admin/labs/{id}/toggle-status(id=${lab.id})}" method="post">
|
|
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
|
<button class="badge badge-btn"
|
|
th:classappend="${lab.status == 'published'} ? 'badge-ok' : 'badge-warn'"
|
|
type="submit" th:text="${lab.status}">—</button>
|
|
</form>
|
|
</td>
|
|
<td class="text-center"><form th:action="@{/admin/labs/{id}/delete(id=${lab.id})}" method="post"><input type="hidden" name="_csrf" th:value="${csrfToken}"><button class="btn btn-danger btn-sm" type="submit">Удалить</button></form></td>
|
|
</tr>
|
|
<tr th:if="${#lists.isEmpty(labs)}"><td colspan="5" class="empty">Лабораторных пока нет.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
|
|
<dialog id="modal-import-lab" class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">Импорт лабораторной</h2>
|
|
<button class="modal-close" type="button" onclick="closeModal('modal-import-lab')">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form class="form-stack" th:action="@{/admin/labs/import}" method="post" enctype="multipart/form-data">
|
|
<div><label class="label" for="labFile">Файл</label>
|
|
<input class="input" id="labFile" type="file" name="file" accept=".zip" required></div>
|
|
<p class="hint">
|
|
Формат — ZIP: <code class="code">lab.yaml</code> + <code class="code">README.md</code> +
|
|
<code class="code">variants/*.md</code> (front-matter: <code class="code">level</code>,
|
|
<code class="code">number</code>, <code class="code">title</code>) +
|
|
<code class="code">assets/</code> (картинки). Пример структуры — в
|
|
<code class="code">deploy/IMPORT.md</code>.
|
|
</p>
|
|
<p class="hint">Даты сдачи и защиты после импорта задаются на карточке лабораторной.</p>
|
|
<button class="btn btn-primary" type="submit">Импортировать</button>
|
|
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
|
</form>
|
|
</div>
|
|
</dialog>
|
|
|
|
<dialog id="modal-create-lab" class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">Новая лабораторная</h2>
|
|
<button class="modal-close" type="button" onclick="closeModal('modal-create-lab')">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form class="form-stack" th:action="@{/admin/labs/create}" method="post">
|
|
<div class="grid grid-2">
|
|
<div><label class="label" for="labNumber">Номер</label>
|
|
<input class="input" id="labNumber" name="number" type="number" min="1" required></div>
|
|
<div><label class="label" for="labPoints">Максимум баллов</label>
|
|
<input class="input" id="labPoints" name="pointsMax" type="number" step="0.1" min="0" value="5" required></div>
|
|
</div>
|
|
<div><label class="label" for="labTitle">Название</label>
|
|
<input class="input" id="labTitle" name="title" required></div>
|
|
<div class="grid grid-2">
|
|
<div><label class="label" for="labDeadline">Срок сдачи</label>
|
|
<input class="input" id="labDeadline" name="deadlineAt" type="datetime-local"></div>
|
|
<div><label class="label" for="labDefense">Дата защиты</label>
|
|
<input class="input" id="labDefense" name="defenseAt" type="datetime-local"></div>
|
|
</div>
|
|
<div><label class="label" for="labStatus">Статус</label>
|
|
<select class="input" id="labStatus" name="status">
|
|
<option value="draft">draft</option>
|
|
<option value="published">published</option>
|
|
</select></div>
|
|
<div><label class="label" for="labLevels">Уровни (по одному на строку: код; название)</label>
|
|
<textarea class="input" id="labLevels" name="levels" rows="4" placeholder="basic; Основная группа advanced; Повышенная сложность"></textarea>
|
|
<p class="hint">Если оставить пустым, будет создан уровень «Основная группа». Варианты добавите на странице лабы.</p></div>
|
|
<div><label class="label" for="labDesc">Условие (markdown, можно позже)</label>
|
|
<textarea class="input" id="labDesc" name="descriptionMd" rows="6"></textarea></div>
|
|
<button class="btn btn-primary" type="submit">Создать</button>
|
|
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
|
</form>
|
|
</div>
|
|
</dialog>
|
|
</div>
|
|
</body>
|
|
</html> |