128 lines
4.7 KiB
TypeScript
128 lines
4.7 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react'
|
||
import {
|
||
Box,
|
||
Text,
|
||
} from '@chakra-ui/react'
|
||
import { Alert } from '@chakra-ui/react/alert'
|
||
|
||
import type { ChallengeChain, ChallengeTask } from '../../__data__/types'
|
||
import { useChallenge } from '../../context/ChallengeContext'
|
||
import { TaskWorkspace } from '../../components/personal'
|
||
import { Header } from '../../components/Header'
|
||
import { LoginForm } from '../../components/LoginForm'
|
||
import { ChainSelector } from '../../components/ChainSelector'
|
||
|
||
export const MainPage = () => {
|
||
const { nickname, eventEmitter } = useChallenge()
|
||
const [selectedChain, setSelectedChain] = useState<ChallengeChain | null>(null)
|
||
const [selectedTask, setSelectedTask] = useState<ChallengeTask | null>(null)
|
||
const [isOffline, setIsOffline] = useState(() =>
|
||
typeof navigator !== 'undefined' ? !navigator.onLine : false,
|
||
)
|
||
const [notification, setNotification] = useState<{ status: 'success' | 'warning'; title: string; description?: string } | null>(null)
|
||
const notificationTimeoutRef = useRef<number | null>(null)
|
||
|
||
const handleSelectChain = (chain: ChallengeChain) => {
|
||
setSelectedChain(chain)
|
||
setSelectedTask(chain.tasks[0])
|
||
}
|
||
|
||
const handleTaskComplete = () => {
|
||
if (!selectedChain) return
|
||
const currentIndex = selectedChain.tasks.findIndex((item) => item.id === selectedTask?.id)
|
||
const nextTask = currentIndex >= 0 ? selectedChain.tasks[currentIndex + 1] : null
|
||
|
||
if (nextTask) {
|
||
setSelectedTask(nextTask)
|
||
} else {
|
||
// Цепочка завершена, возвращаемся к выбору
|
||
setSelectedChain(null)
|
||
setSelectedTask(null)
|
||
}
|
||
}
|
||
|
||
useEffect(() => {
|
||
const unsubscribe = eventEmitter.on('submission_completed', (event) => {
|
||
const submission = (event.data as { submission?: { status: string; attemptNumber: number } })?.submission
|
||
const accepted = submission?.status === 'accepted'
|
||
const title = accepted ? 'Задание принято' : 'Задание требует доработки'
|
||
const description = submission ? `Попытка №${submission.attemptNumber}` : undefined
|
||
|
||
if (notificationTimeoutRef.current) {
|
||
window.clearTimeout(notificationTimeoutRef.current)
|
||
}
|
||
|
||
setNotification({ status: accepted ? 'success' : 'warning', title, description })
|
||
notificationTimeoutRef.current = window.setTimeout(() => setNotification(null), 4000)
|
||
})
|
||
|
||
return () => {
|
||
unsubscribe()
|
||
if (notificationTimeoutRef.current) {
|
||
window.clearTimeout(notificationTimeoutRef.current)
|
||
}
|
||
}
|
||
}, [eventEmitter])
|
||
|
||
useEffect(() => {
|
||
const handleOnline = () => setIsOffline(false)
|
||
const handleOffline = () => setIsOffline(true)
|
||
|
||
window.addEventListener('online', handleOnline)
|
||
window.addEventListener('offline', handleOffline)
|
||
|
||
return () => {
|
||
window.removeEventListener('online', handleOnline)
|
||
window.removeEventListener('offline', handleOffline)
|
||
}
|
||
}, [])
|
||
|
||
// Если пользователь не авторизован, показываем форму входа
|
||
if (!nickname) {
|
||
return <LoginForm />
|
||
}
|
||
|
||
// Если цепочка не выбрана, показываем селектор цепочек
|
||
if (!selectedChain) {
|
||
return (
|
||
<>
|
||
<Header />
|
||
<ChainSelector onSelectChain={handleSelectChain} />
|
||
</>
|
||
)
|
||
}
|
||
|
||
const taskProgress = `Задание ${selectedChain.tasks.findIndex(t => t.id === selectedTask?.id) + 1} из ${selectedChain.tasks.length}`
|
||
|
||
// Показываем выбранную цепочку и задания
|
||
return (
|
||
<>
|
||
<Header chainName={selectedChain.name} taskProgress={taskProgress} />
|
||
<Box bg="gray.50" minH="100vh" py={4} px={{ base: 4, md: 8 }}>
|
||
<Box maxW="1200px" mx="auto">
|
||
{notification && (
|
||
<Alert.Root status={notification.status} borderRadius="md" mb={4}>
|
||
<Alert.Indicator />
|
||
<Box ml={3}>
|
||
<Text fontWeight="semibold">{notification.title}</Text>
|
||
{notification.description && <Text fontSize="sm">{notification.description}</Text>}
|
||
</Box>
|
||
</Alert.Root>
|
||
)}
|
||
|
||
{isOffline && (
|
||
<Alert.Root status="warning" borderRadius="md" mb={4}>
|
||
<Alert.Indicator />
|
||
Вы находитесь офлайн. Черновики сохраняются локально и будут отправлены после восстановления связи.
|
||
</Alert.Root>
|
||
)}
|
||
|
||
{selectedTask && (
|
||
<TaskWorkspace task={selectedTask} onTaskComplete={handleTaskComplete} />
|
||
)}
|
||
</Box>
|
||
</Box>
|
||
</>
|
||
)
|
||
}
|