import { useTranslation } from 'react-i18next'; import { Group, Paper, SimpleGrid, Text, Title, Badge } from '@mantine/core'; import { IconHourglass } from '@tabler/icons-react'; import { useGetExamWaitMetricsQuery } from '../api/exam-api'; import type { WaitStat } from '../types/exam'; function minutes(value: number | null, t: (key: string, options?: Record) => string): string { if (value === null) return '—'; const abs = Math.abs(value); const label = abs >= 1440 ? t('exam.metrics.days', { value: Math.round((abs / 1440) * 10) / 10 }) : abs >= 60 ? t('exam.metrics.hours', { value: Math.round((abs / 60) * 10) / 10 }) : t('exam.metrics.minutes', { value: Math.round(abs * 10) / 10 }); return value < 0 ? `−${label}` : label; } function StatCard({ label, stat, t }: { label: string; stat: WaitStat; t: (key: string, options?: Record) => string }) { return ( {label} {minutes(stat.averageMinutes, t)} {t('exam.metrics.average')} {t('exam.metrics.min')}: {minutes(stat.minMinutes, t)} {t('exam.metrics.max')}: {minutes(stat.maxMinutes, t)} {t('exam.metrics.count')}: {stat.count} ); } /** * Exam wait metrics — how long candidates waited at each step up to the * sitting, read off the registration, attendance and attempt timestamps * the workflow already writes. Analytics only: nothing here can change a * registration, an attendance ruling, a result or a certificate. */ export function ExamWaitMetricsPanel({ examId }: { examId: string }) { const { t } = useTranslation(); const { data, isError } = useGetExamWaitMetricsQuery(examId); if (isError || !data) return null; return ( {t('exam.metrics.section')} {t('exam.metrics.candidates', { count: data.candidateCount })} {t('exam.metrics.attended', { count: data.attendedCount })} {t('exam.metrics.started', { count: data.startedCount })} {t('exam.metrics.hint')} ); }