import React, { useEffect, useState, useRef } from 'react'; import * as Icons from 'react-icons/fi'; import taService from '../../services/ta.service'; const QuizPlayer = ({ quizId, onComplete, isOpen = true, onClose = () => {}, displayMode = 'inline' }) => { const [state, setState] = useState({ loading: false, submitting: false, quiz: null, questions: [], answers: {}, submitted: false, result: null, error: '', timeRemaining: null, isDeadlinePassed: false }); const cacheRef = useRef({}); const isModal = displayMode === 'modal'; const shouldRender = !isModal || isOpen; // Format time for display const formatTime = (ms) => { const seconds = Math.floor(ms / 1000); const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; if (hours >= 24) return `${Math.floor(hours / 24)}d ${hours % 24}h ${minutes}m`; return `${hours}h ${minutes}m ${secs}s`; }; // Fetch quiz data useEffect(() => { if (!quizId || !shouldRender) return; let cancelled = false; const fetchQuiz = async () => { setState(prev => ({ ...prev, loading: true, error: '' })); // Use cache if available if (cacheRef.current[quizId]) { const cached = cacheRef.current[quizId]; setState(prev => ({ ...prev, loading: false, quiz: cached.quiz, questions: cached.questions })); return; } try { const res = await taService.getQuizForStudent(quizId); if (cancelled) return; if (res.success) { const questions = (res.data.questions || []).map(q => ({ ...q, options: typeof q.options === 'string' ? JSON.parse(q.options) : q.options })); cacheRef.current[quizId] = { quiz: res.data.quiz, questions }; setState(prev => ({ ...prev, loading: false, quiz: res.data.quiz, questions })); } else { setState(prev => ({ ...prev, loading: false, error: res.error || 'Không thể tải quiz', isDeadlinePassed: false, timeRemaining: null })); } } catch (err) { if (!cancelled) { setState(prev => ({ ...prev, loading: false, error: err.response?.data?.message || err.message || 'Có lỗi xảy ra', isDeadlinePassed: false, timeRemaining: null })); } } }; fetchQuiz(); return () => { cancelled = true; }; }, [quizId, shouldRender]); // Countdown timer useEffect(() => { if (!state.quiz?.due_at || state.submitted) { setState(prev => ({ ...prev, timeRemaining: null, isDeadlinePassed: false })); return; } const update = () => { const remaining = new Date(state.quiz.due_at).getTime() - Date.now(); if (remaining <= 0) { setState(prev => ({ ...prev, timeRemaining: null, isDeadlinePassed: true })); } else { setState(prev => ({ ...prev, timeRemaining: remaining, isDeadlinePassed: false })); } }; update(); const interval = setInterval(update, 1000); return () => clearInterval(interval); }, [state.quiz?.due_at, state.submitted]); const answeredCount = Object.keys(state.answers).length; const progress = state.questions.length ? (answeredCount / state.questions.length) * 100 : 0; const handleSelectAnswer = (questionId, optionId) => { if (state.isDeadlinePassed) return; setState(prev => ({ ...prev, answers: { ...prev.answers, [questionId]: optionId } })); }; const handleSubmit = async () => { if (!state.questions.length || state.submitting || state.isDeadlinePassed) return; setState(prev => ({ ...prev, submitting: true })); try { const answers = state.questions.map(q => ({ question_id: q.id, selected_option: state.answers[q.id], time_spent_seconds: 0 })); const res = await taService.submitQuizAttempt(quizId, { answers }); if (res.success) { setState(prev => ({ ...prev, submitted: true, result: res.data || res })); onComplete?.(res.data || res); } else { setState(prev => ({ ...prev, error: res.error || 'Không thể nộp quiz' })); } } catch (err) { const msg = err.response?.data?.message || err.message; if (msg?.includes('quá hạn')) { setState(prev => ({ ...prev, isDeadlinePassed: true, error: 'Đã quá hạn nộp bài.' })); } else { setState(prev => ({ ...prev, error: msg || 'Không thể nộp quiz' })); } } finally { setState(prev => ({ ...prev, submitting: false })); } }; // Shell component const Shell = ({ children }) => { if (!isModal) return
{children}
; return (
{children}
); }; // Loading state if (!shouldRender) return null; if (state.loading) { return (
Đang tải quiz...
); } // Error state if (state.error || !state.quiz) { return (

{state.error || 'Không thể tải quiz'}

{isModal && }
); } // No questions if (!state.questions.length) { return (

{state.quiz.title || 'Quiz'}

Quiz này chưa có câu hỏi.

); } // Results view if (state.submitted && state.result) { return (

Kết quả Quiz

{state.result.passed ? : }

{state.result.passed ? 'Bạn đã vượt qua' : 'Bạn chưa đạt yêu cầu'}

{state.result.passed ? 'Bạn đã hoàn thành tốt bài quiz.' : 'Hãy ôn tập và thử lại ở bài sau.'}

Điểm số
{state.result.total_score ?? state.result.score}%
Tổng câu
{state.result.total_questions || state.questions.length}
Điểm đạt
{state.quiz.passing_score || 60}%
{isModal && }
); } // Quiz player view - single page scroll return (

{state.quiz.title || 'Quiz'}

{state.quiz.due_at && ( {state.isDeadlinePassed ? 'Đã quá hạn' : `Nộp trước: ${new Date(state.quiz.due_at).toLocaleString('vi-VN', { timeZone: 'Asia/Ho_Chi_Minh' })}`} )} {isModal && }
{/* Countdown timer */} {state.quiz.due_at && !state.submitted && (
{state.isDeadlinePassed ? '⏰ Đã quá hạn nộp bài' : `⏱️ Còn lại: ${formatTime(state.timeRemaining)}`}
)} {/* Progress bar */}
Đã trả lời {answeredCount}/{state.questions.length} câu
{/* Questions */}
{state.questions.map((q, idx) => (
Câu {idx + 1} {q.topic} {q.difficulty === 'easy' ? 'Dễ' : q.difficulty === 'medium' ? 'Trung bình' : 'Khó'}

{q.question_text}

{(typeof q.options === 'string' ? JSON.parse(q.options) : q.options).map((opt, optIdx) => { const selected = state.answers[q.id] === opt.id; return ( ); })}
))}
{/* Submit button */}
); }; export default QuizPlayer;