import { useEffect, useState, useCallback, useRef } from 'react'; import { useAuth } from '../context/AuthContext'; import type { Exam, ExamSubmission } from '../types'; import { ClipboardList, Clock, ChevronLeft, ChevronRight, Send, CheckCircle2, XCircle, Award, AlertTriangle, Timer, ArrowLeft, RefreshCw, Lock, Unlock } from 'lucide-react'; import { normalizePhotoUrl } from '../helpers'; // ========================================== // Exam Environment — Portal do Aluno // ========================================== type ExamView = 'listing' | 'exam' | 'result'; interface ExamResult { total_questions: number; correct_count: number; wrong_count: number; percentage: number; final_score: number; } export default function Avaliacoes() { const { token } = useAuth(); const [exams, setExams] = useState([]); const [submissions, setSubmissions] = useState([]); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState<'exams' | 'activities'>('exams'); // Exam mode state const [view, setView] = useState('listing'); const [activeExam, setActiveExam] = useState(null); const [currentQ, setCurrentQ] = useState(0); const [answers, setAnswers] = useState>({}); const [timeLeft, setTimeLeft] = useState(0); const [submitting, setSubmitting] = useState(false); const [result, setResult] = useState(null); const timerRef = useRef | null>(null); // In-app modal state (replaces native alert/confirm) const [modalMsg, setModalMsg] = useState(''); const [modalType, setModalType] = useState<'info' | 'error' | 'confirm' | 'loading'>('info'); const [showModal, setShowModal] = useState(false); const [confirmCallback, setConfirmCallback] = useState<(() => void) | null>(null); const showAppAlert = (msg: string, type: 'info' | 'error' = 'info') => { setModalMsg(msg); setModalType(type); setConfirmCallback(null); setShowModal(true); }; const showAppConfirm = (msg: string, onConfirm: () => void) => { setModalMsg(msg); setModalType('confirm'); setConfirmCallback(() => onConfirm); setShowModal(true); }; // Fetch exams const fetchExams = useCallback(async () => { if (!token) return; try { const res = await fetch('/api/portal/avaliacoes', { headers: { Authorization: `Bearer ${token}` }, }); const data = await res.json(); setExams(data.exams || []); setSubmissions(data.submissions || []); } catch (err) { console.error(err); } finally { setLoading(false); } }, [token]); useEffect(() => { fetchExams(); }, [fetchExams]); // Timer logic useEffect(() => { if (view !== 'exam' || timeLeft <= 0) return; timerRef.current = setInterval(() => { setTimeLeft(prev => { if (prev <= 1) { // Auto-submit when time runs out clearInterval(timerRef.current!); handleSubmit(true); return 0; } return prev - 1; }); }, 1000); return () => { if (timerRef.current) clearInterval(timerRef.current); }; }, [view, timeLeft > 0]); const formatTimer = (seconds: number) => { const m = Math.floor(seconds / 60); const s = seconds % 60; return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; }; // Start exam const startExam = (exam: Exam) => { setActiveExam(exam); setCurrentQ(0); setAnswers({}); setTimeLeft(exam.durationMinutes * 60); setResult(null); setView('exam'); }; // Submit exam const handleSubmit = async (autoSubmit = false) => { if (submitting || !activeExam) return; setSubmitting(true); const typeLabel = (activeExam as any).evaluationType === 'activity' ? 'atividade' : 'prova'; // Show Loading Modal setModalType('loading'); setModalMsg(`Enviando sua ${typeLabel}... Por favor, aguarde e não feche esta janela.`); setShowModal(true); if (timerRef.current) clearInterval(timerRef.current); try { // Artificial delay of 5 seconds to let the student read the message await new Promise(resolve => setTimeout(resolve, 5000)); const res = await fetch('/api/portal/avaliacoes/submeter', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ examId: activeExam.id, answers }), }); const data = await res.json(); if (data.success) { // Show Success Modal setModalType('info'); setModalMsg(`Sua ${typeLabel} foi enviada com sucesso! Clique em OK para ver seu resultado.`); setShowModal(true); setConfirmCallback(() => { setResult(data.result); setView('result'); fetchExams(); }); } else { const errorCode = `ERR-${activeExam.id.substring(0, 4)}-${new Date().getTime().toString().slice(-4)}`; // Use the error message from server if available showAppAlert(data.error || `Não foi possível enviar sua nota. Tente novamente ou contate o suporte. (Código: ${errorCode})`, 'error'); if (!autoSubmit) setView('listing'); } } catch (err) { console.error(err); const errorCode = `CONN-ERR-${new Date().getTime().toString().slice(-4)}`; showAppAlert(`Erro de conexão ao enviar prova. Verifique sua internet. (Código: ${errorCode})`, 'error'); } finally { setSubmitting(false); } }; const selectAnswer = (questionId: string, optionIndex: number) => { setAnswers(prev => ({ ...prev, [questionId]: optionIndex })); }; const getSubmission = (examId: string) => submissions.find(s => s.exam_id === examId); const renderAppModal = () => { if (!showModal) return null; return (
{modalType === 'error' ? : modalType === 'confirm' ? : modalType === 'loading' ? : }

