init + api use
This commit is contained in:
162
src/pages/dashboard/DashboardPage.tsx
Normal file
162
src/pages/dashboard/DashboardPage.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import React from 'react'
|
||||
import { Box, Heading, Grid, Text, VStack, HStack, Badge, Progress } from '@chakra-ui/react'
|
||||
import { useGetSystemStatsQuery } from '../../__data__/api/api'
|
||||
import { StatCard } from '../../components/StatCard'
|
||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||
import { ErrorAlert } from '../../components/ErrorAlert'
|
||||
|
||||
export const DashboardPage: React.FC = () => {
|
||||
const { data: stats, isLoading, error, refetch } = useGetSystemStatsQuery(undefined, {
|
||||
pollingInterval: 10000, // Обновление каждые 10 секунд
|
||||
})
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingSpinner message="Загрузка статистики..." />
|
||||
}
|
||||
|
||||
if (error || !stats) {
|
||||
return <ErrorAlert message="Не удалось загрузить статистику системы" onRetry={refetch} />
|
||||
}
|
||||
|
||||
const acceptanceRate = stats.submissions.total > 0
|
||||
? ((stats.submissions.accepted / stats.submissions.total) * 100).toFixed(1)
|
||||
: '0'
|
||||
|
||||
const rejectionRate = stats.submissions.total > 0
|
||||
? ((stats.submissions.rejected / stats.submissions.total) * 100).toFixed(1)
|
||||
: '0'
|
||||
|
||||
const queueUtilization = stats.queue.maxConcurrency > 0
|
||||
? ((stats.queue.currentlyProcessing / stats.queue.maxConcurrency) * 100).toFixed(0)
|
||||
: '0'
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Heading mb={6}>Dashboard</Heading>
|
||||
|
||||
{/* Main Stats */}
|
||||
<Grid templateColumns="repeat(auto-fit, minmax(250px, 1fr))" gap={6} mb={8}>
|
||||
<StatCard label="Всего пользователей" value={stats.users} colorScheme="blue" />
|
||||
<StatCard label="Всего заданий" value={stats.tasks} colorScheme="teal" />
|
||||
<StatCard label="Всего цепочек" value={stats.chains} colorScheme="purple" />
|
||||
<StatCard label="Всего проверок" value={stats.submissions.total} colorScheme="orange" />
|
||||
</Grid>
|
||||
|
||||
{/* Submissions Stats */}
|
||||
<Box bg="white" p={6} borderRadius="lg" boxShadow="sm" borderWidth="1px" borderColor="gray.200" mb={8}>
|
||||
<Heading size="md" mb={4}>
|
||||
Статистика проверок
|
||||
</Heading>
|
||||
<Grid templateColumns="repeat(auto-fit, minmax(200px, 1fr))" gap={6}>
|
||||
<VStack align="start" gap={2}>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
Принято
|
||||
</Text>
|
||||
<HStack>
|
||||
<Text fontSize="2xl" fontWeight="bold" color="green.600">
|
||||
{stats.submissions.accepted}
|
||||
</Text>
|
||||
<Badge colorPalette="green">{acceptanceRate}%</Badge>
|
||||
</HStack>
|
||||
</VStack>
|
||||
|
||||
<VStack align="start" gap={2}>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
Отклонено
|
||||
</Text>
|
||||
<HStack>
|
||||
<Text fontSize="2xl" fontWeight="bold" color="red.600">
|
||||
{stats.submissions.rejected}
|
||||
</Text>
|
||||
<Badge colorPalette="red">{rejectionRate}%</Badge>
|
||||
</HStack>
|
||||
</VStack>
|
||||
|
||||
<VStack align="start" gap={2}>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
Ожидают
|
||||
</Text>
|
||||
<Text fontSize="2xl" fontWeight="bold" color="yellow.600">
|
||||
{stats.submissions.pending}
|
||||
</Text>
|
||||
</VStack>
|
||||
|
||||
<VStack align="start" gap={2}>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
В процессе
|
||||
</Text>
|
||||
<Text fontSize="2xl" fontWeight="bold" color="blue.600">
|
||||
{stats.submissions.inProgress}
|
||||
</Text>
|
||||
</VStack>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{/* Queue Stats */}
|
||||
<Box bg="white" p={6} borderRadius="lg" boxShadow="sm" borderWidth="1px" borderColor="gray.200" mb={8}>
|
||||
<Heading size="md" mb={4}>
|
||||
Статус очереди
|
||||
</Heading>
|
||||
|
||||
<Grid templateColumns="repeat(auto-fit, minmax(250px, 1fr))" gap={6} mb={4}>
|
||||
<VStack align="start" gap={2}>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
В обработке
|
||||
</Text>
|
||||
<HStack align="baseline">
|
||||
<Text fontSize="2xl" fontWeight="bold" color="teal.600">
|
||||
{stats.queue.currentlyProcessing}
|
||||
</Text>
|
||||
<Text fontSize="sm" color="gray.500">
|
||||
/ {stats.queue.maxConcurrency}
|
||||
</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
|
||||
<VStack align="start" gap={2}>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
Ожидают в очереди
|
||||
</Text>
|
||||
<Text fontSize="2xl" fontWeight="bold" color="orange.600">
|
||||
{stats.queue.waiting}
|
||||
</Text>
|
||||
</VStack>
|
||||
|
||||
<VStack align="start" gap={2}>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
Всего в очереди
|
||||
</Text>
|
||||
<Text fontSize="2xl" fontWeight="bold" color="blue.600">
|
||||
{stats.queue.queueLength}
|
||||
</Text>
|
||||
</VStack>
|
||||
</Grid>
|
||||
|
||||
<Box>
|
||||
<Text fontSize="sm" color="gray.600" mb={2}>
|
||||
Загруженность очереди: {queueUtilization}%
|
||||
</Text>
|
||||
<Progress.Root value={Number(queueUtilization)} colorPalette="teal" size="sm" borderRadius="full">
|
||||
<Progress.Track>
|
||||
<Progress.Range />
|
||||
</Progress.Track>
|
||||
</Progress.Root>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Average Check Time */}
|
||||
<Box bg="white" p={6} borderRadius="lg" boxShadow="sm" borderWidth="1px" borderColor="gray.200">
|
||||
<Heading size="md" mb={2}>
|
||||
Среднее время проверки
|
||||
</Heading>
|
||||
<Text fontSize="3xl" fontWeight="bold" color="purple.600">
|
||||
{(stats.averageCheckTimeMs / 1000).toFixed(2)} сек
|
||||
</Text>
|
||||
<Text fontSize="sm" color="gray.600" mt={2}>
|
||||
Время от отправки решения до получения результата
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user