Refactor submissions page to improve participant progress display; replace table with grid layout for better responsiveness and user interaction. Update data processing for participant overview and overall progress calculation.

This commit is contained in:
2025-12-10 00:01:24 +03:00
parent b3febaeea1
commit e4a1fe4b23

View File

@@ -18,6 +18,7 @@ import {
DialogFooter, DialogFooter,
DialogActionTrigger, DialogActionTrigger,
Progress, Progress,
Grid,
createListCollection, createListCollection,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
@@ -32,7 +33,6 @@ import type {
SubmissionStatus, SubmissionStatus,
ChallengeTask, ChallengeTask,
ChallengeUser, ChallengeUser,
ParticipantProgress,
} from '../../types/challenge' } from '../../types/challenge'
export const SubmissionsPage: React.FC = () => { export const SubmissionsPage: React.FC = () => {
@@ -139,27 +139,29 @@ export const SubmissionsPage: React.FC = () => {
const hasParticipants = participants.length > 0 const hasParticipants = participants.length > 0
const hasSelectedUser = !!selectedUserId const hasSelectedUser = !!selectedUserId
const participantProgressRows = stats.chainsDetailed const participantOverviewRows = participants
.reduce( .map((participant) => {
(acc, chain) => { const chains = participant.chainProgress || []
const rows = chain.participantProgress.map((participant) => ({
...participant,
chainId: chain.chainId,
chainName: chain.name,
totalTasks: chain.totalTasks,
}))
return acc.concat(rows) const totalTasks = chains.reduce((sum, chain) => sum + (chain.totalTasks ?? 0), 0)
}, const completedTasks = chains.reduce(
[] as Array< (sum, chain) => sum + (chain.completedTasks ?? 0),
ParticipantProgress & { 0
chainId: string
chainName: string
totalTasks: number
}
>
) )
.sort((a, b) => a.progressPercent - b.progressPercent)
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 ( return (
<Box> <Box>
@@ -246,86 +248,60 @@ export const SubmissionsPage: React.FC = () => {
{t('challenge.admin.submissions.overview.description')} {t('challenge.admin.submissions.overview.description')}
</Text> </Text>
{participantProgressRows.length === 0 ? ( {participantOverviewRows.length === 0 ? (
<EmptyState <EmptyState
title={t('challenge.admin.detailed.stats.participants.empty')} title={t('challenge.admin.detailed.stats.participants.empty')}
description={t('challenge.admin.detailed.stats.chains.empty')} description={t('challenge.admin.detailed.stats.chains.empty')}
/> />
) : ( ) : (
<Box <Grid
bg="white" templateColumns={{
borderRadius="lg" base: 'repeat(1, minmax(0, 1fr))',
boxShadow="sm" md: 'repeat(2, minmax(0, 1fr))',
borderWidth="1px" lg: 'repeat(3, minmax(0, 1fr))',
borderColor="gray.200" xl: 'repeat(4, minmax(0, 1fr))',
overflowX="auto" }}
gap={2}
> >
<Table.Root size="sm"> {participantOverviewRows.map((row) => {
<Table.Header>
<Table.Row>
<Table.ColumnHeader>
{t('challenge.admin.submissions.overview.table.user')}
</Table.ColumnHeader>
<Table.ColumnHeader>
{t('challenge.admin.submissions.overview.table.chain')}
</Table.ColumnHeader>
<Table.ColumnHeader>
{t('challenge.admin.submissions.overview.table.progress')}
</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body>
{participantProgressRows.map((row) => {
const colorPalette = const colorPalette =
row.progressPercent >= 70 row.overallPercent >= 70
? 'green' ? 'green'
: row.progressPercent >= 40 : row.overallPercent >= 40
? 'orange' ? 'orange'
: 'red' : 'red'
return ( return (
<Table.Row <Box
key={`${row.userId}-${row.chainId}`} key={row.userId}
cursor="pointer" p={2}
borderWidth="1px"
borderRadius="md"
borderColor="gray.200"
_hover={{ bg: 'gray.50' }} _hover={{ bg: 'gray.50' }}
cursor="pointer"
onClick={() => setSelectedUserId(row.userId)} onClick={() => setSelectedUserId(row.userId)}
> >
<Table.Cell fontWeight="medium" w="220px" maxW="220px"> <HStack justify="space-between" mb={1} gap={2}>
<Text truncate>{row.nickname}</Text> <Text fontSize="xs" fontWeight="medium" truncate maxW="150px">
</Table.Cell> {row.nickname}
<Table.Cell w="260px" maxW="260px">
<Text fontSize="sm" color="gray.700" truncate>
{row.chainName}
</Text> </Text>
<Text fontSize="xs" color="gray.500" truncate> <Text fontSize="xs" color="gray.500">
{t('challenge.admin.detailed.stats.chains.total.tasks')}{' '} {row.overallPercent}%
{row.completedCount} / {row.totalTasks}
</Text>
</Table.Cell>
<Table.Cell w="100%" minW="200px">
<VStack align="stretch" gap={1}>
<HStack justify="space-between">
<Text fontSize="sm" color="gray.700">
{row.progressPercent}%
</Text> </Text>
</HStack> </HStack>
<Progress.Root <Progress.Root value={row.overallPercent} size="xs" colorPalette={colorPalette}>
value={row.progressPercent}
size="sm"
colorPalette={colorPalette}
>
<Progress.Track> <Progress.Track>
<Progress.Range /> <Progress.Range />
</Progress.Track> </Progress.Track>
</Progress.Root> </Progress.Root>
</VStack> <Text fontSize="xs" color="gray.500" mt={1}>
</Table.Cell> {row.completedTasks} / {row.totalTasks}
</Table.Row> </Text>
</Box>
) )
})} })}
</Table.Body> </Grid>
</Table.Root>
</Box>
)} )}
</Box> </Box>
) : filteredSubmissions.length === 0 ? ( ) : filteredSubmissions.length === 0 ? (