// LiveResults.tsx import React, { useEffect, useMemo, useState } from 'react'; import { Socket } from 'socket.io-client'; import { GIFTQuestion } from 'gift-pegjs'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons'; import { QuestionType } from '../../Types/QuestionType'; import './liveResult.css'; import { FormControlLabel, FormGroup, Paper, Switch, Table, TableBody, TableCell, TableFooter, TableHead, TableRow } from '@mui/material'; import { StudentType, Answer } from '../../Types/StudentType'; import { formatLatex } from '../GiftTemplate/templates/TextType'; interface LiveResultsProps { socket: Socket | null; questions: QuestionType[]; showSelectedQuestion: (index: number) => void; quizMode: 'teacher' | 'student'; connectedStudents: StudentType[] } // interface Answer { // answer: string | number | boolean; // isCorrect: boolean; // idQuestion: number; // } // interface StudentResult { // username: string; // idUser: string; // answers: Answer[]; // } const LiveResults: React.FC = ({ socket, questions, showSelectedQuestion, connectedStudents }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); const [students, setStudents] = useState(connectedStudents); // const [studentResultsMap, setStudentResultsMap] = useState>(new Map()); const maxQuestions = questions.length; // useEffect(() => { // // Initialize the map with the current students // const newStudentResultsMap = new Map(); // for (const student of students) { // newStudentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] }); // } // setStudentResultsMap(newStudentResultsMap); // }, []) // update when students change // useEffect(() => { // // studentResultsMap is inconsistent with students -- need to update // for (const student of students as StudentType[]) { // } // }, [students]) useEffect(() => { // Update the students state when the initialStudents prop changes setStudents(connectedStudents); console.log(`useEffect: connectedStudents: ${JSON.stringify(connectedStudents)}`); }, [connectedStudents]); useEffect(() => { if (socket) { const submitAnswerHandler = ({ idUser, answer, idQuestion }: { idUser: string; username: string; answer: string | number | boolean; idQuestion: number; }) => { console.log(`Received answer from ${idUser} for question ${idQuestion}: ${answer}`); // print the list of current student names console.log('Current students:'); students.forEach((student) => { console.log(student.name); }); // Update the students state using the functional form of setStudents setStudents((prevStudents) => { let foundStudent = false; const updatedStudents = prevStudents.map((student) => { if (student.id === idUser) { foundStudent = true; const updatedAnswers = student.answers.map((ans) => { const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion), idQuestion }; console.log(`Updating answer for ${student.name} for question ${idQuestion} to ${answer}`); return (ans.idQuestion === idQuestion ? { ...ans, newAnswer } : ans); } ); return { ...student, answers: updatedAnswers }; } return student; }); if (!foundStudent) { console.log(`Student ${idUser} not found in the list of students in LiveResults`); } return updatedStudents; }); // make a copy of the students array so we can update it // const updatedStudents = [...students]; // const student = updatedStudents.find((student) => student.id === idUser); // if (!student) { // // this is a bad thing if an answer was submitted but the student isn't in the list // console.log(`Student ${idUser} not found in the list of students in LiveResults`); // return; // } // const isCorrect = checkIfIsCorrect(answer, idQuestion); // const newAnswer: Answer = { answer, isCorrect, idQuestion }; // student.answers.push(newAnswer); // // print list of answers // console.log('Answers:'); // student.answers.forEach((answer) => { // console.log(answer.answer); // }); // setStudents(updatedStudents); // update the state }; socket.on('submit-answer', submitAnswerHandler); return () => { socket.off('submit-answer'); }; } }, [socket]); const getStudentGrade = (student: StudentType): number => { if (student.answers.length === 0) { return 0; } const uniqueQuestions = new Set(); let correctAnswers = 0; for (const answer of student.answers) { const { idQuestion, isCorrect } = answer; if (!uniqueQuestions.has(idQuestion)) { uniqueQuestions.add(idQuestion); if (isCorrect) { correctAnswers++; } } } return (correctAnswers / questions.length) * 100; }; const classAverage: number = useMemo(() => { let classTotal = 0; students.forEach((student) => { classTotal += getStudentGrade(student); }); return classTotal / students.length; }, [students]); const getCorrectAnswersPerQuestion = (index: number): number => { return ( (students.filter((student) => student.answers.some( (answer) => parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect ) ).length / students.length) * 100 ); }; // (studentResults.filter((student) => // student.answers.some( // (answer) => // parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect // ) // ).length / // studentResults.length) * // 100 // ); // }; function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean { const questionInfo = questions.find((q) => q.question.id ? q.question.id === idQuestion.toString() : false ) as QuestionType | undefined; const answerText = answer.toString(); if (questionInfo) { const question = questionInfo.question as GIFTQuestion; if (question.type === 'TF') { return ( (question.isTrue && answerText == 'true') || (!question.isTrue && answerText == 'false') ); } else if (question.type === 'MC') { return question.choices.some( (choice) => choice.isCorrect && choice.text.text === answerText ); } else if (question.type === 'Numerical') { if (question.choices && !Array.isArray(question.choices)) { if ( question.choices.type === 'high-low' && question.choices.numberHigh && question.choices.numberLow ) { const answerNumber = parseFloat(answerText); if (!isNaN(answerNumber)) { return ( answerNumber <= question.choices.numberHigh && answerNumber >= question.choices.numberLow ); } } } if (question.choices && Array.isArray(question.choices)) { if ( question.choices[0].text.type === 'range' && question.choices[0].text.number && question.choices[0].text.range ) { const answerNumber = parseFloat(answerText); const range = question.choices[0].text.range; const correctAnswer = question.choices[0].text.number; if (!isNaN(answerNumber)) { return ( answerNumber <= correctAnswer + range && answerNumber >= correctAnswer - range ); } } if ( question.choices[0].text.type === 'simple' && question.choices[0].text.number ) { const answerNumber = parseFloat(answerText); if (!isNaN(answerNumber)) { return answerNumber === question.choices[0].text.number; } } } } else if (question.type === 'Short') { return question.choices.some( (choice) => choice.text.text.toUpperCase() === answerText.toUpperCase() ); } } return false; } return (
Résultats du quiz
Afficher les noms
} control={ ) => setShowUsernames(e.target.checked) } /> } /> Afficher les réponses
} control={ ) => setShowCorrectAnswers(e.target.checked) } /> } />
Nom d'utilisateur
{Array.from({ length: maxQuestions }, (_, index) => ( showSelectedQuestion(index)} >
{`Q${index + 1}`}
))}
% réussite
{students.map((student) => (
{showUsernames ? student.name : '******'}
{Array.from({ length: maxQuestions }, (_, index) => { const answer = student.answers.find( (answer) => parseInt(answer.idQuestion.toString()) === index + 1 ); const answerText = answer ? answer.answer.toString() : ''; const isCorrect = answer ? answer.isCorrect : false; return ( {showCorrectAnswers ? (
{formatLatex(answerText)}
) : isCorrect ? ( ) : ( answerText !== '' && ( ) )}
); })} {getStudentGrade(student).toFixed()} %
))}
% réussite
{Array.from({ length: maxQuestions }, (_, index) => ( {students.length > 0 ? `${getCorrectAnswersPerQuestion(index).toFixed()} %` : '-'} ))} {students.length > 0 ? `${classAverage.toFixed()} %` : '-'}
); }; export default LiveResults;