// static/js/testRunner.jsx function TestRunner({ testId }) { const { token, setView } = useContext(AuthContext); const [loading, setLoading] = useState(true); const [testData, setTestData] = useState(null); const [currentQIndex, setCurrentQIndex] = useState(0); const [answers, setAnswers] = useState({}); const [qStartTime, setQStartTime] = useState(Date.now()); const [elapsedSeconds, setElapsedSeconds] = useState(0); const [autoFinished, setAutoFinished] = useState(false); const formatTime = (secs) => { const m = Math.floor(secs / 60); const s = secs % 60; return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; }; // запуск теста useEffect(() => { setLoading(true); (async () => { try { const data = await API.userStartTest(token, testId); console.log('TEST DATA FROM SERVER:', data); setTestData(data); setQStartTime(Date.now()); } catch (err) { console.error(err); alert(err.message || 'Не удалось запустить тест'); setView('dashboard'); } finally { setLoading(false); } })(); }, [testId, token, setView]); // таймер useEffect(() => { if (!testData) return; setElapsedSeconds(0); const timerId = setInterval(() => { setElapsedSeconds(prev => prev + 1); }, 1000); return () => clearInterval(timerId); }, [testData]); // авто-завершение при окончании времени useEffect(() => { if (!testData) return; const timeLimitSeconds = (testData.time_limit || 0) * 60; if (!timeLimitSeconds) return; if (elapsedSeconds >= timeLimitSeconds && !autoFinished) { setAutoFinished(true); handleNext(true); } }, [elapsedSeconds, testData, autoFinished]); const handleNext = async (finish = false) => { const q = testData.questions[currentQIndex]; const ans = answers[q.id] || { selected: [], text: "" }; const timeSpent = Math.round((Date.now() - qStartTime) / 1000); try { await API.userSubmitAnswer(token, testData.attempt_id, { question_id: q.id, selected_options: ans.selected || [], text_answer: ans.text || "", time_spent: timeSpent, }); } catch (e) { console.error(e); alert('Ошибка отправки ответа'); } if (finish) { finishTest(); } else { setCurrentQIndex(prev => prev + 1); setQStartTime(Date.now()); } }; const finishTest = async () => { if (!testData) return; try { const res = await API.userFinishAttempt(token, testData.attempt_id); const percentStr = (typeof res.percent === 'number') ? res.percent.toFixed(1) : '—'; let msg = `Тест завершен! Результат: ${res.passed ? 'СДАЛ' : 'НЕ СДАЛ'}.`; if (typeof res.score === 'number') { if (typeof res.max_score === 'number' && res.max_score > 0) { msg += `\nБаллы: ${res.score} из ${res.max_score} (${percentStr}%).`; } else { msg += `\nБаллы: ${res.score} (${percentStr}%).`; } } if (typeof res.correct_questions === 'number' && typeof res.total_questions === 'number') { msg += `\nПравильных ответов: ${res.correct_questions} из ${res.total_questions}.`; } alert(msg); setView('dashboard'); } catch (err) { console.error(err); alert('Ошибка завершения теста'); setView('dashboard'); } }; if (loading || !testData) { return (
({hintText})
)}