Enhance task navigation and progress tracking across components. Update TaskWorkspace to improve task completion handling and button interactions. Refactor ChainsPage to select the furthest accessible task based on user progress. Implement storage functions for managing furthest task indices, ensuring users can only navigate to available tasks. Update TaskPage to check task accessibility and redirect users appropriately, improving overall user experience.
Some checks failed
platform/bro-js/challenge-pl/pipeline/head There was a failure building this commit
Some checks failed
platform/bro-js/challenge-pl/pipeline/head There was a failure building this commit
This commit is contained in:
@@ -21,7 +21,7 @@ interface TaskWorkspaceProps {
|
|||||||
|
|
||||||
export const TaskWorkspace = ({ task, onTaskComplete }: TaskWorkspaceProps) => {
|
export const TaskWorkspace = ({ task, onTaskComplete }: TaskWorkspaceProps) => {
|
||||||
const { refreshStats } = useChallenge()
|
const { refreshStats } = useChallenge()
|
||||||
const { result, setResult, submit, reset, queueStatus, finalSubmission, isSubmitting } = useSubmission({
|
const { result, setResult, submit, queueStatus, finalSubmission, isSubmitting } = useSubmission({
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -364,12 +364,18 @@ export const TaskWorkspace = ({ task, onTaskComplete }: TaskWorkspaceProps) => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<HStack justify="flex-end" gap={2}>
|
<HStack justify="space-between" gap={2}>
|
||||||
{!isAccepted && (
|
{!isAccepted && (
|
||||||
<>
|
<>
|
||||||
{/* @ts-expect-error Chakra UI v2 uses isDisabled */}
|
<Button
|
||||||
<Button onClick={reset} variant="ghost" size="sm" isDisabled={isChecking}>
|
onClick={onTaskComplete}
|
||||||
Сбросить
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
colorScheme="gray"
|
||||||
|
// @ts-expect-error Chakra UI v2 uses isDisabled
|
||||||
|
isDisabled={isChecking}
|
||||||
|
>
|
||||||
|
Пропустить
|
||||||
</Button>
|
</Button>
|
||||||
{/* @ts-expect-error Chakra UI v2 uses isLoading/isDisabled */}
|
{/* @ts-expect-error Chakra UI v2 uses isLoading/isDisabled */}
|
||||||
<Button onClick={submit} colorScheme="teal" size="sm" isLoading={isChecking} isDisabled={!result.trim() || isChecking}>
|
<Button onClick={submit} colorScheme="teal" size="sm" isLoading={isChecking} isDisabled={!result.trim() || isChecking}>
|
||||||
|
|||||||
@@ -26,10 +26,20 @@ export const ChainsPage = () => {
|
|||||||
|
|
||||||
const handleSelectChain = (chain: ChallengeChain) => {
|
const handleSelectChain = (chain: ChallengeChain) => {
|
||||||
storage.setSelectedChainId(chain.id)
|
storage.setSelectedChainId(chain.id)
|
||||||
// Переходим к первому заданию цепочки
|
|
||||||
if (chain.tasks.length > 0) {
|
if (chain.tasks.length > 0) {
|
||||||
storage.setSelectedTaskId(chain.tasks[0].id)
|
// Получаем самый дальний достигнутый индекс
|
||||||
navigate(URLs.task(chain.id, chain.tasks[0].id))
|
const furthestIndex = storage.getFurthestTaskIndex(chain.id)
|
||||||
|
|
||||||
|
// Если нет прогресса, инициализируем с первого задания
|
||||||
|
const targetIndex = furthestIndex >= 0 ? furthestIndex : 0
|
||||||
|
const targetTask = chain.tasks[targetIndex] || chain.tasks[0]
|
||||||
|
|
||||||
|
storage.setSelectedTaskId(targetTask.id)
|
||||||
|
// Убеждаемся, что прогресс установлен
|
||||||
|
storage.setFurthestTaskIndex(chain.id, targetIndex)
|
||||||
|
|
||||||
|
navigate(URLs.task(chain.id, targetTask.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useMemo } from 'react'
|
import React, { useEffect, useMemo, useState } from 'react'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
HStack,
|
HStack,
|
||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { Alert } from '@chakra-ui/react/alert'
|
|
||||||
|
|
||||||
import { URLs } from '../../__data__/urls'
|
import { URLs } from '../../__data__/urls'
|
||||||
import { useChallenge } from '../../context/ChallengeContext'
|
import { useChallenge } from '../../context/ChallengeContext'
|
||||||
@@ -46,19 +45,48 @@ export const TaskPage = () => {
|
|||||||
return chain.tasks.findIndex(t => t.id === taskId)
|
return chain.tasks.findIndex(t => t.id === taskId)
|
||||||
}, [chain, taskId])
|
}, [chain, taskId])
|
||||||
|
|
||||||
// Сохраняем текущее состояние в storage
|
// Получаем самый дальний достигнутый индекс задания (используем state для реактивности)
|
||||||
|
const [furthestTaskIndex, setFurthestTaskIndex] = useState(() => {
|
||||||
|
if (!chainId) return 0
|
||||||
|
return storage.getFurthestTaskIndex(chainId)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Обновляем furthestTaskIndex при изменении chainId или currentTaskIndex
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chainId && taskId) {
|
if (!chainId) return
|
||||||
|
const currentFurthest = storage.getFurthestTaskIndex(chainId)
|
||||||
|
setFurthestTaskIndex(currentFurthest)
|
||||||
|
}, [chainId, currentTaskIndex])
|
||||||
|
|
||||||
|
// Сохраняем текущее состояние в storage и обновляем прогресс
|
||||||
|
useEffect(() => {
|
||||||
|
if (chainId && taskId && currentTaskIndex >= 0) {
|
||||||
storage.setSelectedChainId(chainId)
|
storage.setSelectedChainId(chainId)
|
||||||
storage.setSelectedTaskId(taskId)
|
storage.setSelectedTaskId(taskId)
|
||||||
|
// Обновляем прогресс, если текущее задание дальше предыдущего
|
||||||
|
const newFurthest = Math.max(furthestTaskIndex, currentTaskIndex)
|
||||||
|
if (newFurthest > furthestTaskIndex) {
|
||||||
|
storage.setFurthestTaskIndex(chainId, newFurthest)
|
||||||
|
setFurthestTaskIndex(newFurthest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [chainId, taskId])
|
}, [chainId, taskId, currentTaskIndex, furthestTaskIndex])
|
||||||
|
|
||||||
|
// Проверка доступности задания
|
||||||
|
const isTaskAccessible = (taskIndex: number): boolean => {
|
||||||
|
return taskIndex <= furthestTaskIndex
|
||||||
|
}
|
||||||
|
|
||||||
const handleTaskComplete = () => {
|
const handleTaskComplete = () => {
|
||||||
if (!chain || currentTaskIndex === -1) return
|
if (!chain || currentTaskIndex === -1) return
|
||||||
|
|
||||||
const nextTask = chain.tasks[currentTaskIndex + 1]
|
const nextTaskIndex = currentTaskIndex + 1
|
||||||
|
const nextTask = chain.tasks[nextTaskIndex]
|
||||||
|
|
||||||
if (nextTask) {
|
if (nextTask) {
|
||||||
|
// Обновляем прогресс перед переходом
|
||||||
|
storage.setFurthestTaskIndex(chain.id, nextTaskIndex)
|
||||||
|
setFurthestTaskIndex(nextTaskIndex) // Обновляем state сразу
|
||||||
navigate(URLs.task(chain.id, nextTask.id))
|
navigate(URLs.task(chain.id, nextTask.id))
|
||||||
} else {
|
} else {
|
||||||
// Цепочка завершена
|
// Цепочка завершена
|
||||||
@@ -68,11 +96,39 @@ export const TaskPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleNavigateToTask = (newTaskId: string) => {
|
const handleNavigateToTask = (newTaskId: string) => {
|
||||||
if (chain) {
|
if (!chain) return
|
||||||
navigate(URLs.task(chain.id, newTaskId))
|
|
||||||
|
const newTaskIndex = chain.tasks.findIndex(t => t.id === newTaskId)
|
||||||
|
if (newTaskIndex === -1) return
|
||||||
|
|
||||||
|
// Проверяем доступность
|
||||||
|
if (!isTaskAccessible(newTaskIndex)) {
|
||||||
|
return // Не переходим к заблокированному заданию
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обновляем прогресс при переходе
|
||||||
|
const newFurthest = Math.max(furthestTaskIndex, newTaskIndex)
|
||||||
|
if (newFurthest > furthestTaskIndex) {
|
||||||
|
storage.setFurthestTaskIndex(chain.id, newFurthest)
|
||||||
|
setFurthestTaskIndex(newFurthest)
|
||||||
|
}
|
||||||
|
navigate(URLs.task(chain.id, newTaskId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверяем доступность текущего задания при загрузке
|
||||||
|
useEffect(() => {
|
||||||
|
if (chain && currentTaskIndex >= 0 && !isTaskAccessible(currentTaskIndex)) {
|
||||||
|
// Если пытаемся открыть недоступное задание, перенаправляем на последнее доступное
|
||||||
|
const lastAccessibleIndex = furthestTaskIndex
|
||||||
|
if (lastAccessibleIndex >= 0 && chain.tasks[lastAccessibleIndex]) {
|
||||||
|
navigate(URLs.task(chain.id, chain.tasks[lastAccessibleIndex].id), { replace: true })
|
||||||
|
} else if (chain.tasks[0]) {
|
||||||
|
// Если нет прогресса, идём к первому заданию
|
||||||
|
navigate(URLs.task(chain.id, chain.tasks[0].id), { replace: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [chain, currentTaskIndex, furthestTaskIndex, navigate])
|
||||||
|
|
||||||
const handleBackToChains = () => {
|
const handleBackToChains = () => {
|
||||||
storage.clearSessionData()
|
storage.clearSessionData()
|
||||||
navigate(URLs.chains)
|
navigate(URLs.chains)
|
||||||
@@ -104,18 +160,27 @@ export const TaskPage = () => {
|
|||||||
<Text fontSize="sm" fontWeight="medium" color="gray.600" mr={2}>
|
<Text fontSize="sm" fontWeight="medium" color="gray.600" mr={2}>
|
||||||
Задания:
|
Задания:
|
||||||
</Text>
|
</Text>
|
||||||
{chain.tasks.map((t, index) => (
|
{chain.tasks.map((t, index) => {
|
||||||
<Button
|
const isAccessible = isTaskAccessible(index)
|
||||||
key={t.id}
|
const isCurrent = t.id === taskId
|
||||||
size="sm"
|
|
||||||
variant={t.id === taskId ? 'solid' : 'outline'}
|
return (
|
||||||
colorScheme={t.id === taskId ? 'teal' : 'gray'}
|
<Button
|
||||||
onClick={() => handleNavigateToTask(t.id)}
|
key={t.id}
|
||||||
minW="40px"
|
size="sm"
|
||||||
>
|
variant={isCurrent ? 'solid' : isAccessible ? 'outline' : 'ghost'}
|
||||||
{index + 1}
|
colorScheme={isCurrent ? 'teal' : isAccessible ? 'gray' : 'gray'}
|
||||||
</Button>
|
// @ts-expect-error Chakra UI v2 uses isDisabled
|
||||||
))}
|
isDisabled={!isAccessible}
|
||||||
|
onClick={() => isAccessible && handleNavigateToTask(t.id)}
|
||||||
|
minW="40px"
|
||||||
|
opacity={isAccessible ? 1 : 0.5}
|
||||||
|
cursor={isAccessible ? 'pointer' : 'not-allowed'}
|
||||||
|
>
|
||||||
|
{isAccessible ? index + 1 : `🔒${index + 1}`}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export const STORAGE_KEYS = {
|
|||||||
SELECTED_TASK_ID: 'challengeSelectedTaskId',
|
SELECTED_TASK_ID: 'challengeSelectedTaskId',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
// Вспомогательная функция для ключа прогресса цепочки
|
||||||
|
const getFurthestTaskKey = (chainId: string) => `challengeFurthestTask_${chainId}`
|
||||||
|
|
||||||
// Получение значений
|
// Получение значений
|
||||||
export const storage = {
|
export const storage = {
|
||||||
getUserId: (): string | null => {
|
getUserId: (): string | null => {
|
||||||
@@ -109,5 +112,28 @@ export const storage = {
|
|||||||
localStorage.removeItem(STORAGE_KEYS.SELECTED_CHAIN_ID)
|
localStorage.removeItem(STORAGE_KEYS.SELECTED_CHAIN_ID)
|
||||||
localStorage.removeItem(STORAGE_KEYS.SELECTED_TASK_ID)
|
localStorage.removeItem(STORAGE_KEYS.SELECTED_TASK_ID)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Получение самого дальнего достигнутого индекса задания в цепочке
|
||||||
|
getFurthestTaskIndex: (chainId: string): number => {
|
||||||
|
if (!isBrowser()) return 0
|
||||||
|
const value = localStorage.getItem(getFurthestTaskKey(chainId))
|
||||||
|
return value ? parseInt(value, 10) : 0
|
||||||
|
},
|
||||||
|
|
||||||
|
// Установка самого дальнего достигнутого индекса задания
|
||||||
|
setFurthestTaskIndex: (chainId: string, index: number): void => {
|
||||||
|
if (!isBrowser()) return
|
||||||
|
const current = storage.getFurthestTaskIndex(chainId)
|
||||||
|
// Обновляем только если новый индекс больше текущего
|
||||||
|
if (index > current) {
|
||||||
|
localStorage.setItem(getFurthestTaskKey(chainId), index.toString())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Очистка прогресса цепочки
|
||||||
|
clearChainProgress: (chainId: string): void => {
|
||||||
|
if (!isBrowser()) return
|
||||||
|
localStorage.removeItem(getFurthestTaskKey(chainId))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user