mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Merge pull request #134 from ets-cfuhrman-pfe/fuhrmanator/issue133
Fuhrmanator/issue133
This commit is contained in:
commit
1f78c44be2
17 changed files with 683 additions and 396 deletions
12
client/src/Types/StudentType.tsx
Normal file
12
client/src/Types/StudentType.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
export interface Answer {
|
||||||
|
answer: string | number | boolean;
|
||||||
|
isCorrect: boolean;
|
||||||
|
idQuestion: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StudentType {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
room?: string;
|
||||||
|
answers: Answer[];
|
||||||
|
}
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
export interface UserType {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
17
client/src/__tests__/Types/StudentType.test.tsx
Normal file
17
client/src/__tests__/Types/StudentType.test.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
//StudentType.test.tsx
|
||||||
|
import { StudentType, Answer } from "../../Types/StudentType";
|
||||||
|
|
||||||
|
const user : StudentType = {
|
||||||
|
name: 'Student',
|
||||||
|
id: '123',
|
||||||
|
answers: new Array<Answer>()
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('StudentType', () => {
|
||||||
|
test('creates a student with name, id and answers', () => {
|
||||||
|
|
||||||
|
expect(user.name).toBe('Student');
|
||||||
|
expect(user.id).toBe('123');
|
||||||
|
expect(user.answers.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
//UserTyper.test.tsx
|
|
||||||
import { UserType } from "../../Types/UserType";
|
|
||||||
|
|
||||||
const user : UserType = {
|
|
||||||
name: 'Student',
|
|
||||||
id: '123'
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('UserType', () => {
|
|
||||||
test('creates a user with name and id', () => {
|
|
||||||
|
|
||||||
expect(user.name).toBe('Student');
|
|
||||||
expect(user.id).toBe('123');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,24 +1,25 @@
|
||||||
// Importez le type UserType s'il n'est pas déjà importé
|
// Importez le type UserType s'il n'est pas déjà importé
|
||||||
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 UserWaitPage from '../../../components/UserWaitPage/UserWaitPage';
|
import StudentWaitPage from '../../../components/StudentWaitPage/StudentWaitPage';
|
||||||
|
import { StudentType, Answer } from '../../../Types/StudentType';
|
||||||
|
|
||||||
describe('UserWaitPage 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(),
|
||||||
};
|
};
|
||||||
|
|
||||||
test('renders UserWaitPage with correct content', () => {
|
test('renders StudentWaitPage with correct content', () => {
|
||||||
render(<UserWaitPage {...mockProps} />);
|
render(<StudentWaitPage {...mockProps} />);
|
||||||
|
|
||||||
//expect(screen.getByText(/Test Room/)).toBeInTheDocument();
|
//expect(screen.getByText(/Test Room/)).toBeInTheDocument();
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ describe('UserWaitPage Component', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('clicking on "Lancer" button opens LaunchQuizDialog', () => {
|
test('clicking on "Lancer" button opens LaunchQuizDialog', () => {
|
||||||
render(<UserWaitPage {...mockProps} />);
|
render(<StudentWaitPage {...mockProps} />);
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: /Lancer/i }));
|
fireEvent.click(screen.getByRole('button', { name: /Lancer/i }));
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ describe('StudentModeQuiz', () => {
|
||||||
fireEvent.click(screen.getByText('Répondre'));
|
fireEvent.click(screen.getByText('Répondre'));
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', '1');
|
expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles quit button click', async () => {
|
test('handles quit button click', async () => {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ describe('TeacherModeQuiz', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
fireEvent.click(screen.getByText('Répondre'));
|
fireEvent.click(screen.getByText('Répondre'));
|
||||||
});
|
});
|
||||||
expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', '1');
|
expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1);
|
||||||
expect(screen.getByText('Votre réponse est "Option A".')).toBeInTheDocument();
|
expect(screen.getByText('Votre réponse est "Option A".')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// LiveResults.tsx
|
// LiveResults.tsx
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { GIFTQuestion } from 'gift-pegjs';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons';
|
import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { QuestionType } from '../../Types/QuestionType';
|
import { QuestionType } from '../../Types/QuestionType';
|
||||||
|
|
@ -15,11 +14,12 @@ import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
TableFooter,
|
TableFooter,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableRow
|
TableRow
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { UserType } from '../../Types/UserType';
|
import { StudentType } from '../../Types/StudentType';
|
||||||
import { formatLatex } from '../GiftTemplate/templates/TextType';
|
import { formatLatex } from '../GiftTemplate/templates/TextType';
|
||||||
|
|
||||||
interface LiveResultsProps {
|
interface LiveResultsProps {
|
||||||
|
|
@ -27,79 +27,121 @@ interface LiveResultsProps {
|
||||||
questions: QuestionType[];
|
questions: QuestionType[];
|
||||||
showSelectedQuestion: (index: number) => void;
|
showSelectedQuestion: (index: number) => void;
|
||||||
quizMode: 'teacher' | 'student';
|
quizMode: 'teacher' | 'student';
|
||||||
students: UserType[]
|
students: 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> = ({ questions, showSelectedQuestion, students }) => {
|
||||||
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 [studentResults, setStudentResults] = useState<StudentResult[]>([]);
|
// const [students, setStudents] = useState<StudentType[]>(initialStudents);
|
||||||
|
// const [studentResultsMap, setStudentResultsMap] = useState<Map<string, StudentResult>>(new Map());
|
||||||
|
|
||||||
const maxQuestions = questions.length;
|
const maxQuestions = questions.length;
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
// Set student list before starting
|
// // Initialize the map with the current students
|
||||||
let newStudents: StudentResult[] = [];
|
// const newStudentResultsMap = new Map<string, StudentResult>();
|
||||||
|
|
||||||
for (const student of students as UserType[]) {
|
// for (const student of students) {
|
||||||
newStudents.push({ username: student.name, idUser: student.id, answers: [] })
|
// newStudentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] });
|
||||||
}
|
// }
|
||||||
|
|
||||||
setStudentResults(newStudents);
|
// setStudentResultsMap(newStudentResultsMap);
|
||||||
|
// }, [])
|
||||||
|
|
||||||
}, [])
|
// update when students change
|
||||||
|
// useEffect(() => {
|
||||||
|
// // studentResultsMap is inconsistent with students -- need to update
|
||||||
|
|
||||||
useEffect(() => {
|
// for (const student of students as StudentType[]) {
|
||||||
if (socket) {
|
// }
|
||||||
const submitAnswerHandler = ({
|
|
||||||
idUser,
|
|
||||||
username,
|
|
||||||
answer,
|
|
||||||
idQuestion
|
|
||||||
}: {
|
|
||||||
idUser: string;
|
|
||||||
username: string;
|
|
||||||
answer: string | number | boolean;
|
|
||||||
idQuestion: number;
|
|
||||||
}) => {
|
|
||||||
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);
|
// }, [students])
|
||||||
return () => {
|
|
||||||
socket.off('submit-answer');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [socket]);
|
|
||||||
|
|
||||||
const getStudentGrade = (student: StudentResult): number => {
|
// 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) {
|
if (student.answers.length === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -124,93 +166,103 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
|
|
||||||
const classAverage: number = useMemo(() => {
|
const classAverage: number = useMemo(() => {
|
||||||
let classTotal = 0;
|
let classTotal = 0;
|
||||||
studentResults.forEach((student) => {
|
|
||||||
|
students.forEach((student) => {
|
||||||
classTotal += getStudentGrade(student);
|
classTotal += getStudentGrade(student);
|
||||||
});
|
});
|
||||||
|
|
||||||
return classTotal / studentResults.length;
|
return classTotal / students.length;
|
||||||
}, [studentResults]);
|
}, [students]);
|
||||||
|
|
||||||
const getCorrectAnswersPerQuestion = (index: number): number => {
|
const getCorrectAnswersPerQuestion = (index: number): number => {
|
||||||
return (
|
return (
|
||||||
(studentResults.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 /
|
).length / students.length) * 100
|
||||||
studentResults.length) *
|
|
||||||
100
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean {
|
// (studentResults.filter((student) =>
|
||||||
const questionInfo = questions.find((q) =>
|
// student.answers.some(
|
||||||
q.question.id ? q.question.id === idQuestion.toString() : false
|
// (answer) =>
|
||||||
) as QuestionType | undefined;
|
// parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect
|
||||||
|
// )
|
||||||
|
// ).length /
|
||||||
|
// studentResults.length) *
|
||||||
|
// 100
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
const answerText = answer.toString();
|
// function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean {
|
||||||
if (questionInfo) {
|
// const questionInfo = questions.find((q) =>
|
||||||
const question = questionInfo.question as GIFTQuestion;
|
// q.question.id ? q.question.id === idQuestion.toString() : false
|
||||||
if (question.type === 'TF') {
|
// ) as QuestionType | undefined;
|
||||||
return (
|
|
||||||
(question.isTrue && answerText == 'true') ||
|
// const answerText = answer.toString();
|
||||||
(!question.isTrue && answerText == 'false')
|
// if (questionInfo) {
|
||||||
);
|
// const question = questionInfo.question as GIFTQuestion;
|
||||||
} else if (question.type === 'MC') {
|
// if (question.type === 'TF') {
|
||||||
return question.choices.some(
|
// return (
|
||||||
(choice) => choice.isCorrect && choice.text.text === answerText
|
// (question.isTrue && answerText == 'true') ||
|
||||||
);
|
// (!question.isTrue && answerText == 'false')
|
||||||
} else if (question.type === 'Numerical') {
|
// );
|
||||||
if (question.choices && !Array.isArray(question.choices)) {
|
// } else if (question.type === 'MC') {
|
||||||
if (
|
// return question.choices.some(
|
||||||
question.choices.type === 'high-low' &&
|
// (choice) => choice.isCorrect && choice.text.text === answerText
|
||||||
question.choices.numberHigh &&
|
// );
|
||||||
question.choices.numberLow
|
// } else if (question.type === 'Numerical') {
|
||||||
) {
|
// if (question.choices && !Array.isArray(question.choices)) {
|
||||||
const answerNumber = parseFloat(answerText);
|
// if (
|
||||||
if (!isNaN(answerNumber)) {
|
// question.choices.type === 'high-low' &&
|
||||||
return (
|
// question.choices.numberHigh &&
|
||||||
answerNumber <= question.choices.numberHigh &&
|
// question.choices.numberLow
|
||||||
answerNumber >= question.choices.numberLow
|
// ) {
|
||||||
);
|
// const answerNumber = parseFloat(answerText);
|
||||||
}
|
// if (!isNaN(answerNumber)) {
|
||||||
}
|
// return (
|
||||||
}
|
// answerNumber <= question.choices.numberHigh &&
|
||||||
if (question.choices && Array.isArray(question.choices)) {
|
// answerNumber >= question.choices.numberLow
|
||||||
if (
|
// );
|
||||||
question.choices[0].text.type === 'range' &&
|
// }
|
||||||
question.choices[0].text.number &&
|
// }
|
||||||
question.choices[0].text.range
|
// }
|
||||||
) {
|
// if (question.choices && Array.isArray(question.choices)) {
|
||||||
const answerNumber = parseFloat(answerText);
|
// if (
|
||||||
const range = question.choices[0].text.range;
|
// question.choices[0].text.type === 'range' &&
|
||||||
const correctAnswer = question.choices[0].text.number;
|
// question.choices[0].text.number &&
|
||||||
if (!isNaN(answerNumber)) {
|
// question.choices[0].text.range
|
||||||
return (
|
// ) {
|
||||||
answerNumber <= correctAnswer + range &&
|
// const answerNumber = parseFloat(answerText);
|
||||||
answerNumber >= correctAnswer - range
|
// const range = question.choices[0].text.range;
|
||||||
);
|
// const correctAnswer = question.choices[0].text.number;
|
||||||
}
|
// if (!isNaN(answerNumber)) {
|
||||||
}
|
// return (
|
||||||
if (
|
// answerNumber <= correctAnswer + range &&
|
||||||
question.choices[0].text.type === 'simple' &&
|
// answerNumber >= correctAnswer - range
|
||||||
question.choices[0].text.number
|
// );
|
||||||
) {
|
// }
|
||||||
const answerNumber = parseFloat(answerText);
|
// }
|
||||||
if (!isNaN(answerNumber)) {
|
// if (
|
||||||
return answerNumber === question.choices[0].text.number;
|
// question.choices[0].text.type === 'simple' &&
|
||||||
}
|
// question.choices[0].text.number
|
||||||
}
|
// ) {
|
||||||
}
|
// const answerNumber = parseFloat(answerText);
|
||||||
} else if (question.type === 'Short') {
|
// if (!isNaN(answerNumber)) {
|
||||||
return question.choices.some(
|
// return answerNumber === question.choices[0].text.number;
|
||||||
(choice) => choice.text.text.toUpperCase() === answerText.toUpperCase()
|
// }
|
||||||
);
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// } else if (question.type === 'Short') {
|
||||||
return false;
|
// return question.choices.some(
|
||||||
}
|
// (choice) => choice.text.text.toUpperCase() === answerText.toUpperCase()
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -243,7 +295,8 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-container">
|
<div className="table-container">
|
||||||
<Table size="small" component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
|
<Table size="small">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell className="sticky-column">
|
<TableCell className="sticky-column">
|
||||||
|
|
@ -278,8 +331,8 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{studentResults.map((student) => (
|
{students.map((student) => (
|
||||||
<TableRow key={student.idUser}>
|
<TableRow key={student.id}>
|
||||||
<TableCell
|
<TableCell
|
||||||
className="sticky-column"
|
className="sticky-column"
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -289,7 +342,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) => {
|
||||||
|
|
@ -360,7 +413,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
color: 'rgba(0, 0, 0)'
|
color: 'rgba(0, 0, 0)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{studentResults.length > 0
|
{students.length > 0
|
||||||
? `${getCorrectAnswersPerQuestion(index).toFixed()} %`
|
? `${getCorrectAnswersPerQuestion(index).toFixed()} %`
|
||||||
: '-'}
|
: '-'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
@ -376,11 +429,12 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
|
||||||
color: 'rgba(0, 0, 0)'
|
color: 'rgba(0, 0, 0)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{studentResults.length > 0 ? `${classAverage.toFixed()} %` : '-'}
|
{students.length > 0 ? `${classAverage.toFixed()} %` : '-'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableFooter>
|
</TableFooter>
|
||||||
</Table>
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import DisconnectButton from '../../components/DisconnectButton/DisconnectButton
|
||||||
|
|
||||||
interface StudentModeQuizProps {
|
interface StudentModeQuizProps {
|
||||||
questions: QuestionType[];
|
questions: QuestionType[];
|
||||||
submitAnswer: (answer: string | number | boolean, idQuestion: string) => void;
|
submitAnswer: (answer: string | number | boolean, idQuestion: number) => void;
|
||||||
disconnectWebSocket: () => void;
|
disconnectWebSocket: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ const StudentModeQuiz: React.FC<StudentModeQuizProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnSubmitAnswer = (answer: string | number | boolean) => {
|
const handleOnSubmitAnswer = (answer: string | number | boolean) => {
|
||||||
const idQuestion = questionInfos.question.id || '-1';
|
const idQuestion = Number(questionInfos.question.id) || -1;
|
||||||
submitAnswer(answer, idQuestion);
|
submitAnswer(answer, idQuestion);
|
||||||
setIsAnswerSubmitted(true);
|
setIsAnswerSubmitted(true);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import { Button, Chip, Grid } from '@mui/material';
|
import { Box, Button, Chip } from '@mui/material';
|
||||||
import { UserType } from '../../Types/UserType';
|
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';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import './userWaitPage.css';
|
import './studentWaitPage.css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
users: UserType[];
|
students: StudentType[];
|
||||||
launchQuiz: () => void;
|
launchQuiz: () => void;
|
||||||
setQuizMode: (mode: 'student' | 'teacher') => void;
|
setQuizMode: (mode: 'student' | 'teacher') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserWaitPage: 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 UserWaitPage: 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>
|
||||||
|
|
||||||
|
|
@ -53,4 +53,4 @@ const UserWaitPage: React.FC<Props> = ({ users, launchQuiz, setQuizMode }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserWaitPage;
|
export default StudentWaitPage;
|
||||||
|
|
@ -11,7 +11,7 @@ import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/
|
||||||
|
|
||||||
interface TeacherModeQuizProps {
|
interface TeacherModeQuizProps {
|
||||||
questionInfos: QuestionType;
|
questionInfos: QuestionType;
|
||||||
submitAnswer: (answer: string | number | boolean, idQuestion: string) => void;
|
submitAnswer: (answer: string | number | boolean, idQuestion: number) => void;
|
||||||
disconnectWebSocket: () => void;
|
disconnectWebSocket: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
|
||||||
}, [questionInfos]);
|
}, [questionInfos]);
|
||||||
|
|
||||||
const handleOnSubmitAnswer = (answer: string | number | boolean) => {
|
const handleOnSubmitAnswer = (answer: string | number | boolean) => {
|
||||||
const idQuestion = questionInfos.question.id || '-1';
|
const idQuestion = Number(questionInfos.question.id) || -1;
|
||||||
submitAnswer(answer, idQuestion);
|
submitAnswer(answer, idQuestion);
|
||||||
setFeedbackMessage(`Votre réponse est "${answer.toString()}".`);
|
setFeedbackMessage(`Votre réponse est "${answer.toString()}".`);
|
||||||
setIsFeedbackDialogOpen(true);
|
setIsFeedbackDialogOpen(true);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { ENV_VARIABLES } from '../../../constants';
|
||||||
|
|
||||||
import StudentModeQuiz from '../../../components/StudentModeQuiz/StudentModeQuiz';
|
import StudentModeQuiz from '../../../components/StudentModeQuiz/StudentModeQuiz';
|
||||||
import TeacherModeQuiz from '../../../components/TeacherModeQuiz/TeacherModeQuiz';
|
import TeacherModeQuiz from '../../../components/TeacherModeQuiz/TeacherModeQuiz';
|
||||||
import webSocketService from '../../../services/WebsocketService';
|
import webSocketService, { AnswerSubmissionToBackendType } from '../../../services/WebsocketService';
|
||||||
import DisconnectButton from '../../../components/DisconnectButton/DisconnectButton';
|
import DisconnectButton from '../../../components/DisconnectButton/DisconnectButton';
|
||||||
|
|
||||||
import './joinRoom.css';
|
import './joinRoom.css';
|
||||||
|
|
@ -99,8 +99,15 @@ const JoinRoom: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnSubmitAnswer = (answer: string | number | boolean, idQuestion: string) => {
|
const handleOnSubmitAnswer = (answer: string | number | boolean, idQuestion: number) => {
|
||||||
webSocketService.submitAnswer(roomName, answer, username, idQuestion);
|
const answerData: AnswerSubmissionToBackendType = {
|
||||||
|
roomName: roomName,
|
||||||
|
answer: answer,
|
||||||
|
username: username,
|
||||||
|
idQuestion: idQuestion
|
||||||
|
};
|
||||||
|
|
||||||
|
webSocketService.submitAnswer(answerData);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isWaitingForTeacher) {
|
if (isWaitingForTeacher) {
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,20 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { parse } from 'gift-pegjs';
|
import { GIFTQuestion, parse } from 'gift-pegjs';
|
||||||
import { QuestionType } from '../../../Types/QuestionType';
|
import { QuestionType } from '../../../Types/QuestionType';
|
||||||
import LiveResultsComponent from '../../../components/LiveResults/LiveResults';
|
import LiveResultsComponent from '../../../components/LiveResults/LiveResults';
|
||||||
// import { QuestionService } from '../../../services/QuestionService';
|
// import { QuestionService } from '../../../services/QuestionService';
|
||||||
import webSocketService from '../../../services/WebsocketService';
|
import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService';
|
||||||
import { QuizType } from '../../../Types/QuizType';
|
import { QuizType } from '../../../Types/QuizType';
|
||||||
|
|
||||||
import './manageRoom.css';
|
import './manageRoom.css';
|
||||||
import { ENV_VARIABLES } from '../../../constants';
|
import { ENV_VARIABLES } from '../../../constants';
|
||||||
import { UserType } from '../../../Types/UserType';
|
import { StudentType, Answer } from '../../../Types/StudentType';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import LoadingCircle from '../../../components/LoadingCircle/LoadingCircle';
|
import LoadingCircle from '../../../components/LoadingCircle/LoadingCircle';
|
||||||
import { Refresh, Error } from '@mui/icons-material';
|
import { Refresh, Error } from '@mui/icons-material';
|
||||||
import UserWaitPage from '../../../components/UserWaitPage/UserWaitPage';
|
import StudentWaitPage from '../../../components/StudentWaitPage/StudentWaitPage';
|
||||||
import DisconnectButton from '../../../components/DisconnectButton/DisconnectButton';
|
import DisconnectButton from '../../../components/DisconnectButton/DisconnectButton';
|
||||||
import QuestionNavigation from '../../../components/QuestionNavigation/QuestionNavigation';
|
import QuestionNavigation from '../../../components/QuestionNavigation/QuestionNavigation';
|
||||||
import Question from '../../../components/Questions/Question';
|
import Question from '../../../components/Questions/Question';
|
||||||
|
|
@ -25,7 +25,7 @@ const ManageRoom: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [roomName, setRoomName] = useState<string>('');
|
const [roomName, setRoomName] = useState<string>('');
|
||||||
const [socket, setSocket] = useState<Socket | null>(null);
|
const [socket, setSocket] = useState<Socket | null>(null);
|
||||||
const [users, setUsers] = useState<UserType[]>([]);
|
const [students, setStudents] = useState<StudentType[]>([]);
|
||||||
const quizId = useParams<{ id: string }>();
|
const quizId = useParams<{ id: string }>();
|
||||||
const [quizQuestions, setQuizQuestions] = useState<QuestionType[] | undefined>();
|
const [quizQuestions, setQuizQuestions] = useState<QuestionType[] | undefined>();
|
||||||
const [quiz, setQuiz] = useState<QuizType | null>(null);
|
const [quiz, setQuiz] = useState<QuizType | null>(null);
|
||||||
|
|
@ -74,7 +74,7 @@ const ManageRoom: React.FC = () => {
|
||||||
setSocket(null);
|
setSocket(null);
|
||||||
setQuizQuestions(undefined);
|
setQuizQuestions(undefined);
|
||||||
setCurrentQuestion(undefined);
|
setCurrentQuestion(undefined);
|
||||||
setUsers([]);
|
setStudents(new Array<StudentType>());
|
||||||
setRoomName('');
|
setRoomName('');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -96,9 +96,10 @@ const ManageRoom: React.FC = () => {
|
||||||
socket.on('create-failure', () => {
|
socket.on('create-failure', () => {
|
||||||
console.log('Error creating room.');
|
console.log('Error creating room.');
|
||||||
});
|
});
|
||||||
socket.on('user-joined', (user: UserType) => {
|
socket.on('user-joined', (student: StudentType) => {
|
||||||
|
console.log(`Student joined: name = ${student.name}, id = ${student.id}`);
|
||||||
|
|
||||||
setUsers((prevUsers) => [...prevUsers, user]);
|
setStudents((prevStudents) => [...prevStudents, student]);
|
||||||
|
|
||||||
if (quizMode === 'teacher') {
|
if (quizMode === 'teacher') {
|
||||||
webSocketService.nextQuestion(roomName, currentQuestion);
|
webSocketService.nextQuestion(roomName, currentQuestion);
|
||||||
|
|
@ -111,7 +112,8 @@ const ManageRoom: React.FC = () => {
|
||||||
setSocket(null);
|
setSocket(null);
|
||||||
});
|
});
|
||||||
socket.on('user-disconnected', (userId: string) => {
|
socket.on('user-disconnected', (userId: string) => {
|
||||||
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== userId));
|
console.log(`Student left: id = ${userId}`);
|
||||||
|
setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId));
|
||||||
});
|
});
|
||||||
setSocket(socket);
|
setSocket(socket);
|
||||||
};
|
};
|
||||||
|
|
@ -119,9 +121,8 @@ const ManageRoom: React.FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This is here to make sure the correct value is sent when user join
|
// This is here to make sure the correct value is sent when user join
|
||||||
if (socket) {
|
if (socket) {
|
||||||
socket.on('user-joined', (user: UserType) => {
|
console.log(`Listening for user-joined in room ${roomName}`);
|
||||||
|
socket.on('user-joined', (_student: StudentType) => {
|
||||||
setUsers((prevUsers) => [...prevUsers, user]);
|
|
||||||
|
|
||||||
if (quizMode === 'teacher') {
|
if (quizMode === 'teacher') {
|
||||||
webSocketService.nextQuestion(roomName, currentQuestion);
|
webSocketService.nextQuestion(roomName, currentQuestion);
|
||||||
|
|
@ -130,7 +131,122 @@ const ManageRoom: React.FC = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [currentQuestion, quizQuestions]);
|
|
||||||
|
if (socket) {
|
||||||
|
// handle the case where user submits an answer
|
||||||
|
console.log(`Listening for submit-answer-room in room ${roomName}`);
|
||||||
|
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
|
||||||
|
const { answer, idQuestion, idUser, username } = answerData;
|
||||||
|
console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`);
|
||||||
|
if (!quizQuestions) {
|
||||||
|
console.log('Quiz questions not found (cannot update answers without them).');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the students state using the functional form of setStudents
|
||||||
|
setStudents((prevStudents) => {
|
||||||
|
// print the list of current student names
|
||||||
|
console.log('Current students:');
|
||||||
|
prevStudents.forEach((student) => {
|
||||||
|
console.log(student.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
let foundStudent = false;
|
||||||
|
const updatedStudents = prevStudents.map((student) => {
|
||||||
|
console.log(`Comparing ${student.id} to ${idUser}`);
|
||||||
|
if (student.id === idUser) {
|
||||||
|
foundStudent = true;
|
||||||
|
const existingAnswer = student.answers.find((ans) => ans.idQuestion === idQuestion);
|
||||||
|
let updatedAnswers: Answer[] = [];
|
||||||
|
if (existingAnswer) {
|
||||||
|
// Update the existing answer
|
||||||
|
updatedAnswers = student.answers.map((ans) => {
|
||||||
|
console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`);
|
||||||
|
return (ans.idQuestion === idQuestion ? { ...ans, answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!) } : ans);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Add a new answer
|
||||||
|
const newAnswer = { idQuestion, answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!) };
|
||||||
|
updatedAnswers = [...student.answers, newAnswer];
|
||||||
|
}
|
||||||
|
return { ...student, answers: updatedAnswers };
|
||||||
|
}
|
||||||
|
return student;
|
||||||
|
});
|
||||||
|
if (!foundStudent) {
|
||||||
|
console.log(`Student ${username} not found in the list.`);
|
||||||
|
}
|
||||||
|
return updatedStudents;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setSocket(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [socket, currentQuestion, quizQuestions]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (socket) {
|
||||||
|
// const submitAnswerHandler = (answerData: answerSubmissionType) => {
|
||||||
|
// const { answer, idQuestion, username } = answerData;
|
||||||
|
// console.log(`Received answer from ${username} 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 === username) {
|
||||||
|
// foundStudent = true;
|
||||||
|
// const updatedAnswers = student.answers.map((ans) => {
|
||||||
|
// const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!), 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 ${username} 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 nextQuestion = () => {
|
const nextQuestion = () => {
|
||||||
if (!quizQuestions || !currentQuestion || !quiz?.content) return;
|
if (!quizQuestions || !currentQuestion || !quiz?.content) return;
|
||||||
|
|
@ -171,8 +287,12 @@ const ManageRoom: React.FC = () => {
|
||||||
|
|
||||||
const launchTeacherMode = () => {
|
const launchTeacherMode = () => {
|
||||||
const quizQuestions = initializeQuizQuestion();
|
const quizQuestions = initializeQuizQuestion();
|
||||||
|
console.log('launchTeacherMode - quizQuestions:', quizQuestions);
|
||||||
|
|
||||||
if (!quizQuestions) return;
|
if (!quizQuestions) {
|
||||||
|
console.log('Error launching quiz (launchTeacherMode). No questions found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setCurrentQuestion(quizQuestions[0]);
|
setCurrentQuestion(quizQuestions[0]);
|
||||||
webSocketService.nextQuestion(roomName, quizQuestions[0]);
|
webSocketService.nextQuestion(roomName, quizQuestions[0]);
|
||||||
|
|
@ -180,18 +300,20 @@ const ManageRoom: React.FC = () => {
|
||||||
|
|
||||||
const launchStudentMode = () => {
|
const launchStudentMode = () => {
|
||||||
const quizQuestions = initializeQuizQuestion();
|
const quizQuestions = initializeQuizQuestion();
|
||||||
|
console.log('launchStudentMode - quizQuestions:', quizQuestions);
|
||||||
|
|
||||||
if (!quizQuestions) {
|
if (!quizQuestions) {
|
||||||
|
console.log('Error launching quiz (launchStudentMode). No questions found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setQuizQuestions(quizQuestions);
|
||||||
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
|
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
|
||||||
};
|
};
|
||||||
|
|
||||||
const launchQuiz = () => {
|
const launchQuiz = () => {
|
||||||
if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) {
|
if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) {
|
||||||
// TODO: This error happens when token expires! Need to handle it properly
|
// TODO: This error happens when token expires! Need to handle it properly
|
||||||
console.log('Error launching quiz. No socket, room name or no questions.');
|
console.log(`Error launching quiz. socket: ${socket}, roomName: ${roomName}, quiz: ${quiz}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (quizMode) {
|
switch (quizMode) {
|
||||||
|
|
@ -217,6 +339,75 @@ const ManageRoom: React.FC = () => {
|
||||||
navigate('/teacher/dashboard');
|
navigate('/teacher/dashboard');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number, questions: QuestionType[]): 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!roomName) {
|
if (!roomName) {
|
||||||
return (
|
return (
|
||||||
<div className="center">
|
<div className="center">
|
||||||
|
|
@ -250,13 +441,13 @@ const ManageRoom: React.FC = () => {
|
||||||
|
|
||||||
<div className='centerTitle'>
|
<div className='centerTitle'>
|
||||||
<div className='title'>Salle: {roomName}</div>
|
<div className='title'>Salle: {roomName}</div>
|
||||||
<div className='userCount subtitle'>Utilisateurs: {users.length}/60</div>
|
<div className='userCount subtitle'>Utilisateurs: {students.length}/60</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='dumb'></div>
|
<div className='dumb'></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/* the following breaks the css (nested room classes) */}
|
{/* the following breaks the css (if 'room' classes are nested) */}
|
||||||
<div className=''>
|
<div className=''>
|
||||||
|
|
||||||
{quizQuestions ? (
|
{quizQuestions ? (
|
||||||
|
|
@ -293,7 +484,7 @@ const ManageRoom: React.FC = () => {
|
||||||
socket={socket}
|
socket={socket}
|
||||||
questions={quizQuestions}
|
questions={quizQuestions}
|
||||||
showSelectedQuestion={showSelectedQuestion}
|
showSelectedQuestion={showSelectedQuestion}
|
||||||
students={users}
|
students={students}
|
||||||
></LiveResultsComponent>
|
></LiveResultsComponent>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -311,8 +502,8 @@ const ManageRoom: React.FC = () => {
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
|
|
||||||
<UserWaitPage
|
<StudentWaitPage
|
||||||
users={users}
|
students={students}
|
||||||
launchQuiz={launchQuiz}
|
launchQuiz={launchQuiz}
|
||||||
setQuizMode={setQuizMode}
|
setQuizMode={setQuizMode}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,22 @@
|
||||||
// WebSocketService.tsx
|
// WebSocketService.tsx
|
||||||
import { io, Socket } from 'socket.io-client';
|
import { io, Socket } from 'socket.io-client';
|
||||||
|
|
||||||
|
// Must (manually) sync these types to server/socket/socket.js
|
||||||
|
|
||||||
|
export type AnswerSubmissionToBackendType = {
|
||||||
|
roomName: string;
|
||||||
|
username: string;
|
||||||
|
answer: string | number | boolean;
|
||||||
|
idQuestion: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AnswerReceptionFromBackendType = {
|
||||||
|
idUser: string;
|
||||||
|
username: string;
|
||||||
|
answer: string | number | boolean;
|
||||||
|
idQuestion: number;
|
||||||
|
};
|
||||||
|
|
||||||
class WebSocketService {
|
class WebSocketService {
|
||||||
private socket: Socket | null = null;
|
private socket: Socket | null = null;
|
||||||
|
|
||||||
|
|
@ -51,19 +67,22 @@ class WebSocketService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
submitAnswer(
|
submitAnswer(answerData: AnswerSubmissionToBackendType
|
||||||
roomName: string,
|
// roomName: string,
|
||||||
answer: string | number | boolean,
|
// answer: string | number | boolean,
|
||||||
username: string,
|
// username: string,
|
||||||
idQuestion: string
|
// idQuestion: string
|
||||||
) {
|
) {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket?.emit('submit-answer', {
|
this.socket?.emit('submit-answer',
|
||||||
answer: answer,
|
// {
|
||||||
roomName: roomName,
|
// answer: answer,
|
||||||
username: username,
|
// roomName: roomName,
|
||||||
idQuestion: idQuestion
|
// username: username,
|
||||||
});
|
// idQuestion: idQuestion
|
||||||
|
// }
|
||||||
|
answerData
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ describe("websocket server", () => {
|
||||||
answer: "answer1",
|
answer: "answer1",
|
||||||
idQuestion: 1,
|
idQuestion: 1,
|
||||||
});
|
});
|
||||||
teacherSocket.on("submit-answer", (answer) => {
|
teacherSocket.on("submit-answer-room", (answer) => {
|
||||||
expect(answer).toEqual({
|
expect(answer).toEqual({
|
||||||
idUser: studentSocket.id,
|
idUser: studentSocket.id,
|
||||||
username: "student1",
|
username: "student1",
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
@ -96,7 +101,7 @@ const setupWebsocket = (io) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("submit-answer", ({ roomName, username, answer, idQuestion }) => {
|
socket.on("submit-answer", ({ roomName, username, answer, idQuestion }) => {
|
||||||
socket.to(roomName).emit("submit-answer", {
|
socket.to(roomName).emit("submit-answer-room", {
|
||||||
idUser: socket.id,
|
idUser: socket.id,
|
||||||
username,
|
username,
|
||||||
answer,
|
answer,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue