Add detailed statistics API v2 documentation and implement frontend components for displaying statistics

This commit is contained in:
Primakov Alexandr Alexandrovich
2025-11-04 21:37:03 +03:00
parent b91ee56bf0
commit fd55d5a214
16 changed files with 2233 additions and 29 deletions
@@ -0,0 +1,160 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Box, Heading, Text, Table, Badge } from '@chakra-ui/react'
import type { ChainDetailed, TaskProgressStatus } from '../../types/challenge'
interface ChainDetailedViewProps {
chains: ChainDetailed[]
}
interface StatusConfig {
label: string
color: 'gray' | 'yellow' | 'blue' | 'orange' | 'green'
}
const getStatusConfig = (status: TaskProgressStatus, t: (key: string) => string): StatusConfig => {
const configs: Record<TaskProgressStatus, StatusConfig> = {
not_started: { label: t('challenge.admin.detailed.stats.status.not.started'), color: 'gray' },
pending: { label: t('challenge.admin.detailed.stats.status.pending'), color: 'yellow' },
in_progress: { label: t('challenge.admin.detailed.stats.status.in.progress'), color: 'blue' },
needs_revision: { label: t('challenge.admin.detailed.stats.status.needs.revision'), color: 'orange' },
completed: { label: t('challenge.admin.detailed.stats.status.completed'), color: 'green' },
}
return configs[status]
}
export const ChainDetailedView: React.FC<ChainDetailedViewProps> = ({ chains }) => {
const { t } = useTranslation()
if (chains.length === 0) {
return (
<Box bg="white" p={6} borderRadius="lg" boxShadow="sm" borderWidth="1px" borderColor="gray.200">
<Heading size="md" mb={4}>
{t('challenge.admin.detailed.stats.chains.title')}
</Heading>
<Box color="gray.500" textAlign="center" py={8}>
{t('challenge.admin.detailed.stats.chains.empty')}
</Box>
</Box>
)
}
const chain = chains[0] // Теперь всегда одна цепочка из API
return (
<Box bg="white" p={6} borderRadius="lg" boxShadow="sm" borderWidth="1px" borderColor="gray.200">
<Heading size="md" mb={4}>
{t('challenge.admin.detailed.stats.chains.title')}
</Heading>
<Box mb={3}>
<Heading size="sm" color="teal.600" mb={1}>
{chain.name}
</Heading>
<Text fontSize="sm" color="gray.600">
{t('challenge.admin.detailed.stats.chains.total.tasks')} {chain.totalTasks}
</Text>
</Box>
{chain.participantProgress.length > 0 ? (
<Box overflowX="auto">
<Table.Root size="sm" variant="outline">
<Table.Header>
<Table.Row bg="gray.50">
<Table.ColumnHeader
position="sticky"
left={0}
bg="gray.50"
zIndex={1}
minW="150px"
borderRight="1px"
borderColor="gray.200"
>
{t('challenge.admin.detailed.stats.chains.participant')}
</Table.ColumnHeader>
{chain.tasks.map((task) => (
<Table.ColumnHeader
key={task.taskId}
textAlign="center"
minW="120px"
maxW="200px"
>
<Text fontSize="xs" lineClamp={2} title={task.title}>
{task.title}
</Text>
</Table.ColumnHeader>
))}
<Table.ColumnHeader
textAlign="center"
minW="100px"
borderLeft="1px"
borderColor="gray.200"
fontWeight="bold"
>
{t('challenge.admin.detailed.stats.chains.progress')}
</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body>
{chain.participantProgress.map((participant) => (
<Table.Row key={participant.userId}>
<Table.Cell
position="sticky"
left={0}
bg="white"
zIndex={1}
fontWeight="medium"
borderRight="1px"
borderColor="gray.200"
>
{participant.nickname}
</Table.Cell>
{participant.taskProgress.map((taskProg) => {
const config = getStatusConfig(taskProg.status, t)
return (
<Table.Cell key={taskProg.taskId} textAlign="center">
<Badge
colorPalette={config.color}
size="sm"
title={taskProg.taskTitle}
>
{config.label}
</Badge>
</Table.Cell>
)
})}
<Table.Cell
textAlign="center"
borderLeft="1px"
borderColor="gray.200"
fontWeight="bold"
>
<Badge
colorPalette={
participant.progressPercent >= 80
? 'green'
: participant.progressPercent >= 50
? 'yellow'
: 'red'
}
>
{participant.progressPercent}%
</Badge>
<Text fontSize="xs" color="gray.600" mt={1}>
{participant.completedCount}/{chain.totalTasks}
</Text>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</Box>
) : (
<Box color="gray.500" textAlign="center" py={4} bg="gray.50" borderRadius="md">
{t('challenge.admin.detailed.stats.chains.no.participants')}
</Box>
)}
</Box>
)
}