2024-03-29 20:08:34 -04:00
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
|
|
|
import { Socket } from 'socket.io-client';
|
2025-01-25 02:02:18 -05:00
|
|
|
import { ParsedGIFTQuestion, BaseQuestion, parse, Question } from 'gift-pegjs';
|
2025-02-20 00:37:01 -05:00
|
|
|
import {
|
|
|
|
|
isSimpleNumericalAnswer,
|
|
|
|
|
isRangeNumericalAnswer,
|
|
|
|
|
isHighLowNumericalAnswer
|
|
|
|
|
} from 'gift-pegjs/typeGuards';
|
2025-01-16 12:37:07 -05:00
|
|
|
import LiveResultsComponent from 'src/components/LiveResults/LiveResults';
|
2025-02-20 00:37:01 -05:00
|
|
|
import webSocketService, {
|
|
|
|
|
AnswerReceptionFromBackendType
|
|
|
|
|
} from '../../../services/WebsocketService';
|
2024-03-29 20:08:34 -04:00
|
|
|
import { QuizType } from '../../../Types/QuizType';
|
2025-01-23 20:12:47 -05:00
|
|
|
import GroupIcon from '@mui/icons-material/Group';
|
2024-03-29 20:08:34 -04:00
|
|
|
import './manageRoom.css';
|
2025-01-10 15:46:17 -05:00
|
|
|
import { ENV_VARIABLES } from 'src/constants';
|
2024-09-26 00:34:30 -04:00
|
|
|
import { StudentType, Answer } from '../../../Types/StudentType';
|
2025-01-16 12:37:07 -05:00
|
|
|
import LoadingCircle from 'src/components/LoadingCircle/LoadingCircle';
|
2024-03-29 20:08:34 -04:00
|
|
|
import { Refresh, Error } from '@mui/icons-material';
|
2025-01-16 12:37:07 -05:00
|
|
|
import StudentWaitPage from 'src/components/StudentWaitPage/StudentWaitPage';
|
|
|
|
|
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
|
2025-01-25 02:02:18 -05:00
|
|
|
import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
2024-03-29 20:08:34 -04:00
|
|
|
import ApiService from '../../../services/ApiService';
|
2025-01-25 02:02:18 -05:00
|
|
|
import { QuestionType } from 'src/Types/QuestionType';
|
2025-02-20 00:19:32 -05:00
|
|
|
import { RoomType } from 'src/Types/RoomType';
|
2025-02-20 02:17:24 -05:00
|
|
|
import { Button, NativeSelect } from '@mui/material';
|
|
|
|
|
import { Dialog, DialogActions, DialogContent, DialogTitle, TextField } from '@mui/material';
|
2024-03-29 20:08:34 -04:00
|
|
|
|
|
|
|
|
const ManageRoom: React.FC = () => {
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
const [roomName, setRoomName] = useState<string>('');
|
|
|
|
|
const [socket, setSocket] = useState<Socket | null>(null);
|
2024-09-25 13:05:36 -04:00
|
|
|
const [students, setStudents] = useState<StudentType[]>([]);
|
2024-03-29 20:08:34 -04:00
|
|
|
const quizId = useParams<{ id: string }>();
|
|
|
|
|
const [quizQuestions, setQuizQuestions] = useState<QuestionType[] | undefined>();
|
|
|
|
|
const [quiz, setQuiz] = useState<QuizType | null>(null);
|
|
|
|
|
const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher');
|
|
|
|
|
const [connectingError, setConnectingError] = useState<string>('');
|
|
|
|
|
const [currentQuestion, setCurrentQuestion] = useState<QuestionType | undefined>(undefined);
|
2025-01-23 20:12:47 -05:00
|
|
|
const [quizStarted, setQuizStarted] = useState(false);
|
2025-02-20 01:37:25 -05:00
|
|
|
const [rooms, setRooms] = useState<RoomType[]>([]);
|
2025-02-20 00:19:32 -05:00
|
|
|
const [selectedRoomId, setSelectedRoomId] = useState<string>('');
|
2025-02-20 02:17:24 -05:00
|
|
|
const [openDialog, setOpenDialog] = useState(false);
|
|
|
|
|
const [newRoomTitle, setNewRoomTitle] = useState('');
|
2025-02-21 20:15:32 -05:00
|
|
|
const [isRoomSelectionVisible, setIsRoomSelectionVisible] = useState(true);
|
2025-02-20 00:19:32 -05:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
if (!ApiService.isLoggedIn()) {
|
2025-02-20 00:37:01 -05:00
|
|
|
navigate('/teacher/login');
|
2025-02-20 00:19:32 -05:00
|
|
|
return;
|
|
|
|
|
}
|
2025-02-21 20:15:32 -05:00
|
|
|
|
|
|
|
|
const userRooms = await ApiService.getUserRooms();
|
|
|
|
|
setRooms(userRooms as RoomType[]);
|
2025-02-20 00:19:32 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fetchData();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleSelectRoom = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
2025-02-21 20:15:32 -05:00
|
|
|
const roomId = event.target.value;
|
|
|
|
|
setSelectedRoomId(roomId);
|
|
|
|
|
|
|
|
|
|
const selectedRoom = rooms.find((room) => room._id === roomId);
|
|
|
|
|
setRoomName(selectedRoom?.title || '');
|
2025-02-20 00:19:32 -05:00
|
|
|
};
|
|
|
|
|
|
2025-02-20 01:37:25 -05:00
|
|
|
useEffect(() => {
|
2025-02-21 20:15:32 -05:00
|
|
|
if (rooms.length > 0 && !selectedRoomId) {
|
|
|
|
|
setSelectedRoomId(rooms[0]._id);
|
2025-02-20 01:37:25 -05:00
|
|
|
}
|
2025-02-21 20:15:32 -05:00
|
|
|
}, [rooms]);
|
2025-02-20 02:17:24 -05:00
|
|
|
|
|
|
|
|
const handleDialogClose = () => {
|
|
|
|
|
setOpenDialog(false);
|
|
|
|
|
};
|
2025-02-20 01:37:25 -05:00
|
|
|
|
|
|
|
|
const handleCreateRoom = async () => {
|
2025-02-20 02:17:24 -05:00
|
|
|
setOpenDialog(true);
|
|
|
|
|
};
|
|
|
|
|
const handleSubmitRoom = async () => {
|
2025-02-20 01:37:25 -05:00
|
|
|
try {
|
2025-02-21 20:15:32 -05:00
|
|
|
if (newRoomTitle.trim()) {
|
|
|
|
|
const createdRoom = await ApiService.createRoom(newRoomTitle);
|
|
|
|
|
|
|
|
|
|
const updatedRooms = await ApiService.getUserRooms();
|
|
|
|
|
setRooms(updatedRooms as RoomType[]);
|
|
|
|
|
|
2025-02-21 22:51:42 -05:00
|
|
|
if (createdRoom) {
|
2025-02-21 20:15:32 -05:00
|
|
|
setSelectedRoomId(createdRoom);
|
|
|
|
|
}
|
2025-02-20 02:17:24 -05:00
|
|
|
setOpenDialog(false);
|
2025-02-21 20:15:32 -05:00
|
|
|
setNewRoomTitle('');
|
2025-02-20 01:37:25 -05:00
|
|
|
}
|
|
|
|
|
} catch (error) {
|
2025-02-21 20:15:32 -05:00
|
|
|
console.error('Error creating Room::', error);
|
2025-02-20 01:37:25 -05:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (quizId.id) {
|
|
|
|
|
const fetchquiz = async () => {
|
|
|
|
|
const quiz = await ApiService.getQuiz(quizId.id as string);
|
|
|
|
|
|
|
|
|
|
if (!quiz) {
|
2025-02-20 00:37:01 -05:00
|
|
|
window.alert(
|
|
|
|
|
`Une erreur est survenue.\n Le quiz ${quizId.id} n'a pas été trouvé\nVeuillez réessayer plus tard`
|
|
|
|
|
);
|
2024-03-29 20:08:34 -04:00
|
|
|
console.error('Quiz not found for id:', quizId.id);
|
|
|
|
|
navigate('/teacher/dashboard');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setQuiz(quiz as QuizType);
|
|
|
|
|
|
|
|
|
|
if (!socket) {
|
2024-10-30 21:40:35 -04:00
|
|
|
console.log(`no socket in ManageRoom, creating one.`);
|
2024-03-29 20:08:34 -04:00
|
|
|
createWebSocketRoom();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return () => {
|
|
|
|
|
// webSocketService.disconnect();
|
|
|
|
|
// };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fetchquiz();
|
|
|
|
|
} else {
|
2025-02-20 00:37:01 -05:00
|
|
|
window.alert(
|
|
|
|
|
`Une erreur est survenue.\n Le quiz ${quizId.id} n'a pas été trouvé\nVeuillez réessayer plus tard`
|
|
|
|
|
);
|
2024-03-29 20:08:34 -04:00
|
|
|
console.error('Quiz not found for id:', quizId.id);
|
|
|
|
|
navigate('/teacher/dashboard');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}, [quizId]);
|
|
|
|
|
|
2025-02-21 20:15:32 -05:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (rooms.length > 0 && !selectedRoomId) {
|
|
|
|
|
setSelectedRoomId(rooms[0].title);
|
|
|
|
|
}
|
|
|
|
|
}, [rooms]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!newRoomTitle && !selectedRoomId) {
|
|
|
|
|
setConnectingError('Aucun nom de salle sélectionné ou créé.');
|
|
|
|
|
}
|
|
|
|
|
}, [newRoomTitle, selectedRoomId]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (selectedRoomId && selectedRoomId.trim() !== '') {
|
|
|
|
|
console.log(`Sélection d'une nouvelle salle: ${selectedRoomId}`);
|
|
|
|
|
createWebSocketRoom();
|
|
|
|
|
}
|
|
|
|
|
}, [selectedRoomId]);
|
|
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
const disconnectWebSocket = () => {
|
|
|
|
|
if (socket) {
|
|
|
|
|
webSocketService.endQuiz(roomName);
|
|
|
|
|
webSocketService.disconnect();
|
|
|
|
|
setSocket(null);
|
|
|
|
|
setQuizQuestions(undefined);
|
|
|
|
|
setCurrentQuestion(undefined);
|
2024-09-25 14:53:17 -04:00
|
|
|
setStudents(new Array<StudentType>());
|
2024-03-29 20:08:34 -04:00
|
|
|
setRoomName('');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const createWebSocketRoom = () => {
|
2024-10-30 17:19:11 -04:00
|
|
|
console.log('Creating WebSocket room...');
|
2024-03-29 20:08:34 -04:00
|
|
|
setConnectingError('');
|
2025-02-21 22:51:42 -05:00
|
|
|
|
|
|
|
|
const handleRoomCreation = (socket: Socket, roomToCreate?: string) => {
|
|
|
|
|
socket.on('connect', () => {
|
|
|
|
|
if (roomToCreate) {
|
|
|
|
|
webSocketService.createRoom(roomToCreate);
|
|
|
|
|
} else {
|
|
|
|
|
socket.emit("create-room");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.on('create-success', (createdRoomName: string) => {
|
|
|
|
|
console.log('Salle créée/jointe:', createdRoomName);
|
|
|
|
|
setRoomName(createdRoomName);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.on('create-failure', (errorMessage: string) => {
|
|
|
|
|
setConnectingError(errorMessage);
|
|
|
|
|
console.error('Erreur création salle:', errorMessage);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (rooms.length === 0) {
|
|
|
|
|
console.log('Tentative de création de salle automatique...');
|
|
|
|
|
const newSocket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
|
|
|
|
|
handleRoomCreation(newSocket);
|
|
|
|
|
setSocket(newSocket);
|
|
|
|
|
} else {
|
|
|
|
|
const targetRoom = rooms.find((room) => room._id === selectedRoomId) || rooms[0];
|
|
|
|
|
if (!targetRoom) {
|
|
|
|
|
setConnectingError('Aucune salle disponible');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('Utilisation de la salle:', targetRoom.title);
|
|
|
|
|
const newSocket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
|
|
|
|
|
handleRoomCreation(newSocket, targetRoom.title);
|
|
|
|
|
setSocket(newSocket);
|
2025-02-21 20:15:32 -05:00
|
|
|
}
|
2025-02-21 22:51:42 -05:00
|
|
|
|
|
|
|
|
socket?.on('connect_error', (error) => {
|
|
|
|
|
setConnectingError('Erreur de connexion au serveur...');
|
|
|
|
|
console.error('Connection error:', error);
|
2024-03-29 20:08:34 -04:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-06 19:25:30 -04:00
|
|
|
useEffect(() => {
|
|
|
|
|
// This is here to make sure the correct value is sent when user join
|
|
|
|
|
if (socket) {
|
2024-09-26 00:34:30 -04:00
|
|
|
console.log(`Listening for user-joined in room ${roomName}`);
|
2025-02-07 19:36:11 -05:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
2024-09-25 13:05:36 -04:00
|
|
|
socket.on('user-joined', (_student: StudentType) => {
|
2024-04-06 19:25:30 -04:00
|
|
|
if (quizMode === 'teacher') {
|
|
|
|
|
webSocketService.nextQuestion(roomName, currentQuestion);
|
|
|
|
|
} else if (quizMode === 'student') {
|
|
|
|
|
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-09-26 00:34:30 -04:00
|
|
|
|
|
|
|
|
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;
|
2025-02-20 00:37:01 -05:00
|
|
|
console.log(
|
|
|
|
|
`Received answer from ${username} for question ${idQuestion}: ${answer}`
|
|
|
|
|
);
|
2024-09-26 00:34:30 -04:00
|
|
|
if (!quizQuestions) {
|
|
|
|
|
console.log('Quiz questions not found (cannot update answers without them).');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-01-23 20:12:47 -05:00
|
|
|
|
2024-09-26 00:34:30 -04:00
|
|
|
// 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);
|
|
|
|
|
});
|
2025-01-23 20:12:47 -05:00
|
|
|
|
2024-09-26 00:34:30 -04:00
|
|
|
let foundStudent = false;
|
|
|
|
|
const updatedStudents = prevStudents.map((student) => {
|
|
|
|
|
console.log(`Comparing ${student.id} to ${idUser}`);
|
|
|
|
|
if (student.id === idUser) {
|
|
|
|
|
foundStudent = true;
|
2025-02-20 00:37:01 -05:00
|
|
|
const existingAnswer = student.answers.find(
|
|
|
|
|
(ans) => ans.idQuestion === idQuestion
|
|
|
|
|
);
|
2024-09-26 00:34:30 -04:00
|
|
|
let updatedAnswers: Answer[] = [];
|
|
|
|
|
if (existingAnswer) {
|
|
|
|
|
// Update the existing answer
|
|
|
|
|
updatedAnswers = student.answers.map((ans) => {
|
|
|
|
|
console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`);
|
2025-02-20 00:37:01 -05:00
|
|
|
return ans.idQuestion === idQuestion
|
|
|
|
|
? {
|
|
|
|
|
...ans,
|
|
|
|
|
answer,
|
|
|
|
|
isCorrect: checkIfIsCorrect(
|
|
|
|
|
answer,
|
|
|
|
|
idQuestion,
|
|
|
|
|
quizQuestions!
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
: ans;
|
2024-09-26 00:34:30 -04:00
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// Add a new answer
|
2025-02-20 00:37:01 -05:00
|
|
|
const newAnswer = {
|
|
|
|
|
idQuestion,
|
|
|
|
|
answer,
|
|
|
|
|
isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!)
|
|
|
|
|
};
|
2024-09-26 00:34:30 -04:00
|
|
|
updatedAnswers = [...student.answers, newAnswer];
|
|
|
|
|
}
|
|
|
|
|
return { ...student, answers: updatedAnswers };
|
2025-01-23 20:12:47 -05:00
|
|
|
}
|
2024-09-26 00:34:30 -04:00
|
|
|
return student;
|
|
|
|
|
});
|
|
|
|
|
if (!foundStudent) {
|
|
|
|
|
console.log(`Student ${username} not found in the list.`);
|
|
|
|
|
}
|
|
|
|
|
return updatedStudents;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
setSocket(socket);
|
|
|
|
|
}
|
|
|
|
|
}, [socket, currentQuestion, quizQuestions]);
|
|
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
const nextQuestion = () => {
|
|
|
|
|
if (!quizQuestions || !currentQuestion || !quiz?.content) return;
|
|
|
|
|
|
|
|
|
|
const nextQuestionIndex = Number(currentQuestion?.question.id);
|
|
|
|
|
|
|
|
|
|
if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return;
|
|
|
|
|
|
|
|
|
|
setCurrentQuestion(quizQuestions[nextQuestionIndex]);
|
|
|
|
|
webSocketService.nextQuestion(roomName, quizQuestions[nextQuestionIndex]);
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-22 15:28:45 -05:00
|
|
|
const previousQuestion = () => {
|
|
|
|
|
if (!quizQuestions || !currentQuestion || !quiz?.content) return;
|
2024-03-29 20:08:34 -04:00
|
|
|
|
2025-01-22 15:28:45 -05:00
|
|
|
const prevQuestionIndex = Number(currentQuestion?.question.id) - 2; // -2 because question.id starts at index 1
|
2024-03-29 20:08:34 -04:00
|
|
|
|
2025-01-22 15:28:45 -05:00
|
|
|
if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return;
|
|
|
|
|
setCurrentQuestion(quizQuestions[prevQuestionIndex]);
|
|
|
|
|
webSocketService.nextQuestion(roomName, quizQuestions[prevQuestionIndex]);
|
|
|
|
|
};
|
2024-03-29 20:08:34 -04:00
|
|
|
|
|
|
|
|
const initializeQuizQuestion = () => {
|
|
|
|
|
const quizQuestionArray = quiz?.content;
|
|
|
|
|
if (!quizQuestionArray) return null;
|
|
|
|
|
const parsedQuestions = [] as QuestionType[];
|
|
|
|
|
|
|
|
|
|
quizQuestionArray.forEach((question, index) => {
|
2025-01-25 02:02:18 -05:00
|
|
|
parsedQuestions.push({ question: parse(question)[0] as BaseQuestion });
|
2024-03-29 20:08:34 -04:00
|
|
|
parsedQuestions[index].question.id = (index + 1).toString();
|
|
|
|
|
});
|
|
|
|
|
if (parsedQuestions.length === 0) return null;
|
|
|
|
|
|
|
|
|
|
setQuizQuestions(parsedQuestions);
|
|
|
|
|
return parsedQuestions;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const launchTeacherMode = () => {
|
|
|
|
|
const quizQuestions = initializeQuizQuestion();
|
2024-09-26 00:34:30 -04:00
|
|
|
console.log('launchTeacherMode - quizQuestions:', quizQuestions);
|
2024-03-29 20:08:34 -04:00
|
|
|
|
2024-09-26 00:34:30 -04:00
|
|
|
if (!quizQuestions) {
|
|
|
|
|
console.log('Error launching quiz (launchTeacherMode). No questions found.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-03-29 20:08:34 -04:00
|
|
|
|
|
|
|
|
setCurrentQuestion(quizQuestions[0]);
|
|
|
|
|
webSocketService.nextQuestion(roomName, quizQuestions[0]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const launchStudentMode = () => {
|
|
|
|
|
const quizQuestions = initializeQuizQuestion();
|
2024-09-26 00:34:30 -04:00
|
|
|
console.log('launchStudentMode - quizQuestions:', quizQuestions);
|
2024-03-29 20:08:34 -04:00
|
|
|
|
|
|
|
|
if (!quizQuestions) {
|
2024-09-26 00:34:30 -04:00
|
|
|
console.log('Error launching quiz (launchStudentMode). No questions found.');
|
2024-03-29 20:08:34 -04:00
|
|
|
return;
|
|
|
|
|
}
|
2024-09-26 00:34:30 -04:00
|
|
|
setQuizQuestions(quizQuestions);
|
2024-03-29 20:08:34 -04:00
|
|
|
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const launchQuiz = () => {
|
2024-09-24 19:56:47 -04:00
|
|
|
if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) {
|
|
|
|
|
// TODO: This error happens when token expires! Need to handle it properly
|
2025-02-20 00:37:01 -05:00
|
|
|
console.log(
|
|
|
|
|
`Error launching quiz. socket: ${socket}, roomName: ${roomName}, quiz: ${quiz}`
|
|
|
|
|
);
|
2025-01-23 20:12:47 -05:00
|
|
|
setQuizStarted(true);
|
|
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
switch (quizMode) {
|
|
|
|
|
case 'student':
|
2025-01-23 20:12:47 -05:00
|
|
|
setQuizStarted(true);
|
2024-03-29 20:08:34 -04:00
|
|
|
return launchStudentMode();
|
|
|
|
|
case 'teacher':
|
2025-01-23 20:12:47 -05:00
|
|
|
setQuizStarted(true);
|
2024-03-29 20:08:34 -04:00
|
|
|
return launchTeacherMode();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const showSelectedQuestion = (questionIndex: number) => {
|
|
|
|
|
if (quiz?.content && quizQuestions) {
|
|
|
|
|
setCurrentQuestion(quizQuestions[questionIndex]);
|
2024-09-26 00:34:30 -04:00
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
if (quizMode === 'teacher') {
|
|
|
|
|
webSocketService.nextQuestion(roomName, quizQuestions[questionIndex]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleReturn = () => {
|
|
|
|
|
disconnectWebSocket();
|
|
|
|
|
navigate('/teacher/dashboard');
|
|
|
|
|
};
|
|
|
|
|
|
2025-02-20 00:37:01 -05:00
|
|
|
function checkIfIsCorrect(
|
|
|
|
|
answer: string | number | boolean,
|
|
|
|
|
idQuestion: number,
|
|
|
|
|
questions: QuestionType[]
|
|
|
|
|
): boolean {
|
2024-09-26 00:34:30 -04:00
|
|
|
const questionInfo = questions.find((q) =>
|
|
|
|
|
q.question.id ? q.question.id === idQuestion.toString() : false
|
|
|
|
|
) as QuestionType | undefined;
|
|
|
|
|
|
|
|
|
|
const answerText = answer.toString();
|
|
|
|
|
if (questionInfo) {
|
2025-01-25 02:02:18 -05:00
|
|
|
const question = questionInfo.question as ParsedGIFTQuestion;
|
2024-09-26 00:34:30 -04:00
|
|
|
if (question.type === 'TF') {
|
|
|
|
|
return (
|
|
|
|
|
(question.isTrue && answerText == 'true') ||
|
|
|
|
|
(!question.isTrue && answerText == 'false')
|
|
|
|
|
);
|
|
|
|
|
} else if (question.type === 'MC') {
|
|
|
|
|
return question.choices.some(
|
2025-01-25 02:02:18 -05:00
|
|
|
(choice) => choice.isCorrect && choice.formattedText.text === answerText
|
2024-09-26 00:34:30 -04:00
|
|
|
);
|
|
|
|
|
} else if (question.type === 'Numerical') {
|
2025-01-25 02:02:18 -05:00
|
|
|
if (isHighLowNumericalAnswer(question.choices[0])) {
|
|
|
|
|
const choice = question.choices[0];
|
|
|
|
|
const answerNumber = parseFloat(answerText);
|
|
|
|
|
if (!isNaN(answerNumber)) {
|
|
|
|
|
return (
|
2025-02-20 00:37:01 -05:00
|
|
|
answerNumber <= choice.numberHigh && answerNumber >= choice.numberLow
|
2025-01-25 02:02:18 -05:00
|
|
|
);
|
2024-09-26 00:34:30 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-01-25 02:02:18 -05:00
|
|
|
if (isRangeNumericalAnswer(question.choices[0])) {
|
|
|
|
|
const answerNumber = parseFloat(answerText);
|
|
|
|
|
const range = question.choices[0].range;
|
|
|
|
|
const correctAnswer = question.choices[0].number;
|
|
|
|
|
if (!isNaN(answerNumber)) {
|
|
|
|
|
return (
|
|
|
|
|
answerNumber <= correctAnswer + range &&
|
|
|
|
|
answerNumber >= correctAnswer - range
|
|
|
|
|
);
|
2024-09-26 00:34:30 -04:00
|
|
|
}
|
2025-01-25 02:02:18 -05:00
|
|
|
}
|
|
|
|
|
if (isSimpleNumericalAnswer(question.choices[0])) {
|
|
|
|
|
const answerNumber = parseFloat(answerText);
|
|
|
|
|
if (!isNaN(answerNumber)) {
|
|
|
|
|
return answerNumber === question.choices[0].number;
|
2024-09-26 00:34:30 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (question.type === 'Short') {
|
|
|
|
|
return question.choices.some(
|
2025-01-25 02:02:18 -05:00
|
|
|
(choice) => choice.text.toUpperCase() === answerText.toUpperCase()
|
2024-09-26 00:34:30 -04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
if (!roomName) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="center">
|
|
|
|
|
{!connectingError ? (
|
|
|
|
|
<LoadingCircle text="Veuillez attendre la connexion au serveur..." />
|
|
|
|
|
) : (
|
|
|
|
|
<div className="center-v-align">
|
|
|
|
|
<Error sx={{ padding: 0 }} />
|
|
|
|
|
<div className="text-base">{connectingError}</div>
|
|
|
|
|
<Button
|
|
|
|
|
variant="contained"
|
|
|
|
|
startIcon={<Refresh />}
|
|
|
|
|
onClick={createWebSocketRoom}
|
|
|
|
|
>
|
|
|
|
|
Reconnecter
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2025-02-20 00:37:01 -05:00
|
|
|
<div className="room">
|
|
|
|
|
<div className="roomHeader">
|
2024-03-29 20:08:34 -04:00
|
|
|
<DisconnectButton
|
|
|
|
|
onReturn={handleReturn}
|
|
|
|
|
askConfirm
|
2025-02-20 00:37:01 -05:00
|
|
|
message={`Êtes-vous sûr de vouloir quitter?`}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
className="headerContent"
|
|
|
|
|
style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
width: '100%'
|
|
|
|
|
}}
|
|
|
|
|
>
|
2025-01-23 20:12:47 -05:00
|
|
|
<div style={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
|
2025-02-20 00:37:01 -05:00
|
|
|
<div className="title">Salle: {roomName}</div>
|
2025-01-23 20:12:47 -05:00
|
|
|
</div>
|
|
|
|
|
{quizStarted && (
|
2025-02-20 00:37:01 -05:00
|
|
|
<div
|
|
|
|
|
className="userCount subtitle smallText"
|
|
|
|
|
style={{ display: 'flex', alignItems: 'center' }}
|
|
|
|
|
>
|
2025-01-23 20:12:47 -05:00
|
|
|
<GroupIcon style={{ marginRight: '5px' }} />
|
|
|
|
|
{students.length}/60
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2024-03-29 20:08:34 -04:00
|
|
|
</div>
|
2025-02-20 00:37:01 -05:00
|
|
|
|
|
|
|
|
<div className="dumb"></div>
|
2025-02-20 00:19:32 -05:00
|
|
|
</div>
|
2025-02-21 20:15:32 -05:00
|
|
|
{isRoomSelectionVisible && (
|
|
|
|
|
<div className="roomSelection">
|
|
|
|
|
<div className="select">
|
|
|
|
|
<NativeSelect
|
|
|
|
|
id="select-room"
|
|
|
|
|
color="primary"
|
|
|
|
|
value={selectedRoomId}
|
|
|
|
|
onChange={handleSelectRoom}
|
|
|
|
|
>
|
|
|
|
|
<option value=""> Sélectionner une salle </option>
|
|
|
|
|
{rooms.map((room: RoomType) => (
|
|
|
|
|
<option value={room._id} key={room._id}>
|
|
|
|
|
{' '}
|
|
|
|
|
{room.title}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</NativeSelect>
|
|
|
|
|
</div>
|
2025-02-20 00:37:01 -05:00
|
|
|
|
2025-02-21 20:15:32 -05:00
|
|
|
<div
|
|
|
|
|
className="actions"
|
|
|
|
|
style={{ display: 'flex', justifyContent: 'flex-end' }}
|
2025-02-20 02:17:24 -05:00
|
|
|
>
|
2025-02-21 20:15:32 -05:00
|
|
|
<Button
|
|
|
|
|
variant="contained"
|
|
|
|
|
color="primary"
|
|
|
|
|
onClick={handleCreateRoom}
|
|
|
|
|
style={{
|
|
|
|
|
width: 'auto',
|
|
|
|
|
marginLeft: '30px',
|
|
|
|
|
height: '40px',
|
|
|
|
|
padding: '0 20px'
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Ajouter une nouvelle salle
|
2025-02-20 02:17:24 -05:00
|
|
|
</Button>
|
2025-02-21 20:15:32 -05:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Dialog pour créer une salle */}
|
|
|
|
|
<Dialog open={openDialog} onClose={handleDialogClose} maxWidth="sm" fullWidth>
|
|
|
|
|
<DialogTitle>Créer une nouvelle salle</DialogTitle>
|
|
|
|
|
<DialogContent>
|
|
|
|
|
<TextField
|
|
|
|
|
autoFocus
|
|
|
|
|
margin="dense"
|
|
|
|
|
label="Titre de la salle"
|
|
|
|
|
type="text"
|
|
|
|
|
fullWidth
|
|
|
|
|
value={newRoomTitle}
|
|
|
|
|
onChange={(e) => setNewRoomTitle(e.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
<DialogActions>
|
|
|
|
|
<Button onClick={handleDialogClose} color="secondary">
|
|
|
|
|
Annuler
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onClick={handleSubmitRoom} color="primary">
|
|
|
|
|
Créer
|
|
|
|
|
</Button>
|
|
|
|
|
</DialogActions>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2024-03-29 20:08:34 -04:00
|
|
|
|
2025-02-20 00:37:01 -05:00
|
|
|
{/* the following breaks the css (if 'room' classes are nested) */}
|
|
|
|
|
<div className="">
|
2024-03-29 20:08:34 -04:00
|
|
|
{quizQuestions ? (
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
|
|
|
<div className="title center-h-align mb-2">{quiz?.title}</div>
|
2025-01-23 20:23:15 -05:00
|
|
|
{!isNaN(Number(currentQuestion?.question.id)) && (
|
2025-02-20 00:37:01 -05:00
|
|
|
<strong className="number of questions">
|
|
|
|
|
Question {Number(currentQuestion?.question.id)}/
|
|
|
|
|
{quizQuestions?.length}
|
2025-01-23 20:23:15 -05:00
|
|
|
</strong>
|
|
|
|
|
)}
|
2024-03-29 20:08:34 -04:00
|
|
|
|
2025-02-20 00:37:01 -05:00
|
|
|
{quizMode === 'teacher' && (
|
2024-03-29 20:08:34 -04:00
|
|
|
<div className="mb-1">
|
2025-01-21 15:35:07 -05:00
|
|
|
{/* <QuestionNavigation
|
2024-03-29 20:08:34 -04:00
|
|
|
currentQuestionId={Number(currentQuestion?.question.id)}
|
|
|
|
|
questionsLength={quizQuestions?.length}
|
|
|
|
|
previousQuestion={previousQuestion}
|
|
|
|
|
nextQuestion={nextQuestion}
|
2025-01-21 15:35:07 -05:00
|
|
|
/> */}
|
2024-03-29 20:08:34 -04:00
|
|
|
</div>
|
|
|
|
|
)}
|
2025-02-20 00:37:01 -05:00
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
<div className="mb-2 flex-column-wrapper">
|
|
|
|
|
<div className="preview-and-result-container">
|
|
|
|
|
{currentQuestion && (
|
2025-01-23 22:38:22 -05:00
|
|
|
<QuestionDisplay
|
2024-03-29 20:08:34 -04:00
|
|
|
showAnswer={false}
|
2025-01-25 02:02:18 -05:00
|
|
|
question={currentQuestion?.question as Question}
|
2024-03-29 20:08:34 -04:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<LiveResultsComponent
|
|
|
|
|
quizMode={quizMode}
|
|
|
|
|
socket={socket}
|
|
|
|
|
questions={quizQuestions}
|
|
|
|
|
showSelectedQuestion={showSelectedQuestion}
|
2024-09-26 00:34:30 -04:00
|
|
|
students={students}
|
2024-03-29 20:08:34 -04:00
|
|
|
></LiveResultsComponent>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-02-20 00:37:01 -05:00
|
|
|
|
2024-03-29 20:08:34 -04:00
|
|
|
{quizMode === 'teacher' && (
|
2025-02-20 00:37:01 -05:00
|
|
|
<div
|
|
|
|
|
className="questionNavigationButtons"
|
|
|
|
|
style={{ display: 'flex', justifyContent: 'center' }}
|
|
|
|
|
>
|
2025-01-23 20:12:47 -05:00
|
|
|
<div className="previousQuestionButton">
|
2025-02-20 00:37:01 -05:00
|
|
|
<Button
|
|
|
|
|
onClick={previousQuestion}
|
2025-01-23 20:12:47 -05:00
|
|
|
variant="contained"
|
2025-02-20 00:37:01 -05:00
|
|
|
disabled={Number(currentQuestion?.question.id) <= 1}
|
|
|
|
|
>
|
2025-01-23 20:12:47 -05:00
|
|
|
Question précédente
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="nextQuestionButton">
|
2025-02-20 00:37:01 -05:00
|
|
|
<Button
|
|
|
|
|
onClick={nextQuestion}
|
2025-01-23 20:12:47 -05:00
|
|
|
variant="contained"
|
2025-02-20 00:37:01 -05:00
|
|
|
disabled={
|
|
|
|
|
Number(currentQuestion?.question.id) >=
|
|
|
|
|
quizQuestions.length
|
|
|
|
|
}
|
2025-01-23 20:12:47 -05:00
|
|
|
>
|
|
|
|
|
Prochaine question
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2025-02-20 00:37:01 -05:00
|
|
|
</div>
|
|
|
|
|
)}
|
2024-03-29 20:08:34 -04:00
|
|
|
</div>
|
|
|
|
|
) : (
|
2024-09-25 13:20:09 -04:00
|
|
|
<StudentWaitPage
|
2024-09-25 14:53:17 -04:00
|
|
|
students={students}
|
2024-03-29 20:08:34 -04:00
|
|
|
launchQuiz={launchQuiz}
|
|
|
|
|
setQuizMode={setQuizMode}
|
2025-02-21 20:15:32 -05:00
|
|
|
setIsRoomSelectionVisible={setIsRoomSelectionVisible}
|
2024-03-29 20:08:34 -04:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-02-20 00:37:01 -05:00
|
|
|
export default ManageRoom;
|