import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { Box, Heading, Table, Input, Text, Button, HStack, VStack, Select, DialogRoot, DialogContent, DialogHeader, DialogTitle, DialogBody, DialogFooter, DialogActionTrigger, Progress, Grid, createListCollection, } from '@chakra-ui/react' import ReactMarkdown from 'react-markdown' import { useGetSystemStatsV2Query, useGetUserSubmissionsQuery } from '../../__data__/api/api' import { LoadingSpinner } from '../../components/LoadingSpinner' import { ErrorAlert } from '../../components/ErrorAlert' import { EmptyState } from '../../components/EmptyState' import { StatusBadge } from '../../components/StatusBadge' import type { ActiveParticipant, ChallengeSubmission, SubmissionStatus, ChallengeTask, ChallengeUser, } from '../../types/challenge' export const SubmissionsPage: React.FC = () => { const { t } = useTranslation() const { data: stats, isLoading: isStatsLoading, error: statsError, refetch: refetchStats } = useGetSystemStatsV2Query(undefined) const [searchQuery, setSearchQuery] = useState('') const [statusFilter, setStatusFilter] = useState('all') const [selectedSubmission, setSelectedSubmission] = useState(null) const [selectedUserId, setSelectedUserId] = useState(null) const { data: submissions, isLoading: isSubmissionsLoading, error: submissionsError, refetch: refetchSubmissions, } = useGetUserSubmissionsQuery( { userId: selectedUserId!, taskId: undefined }, { skip: !selectedUserId } ) const isLoading = isStatsLoading || (selectedUserId && isSubmissionsLoading) const error = statsError || submissionsError const handleRetry = () => { refetchStats() if (selectedUserId) { refetchSubmissions() } } if (isLoading) { return } if (error || !stats) { return } const participants: ActiveParticipant[] = stats.activeParticipants || [] const submissionsList: ChallengeSubmission[] = submissions || [] const normalizedSearchQuery = (searchQuery ?? '').toLowerCase() const filteredSubmissions = submissionsList.filter((submission) => { const rawUser = submission.user as ChallengeUser | string | undefined const rawTask = submission.task as ChallengeTask | string | undefined const nickname = rawUser && typeof rawUser === 'object' && 'nickname' in rawUser ? (rawUser.nickname ?? '') : '' const title = rawTask && typeof rawTask === 'object' && 'title' in rawTask ? (rawTask.title ?? '') : '' const matchesSearch = nickname.toLowerCase().includes(normalizedSearchQuery) || title.toLowerCase().includes(normalizedSearchQuery) const matchesStatus = statusFilter === 'all' || submission.status === statusFilter return matchesSearch && matchesStatus }) const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleString('ru-RU', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }) } const getCheckTime = (submission: ChallengeSubmission) => { if (!submission.checkedAt) return '—' const submitted = new Date(submission.submittedAt).getTime() const checked = new Date(submission.checkedAt).getTime() const diff = Math.round((checked - submitted) / 1000) return t('challenge.admin.submissions.check.time', { time: diff }) } const statusOptions = createListCollection({ items: [ { label: t('challenge.admin.submissions.status.all'), value: 'all' }, { label: t('challenge.admin.submissions.status.accepted'), value: 'accepted' }, { label: t('challenge.admin.submissions.status.needs.revision'), value: 'needs_revision' }, { label: t('challenge.admin.submissions.status.in.progress'), value: 'in_progress' }, { label: t('challenge.admin.submissions.status.pending'), value: 'pending' }, ], }) const userOptions = createListCollection({ items: participants.map((participant) => ({ label: `${participant.nickname} (${participant.userId})`, value: participant.userId, })), }) const hasParticipants = participants.length > 0 const hasSelectedUser = !!selectedUserId const participantOverviewRows = participants .map((participant) => { const chains = participant.chainProgress || [] const totalTasks = chains.reduce((sum, chain) => sum + (chain.totalTasks ?? 0), 0) const completedTasks = chains.reduce( (sum, chain) => sum + (chain.completedTasks ?? 0), 0 ) const overallPercent = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0 return { userId: participant.userId, nickname: participant.nickname, totalSubmissions: participant.totalSubmissions, completedTasks, totalTasks, overallPercent, } }) .sort((a, b) => a.overallPercent - b.overallPercent) return ( {t('challenge.admin.submissions.title')} {/* Filters */} {hasParticipants && ( setSelectedUserId(e.value[0] ?? null)} maxW="300px" > {userOptions.items.map((option) => ( {option.label} ))} {hasSelectedUser && ( )} {submissionsList.length > 0 && ( <> setSearchQuery(e.target.value)} maxW="400px" /> setStatusFilter(e.value[0] as SubmissionStatus | 'all')} maxW="200px" > {statusOptions.items.map((option) => ( {option.label} ))} )} )} {!hasParticipants ? ( ) : !hasSelectedUser ? ( {t('challenge.admin.submissions.overview.title')} {t('challenge.admin.submissions.overview.description')} {participantOverviewRows.length === 0 ? ( ) : ( {participantOverviewRows.map((row) => { const colorPalette = row.overallPercent >= 70 ? 'green' : row.overallPercent >= 40 ? 'orange' : 'red' return ( setSelectedUserId(row.userId)} > {row.nickname} {row.overallPercent}% {row.completedTasks} / {row.totalTasks} ) })} )} ) : filteredSubmissions.length === 0 ? ( ) : ( {t('challenge.admin.submissions.table.user')} {t('challenge.admin.submissions.table.task')} {t('challenge.admin.submissions.table.status')} {t('challenge.admin.submissions.table.attempt')} {t('challenge.admin.submissions.table.submitted')} {t('challenge.admin.submissions.table.check.time')} {t('challenge.admin.submissions.table.actions')} {filteredSubmissions.map((submission) => { const rawUser = submission.user as ChallengeUser | string | undefined const rawTask = submission.task as ChallengeTask | string | undefined const nickname = rawUser && typeof rawUser === 'object' && 'nickname' in rawUser ? (rawUser.nickname ?? '') : typeof rawUser === 'string' ? rawUser : '' const title = rawTask && typeof rawTask === 'object' && 'title' in rawTask ? (rawTask.title ?? '') : typeof rawTask === 'string' ? rawTask : '' return ( {nickname} {title} #{submission.attemptNumber} {formatDate(submission.submittedAt)} {getCheckTime(submission)} ) })} )} {/* Submission Details Modal */} setSelectedSubmission(null)} /> ) } interface SubmissionDetailsModalProps { submission: ChallengeSubmission | null isOpen: boolean onClose: () => void } const SubmissionDetailsModal: React.FC = ({ submission, isOpen, onClose, }) => { const { t } = useTranslation() if (!submission) return null const rawUser = submission.user as ChallengeUser | string | undefined const rawTask = submission.task as ChallengeTask | string | undefined const userNickname = rawUser && typeof rawUser === 'object' && 'nickname' in rawUser ? (rawUser.nickname ?? '') : typeof rawUser === 'string' ? rawUser : '' const taskTitle = rawTask && typeof rawTask === 'object' && 'title' in rawTask ? (rawTask.title ?? '') : typeof rawTask === 'string' ? rawTask : '' const taskDescription = rawTask && typeof rawTask === 'object' && 'description' in rawTask ? (rawTask.description ?? '') : '' const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleString('ru-RU', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', }) } const getCheckTimeValue = () => { if (!submission.checkedAt) return null const submitted = new Date(submission.submittedAt).getTime() const checked = new Date(submission.checkedAt).getTime() return ((checked - submitted) / 1000).toFixed(2) } return ( !e.open && onClose()} size="xl"> {t('challenge.admin.submissions.details.title')} #{submission.attemptNumber} {/* Meta */} {t('challenge.admin.submissions.details.user')} {userNickname} {t('challenge.admin.submissions.details.status')} {t('challenge.admin.submissions.details.submitted')} {formatDate(submission.submittedAt)} {submission.checkedAt && ( <> {t('challenge.admin.submissions.details.checked')} {formatDate(submission.checkedAt)} {t('challenge.admin.submissions.details.check.time')} {t('challenge.admin.submissions.check.time', { time: getCheckTimeValue() })} )} {/* Task */} {t('challenge.admin.submissions.details.task')} {taskTitle} {taskDescription} {/* Solution */} {t('challenge.admin.submissions.details.solution')} {submission.result} {/* Feedback */} {submission.feedback && ( {t('challenge.admin.submissions.details.feedback')} {submission.feedback} )} ) }