import React, { useMemo, useState } from 'react'
import {
Badge,
Box,
HStack,
Heading,
SimpleGrid,
Table,
TableBody,
TableCell,
TableColumnHeader,
TableContainer,
TableHeader,
TableRow,
Text,
VStack,
Select,
} from '@chakra-ui/react'
import {
useGetAllSubmissionsQuery,
useGetChainsQuery,
useGetSystemStatsQuery,
} from '../../__data__/api/api'
import type { ChallengeChain } from '../../__data__/types'
import { StatCard } from '../../components/personal'
import { ABTestPanel } from '../../components/admin/ABTestPanel'
import { mapTaskMetrics, detectIssues, msToMinutes } from '../../utils/analytics'
import { keycloak } from '../../__data__/kc'
const formatNumber = (value: number | undefined) => {
if (!value && value !== 0) return '—'
return Intl.NumberFormat('ru-RU').format(value)
}
const hasTeacherRole = () => {
try {
return keycloak?.hasResourceRole?.('teacher', 'journal') ?? false
} catch (error) {
return false
}
}
export const AdminDashboard = () => {
const isTeacher = hasTeacherRole()
const { data: systemStats, isLoading } = useGetSystemStatsQuery()
const { data: chains = [] } = useGetChainsQuery(undefined, { skip: !isTeacher })
const { data: submissions = [] } = useGetAllSubmissionsQuery(undefined, { skip: !isTeacher })
const issues = useMemo(() => (systemStats ? detectIssues(systemStats) : []), [systemStats])
const taskMetrics = useMemo(() => mapTaskMetrics(submissions), [submissions])
const [difficultyFilter, setDifficultyFilter] = useState<'all' | 'easy' | 'medium' | 'hard'>('all')
const filteredTaskMetrics = useMemo(() => {
if (difficultyFilter === 'all') return taskMetrics
return taskMetrics.filter((metric) => metric.difficulty === difficultyFilter)
}, [difficultyFilter, taskMetrics])
if (!isTeacher) {
return (
Требуется роль преподавателя
У вас нет доступа к панели администратора. Обратитесь к администратору Keycloak для назначения роли
teacher
.
)
}
if (isLoading || !systemStats) {
return (
Загружаем системные метрики...
)
}
return (
Панель преподавателя
Статус очереди
Всего в очереди
{formatNumber(systemStats.queue.queueLength)}
В ожидании
{formatNumber(systemStats.queue.waiting)}
В обработке
{formatNumber(systemStats.queue.currentlyProcessing)} / {formatNumber(systemStats.queue.maxConcurrency)}
Проблемные области
{issues.length === 0 ? (
Критических проблем не обнаружено.
) : (
{issues.map((issue) => (
{issue.message}
Сущность: {issue.affectedEntity} · Важность: {issue.severity}
))}
)}
Метрики по заданиям
{filteredTaskMetrics.length === 0 ? (
Недостаточно данных о проверках для построения аналитики.
) : (
Задание
Попыток
Успешность
Сред. попыток
Сред. время (мин)
Сложность
{filteredTaskMetrics.map((metric) => (
{metric.title}
ID: {metric.taskId}
{formatNumber(metric.attemptsCount)}
{formatNumber(Math.round(metric.successRate))}%
{formatNumber(Math.round(metric.avgAttempts * 10) / 10)}
{formatNumber(Math.round(msToMinutes(metric.avgTimeToComplete * 1000)))}
{metric.difficulty}
))}
)}
Цепочки заданий
{chains.length === 0 ? (
Цепочки не найдены. Создайте первое задание, чтобы начать.
) : (
{chains.map((chain: ChallengeChain) => (
{chain.name}
Заданий: {chain.tasks.length} · Последнее обновление:{' '}
{new Date(chain.updatedAt).toLocaleString()}
))}
)}
)
}