Add user filtering and progress overview to submissions page; enhance localization for user selection and progress display
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
DialogBody,
|
||||
DialogFooter,
|
||||
DialogActionTrigger,
|
||||
Progress,
|
||||
createListCollection,
|
||||
} from '@chakra-ui/react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
@@ -31,6 +32,7 @@ import type {
|
||||
SubmissionStatus,
|
||||
ChallengeTask,
|
||||
ChallengeUser,
|
||||
ParticipantProgress,
|
||||
} from '../../types/challenge'
|
||||
|
||||
export const SubmissionsPage: React.FC = () => {
|
||||
@@ -125,6 +127,28 @@ export const SubmissionsPage: React.FC = () => {
|
||||
const hasParticipants = participants.length > 0
|
||||
const hasSelectedUser = !!selectedUserId
|
||||
|
||||
const participantProgressRows = stats.chainsDetailed
|
||||
.reduce(
|
||||
(acc, chain) => {
|
||||
const rows = chain.participantProgress.map((participant) => ({
|
||||
...participant,
|
||||
chainId: chain.chainId,
|
||||
chainName: chain.name,
|
||||
totalTasks: chain.totalTasks,
|
||||
}))
|
||||
|
||||
return acc.concat(rows)
|
||||
},
|
||||
[] as Array<
|
||||
ParticipantProgress & {
|
||||
chainId: string
|
||||
chainName: string
|
||||
totalTasks: number
|
||||
}
|
||||
>
|
||||
)
|
||||
.sort((a, b) => a.progressPercent - b.progressPercent)
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Heading mb={6}>{t('challenge.admin.submissions.title')}</Heading>
|
||||
@@ -132,7 +156,7 @@ export const SubmissionsPage: React.FC = () => {
|
||||
{/* Filters */}
|
||||
{hasParticipants && (
|
||||
<VStack mb={4} gap={3} align="stretch">
|
||||
<HStack gap={4}>
|
||||
<HStack gap={4} align="center">
|
||||
<Select.Root
|
||||
collection={userOptions}
|
||||
value={selectedUserId ? [selectedUserId] : []}
|
||||
@@ -151,6 +175,20 @@ export const SubmissionsPage: React.FC = () => {
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
|
||||
{hasSelectedUser && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setSelectedUserId(null)
|
||||
setSearchQuery('')
|
||||
setStatusFilter('all')
|
||||
}}
|
||||
>
|
||||
{t('challenge.admin.submissions.filter.user.clear')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{submissionsList.length > 0 && (
|
||||
<>
|
||||
<Input
|
||||
@@ -188,10 +226,96 @@ export const SubmissionsPage: React.FC = () => {
|
||||
description={t('challenge.admin.submissions.empty.description')}
|
||||
/>
|
||||
) : !hasSelectedUser ? (
|
||||
<EmptyState
|
||||
title={t('challenge.admin.submissions.empty.title')}
|
||||
description={t('challenge.admin.submissions.filter.user')}
|
||||
/>
|
||||
<Box>
|
||||
<Heading size="md" mb={4}>
|
||||
{t('challenge.admin.submissions.overview.title')}
|
||||
</Heading>
|
||||
<Text mb={4} color="gray.600">
|
||||
{t('challenge.admin.submissions.overview.description')}
|
||||
</Text>
|
||||
|
||||
{participantProgressRows.length === 0 ? (
|
||||
<EmptyState
|
||||
title={t('challenge.admin.detailed.stats.participants.empty')}
|
||||
description={t('challenge.admin.detailed.stats.chains.empty')}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
bg="white"
|
||||
borderRadius="lg"
|
||||
boxShadow="sm"
|
||||
borderWidth="1px"
|
||||
borderColor="gray.200"
|
||||
overflowX="auto"
|
||||
>
|
||||
<Table.Root size="sm">
|
||||
<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 =
|
||||
row.progressPercent >= 70
|
||||
? 'green'
|
||||
: row.progressPercent >= 40
|
||||
? 'orange'
|
||||
: 'red'
|
||||
|
||||
return (
|
||||
<Table.Row
|
||||
key={`${row.userId}-${row.chainId}`}
|
||||
cursor="pointer"
|
||||
_hover={{ bg: 'gray.50' }}
|
||||
onClick={() => setSelectedUserId(row.userId)}
|
||||
>
|
||||
<Table.Cell fontWeight="medium" w="220px" maxW="220px">
|
||||
<Text truncate>{row.nickname}</Text>
|
||||
</Table.Cell>
|
||||
<Table.Cell w="260px" maxW="260px">
|
||||
<Text fontSize="sm" color="gray.700" truncate>
|
||||
{row.chainName}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="gray.500" truncate>
|
||||
{t('challenge.admin.detailed.stats.chains.total.tasks')}{' '}
|
||||
{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>
|
||||
</HStack>
|
||||
<Progress.Root
|
||||
value={row.progressPercent}
|
||||
size="sm"
|
||||
colorPalette={colorPalette}
|
||||
>
|
||||
<Progress.Track>
|
||||
<Progress.Range />
|
||||
</Progress.Track>
|
||||
</Progress.Root>
|
||||
</VStack>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
})}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : filteredSubmissions.length === 0 ? (
|
||||
<EmptyState
|
||||
title={t('challenge.admin.submissions.search.empty.title')}
|
||||
|
||||
Reference in New Issue
Block a user