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:
@@ -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
|
const overallPercent =
|
||||||
}
|
totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0
|
||||||
>
|
|
||||||
)
|
return {
|
||||||
.sort((a, b) => a.progressPercent - b.progressPercent)
|
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>
|
const colorPalette =
|
||||||
<Table.Row>
|
row.overallPercent >= 70
|
||||||
<Table.ColumnHeader>
|
? 'green'
|
||||||
{t('challenge.admin.submissions.overview.table.user')}
|
: row.overallPercent >= 40
|
||||||
</Table.ColumnHeader>
|
? 'orange'
|
||||||
<Table.ColumnHeader>
|
: 'red'
|
||||||
{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 =
|
|
||||||
row.progressPercent >= 70
|
|
||||||
? 'green'
|
|
||||||
: row.progressPercent >= 40
|
|
||||||
? 'orange'
|
|
||||||
: 'red'
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Row
|
<Box
|
||||||
key={`${row.userId}-${row.chainId}`}
|
key={row.userId}
|
||||||
cursor="pointer"
|
p={2}
|
||||||
_hover={{ bg: 'gray.50' }}
|
borderWidth="1px"
|
||||||
onClick={() => setSelectedUserId(row.userId)}
|
borderRadius="md"
|
||||||
>
|
borderColor="gray.200"
|
||||||
<Table.Cell fontWeight="medium" w="220px" maxW="220px">
|
_hover={{ bg: 'gray.50' }}
|
||||||
<Text truncate>{row.nickname}</Text>
|
cursor="pointer"
|
||||||
</Table.Cell>
|
onClick={() => setSelectedUserId(row.userId)}
|
||||||
<Table.Cell w="260px" maxW="260px">
|
>
|
||||||
<Text fontSize="sm" color="gray.700" truncate>
|
<HStack justify="space-between" mb={1} gap={2}>
|
||||||
{row.chainName}
|
<Text fontSize="xs" fontWeight="medium" truncate maxW="150px">
|
||||||
</Text>
|
{row.nickname}
|
||||||
<Text fontSize="xs" color="gray.500" truncate>
|
</Text>
|
||||||
{t('challenge.admin.detailed.stats.chains.total.tasks')}{' '}
|
<Text fontSize="xs" color="gray.500">
|
||||||
{row.completedCount} / {row.totalTasks}
|
{row.overallPercent}%
|
||||||
</Text>
|
</Text>
|
||||||
</Table.Cell>
|
</HStack>
|
||||||
<Table.Cell w="100%" minW="200px">
|
<Progress.Root value={row.overallPercent} size="xs" colorPalette={colorPalette}>
|
||||||
<VStack align="stretch" gap={1}>
|
<Progress.Track>
|
||||||
<HStack justify="space-between">
|
<Progress.Range />
|
||||||
<Text fontSize="sm" color="gray.700">
|
</Progress.Track>
|
||||||
{row.progressPercent}%
|
</Progress.Root>
|
||||||
</Text>
|
<Text fontSize="xs" color="gray.500" mt={1}>
|
||||||
</HStack>
|
{row.completedTasks} / {row.totalTasks}
|
||||||
<Progress.Root
|
</Text>
|
||||||
value={row.progressPercent}
|
</Box>
|
||||||
size="sm"
|
)
|
||||||
colorPalette={colorPalette}
|
})}
|
||||||
>
|
</Grid>
|
||||||
<Progress.Track>
|
|
||||||
<Progress.Range />
|
|
||||||
</Progress.Track>
|
|
||||||
</Progress.Root>
|
|
||||||
</VStack>
|
|
||||||
</Table.Cell>
|
|
||||||
</Table.Row>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Table.Body>
|
|
||||||
</Table.Root>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
) : filteredSubmissions.length === 0 ? (
|
) : filteredSubmissions.length === 0 ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user