// static/js/admin.Analytics.jsx function AdminAnalytics({ token }) { const [tests, setTests] = useState([]); const [selectedTestId, setSelectedTestId] = useState(""); const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); // Загружаем список тестов для выбора useEffect(() => { let cancelled = false; const loadTests = async () => { try { setError(""); const data = await API.adminGetTests(token); if (!cancelled) { setTests(data); } } catch (e) { console.error(e); if (!cancelled) { setError("Не удалось загрузить список тестов"); } } }; loadTests(); return () => { cancelled = true; }; }, [token]); const loadDetail = async (testId) => { if (!testId) { setDetail(null); return; } setLoading(true); setError(""); try { const data = await API.adminGetTestAnalytics(token, testId); setDetail(data); } catch (e) { console.error(e); setError(e.message || "Ошибка при загрузке аналитики"); } finally { setLoading(false); } }; const handleSelectChange = (e) => { const val = e.target.value; setSelectedTestId(val); if (val) { loadDetail(val); } else { setDetail(null); } }; // Скачивание CSV по выбранному тесту const downloadCsv = async () => { if (!selectedTestId) { alert("Сначала выберите тест"); return; } try { const blob = await API.adminDownloadTestCsv(token, selectedTestId); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = detail ? `test_${detail.test.id}_results.csv` : "results.csv"; a.click(); window.URL.revokeObjectURL(url); } catch (e) { console.error(e); alert(e.message || "Ошибка при скачивании отчета"); } }; // Безопасно достаем список тех, кто не проходил тест const neverAttempted = detail && Array.isArray(detail.never_attempted_users) ? detail.never_attempted_users : []; return (
{/* Заголовок блока аналитики */}

Аналитика тестов

Выберите тест, чтобы посмотреть статистику и выгрузить отчет в Excel.

{/* Блок выбора теста */}

Отчеты строятся только по завершенным попыткам (статус "completed").

{error && (
{error}
)} {loading && (
Загружаем аналитику по тесту...
)} {!loading && !detail && !error && (
Выберите тест выше, чтобы увидеть статистику.
)} {/* Детальная аналитика + кнопка скачивания в блоке выбранного теста */} {!loading && detail && (
{/* Общая статистика по тесту */}

{detail.test.title}

ID теста: {detail.test.id}

{detail.test.description}
Всего попыток
{detail.total_attempts}
Уникальных сотрудников
{detail.unique_users}
Завершено попыток
{detail.completed}
Прошли тест
{detail.passed}{" "} ({detail.completed > 0 ? Math.round(detail.passed / detail.completed * 100) : 0}% от завершивших)
Средний балл
{detail.avg_score}
Средний % правильных
{detail.avg_percent}%
Среднее время
{detail.avg_time_minutes} мин
Распределение попыток
{Object.keys(detail.attempts_distribution || {}).length === 0 && ( Нет данных )} {Object.entries(detail.attempts_distribution || {}).map(([tryNum, count]) => (
{tryNum}-я попытка: {count}
))}
{/* Таблица по сотрудникам с попытками */}

Результаты сотрудников (те, кто проходил тест)

Сотрудников с попытками: {detail.users.length}
{detail.users.length === 0 ? (
Пока нет завершенных попыток.
) : (
{detail.users.map(u => ( ))}
Сотрудник Email Попыток 1-я успешная попытка Лучшая % Последняя % Прошел в последней Суммарное время (мин)
{u.name} {u.email} {u.attempts} {u.first_pass_attempt ? `${u.first_pass_attempt}-я` : "не прошел"} {u.best_percent}% {u.last_percent}% {u.last_passed ? ( ✅ прошел ) : ( ❌ не прошел )} {u.total_time_minutes}
)}
{/* НОВАЯ ТАБЛИЦА: сотрудники, которые ещё НЕ проходили тест */}

Сотрудники, которые ещё не проходили этот тест

Таких сотрудников: {neverAttempted.length}
{neverAttempted.length === 0 ? (
Все специалисты уже хотя бы раз открывали этот тест.
) : (
{neverAttempted.map(u => ( ))}
Сотрудник Email
{u.name} {u.email}
)}
{/* Таблица по вопросам */}

Сложность вопросов

Всего вопросов: {detail.questions.length}
{detail.questions.length === 0 ? (
Пока нет ответов на вопросы.
) : (
{detail.questions.map(q => ( ))}
Вопрос Ответов Правильных % правильных Среднее время (сек)
{q.text} {q.total_answers} {q.correct_answers} {q.correct_percent}% {q.avg_time_seconds}
)}
)}
); }