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
{state.error || 'Không thể tải quiz'}
{isModal && }Quiz này chưa có câu hỏi.
{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.'}