Enhance task management by adding skip functionality in TaskWorkspace and TaskPage. Implement storage methods for tracking skipped tasks, allowing users to navigate to the next task or the first skipped task seamlessly. Update polling manager configuration for improved performance.
Some checks failed
platform/bro-js/challenge-pl/pipeline/head There was a failure building this commit

This commit is contained in:
2025-12-14 15:39:45 +03:00
parent f7df4c755d
commit b9af3c4ee5
5 changed files with 112 additions and 15 deletions

View File

@@ -18,9 +18,10 @@ import { LearningMaterialViewer } from './LearningMaterialViewer'
interface TaskWorkspaceProps {
task: ChallengeTask
onTaskComplete?: () => void
onTaskSkip?: () => void
}
export const TaskWorkspace = ({ task, onTaskComplete }: TaskWorkspaceProps) => {
export const TaskWorkspace = ({ task, onTaskComplete, onTaskSkip }: TaskWorkspaceProps) => {
const { refreshStats } = useChallenge()
const { result, setResult, submit, queueStatus, finalSubmission, isSubmitting } = useSubmission({
taskId: task.id,
@@ -397,7 +398,7 @@ export const TaskWorkspace = ({ task, onTaskComplete }: TaskWorkspaceProps) => {
{!isAccepted && (
<>
<Button
onClick={onTaskComplete}
onClick={onTaskSkip}
variant="outline"
size="sm"
colorScheme="gray"

View File

@@ -81,7 +81,7 @@ export const ChallengeProvider = ({ children }: PropsWithChildren) => {
const metricsCollector = useMemo(() => new MetricsCollector(), [])
const behaviorTracker = useMemo(() => new BehaviorTracker(), [])
const eventEmitter = useMemo(() => new ChallengeEventEmitter(), [])
const pollingManager = useMemo(() => new PollingManager({ initialDelay: 800, maxDelay: 5000, multiplier: 1.15 }), [])
const pollingManager = useMemo(() => new PollingManager({ initialDelay: 800, maxDelay: 5000, multiplier: 1 }), [])
const [userId, setUserId] = useState<string | null>(() => storage.getUserId())
const [nickname, setNickname] = useState<string | null>(() => storage.getNickname())

View File

@@ -51,11 +51,20 @@ export const TaskPage = () => {
return storage.getFurthestTaskIndex(chainId)
})
// Отслеживаем пропущенные задания
const [skippedTasks, setSkippedTasks] = useState<string[]>(() => {
if (!chainId) return []
return storage.getSkippedTasks(chainId)
})
// Обновляем furthestTaskIndex при изменении chainId или currentTaskIndex
useEffect(() => {
if (!chainId) return
const currentFurthest = storage.getFurthestTaskIndex(chainId)
setFurthestTaskIndex(currentFurthest)
// Также обновляем список пропущенных заданий
const currentSkipped = storage.getSkippedTasks(chainId)
setSkippedTasks(currentSkipped)
}, [chainId, currentTaskIndex])
// Сохраняем текущее состояние в storage и обновляем прогресс
@@ -77,8 +86,12 @@ export const TaskPage = () => {
return taskIndex <= furthestTaskIndex
}
const handleTaskComplete = () => {
if (!chain || currentTaskIndex === -1) return
const handleTaskSkip = () => {
if (!chain || currentTaskIndex === -1 || !taskId) return
// Добавляем задание в список пропущенных
storage.addSkippedTask(chain.id, taskId)
setSkippedTasks(storage.getSkippedTasks(chain.id))
const nextTaskIndex = currentTaskIndex + 1
const nextTask = chain.tasks[nextTaskIndex]
@@ -89,11 +102,51 @@ export const TaskPage = () => {
setFurthestTaskIndex(nextTaskIndex) // Обновляем state сразу
navigate(URLs.task(chain.id, nextTask.id))
} else {
// Цепочка завершена
// Достигнут конец списка заданий - проверяем пропущенные
const currentSkipped = storage.getSkippedTasks(chain.id)
if (currentSkipped.length > 0) {
// Есть пропущенные задания - переходим к первому пропущенному
const firstSkippedId = currentSkipped[0]
navigate(URLs.task(chain.id, firstSkippedId))
} else {
// Нет пропущенных заданий - переходим на страницу завершения
storage.clearSessionData()
navigate(URLs.completed(chain.id))
}
}
}
const handleTaskComplete = () => {
if (!chain || currentTaskIndex === -1) return
// При успешном выполнении удаляем задание из пропущенных (если оно там было)
if (taskId) {
storage.removeSkippedTask(chain.id, taskId)
setSkippedTasks(storage.getSkippedTasks(chain.id))
}
const nextTaskIndex = currentTaskIndex + 1
const nextTask = chain.tasks[nextTaskIndex]
if (nextTask) {
// Обновляем прогресс перед переходом
storage.setFurthestTaskIndex(chain.id, nextTaskIndex)
setFurthestTaskIndex(nextTaskIndex) // Обновляем state сразу
navigate(URLs.task(chain.id, nextTask.id))
} else {
// Достигнут конец списка заданий - проверяем пропущенные
const currentSkipped = storage.getSkippedTasks(chain.id)
if (currentSkipped.length > 0) {
// Есть пропущенные задания - переходим к первому пропущенному
const firstSkippedId = currentSkipped[0]
navigate(URLs.task(chain.id, firstSkippedId))
} else {
// Нет пропущенных заданий - переходим на страницу завершения
storage.clearSessionData()
navigate(URLs.completed(chain.id))
}
}
}
const handleNavigateToTask = (newTaskId: string) => {
if (!chain) return
@@ -171,19 +224,23 @@ export const TaskPage = () => {
const taskIndex = chain.tasks.indexOf(t)
const isAccessible = isTaskAccessible(taskIndex)
const isCurrent = t.id === taskId
const isSkipped = skippedTasks.includes(t.id)
return (
<Button
key={t.id}
size="sm"
variant={isCurrent ? 'solid' : isAccessible ? 'outline' : 'ghost'}
colorScheme={isCurrent ? 'teal' : 'gray'}
colorScheme={isCurrent ? 'teal' : isSkipped ? 'gray' : 'gray'}
// @ts-expect-error Chakra UI v2 uses isDisabled
isDisabled={!isAccessible}
onClick={() => isAccessible && handleNavigateToTask(t.id)}
minW="40px"
opacity={isAccessible ? 1 : 0.5}
opacity={isAccessible ? (isSkipped ? 0.6 : 1) : 0.5}
cursor={isAccessible ? 'pointer' : 'not-allowed'}
bg={isSkipped && !isCurrent ? 'gray.200' : undefined}
color={isSkipped && !isCurrent ? 'gray.500' : undefined}
_hover={isAccessible ? (isSkipped ? { bg: 'gray.300' } : undefined) : undefined}
>
{isAccessible ? taskIndex + 1 : `🔒${taskIndex + 1}`}
</Button>
@@ -202,7 +259,7 @@ export const TaskPage = () => {
</Flex>
</Box>
<TaskWorkspace task={task} onTaskComplete={handleTaskComplete} />
<TaskWorkspace task={task} onTaskComplete={handleTaskComplete} onTaskSkip={handleTaskSkip} />
</Box>
</Box>
</>

View File

@@ -16,7 +16,7 @@ export class PollingManager {
constructor(options: PollingOptions = {}) {
this.currentDelay = options.initialDelay ?? 2000
this.maxDelay = options.maxDelay ?? 10000
this.multiplier = options.multiplier ?? 1.5
this.multiplier = options.multiplier ?? 1.01
}
async start(callback: PollCallback) {

View File

@@ -16,8 +16,9 @@ export const STORAGE_KEYS = {
SELECTED_TASK_ID: 'challengeSelectedTaskId',
} as const
// Вспомогательная функция для ключа прогресса цепочки
// Вспомогательные функции для ключей
const getFurthestTaskKey = (chainId: string) => `challengeFurthestTask_${chainId}`
const getSkippedTasksKey = (chainId: string) => `challengeSkippedTasks_${chainId}`
// Получение значений
export const storage = {
@@ -121,11 +122,11 @@ export const storage = {
// Очистка всех прогрессов по цепочкам
clearAllChainProgress: (): void => {
if (!isBrowser()) return
// Перебираем все ключи localStorage и удаляем те, что начинаются с challengeFurthestTask_
// Перебираем все ключи localStorage и удаляем те, что начинаются с challengeFurthestTask_ или challengeSkippedTasks_
const keysToRemove: string[] = []
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key && key.startsWith('challengeFurthestTask_')) {
if (key && (key.startsWith('challengeFurthestTask_') || key.startsWith('challengeSkippedTasks_'))) {
keysToRemove.push(key)
}
}
@@ -161,5 +162,43 @@ export const storage = {
if (!isBrowser()) return
localStorage.removeItem(getFurthestTaskKey(chainId))
},
// Получение пропущенных заданий для цепочки
getSkippedTasks: (chainId: string): string[] => {
if (!isBrowser()) return []
const value = localStorage.getItem(getSkippedTasksKey(chainId))
return value ? JSON.parse(value) : []
},
// Добавление задания в список пропущенных
addSkippedTask: (chainId: string, taskId: string): void => {
if (!isBrowser()) return
const skipped = storage.getSkippedTasks(chainId)
if (!skipped.includes(taskId)) {
skipped.push(taskId)
localStorage.setItem(getSkippedTasksKey(chainId), JSON.stringify(skipped))
}
},
// Удаление задания из списка пропущенных (когда оно выполнено)
removeSkippedTask: (chainId: string, taskId: string): void => {
if (!isBrowser()) return
const skipped = storage.getSkippedTasks(chainId)
const filtered = skipped.filter(id => id !== taskId)
localStorage.setItem(getSkippedTasksKey(chainId), JSON.stringify(filtered))
},
// Проверка, пропущено ли задание
isTaskSkipped: (chainId: string, taskId: string): boolean => {
if (!isBrowser()) return false
const skipped = storage.getSkippedTasks(chainId)
return skipped.includes(taskId)
},
// Очистка всех пропущенных заданий цепочки
clearSkippedTasks: (chainId: string): void => {
if (!isBrowser()) return
localStorage.removeItem(getSkippedTasksKey(chainId))
},
}