One StudentType that also has answers

This commit is contained in:
C. Fuhrman 2024-09-25 14:53:17 -04:00
parent fa4b6b5bd3
commit 91fa75131a
7 changed files with 85 additions and 93 deletions

View file

@ -8,5 +8,5 @@ export interface StudentType {
name: string; name: string;
id: string; id: string;
room?: string; room?: string;
answers?: Answer[]; answers: Answer[];
} }

View file

@ -1,15 +1,17 @@
//StudentType.test.tsx //StudentType.test.tsx
import { StudentType } from "../../Types/StudentType"; import { StudentType, Answer } from "../../Types/StudentType";
const user : StudentType = { const user : StudentType = {
name: 'Student', name: 'Student',
id: '123' id: '123',
answers: new Array<Answer>()
} }
describe('StudentType', () => { describe('StudentType', () => {
test('creates a student with name and id', () => { test('creates a student with name, id and answers', () => {
expect(user.name).toBe('Student'); expect(user.name).toBe('Student');
expect(user.id).toBe('123'); expect(user.id).toBe('123');
expect(user.answers.length).toBe(0);
}); });
}); });

View file

@ -2,16 +2,17 @@
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import StudentWaitPage from '../../../components/StudentWaitPage/StudentWaitPage'; import StudentWaitPage from '../../../components/StudentWaitPage/StudentWaitPage';
import { StudentType, Answer } from '../../../Types/StudentType';
describe('StudentWaitPage Component', () => { describe('StudentWaitPage Component', () => {
const mockUsers = [ const mockUsers: StudentType[] = [
{ id: '1', name: 'User1' }, { id: '1', name: 'User1', answers: new Array<Answer>() },
{ id: '2', name: 'User2' }, { id: '2', name: 'User2', answers: new Array<Answer>() },
{ id: '3', name: 'User3' }, { id: '3', name: 'User3', answers: new Array<Answer>() },
]; ];
const mockProps = { const mockProps = {
users: mockUsers, students: mockUsers,
launchQuiz: jest.fn(), launchQuiz: jest.fn(),
roomName: 'Test Room', roomName: 'Test Room',
setQuizMode: jest.fn(), setQuizMode: jest.fn(),

View file

@ -19,7 +19,7 @@ import {
TableHead, TableHead,
TableRow TableRow
} from '@mui/material'; } from '@mui/material';
import { StudentType } from '../../Types/StudentType'; import { StudentType, Answer } from '../../Types/StudentType';
import { formatLatex } from '../GiftTemplate/templates/TextType'; import { formatLatex } from '../GiftTemplate/templates/TextType';
interface LiveResultsProps { interface LiveResultsProps {
@ -27,50 +27,53 @@ interface LiveResultsProps {
questions: QuestionType[]; questions: QuestionType[];
showSelectedQuestion: (index: number) => void; showSelectedQuestion: (index: number) => void;
quizMode: 'teacher' | 'student'; quizMode: 'teacher' | 'student';
students: StudentType[] connectedStudents: StudentType[]
} }
interface Answer { // interface Answer {
answer: string | number | boolean; // answer: string | number | boolean;
isCorrect: boolean; // isCorrect: boolean;
idQuestion: number; // idQuestion: number;
} // }
interface StudentResult { // interface StudentResult {
username: string; // username: string;
idUser: string; // idUser: string;
answers: Answer[]; // answers: Answer[];
} // }
const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelectedQuestion, students }) => { const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelectedQuestion, connectedStudents }) => {
const [showUsernames, setShowUsernames] = useState<boolean>(false); const [showUsernames, setShowUsernames] = useState<boolean>(false);
const [showCorrectAnswers, setShowCorrectAnswers] = useState<boolean>(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState<boolean>(false);
const [studentResultsMap, setStudentResultsMap] = useState<Map<string, StudentResult>>(new Map()); const [students, setStudents] = useState<StudentType[]>(connectedStudents);
// const [studentResultsMap, setStudentResultsMap] = useState<Map<string, StudentResult>>(new Map());
const maxQuestions = questions.length; const maxQuestions = questions.length;
useEffect(() => { // useEffect(() => {
// Initialize the map with the current students // // Initialize the map with the current students
const newStudentResultsMap = new Map<string, StudentResult>(); // const newStudentResultsMap = new Map<string, StudentResult>();
for (const student of students) { // for (const student of students) {
newStudentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] }); // newStudentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] });
} // }
setStudentResultsMap(newStudentResultsMap); // setStudentResultsMap(newStudentResultsMap);
}, []) // }, [])
// update when students change // update when students change
// useEffect(() => {
// // studentResultsMap is inconsistent with students -- need to update
// for (const student of students as StudentType[]) {
// }
// }, [students])
useEffect(() => { useEffect(() => {
// studentResultsMap is inconsistent with students -- need to update // Update the students state when the initialStudents prop changes
setStudents(connectedStudents);
}, [connectedStudents]);
for (const student of students as StudentType[]) {
if (!studentResultsMap.has(student.id)) {
studentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] });
}
}
}, [students])
useEffect(() => { useEffect(() => {
if (socket) { if (socket) {
@ -84,35 +87,19 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
answer: string | number | boolean; answer: string | number | boolean;
idQuestion: number; idQuestion: number;
}) => { }) => {
// Update the student results in the map with the answer // make a copy of the students array so we can update it
setStudentResultsMap((currentResults) => { const updatedStudents = [...students];
const studentResult = currentResults.get(idUser);
if (!studentResult) { const student = updatedStudents.find((student) => student.id === idUser);
return currentResults; if (!student) {
// this is a bad thing if an answer was submitted but the student isn't in the list
return;
} }
const isCorrect = checkIfIsCorrect(answer, idQuestion); const isCorrect = checkIfIsCorrect(answer, idQuestion);
studentResult.answers.push({ answer, isCorrect, idQuestion }); const newAnswer: Answer = { answer, isCorrect, idQuestion };
return new Map(currentResults).set(idUser, studentResult); student.answers.push(newAnswer);
}); setStudents(updatedStudents); // update the state
// setStudentResults((currentResults) => {
// const userIndex = currentResults.findIndex(
// (result) => result.idUser === idUser
// );
// const isCorrect = checkIfIsCorrect(answer, idQuestion);
// if (userIndex !== -1) {
// const newResults = [...currentResults];
// newResults[userIndex].answers.push({ answer, isCorrect, idQuestion });
// return newResults;
// } else {
// return [
// ...currentResults,
// { idUser, username, answers: [{ answer, isCorrect, idQuestion }] }
// ];
// }
// });
}; };
socket.on('submit-answer', submitAnswerHandler); socket.on('submit-answer', submitAnswerHandler);
@ -122,7 +109,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
} }
}, [socket]); }, [socket]);
const getStudentGrade = (student: StudentResult): number => { const getStudentGrade = (student: StudentType): number => {
if (student.answers.length === 0) { if (student.answers.length === 0) {
return 0; return 0;
} }
@ -148,24 +135,21 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
const classAverage: number = useMemo(() => { const classAverage: number = useMemo(() => {
let classTotal = 0; let classTotal = 0;
const studentResults = Array.from(studentResultsMap.values()); students.forEach((student) => {
studentResults.forEach((student) => {
classTotal += getStudentGrade(student); classTotal += getStudentGrade(student);
}); });
return classTotal / studentResults.length; return classTotal / students.length;
}, [studentResultsMap]); }, [students]);
const getCorrectAnswersPerQuestion = (index: number): number => { const getCorrectAnswersPerQuestion = (index: number): number => {
return ( return (
(Array.from(studentResultsMap.values()).filter((student) => (students.filter((student) =>
student.answers.some( student.answers.some(
(answer) => (answer) =>
parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect
) )
).length / studentResultsMap.size) * ).length / students.length) * 100
100
); );
}; };
@ -314,8 +298,8 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{Array.from(studentResultsMap.values()).map((student) => ( {students.map((student) => (
<TableRow key={student.idUser}> <TableRow key={student.id}>
<TableCell <TableCell
className="sticky-column" className="sticky-column"
sx={{ sx={{
@ -325,7 +309,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
}} }}
> >
<div className="text-base"> <div className="text-base">
{showUsernames ? student.username : '******'} {showUsernames ? student.name : '******'}
</div> </div>
</TableCell> </TableCell>
{Array.from({ length: maxQuestions }, (_, index) => { {Array.from({ length: maxQuestions }, (_, index) => {
@ -396,7 +380,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
color: 'rgba(0, 0, 0)' color: 'rgba(0, 0, 0)'
}} }}
> >
{studentResultsMap.size > 0 {students.length > 0
? `${getCorrectAnswersPerQuestion(index).toFixed()} %` ? `${getCorrectAnswersPerQuestion(index).toFixed()} %`
: '-'} : '-'}
</TableCell> </TableCell>
@ -412,7 +396,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
color: 'rgba(0, 0, 0)' color: 'rgba(0, 0, 0)'
}} }}
> >
{studentResultsMap.size > 0 ? `${classAverage.toFixed()} %` : '-'} {students.length > 0 ? `${classAverage.toFixed()} %` : '-'}
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableFooter> </TableFooter>

View file

@ -1,4 +1,4 @@
import { Button, Chip, Grid } from '@mui/material'; import { Box, Button, Chip } from '@mui/material';
import { StudentType } from '../../Types/StudentType'; import { StudentType } from '../../Types/StudentType';
import { PlayArrow } from '@mui/icons-material'; import { PlayArrow } from '@mui/icons-material';
import LaunchQuizDialog from '../LaunchQuizDialog/LaunchQuizDialog'; import LaunchQuizDialog from '../LaunchQuizDialog/LaunchQuizDialog';
@ -6,12 +6,12 @@ import { useState } from 'react';
import './studentWaitPage.css'; import './studentWaitPage.css';
interface Props { interface Props {
users: StudentType[]; students: StudentType[];
launchQuiz: () => void; launchQuiz: () => void;
setQuizMode: (mode: 'student' | 'teacher') => void; setQuizMode: (mode: 'student' | 'teacher') => void;
} }
const StudentWaitPage: React.FC<Props> = ({ users, launchQuiz, setQuizMode }) => { const StudentWaitPage: React.FC<Props> = ({ students, launchQuiz, setQuizMode }) => {
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false); const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
return ( return (
@ -30,15 +30,15 @@ const StudentWaitPage: React.FC<Props> = ({ users, launchQuiz, setQuizMode }) =>
<div className="students"> <div className="students">
<Grid container spacing={3}> <Box display="flex" flexWrap="wrap" gap={3}>
{users.map((user, index) => ( {students.map((student, index) => (
<Grid item key={user.name + index}> <Box key={student.name + index} >
<Chip label={user.name} sx={{ width: '100%' }} /> <Chip label={student.name} sx={{ width: '100%' }} />
</Grid> </Box>
))} ))}
</Grid> </Box>
</div> </div>

View file

@ -74,7 +74,7 @@ const ManageRoom: React.FC = () => {
setSocket(null); setSocket(null);
setQuizQuestions(undefined); setQuizQuestions(undefined);
setCurrentQuestion(undefined); setCurrentQuestion(undefined);
setStudents([]); setStudents(new Array<StudentType>());
setRoomName(''); setRoomName('');
} }
}; };
@ -293,7 +293,7 @@ const ManageRoom: React.FC = () => {
socket={socket} socket={socket}
questions={quizQuestions} questions={quizQuestions}
showSelectedQuestion={showSelectedQuestion} showSelectedQuestion={showSelectedQuestion}
students={students} connectedStudents={students}
></LiveResultsComponent> ></LiveResultsComponent>
</div> </div>
@ -312,7 +312,7 @@ const ManageRoom: React.FC = () => {
) : ( ) : (
<StudentWaitPage <StudentWaitPage
users={students} students={students}
launchQuiz={launchQuiz} launchQuiz={launchQuiz}
setQuizMode={setQuizMode} setQuizMode={setQuizMode}
/> />

View file

@ -49,10 +49,15 @@ const setupWebsocket = (io) => {
io.sockets.adapter.rooms.get(enteredRoomName).size; io.sockets.adapter.rooms.get(enteredRoomName).size;
if (clientsInRoom <= MAX_USERS_PER_ROOM) { if (clientsInRoom <= MAX_USERS_PER_ROOM) {
const newStudent = {
id: socket.id,
name: username,
answers: [],
};
socket.join(enteredRoomName); socket.join(enteredRoomName);
socket socket
.to(enteredRoomName) .to(enteredRoomName)
.emit("user-joined", { name: username, id: socket.id }); .emit("user-joined", newStudent);
socket.emit("join-success"); socket.emit("join-success");
} else { } else {
socket.emit("join-failure", "La salle est remplie"); socket.emit("join-failure", "La salle est remplie");