{modalMsg}

{modalType !== 'loading' && (
{modalType === 'confirm' ? ( <> ) : ( )}
)}
); }; // ========================================== // RENDER: Listing // ========================================== if (loading) { return (
{[1, 2, 3].map(i => (
))}
{renderAppModal()}
); } // ========================================== // RENDER: Exam Mode (Focused) // ========================================== if (view === 'exam' && activeExam) { const questions = activeExam.questions || []; const question = questions[currentQ]; const totalQ = questions.length; const answeredCount = Object.keys(answers).length; const isUrgent = timeLeft <= 60; const progress = totalQ > 0 ? ((currentQ + 1) / totalQ) * 100 : 0; return (
{/* Exam Header */}

{activeExam.title}

Questão {currentQ + 1} de {totalQ} • {answeredCount}/{totalQ} respondidas

{/* Timer */}
{formatTimer(timeLeft)}
{/* Progress Bar */}
{/* Question Area */}
{question && (
{/* Question Text & Image */}
QUESTÃO {currentQ + 1}

{question.text}

{question.imageUrl && (
Imagem de Apoio
Imagem de apoio window.open(normalizePhotoUrl(question.imageUrl), '_blank')} />
)}
{/* Options */}
{question.options.map((opt, idx) => { const isSelected = answers[question.id] === idx; const letter = String.fromCharCode(65 + idx); // A, B, C, D... return ( ); })}
)}
{/* Navigation Footer */}
{/* Question dots */}
{questions.map((q, i) => ( ))}
{currentQ < totalQ - 1 ? ( ) : ( )}
{renderAppModal()}
); } // ========================================== // RENDER: Result Screen // ========================================== if (view === 'result' && result) { const isApproved = result.final_score >= 6; const getMessage = () => { if (result.percentage >= 90) return 'Excelente! Você arrasou! 🎉'; if (result.percentage >= 70) return 'Muito bem! Continue assim! 💪'; if (result.percentage >= 50) return 'Bom resultado. Pratique mais! 📚'; return 'Não desanime! Revise o conteúdo e tente novamente. 📖'; }; return (
{/* Icon */}
{isApproved ? : }

Prova Finalizada!

{getMessage()}

{/* Score Display */}

{result.final_score.toFixed(1)}

NOTA FINAL

{result.percentage.toFixed(0)}%

APROVEITAMENTO

{/* Details */}

{result.total_questions}

QUESTÕES

{result.correct_count}

ACERTOS

{result.wrong_count}

ERROS

{renderAppModal()}
); } // ========================================== // RENDER: Listing (Default) // ========================================== const filteredExams = exams.filter(e => { const isActivity = (e as any).evaluationType === 'activity'; return activeTab === 'activities' ? isActivity : !isActivity; }); return (

Atividades e Provas

Provas e atividades disponíveis para você

{/* Tabs */}
{filteredExams.length === 0 ? (

Nenhuma {activeTab === 'activities' ? 'atividade' : 'prova'} disponível no momento.

As {activeTab === 'activities' ? 'atividades' : 'provas'} aparecerão aqui quando forem publicadas pelo professor.

) : (
{filteredExams.map(exam => { const sub = getSubmission(exam.id); const isDone = !!sub; return (
{isDone && (
REALIZADA
)}
{(exam as any).evaluationType === 'activity' ? 'Atividade' : 'Prova'} Vale: {(exam as any).maxScore ?? 10} pts

{exam.title}

{(exam as any).description && (

{(exam as any).description}

)}
{exam.durationMinutes} minutos
{exam.questions.length} questões
{isDone ? (

SUA NOTA

{sub!.final_score.toFixed(1)}

ACERTOS

{sub!.correct_count}/{sub!.total_questions}

{(exam as any).allowRetake ? ( ) : (
Bloqueado
)}
{(exam as any).allowRetake ? : }
) : ( )}
); })}
)} {renderAppModal()}
); }