Add detailed statistics API v2 documentation and implement frontend components for displaying statistics
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user