import React, { useState, useMemo } from 'react'; export interface QuizProps { title: string; questions: QuizQuestion[]; showScore?: boolean; showProgress?: boolean; passingScore?: number; onchange: (value: QuizState) => void; } interface QuizQuestion { id: string; type: "mcq" | "short-answer"; question: string; points: number; // MCQ specific choices?: string[]; correctAnswer?: string; // Short answer specific placeholder?: string; correctAnswers?: string[]; } interface QuizAnswer { answer: string; isCorrect: boolean; points: number; submitted: boolean; } interface QuizState { answers: Record; totalScore: number; maxScore: number; questionsAnswered: number; isCompleted: boolean; } export const Quiz: React.FC = ({ title, questions, showScore = true, showProgress = true, passingScore, onchange }) => { const [answers, setAnswers] = useState>({}); const [draftInputs, setDraftInputs] = useState>({}); const quizState = useMemo(() => { const maxScore = questions.reduce((sum, q) => sum + q.points, 0); const totalScore = Object.values(answers).reduce((sum, a) => sum + a.points, 0); const questionsAnswered = Object.keys(answers).length; const isCompleted = questionsAnswered === questions.length; return { answers, totalScore, maxScore, questionsAnswered, isCompleted }; }, [answers, questions]); const handleQuestionAnswer = (questionId: string, userAnswer: string) => { const question = questions.find(q => q.id === questionId); if (!question || answers[questionId]?.submitted) return; let isCorrect = false; if (question.type === "mcq") { isCorrect = question.correctAnswer === userAnswer; } else if (question.type === "short-answer") { if (question.correctAnswers) { isCorrect = question.correctAnswers.some( correct => correct.toLowerCase() === userAnswer.toLowerCase() ); } } const newAnswer: QuizAnswer = { answer: userAnswer, isCorrect, points: isCorrect ? question.points : 0, submitted: true }; const newAnswers = { ...answers, [questionId]: newAnswer }; setAnswers(newAnswers); // Trigger onChange with updated state const updatedState = { answers: newAnswers, totalScore: Object.values(newAnswers).reduce((sum, a) => sum + a.points, 0), maxScore: quizState.maxScore, questionsAnswered: Object.keys(newAnswers).length, isCompleted: Object.keys(newAnswers).length === questions.length }; onchange(updatedState); }; const getProgressPercentage = () => { return Math.round((quizState.questionsAnswered / questions.length) * 100); }; const getScorePercentage = () => { return quizState.maxScore > 0 ? Math.round((quizState.totalScore / quizState.maxScore) * 100) : 0; }; const isPassing = () => { return passingScore ? getScorePercentage() >= passingScore : null; }; const renderMCQQuestion = (question: QuizQuestion) => { const answer = answers[question.id]; const isAnswered = answer?.submitted; const hasCorrect = typeof question.correctAnswer !== 'undefined'; return (

{question.question}

{question.points} pts
{question.choices?.map((choice, index) => { let choiceClass = 'quiz-choice'; if (isAnswered && hasCorrect) { if (choice === question.correctAnswer) { choiceClass += ' quiz-choice-correct'; } else if (choice === answer.answer && choice !== question.correctAnswer) { choiceClass += ' quiz-choice-incorrect'; } } else if (choice === answer?.answer) { choiceClass += ' quiz-choice-selected'; } return ( ); })}
{isAnswered && ( hasCorrect ? (
{answer.isCorrect ? `✓ Correct! (+${answer.points} pts)` : `✗ Incorrect. The correct answer is: ${question.correctAnswer}`}
) : (
{`You selected: ${answer.answer}`}
) )}
); }; const renderShortAnswerQuestion = (question: QuizQuestion) => { const answer = answers[question.id]; const isAnswered = answer?.submitted; const hasAnswers = (question.correctAnswers && question.correctAnswers.length > 0) || false; const inputValue = draftInputs[question.id] ?? ''; const handleSubmit = () => { if (!inputValue.trim() || isAnswered) return; handleQuestionAnswer(question.id, inputValue.trim()); // Clear draft after submission setDraftInputs(prev => { const next = { ...prev }; delete next[question.id]; return next; }); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !isAnswered) { handleSubmit(); } }; return (

{question.question}

{question.points} pts
{ if (!isAnswered) { const val = e.target.value; setDraftInputs(prev => ({ ...prev, [question.id]: val })); } }} onKeyDown={handleKeyDown} placeholder={question.placeholder || "Type your answer here..."} disabled={isAnswered} className={`quiz-input ${ isAnswered ? hasAnswers ? (answer.isCorrect ? 'quiz-input-correct' : 'quiz-input-incorrect') : '' : '' }`} /> {!isAnswered && ( )}
{isAnswered && ( hasAnswers ? (
{answer.isCorrect ? `✓ Correct! (+${answer.points} pts)` : `✗ Incorrect. Accepted answers: ${question.correctAnswers?.join(', ') || 'N/A'}`}
) : (
{`Your answer: ${answer.answer}`}
) )}
); }; return (
{/* Quiz Header */}

{title}

{showProgress && (
Progress: {quizState.questionsAnswered}/{questions.length} questions
)} {showScore && (
Score: {quizState.totalScore}/{quizState.maxScore} ({getScorePercentage()}%)
)}
{/* Questions */}
{questions.map((question) => question.type === "mcq" ? renderMCQQuestion(question) : renderShortAnswerQuestion(question) )}
{/* Quiz Summary */} {quizState.isCompleted && (

Quiz Complete!

Final Score: {quizState.totalScore}/{quizState.maxScore} points ({getScorePercentage()}%)
{passingScore && (
{isPassing() ? '🎉 Passed!' : '❌ Failed'} (Passing: {passingScore}%)
)}
)}
); };