'use client'; import { useEffect, useMemo, useRef, useState } from 'react'; import { apiFetch } from '../lib/api'; interface CodingChallengeState { id: number; order: number; title: string; prompt: string; starter_code: string | null; is_passed: boolean; is_unlocked: boolean; last_submitted_code: string | null; last_stdout: string | null; last_stderr: string | null; last_exit_code: number | null; updated_at: string | null; } interface CodingMissionState { mission_id: number; total_challenges: number; completed_challenges: number; current_challenge_id: number | null; is_mission_completed: boolean; challenges: CodingChallengeState[]; } interface CodingMissionPanelProps { missionId: number; token?: string; initialState: CodingMissionState | null; initialCompleted?: boolean; } interface RunResult { stdout: string; stderr: string; exit_code: number; is_passed: boolean; mission_completed: boolean; expected_output?: string | null; } export function CodingMissionPanel({ missionId, token, initialState, initialCompleted = false }: CodingMissionPanelProps) { const [state, setState] = useState(initialState); const [missionCompleted, setMissionCompleted] = useState(initialState?.is_mission_completed || initialCompleted); const [editorCode, setEditorCode] = useState(''); const [status, setStatus] = useState(null); const [runResult, setRunResult] = useState(null); const [loading, setLoading] = useState(false); const previousChallengeIdRef = useRef(null); const activeChallenge = useMemo(() => { if (!state || !state.challenges.length) { return null; } if (state.current_challenge_id) { const current = state.challenges.find((challenge) => challenge.id === state.current_challenge_id); if (current) { return current; } } const nextUnlocked = state.challenges.find((challenge) => challenge.is_unlocked && !challenge.is_passed); if (nextUnlocked) { return nextUnlocked; } const firstIncomplete = state.challenges.find((challenge) => !challenge.is_passed); if (firstIncomplete) { return firstIncomplete; } return state.challenges[state.challenges.length - 1]; }, [state]); useEffect(() => { if (!state) { return; } setMissionCompleted(state.is_mission_completed); }, [state]); useEffect(() => { if (!activeChallenge) { previousChallengeIdRef.current = null; return; } if (previousChallengeIdRef.current === activeChallenge.id) { return; } previousChallengeIdRef.current = activeChallenge.id; const baseCode = activeChallenge.last_submitted_code ?? activeChallenge.starter_code ?? ''; setEditorCode(baseCode); setRunResult(null); setStatus(null); }, [activeChallenge]); if (!state) { return (

Не удалось загрузить задания для миссии. Попробуйте обновить страницу или обратитесь к HR, чтобы проверить настройки миссии.

); } const handleRefresh = async () => { if (!token) { return; } try { const updated = await apiFetch(`/api/missions/${missionId}/coding/challenges`, { authToken: token }); setState(updated); } catch (error) { if (error instanceof Error) { setStatus(error.message); } } }; const handleRun = async () => { if (!token) { setStatus('Не удалось получить токен авторизации. Перезайдите в систему.'); return; } if (!activeChallenge) { setStatus('Все задания выполнены — можно переходить к другим миссиям.'); return; } if (!editorCode.trim()) { setStatus('Добавьте решение в редакторе, прежде чем запускать проверку.'); return; } try { setLoading(true); setStatus(null); const result = await apiFetch( `/api/missions/${missionId}/coding/challenges/${activeChallenge.id}/run`, { method: 'POST', body: JSON.stringify({ code: editorCode }), authToken: token } ); setRunResult(result); setMissionCompleted(result.mission_completed); setStatus( result.is_passed ? 'Отлично! Задание пройдено. Можно переходить к следующему шагу.' : 'Проверка не пройдена. Сверьтесь с выводом программы и подсказками.' ); await handleRefresh(); } catch (error) { if (error instanceof Error) { setStatus(error.message); } else { setStatus('Неожиданная ошибка при выполнении проверки. Попробуйте повторить позже.'); } } finally { setLoading(false); } }; return (
{state.challenges.map((challenge) => { const isActive = activeChallenge?.id === challenge.id && !missionCompleted; return (

{challenge.order}. {challenge.title}

{challenge.is_passed && ✓ Готово}

{challenge.prompt}

{!challenge.is_unlocked && !challenge.is_passed && (

🔒 Задание откроется после успешного решения предыдущих пунктов.

)} {challenge.is_passed && challenge.last_stdout && (
Последний вывод программы:
                  {challenge.last_stdout}
                
)} {isActive && (