'use strict'; /* admin → tests section (тест-шаблоны: создание + редактирование + список вопросов) */ (function () { 'use strict'; let inited = false; let allTests = []; let openTstId = null; let editingTstId = null; let _tstShowAnswers = true; const _tstPickerCache = {}; async function load() { const subj = document.getElementById('tst-subj').value; const wrap = document.getElementById('tst-list-wrap'); wrap.innerHTML = '
'; try { allTests = await LS.getTests(subj || null); renderTests(); } catch (e) { wrap.innerHTML = `
Ошибка: ${esc(e.message)}
`; } } function renderTests() { const { fmtDate } = AdminCtx; const search = document.getElementById('tst-search').value.toLowerCase(); const filtered = search ? allTests.filter(t => t.title.toLowerCase().includes(search)) : allTests; document.getElementById('tst-count').textContent = `${filtered.length} тестов`; const wrap = document.getElementById('tst-list-wrap'); if (!filtered.length) { wrap.innerHTML = '
Тестов не найдено
'; return; } const SUBJ_N = { bio:'Биология', chem:'Химия', math:'Математика', phys:'Физика' }; wrap.innerHTML = `
${filtered.map(t => `
#${t.id}
${esc(t.title)}
${SUBJ_N[t.subject_slug]||t.subject_slug} ${t.question_count} вопросов ${fmtDate(t.created_at)} ${t.available_to_students ? `Доступен ученикам` : ''} ${t.description ? `${esc(t.description)}` : ''}
`).join('')}
`; if (window.lucide) lucide.createIcons(); } async function toggleTstDrawer(id) { const drawer = document.getElementById('tstdrawer-' + id); if (!drawer) return; if (openTstId && openTstId !== id) { const old = document.getElementById('tstdrawer-' + openTstId); if (old) old.style.display = 'none'; } if (openTstId === id) { drawer.style.display = 'none'; openTstId = null; return; } openTstId = id; drawer.style.display = ''; await renderTstDrawer(id); } async function renderTstDrawer(id) { const inner = document.getElementById('tstdinner-' + id); if (!inner) return; inner.innerHTML = '
'; try { const [t, subjectQs] = await Promise.all([ LS.getTest(id), LS.getQuestions( (_tstPickerCache[id]?.subject_slug) || allTests.find(x => x.id === id)?.subject_slug || '', null, 'date_asc' ).catch(() => []), ]); const inIds = new Set(t.questions.map(q => q.id)); _tstPickerCache[id] = { subjectQs, inIds, subject_slug: t.subject_slug }; inner.innerHTML = `
Вопросы в тесте (${t.questions.length})
${renderTstQList(t.questions, id)}
Добавить вопросы
${renderTstPicker(subjectQs, inIds, id)}
`; AdminCtx.renderMath(inner); if (window.lucide) lucide.createIcons(); } catch (e) { inner.innerHTML = `
Ошибка: ${esc(e.message)}
`; } } function renderTstQList(questions, tid) { const { DIFF_LABELS, qTypeBadge, qOptsPreview } = AdminCtx; if (!questions.length) return '
Вопросов нет. Добавьте справа
'; return questions.map((q, i) => `
${i+1}.
${esc(q.text)}
${DIFF_LABELS[q.difficulty]||q.difficulty} ${qTypeBadge(q.type)} ${qOptsPreview(q)}
`).join(''); } function renderTstPicker(questions, inIds, tid) { const { DIFF_LABELS, qTypeBadge, qOptsPreview } = AdminCtx; if (!questions.length) return '
Вопросов нет в этом предмете
'; return questions.map(q => { const added = inIds.has(q.id); return `
${esc(q.text)}
${DIFF_LABELS[q.difficulty]||''} ${qTypeBadge(q.type)} ${qOptsPreview(q)}
`; }).join(''); } async function filterTstPicker(tid) { const search = document.getElementById('tstps-'+tid)?.value.toLowerCase() || ''; const cache = _tstPickerCache[tid]; if (!cache) return; const filtered = search ? cache.subjectQs.filter(q => q.text.toLowerCase().includes(search)) : cache.subjectQs; const picker = document.getElementById('tstpicker-'+tid); if (picker) { picker.innerHTML = renderTstPicker(filtered, cache.inIds, tid); AdminCtx.renderMath(picker); if(window.lucide)lucide.createIcons(); } } async function tstAddQ(tid, qid) { const btn = document.getElementById(`tstbtn-${tid}-${qid}`); if (btn) { btn.disabled = true; btn.textContent = '…'; } try { await LS.addQuestionsToTest(tid, [qid]); const t = allTests.find(x => x.id === tid); if (t) t.question_count++; renderTests(); openTstId = tid; document.getElementById('tstdrawer-' + tid).style.display = ''; await renderTstDrawer(tid); } catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); if (btn) { btn.disabled=false; btn.textContent='+'; } } } async function tstRemoveQ(tid, qid) { try { await LS.removeQFromTest(tid, qid); const t = allTests.find(x => x.id === tid); if (t) t.question_count = Math.max(0, t.question_count - 1); renderTests(); openTstId = tid; document.getElementById('tstdrawer-' + tid).style.display = ''; await renderTstDrawer(tid); } catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); } } /* ── Test modal ── */ function setTstShowAnswers(val) { _tstShowAnswers = val; document.getElementById('tstf-show-yes').classList.toggle('active', val); document.getElementById('tstf-show-no').classList.toggle('active', !val); } function openTstModal(t = null) { editingTstId = t ? t.id : null; document.getElementById('tst-modal-title').textContent = t ? `Редактировать: ${t.title}` : 'Создать тест'; document.getElementById('tstf-title').value = t?.title || ''; document.getElementById('tstf-subject').value = t?.subject_slug || ''; document.getElementById('tstf-desc').value = t?.description || ''; document.getElementById('tstf-time').value = t?.time_limit || ''; document.getElementById('tstf-error').textContent = ''; setTstShowAnswers(t ? (t.show_answers !== 0) : true); document.getElementById('tst-modal').classList.add('open'); setTimeout(() => document.getElementById('tstf-title').focus(), 80); } function editTst(id) { const t = allTests.find(x => x.id === id); if (t) openTstModal(t); } function closeTstModal() { document.getElementById('tst-modal').classList.remove('open'); editingTstId = null; } async function saveTst() { const title = document.getElementById('tstf-title').value.trim(); const subject_slug= document.getElementById('tstf-subject').value; const description = document.getElementById('tstf-desc').value.trim(); const errEl = document.getElementById('tstf-error'); errEl.textContent = ''; if (!title) { errEl.textContent = 'Введите название'; return; } if (!subject_slug) { errEl.textContent = 'Выберите предмет'; return; } const btn = document.getElementById('tstf-save'); btn.disabled = true; btn.textContent = 'Сохранение…'; const show_answers = _tstShowAnswers ? 1 : 0; const timeVal = parseInt(document.getElementById('tstf-time').value, 10); const time_limit = timeVal >= 1 ? Math.min(600, timeVal) : null; try { if (editingTstId) { await LS.updateTest(editingTstId, { title, subject_slug, description: description||null, show_answers, time_limit }); const idx = allTests.findIndex(x => x.id === editingTstId); if (idx !== -1) Object.assign(allTests[idx], { title, subject_slug, description, show_answers, time_limit }); } else { const { id } = await LS.createTest({ title, subject_slug, description: description||null, show_answers, time_limit }); allTests.unshift({ id, title, subject_slug, description, question_count: 0, created_at: new Date().toISOString() }); closeTstModal(); renderTests(); openTstId = id; document.getElementById('tstdrawer-' + id).style.display = ''; await renderTstDrawer(id); return; } closeTstModal(); renderTests(); } catch (e) { errEl.textContent = 'Ошибка: ' + e.message; } finally { btn.disabled = false; btn.textContent = 'Сохранить'; } } async function deleteTst(id) { const t = allTests.find(x => x.id === id); if (!await LS.confirm(`Удалить тест «${t?.title}»?`, { title: 'Удалить тест', confirmText: 'Удалить' })) return; try { await LS.deleteTest(id); allTests = allTests.filter(x => x.id !== id); if (openTstId === id) openTstId = null; renderTests(); } 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; window.toggleTstDrawer = toggleTstDrawer; window.filterTstPicker = filterTstPicker; window.tstAddQ = tstAddQ; window.tstRemoveQ = tstRemoveQ; window.setTstShowAnswers = setTstShowAnswers; window.openTstModal = openTstModal; window.editTst = editTst; window.closeTstModal = closeTstModal; window.saveTst = saveTst; window.deleteTst = deleteTst; window.toggleTstAvail = toggleTstAvail; window.AdminSections = window.AdminSections || {}; window.AdminSections.tests = { init: async () => { if (inited) return; inited = true; await load(); }, reload: load, }; })();