feat(tests): витрина доступных тестов ученику + флаг «доступен ученикам»

Раньше ученик видел лишь 1 тест на предмет (дефолтный). Теперь учитель/админ
может пометить любой свой тест доступным, и он появляется в каталоге на дашборде.

- Миграция 079: tests.available_to_students (default 0).
- testController: list для ученика отдаёт тесты с available_to_students=1 и вопросами;
  create/update принимают флаг; update сделан частичным (не затирает поля при toggle).
- admin «Тесты»: бейдж «Доступен ученикам» + быстрый тумблер «Ученикам/Скрыть»
  (toggleTstAvail; конструктор доступен и учителям — видят свои тесты).
- Дашборд: виджет «Тесты» → секция «Доступные тесты» (loadAvailableTests), клик
  запускает фикс-тест. Прячется, если доступных нет.

⚠️ Живой БД нужен npm run migrate (колонка).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-23 11:03:42 +03:00
parent c5d440a7a9
commit c6d323ec6d
4 changed files with 75 additions and 13 deletions
+17
View File
@@ -40,10 +40,12 @@
<span class="q-badge q-badge-subj">${SUBJ_N[t.subject_slug]||t.subject_slug}</span>
<span style="font-size:0.75rem;color:var(--text-3)">${t.question_count} вопросов</span>
<span style="font-size:0.75rem;color:var(--text-3)">${fmtDate(t.created_at)}</span>
${t.available_to_students ? `<span class="q-badge" style="background:rgba(6,214,160,.14);color:#059669">Доступен ученикам</span>` : ''}
${t.description ? `<span style="font-size:0.75rem;color:var(--text-2)">${esc(t.description)}</span>` : ''}
</div>
</div>
<div class="q-card-actions">
<button class="btn-edit-q" onclick="toggleTstAvail(${t.id})" title="Показывать ли тест ученикам в каталоге">${t.available_to_students ? 'Скрыть' : 'Ученикам'}</button>
<button class="btn-edit-q" onclick="editTst(${t.id})">Изменить</button>
<button class="btn-del-q" onclick="deleteTst(${t.id})" title="Удалить"><i data-lucide="x" style="width:14px;height:14px"></i></button>
</div>
@@ -261,6 +263,20 @@
} catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); }
}
// Открыть/скрыть тест для учеников (попадает в каталог на дашборде)
async function toggleTstAvail(id) {
const t = allTests.find(x => x.id === id);
if (!t) return;
if (!t.question_count) { LS.toast('Сначала добавьте вопросы в тест', 'error'); return; }
const next = t.available_to_students ? 0 : 1;
try {
await LS.updateTest(id, { available_to_students: next });
t.available_to_students = next;
renderTests();
LS.toast(next ? 'Тест открыт ученикам' : 'Тест скрыт от учеников', 'success');
} catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); }
}
// Expose handlers
window.loadTests = load;
window.renderTests = renderTests;
@@ -274,6 +290,7 @@
window.closeTstModal = closeTstModal;
window.saveTst = saveTst;
window.deleteTst = deleteTst;
window.toggleTstAvail = toggleTstAvail;
window.AdminSections = window.AdminSections || {};
window.AdminSections.tests = {