'use strict'; /* ────────────────────────────────────────────────────────────────── Practice view — random / unsolved tasks trainer. Renders a batch of N TaskCards; once all are answered (or user clicks "Завершить"), shows a summary card with score + restart. ────────────────────────────────────────────────────────────────── */ (async function () { await EP.boot(); const examKey = EP.examKey; const main = document.getElementById('ep-main'); // Per-session state let batch = null; // { strategy, session_id, tasks: [...] } let strategy = readPersistedStrategy() || 'random'; let count = readPersistedCount(); let finalized = false; const results = new Map(); // taskId -> { isCorrect: 0|1|null, attempts: number } /* ── Strategy persistence (local pref) ──────────────────────── */ function readPersistedStrategy() { try { return localStorage.getItem(`exam_prep_${examKey}_practice_strategy`); } catch { return null; } } function persistStrategy(s) { try { localStorage.setItem(`exam_prep_${examKey}_practice_strategy`, s); } catch {} } function readPersistedCount() { try { const n = Number(localStorage.getItem(`exam_prep_${examKey}_practice_count`)); return (n >= 5 && n <= 30) ? n : 10; } catch { return 10; } } function persistCount(n) { try { localStorage.setItem(`exam_prep_${examKey}_practice_count`, String(n)); } catch {} } /* ── Boot: show controls and load first batch ───────────────── */ await loadBatch(); /* ── Controls bar ───────────────────────────────────────────── */ function controlsHTML() { const opts = [5, 10, 15, 20].map(n => ` `).join(''); return `
`; } function renderProgress() { const el = document.getElementById('pr-progress'); if (!el || !batch) return; const total = batch.tasks.length; const answered = Array.from(results.values()).filter(r => r.isCorrect != null).length; const correct = Array.from(results.values()).filter(r => r.isCorrect === 1).length; const pct = total ? Math.round((answered / total) * 100) : 0; el.innerHTML = `
Прогресс сессии ${answered}/${total} · ${correct} верно
`; } /* ── Batch loading + render ─────────────────────────────────── */ async function loadBatch() { finalized = false; results.clear(); main.innerHTML = controlsHTML() + `

Загрузка задач…

`; if (window.lucide) lucide.createIcons(); wireControls(); try { batch = await EP.api.getPracticeNext(examKey, { count, strategy }); } catch (e) { main.querySelector('.ep-empty').outerHTML = `

Не удалось загрузить задачи

${escapeHtml(e.message || String(e))}

`; if (window.lucide) lucide.createIcons(); return; } if (!batch.tasks.length) { main.querySelector('.ep-empty').outerHTML = `

Не нашлось задач по этой стратегии

Попробуйте другую — или это знак, что вы уже всё решили.

`; if (window.lucide) lucide.createIcons(); return; } renderBatch(); } function renderBatch() { // Replace empty placeholder with task list const empty = main.querySelector('.ep-empty'); const taskContainer = document.createElement('div'); taskContainer.className = 'pr-tasks'; if (empty) empty.replaceWith(taskContainer); batch.tasks.forEach((task, i) => { results.set(task.id, { isCorrect: null, attempts: 0 }); EP.TaskCard.render(taskContainer, task, { mode: 'practice', sessionId: batch.session_id, numbering: i + 1, onAttempt: ({ taskId, isCorrect, attempt }) => { if (isCorrect == null) return; // ignore solution-only events const r = results.get(taskId); if (!r) return; // We record only the FIRST attempt result toward batch score. if (r.isCorrect == null) r.isCorrect = isCorrect; r.attempts = attempt || r.attempts + 1; renderProgress(); maybeFinalize(); }, }); }); // Bottom: "Завершить" button (allow finalizing early) const finish = document.createElement('div'); finish.className = 'pr-finish-row'; finish.innerHTML = ` `; main.appendChild(finish); finish.querySelector('.pr-finish-btn').onclick = () => finalize(); renderProgress(); if (window.lucide) lucide.createIcons(); } function maybeFinalize() { const answered = Array.from(results.values()).filter(r => r.isCorrect != null).length; if (answered === batch.tasks.length && !finalized) { finalize(); } } function finalize() { if (finalized) return; finalized = true; const total = batch.tasks.length; const answered = Array.from(results.values()).filter(r => r.isCorrect != null).length; const correct = Array.from(results.values()).filter(r => r.isCorrect === 1).length; const acc = answered ? Math.round((correct / answered) * 100) : 0; const summary = document.createElement('div'); summary.className = 'pr-summary ep-card'; summary.innerHTML = `
Решено
${answered} / ${total}
Верно
${correct}
${acc}% точности
Стратегия
${strategy === 'unsolved' ? 'Нерешённые' : 'Случайные'}
На дашборд
`; main.appendChild(summary); summary.querySelector('#pr-summary-restart').onclick = () => loadBatch(); if (window.lucide) lucide.createIcons(); summary.scrollIntoView({ behavior: 'smooth', block: 'start' }); } /* ── Wire controls (strategy toggle, count select, restart) ── */ function wireControls() { main.querySelectorAll('[data-strat]').forEach(btn => { btn.onclick = () => { if (btn.disabled) return; strategy = btn.dataset.strat; persistStrategy(strategy); loadBatch(); }; }); const sel = main.querySelector('.pr-count-select'); if (sel) { sel.onchange = () => { count = Number(sel.value) || 10; persistCount(count); }; } const restart = main.querySelector('.pr-restart'); if (restart) restart.onclick = () => loadBatch(); } function escapeHtml(s) { return String(s || '').replace(/[&<>"']/g, c => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' }[c])); } })();