187 lines
8.0 KiB
HTML
187 lines
8.0 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Тесты — каталог{% endblock %}
|
|
|
|
{% macro catalog_test_params_line(t) -%}
|
|
{%- set tl = t.time_limit -%}
|
|
{%- set timestr = 'без ограничения' if tl is none or tl == 0 else (tl|string ~ ' мин') -%}
|
|
{%- set rm = t.result_mode or 'end' -%}
|
|
{%- set res = 'сразу' if rm == 'immediate' else 'в конце' -%}
|
|
{%- if rm != 'immediate' -%}
|
|
{%- set hint = 'недоступны' -%}
|
|
{%- elif t.hints_enabled -%}
|
|
{%- set hint = 'вкл' -%}
|
|
{%- else -%}
|
|
{%- set hint = 'выкл' -%}
|
|
{%- endif -%}
|
|
Порог: {{ t.passing_threshold }}% · Вопросов: {{ t.questions_count }} · Время: {{ timestr }} · Результат: {{ res }} · Подсказки: {{ hint }}
|
|
{%- endmacro %}
|
|
|
|
{% block content %}
|
|
<section class="legacy-list-shell">
|
|
<h1 class="font-headline legacy-list-title">Тесты</h1>
|
|
<div class="legacy-list-toolbar">
|
|
<button id="btn-create-test" class="btn btn-ghost" type="button">
|
|
Создать
|
|
</button>
|
|
</div>
|
|
|
|
{% if visible %}
|
|
<ul class="list-stack" aria-label="Тесты в общем списке">
|
|
{% for t in visible %}
|
|
<li class="list-row list-row--split">
|
|
<div class="list-row__main">
|
|
<a href="{{ url_for('tests.tests_editor_page', test_id=t.id) }}" class="list-row__link">
|
|
<span class="list-row__title">{{ t.title }}</span>
|
|
<span class="list-row__meta">
|
|
{{ t.author_full_name or '—' }}
|
|
<span class="list-row__meta-tail"> · Версия {{ t.version }}</span>
|
|
</span>
|
|
<span class="list-row__params muted" style="display:block;font-size:0.78rem;margin-top:0.2rem;line-height:1.35;">{{ catalog_test_params_line(t) }}</span>
|
|
</a>
|
|
</div>
|
|
<div class="list-row__side">
|
|
<button type="button" class="btn btn-ghost btn-start-pass" data-test-id="{{ t.id }}">
|
|
{{ 'Продолжить' if t.has_in_progress_attempt else 'Пройти' }}
|
|
</button>
|
|
</div>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
<p class="text-muted">Нет тестов</p>
|
|
{% endif %}
|
|
|
|
{% if hidden %}
|
|
<h2 class="font-headline legacy-list-subtitle">Скрытые вами из списка</h2>
|
|
<ul class="list-stack" aria-label="Скрытые тесты автора">
|
|
{% for t in hidden %}
|
|
<li class="list-row list-row--split list-row--hidden">
|
|
<div class="list-row__main">
|
|
<a href="{{ url_for('tests.tests_editor_page', test_id=t.id) }}" class="list-row__link">
|
|
<span class="list-row__title">{{ t.title }}</span>
|
|
<span class="list-row__meta">
|
|
{{ t.author_full_name or '—' }}
|
|
<span class="list-row__meta-tail"> · Версия {{ t.version }} · скрыт</span>
|
|
</span>
|
|
<span class="list-row__params muted" style="display:block;font-size:0.78rem;margin-top:0.2rem;line-height:1.35;">{{ catalog_test_params_line(t) }}</span>
|
|
</a>
|
|
</div>
|
|
<div class="list-row__side">
|
|
<button type="button" class="btn btn-ghost btn-start-pass" data-test-id="{{ t.id }}">
|
|
{{ 'Продолжить' if t.has_in_progress_attempt else 'Пройти' }}
|
|
</button>
|
|
</div>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% endif %}
|
|
</section>
|
|
|
|
<dialog id="dlg-create"
|
|
class="m-0 p-0 w-full sm:w-full sm:max-w-md
|
|
h-fit max-h-[calc(100vh-2rem)]
|
|
rounded-2xl bg-white backdrop:bg-ink-900/50 m-auto">
|
|
<form method="dialog" class="flex flex-col sm:h-auto bg-white sm:rounded-2xl">
|
|
<div class="px-4 sm:px-5 py-3 border-b border-ink-300/60 flex items-center justify-between">
|
|
<h2 class="text-lg font-semibold">Новый тест</h2>
|
|
<button type="button" id="dlg-cancel-x"
|
|
class="p-2 rounded hover:bg-ink-100 min-w-10 min-h-10"
|
|
aria-label="Закрыть">
|
|
<span class="material-symbols-outlined">close</span>
|
|
</button>
|
|
</div>
|
|
<div class="flex-1 overflow-y-auto px-4 sm:px-5 py-4 space-y-3">
|
|
<label class="block">
|
|
<span class="text-sm font-medium text-ink-700">Название</span>
|
|
<input id="new-test-title" type="text" required maxlength="200"
|
|
class="mt-1 w-full rounded-lg border border-ink-300 px-3 py-3
|
|
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20" />
|
|
</label>
|
|
<label class="block">
|
|
<span class="text-sm font-medium text-ink-700">Описание (опц.)</span>
|
|
<textarea id="new-test-desc" rows="3"
|
|
class="mt-1 w-full rounded-lg border border-ink-300 px-3 py-2
|
|
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20"></textarea>
|
|
</label>
|
|
</div>
|
|
<div class="px-4 sm:px-5 py-3 border-t border-ink-300/60 flex justify-end gap-2
|
|
bg-ink-100/40 sm:rounded-b-2xl
|
|
pb-[max(env(safe-area-inset-bottom),0.75rem)]">
|
|
<button type="button" id="dlg-cancel"
|
|
class="px-4 py-2.5 rounded-lg text-ink-700 hover:bg-ink-100 min-h-11">Отмена</button>
|
|
<button type="button" id="dlg-submit"
|
|
class="px-4 py-2.5 rounded-lg bg-brand-600 hover:bg-brand-700 text-white min-h-11">
|
|
Создать
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</dialog>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
(() => {
|
|
const dlg = document.getElementById('dlg-create');
|
|
const titleEl = document.getElementById('new-test-title');
|
|
const descEl = document.getElementById('new-test-desc');
|
|
|
|
document.getElementById('btn-create-test').addEventListener('click', () => {
|
|
titleEl.value = '';
|
|
descEl.value = '';
|
|
if (typeof dlg.showModal === 'function') dlg.showModal();
|
|
else dlg.setAttribute('open', 'open');
|
|
setTimeout(() => titleEl.focus(), 50);
|
|
});
|
|
document.getElementById('dlg-cancel').addEventListener('click', () => dlg.close());
|
|
document.getElementById('dlg-cancel-x').addEventListener('click', () => dlg.close());
|
|
|
|
document.getElementById('dlg-submit').addEventListener('click', async () => {
|
|
const title = titleEl.value.trim();
|
|
if (!title) { titleEl.focus(); return; }
|
|
try {
|
|
const r = await fetch('/api/tests', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ title, description: descEl.value.trim() || null }),
|
|
});
|
|
const data = await r.json();
|
|
if (!r.ok) throw new Error(data.error || 'Не удалось создать тест.');
|
|
window.location.href = `/tests/${data.testId}/edit`;
|
|
} catch (e) {
|
|
alert(e.message || 'Не удалось создать тест.');
|
|
}
|
|
});
|
|
|
|
const passButtons = Array.from(document.querySelectorAll('.btn-start-pass'));
|
|
passButtons.forEach((btn) => {
|
|
btn.addEventListener('click', async () => {
|
|
const testId = btn.dataset.testId;
|
|
if (!testId) return;
|
|
btn.disabled = true;
|
|
const oldText = (btn.textContent || '').trim() || 'Пройти';
|
|
btn.textContent = `${oldText}…`;
|
|
try {
|
|
const r = await fetch(`/api/tests/${testId}/attempts/start`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({}),
|
|
});
|
|
let data = {};
|
|
try { data = await r.json(); } catch (_) {}
|
|
if (!r.ok || !data.attempt || !data.attempt.id) {
|
|
// Если нет страницы попытки, уводим в редактор.
|
|
// Тогда ведём в карточку теста, чтобы пользователь не попадал на not_found.
|
|
window.location.href = `/tests/${testId}/edit`;
|
|
return;
|
|
}
|
|
window.location.href = `/tests/${testId}/attempt/${data.attempt.id}`;
|
|
} catch (e) {
|
|
window.location.href = `/tests/${testId}/edit`;
|
|
return;
|
|
}
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|