From 6d714d1d77fcfd542afe8de0f5e48e01e674db1e Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Wed, 23 Apr 2025 21:46:19 -0400 Subject: [PATCH] [FEATURE] Zoomer sur les images en cliquant dessus Fixes #306 --- client/src/App.tsx | 6 +- .../MultipleChoiceQuestionDisplay.tsx | 169 +++++++++--------- .../NumericalQuestionDisplay.tsx | 128 ++++++------- .../QuestionsDisplay/QuestionDisplay.tsx | 53 ++---- .../ShortAnswerQuestionDisplay.tsx | 132 +++++++------- .../TrueFalseQuestionDisplay.tsx | 58 +++--- .../StudentModeQuiz/StudentModeQuiz.tsx | 38 +--- .../TeacherModeQuiz/TeacherModeQuiz.tsx | 76 +++----- .../src/pages/Student/JoinRoom/JoinRoom.tsx | 81 ++++----- .../pages/Student/JoinRoom/QuizContext.tsx | 56 ++++-- .../pages/Student/JoinRoom/QuizProvider.tsx | 91 +++++++++- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 101 ++++++----- 12 files changed, 502 insertions(+), 487 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index a3e33fa..2e39945 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -13,6 +13,7 @@ import ResetPassword from './pages/AuthManager/providers/SimpleLogin/ResetPasswo import ManageRoom from './pages/Teacher/ManageRoom/ManageRoom'; import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz'; + // Pages espace étudiant import JoinRoom from './pages/Student/JoinRoom/JoinRoom'; @@ -25,6 +26,7 @@ import Footer from './components/Footer/Footer'; import ApiService from './services/ApiService'; import OAuthCallback from './pages/AuthManager/callback/AuthCallback'; +import { QuizProvider } from './pages/Student/JoinRoom/QuizProvider'; const App: React.FC = () => { const [isAuthenticated, setIsAuthenticated] = useState(ApiService.isLoggedIn()); @@ -78,13 +80,13 @@ const App: React.FC = () => { /> : } + element={isTeacherAuthenticated ? : } /> {/* Pages espace étudiant */} : } + element={( !isRoomRequireAuthentication || isAuthenticated ) ? : } /> {/* Pages authentification */} diff --git a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx index d023a40..6dd7e60 100644 --- a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx @@ -5,55 +5,52 @@ import { Button } from '@mui/material'; import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { MultipleChoiceQuestion } from 'gift-pegjs'; import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; +import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; +import { QuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; -interface Props { - question: MultipleChoiceQuestion; - handleOnSubmitAnswer?: (answer: AnswerType) => void; - showAnswer?: boolean; - passedAnswer?: AnswerType; -} +const MultipleChoiceQuestionDisplay: React.FC = () => { + const { questions, index, answer, submitAnswer } = useQuizContext(); + console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(answer)); -const MultipleChoiceQuestionDisplay: React.FC = (props) => { - const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props; - console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(passedAnswer)); + const question = questions[Number(index)].question as MultipleChoiceQuestion; - const [answer, setAnswer] = useState(() => { - if (passedAnswer && passedAnswer.length > 0) { - return passedAnswer; + const [actualAnswer, setActualAnswer] = useState(() => { + if (answer && answer.length > 0) { + return answer; } return []; }); let disableButton = false; - if (handleOnSubmitAnswer === undefined) { + if (submitAnswer === undefined) { disableButton = true; } useEffect(() => { - console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(passedAnswer)); - if (passedAnswer !== undefined) { - setAnswer(passedAnswer); + console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(answer)); + if (answer !== undefined) { + setActualAnswer(answer); } else { - setAnswer([]); + setActualAnswer([]); } - }, [passedAnswer, question.id]); + }, [answer, index]); const handleOnClickAnswer = (choice: string) => { - setAnswer((prevAnswer) => { - console.log(`handleOnClickAnswer -- setAnswer(): prevAnswer: ${prevAnswer}, choice: ${choice}`); + setActualAnswer((answer) => { + console.log(`handleOnClickAnswer -- setAnswer(): prevAnswer: ${answer}, choice: ${choice}`); const correctAnswersCount = question.choices.filter((c) => c.isCorrect).length; if (correctAnswersCount === 1) { // If only one correct answer, replace the current selection - return prevAnswer.includes(choice) ? [] : [choice]; + return answer.includes(choice) ? [] : [choice]; } else { // Allow multiple selections if there are multiple correct answers - if (prevAnswer.includes(choice)) { + if (answer.includes(choice)) { // Remove the choice if it's already selected - return prevAnswer.filter((selected) => selected !== choice); + return answer.filter((selected) => selected !== choice); } else { // Add the choice if it's not already selected - return [...prevAnswer, choice]; + return [...answer, choice]; } } }); @@ -63,70 +60,74 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { const alphabet = alpha.map((x) => String.fromCharCode(x)); return ( -
-
-
-
-
- {question.choices.map((choice, i) => { - console.log(`answer: ${answer}, choice: ${choice.formattedText.text}`); - const selected = answer.includes(choice.formattedText.text) ? 'selected' : ''; - return ( -
-
- {choice.formattedFeedback && showAnswer && ( -
-
-
- )} - + ); + })} +
+ {question.formattedGlobalFeedback && showAnswer && ( +
+
- ); - })} -
- {question.formattedGlobalFeedback && showAnswer && ( -
-
+ )} + {!showAnswer && submitAnswer && ( + + )}
)} - {!showAnswer && handleOnSubmitAnswer && ( - - )} -
+ ); }; diff --git a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx index be28f57..d46981d 100644 --- a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx @@ -6,27 +6,24 @@ import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemp import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards'; import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; +import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; +import { QuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; -interface Props { - question: NumericalQuestion; - handleOnSubmitAnswer?: (answer: AnswerType) => void; - showAnswer?: boolean; - passedAnswer?: AnswerType; -} +const NumericalQuestionDisplay: React.FC = () => { + const { questions, index, answer, submitAnswer } = useQuizContext(); -const NumericalQuestionDisplay: React.FC = (props) => { - const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = - props; - const [answer, setAnswer] = useState(passedAnswer || []); + const question = questions[Number(index)].question as NumericalQuestion; + + const [actualAnswer, setActualAnswer] = useState(answer || []); const correctAnswers = question.choices; let correctAnswer = ''; useEffect(() => { - if (passedAnswer !== null && passedAnswer !== undefined) { - setAnswer(passedAnswer); - } - }, [passedAnswer]); - + if (answer !== null && answer !== undefined) { + setActualAnswer(answer); + } + }, [answer]); + //const isSingleAnswer = correctAnswers.length === 1; if (isSimpleNumericalAnswer(correctAnswers[0])) { @@ -44,57 +41,62 @@ const NumericalQuestionDisplay: React.FC = (props) => { } return ( -
-
-
-
- {showAnswer ? ( - <> -
- La bonne réponse est: - {correctAnswer}
- - Votre réponse est: {answer.toString()} - - {question.formattedGlobalFeedback &&
-
-
} - - - ) : ( - <> -
- ) => { - setAnswer([e.target.valueAsNumber]); - }} - inputProps={{ 'data-testid': 'number-input' }} - /> + + {({ showAnswer }) => ( +
+
+
- {question.formattedGlobalFeedback && showAnswer && ( -
-
-
+ {showAnswer ? ( + <> +
+ La bonne réponse est: + {correctAnswer}
+ + Votre réponse est: {actualAnswer.toString()} + + {question.formattedGlobalFeedback &&
+
+
} + + + ) : ( + <> +
+ ) => { + setActualAnswer([e.target.valueAsNumber]); + }} + inputProps={{ 'data-testid': 'number-input' }} + /> +
+ {question.formattedGlobalFeedback && showAnswer && ( +
+
+
+ )} + {submitAnswer && ( + + )} + )} - {handleOnSubmitAnswer && ( - - )} - + +
)} -
+ ); }; diff --git a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx index 700f315..eb6df29 100644 --- a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx @@ -1,74 +1,41 @@ import React from 'react'; import { Question } from 'gift-pegjs'; - import TrueFalseQuestionDisplay from './TrueFalseQuestionDisplay/TrueFalseQuestionDisplay'; import MultipleChoiceQuestionDisplay from './MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay'; import NumericalQuestionDisplay from './NumericalQuestionDisplay/NumericalQuestionDisplay'; import ShortAnswerQuestionDisplay from './ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay'; -import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; -// import useCheckMobileScreen from '../../services/useCheckMobileScreen'; - -interface QuestionProps { - question: Question; - handleOnSubmitAnswer?: (answer: AnswerType) => void; - showAnswer?: boolean; - answer?: AnswerType; - -} -const QuestionDisplay: React.FC = ({ - question, - handleOnSubmitAnswer, - showAnswer, - answer, -}) => { - // const isMobile = useCheckMobileScreen(); - // const imgWidth = useMemo(() => { - // return isMobile ? '100%' : '20%'; - // }, [isMobile]); +import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; +const QuestionDisplay: React.FC = () => { + const { questions, index } = useQuizContext(); + const question = questions[Number(index)].question as Question; let questionTypeComponent = null; + switch (question?.type) { case 'TF': questionTypeComponent = ( ); break; case 'MC': - + questionTypeComponent = ( ); break; case 'Numerical': if (question.choices) { - questionTypeComponent = ( - - ); + questionTypeComponent = ( + + ); } break; case 'Short': questionTypeComponent = ( ); break; diff --git a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx index 4b15e4d..d3e0fbc 100644 --- a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx @@ -4,82 +4,82 @@ import { Button, TextField } from '@mui/material'; import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { ShortAnswerQuestion } from 'gift-pegjs'; import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; +import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; +import { QuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; -interface Props { - question: ShortAnswerQuestion; - handleOnSubmitAnswer?: (answer: AnswerType) => void; - showAnswer?: boolean; - passedAnswer?: AnswerType; -} +const ShortAnswerQuestionDisplay: React.FC = () => { -const ShortAnswerQuestionDisplay: React.FC = (props) => { + const { questions, index, answer, submitAnswer } = useQuizContext(); + const [actualAnswer, setActualAnswer] = useState(answer || []); + const question = questions[Number(index)].question as ShortAnswerQuestion; - const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props; - const [answer, setAnswer] = useState(passedAnswer || []); - useEffect(() => { - if (passedAnswer !== undefined) { - setAnswer(passedAnswer); - } - }, [passedAnswer]); - console.log("Answer" , answer); + if (answer !== undefined) { + setActualAnswer(answer); + } + }, [answer]); + console.log("Answer", actualAnswer); return ( -
-
-
-
- {showAnswer ? ( - <> -
- - La bonne réponse est: - - {question.choices.map((choice) => ( -
- {choice.text} + + {({ showAnswer }) => ( +
+
+
+
+ {showAnswer ? ( + <> +
+ + La bonne réponse est: + + {question.choices.map((choice) => ( +
+ {choice.text} +
+ ))} +
+ + Votre réponse est: {actualAnswer} +
- ))} - - - Votre réponse est: {answer} - -
- {question.formattedGlobalFeedback &&
-
-
} - - ) : ( - <> -
- { - setAnswer([e.target.value]); - }} - disabled={showAnswer} - aria-label="short-answer-input" - /> -
- {handleOnSubmitAnswer && ( - + {question.formattedGlobalFeedback &&
+
+
} + + ) : ( + <> +
+ { + setActualAnswer([e.target.value]); + }} + disabled={showAnswer} + aria-label="short-answer-input" + /> +
+ {submitAnswer && ( + + )} + )} - +
)} -
+ ); }; diff --git a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx index 5e0abe0..343f5f9 100644 --- a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx @@ -4,51 +4,43 @@ import '../questionStyle.css'; import { Button } from '@mui/material'; import { TrueFalseQuestion } from 'gift-pegjs'; import { FormattedTextTemplate } from 'src/components/GiftTemplate/templates/TextTypeTemplate'; -import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; import { QuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; +import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; -interface Props { - question: TrueFalseQuestion; - handleOnSubmitAnswer?: (answer: AnswerType) => void; - // showAnswer?: boolean; - passedAnswer?: AnswerType; -} -const TrueFalseQuestionDisplay: React.FC = (props) => { - const { question, - // showAnswer, - handleOnSubmitAnswer, passedAnswer } = - props; +const TrueFalseQuestionDisplay: React.FC = () => { + + const { questions, index, answer, submitAnswer } = useQuizContext(); - const [answer, setAnswer] = useState(() => { - if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) { - return passedAnswer[0]; + const question = questions[Number(index)].question as TrueFalseQuestion; + + const [actualAnswer, setActualAnswer] = useState(() => { + if (answer && (answer[0] === true || answer[0] === false)) { + return answer[0]; } return undefined; }); let disableButton = false; - if (handleOnSubmitAnswer === undefined) { + if (submitAnswer === undefined) { disableButton = true; } - - useEffect(() => { - console.log("passedAnswer", passedAnswer); - if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) { - setAnswer(passedAnswer[0]); + console.log("passedAnswer", answer); + if (answer && (answer[0] === true || answer[0] === false)) { + setActualAnswer(answer[0]); } else { - setAnswer(undefined); + setActualAnswer(undefined); } - }, [passedAnswer, question.id]); + }, [answer, index]); const handleOnClickAnswer = (choice: boolean) => { - setAnswer(choice); + setActualAnswer(choice); }; - const selectedTrue = answer ? 'selected' : ''; - const selectedFalse = answer !== undefined && !answer ? 'selected' : ''; + const selectedTrue = actualAnswer ? 'selected' : ''; + const selectedFalse = actualAnswer !== undefined && !actualAnswer ? 'selected' : ''; return ( @@ -76,7 +68,7 @@ const TrueFalseQuestionDisplay: React.FC = (props) => {
V
Vrai
- {showAnswer && answer && question.trueFormattedFeedback && ( + {showAnswer && actualAnswer && question.trueFormattedFeedback && (
= (props) => {
F
Faux
- {showAnswer && !answer && question.falseFormattedFeedback && ( + {showAnswer && !actualAnswer && question.falseFormattedFeedback && (
= (props) => { />
)} - {!showAnswer && handleOnSubmitAnswer && ( + {!showAnswer && submitAnswer && ( diff --git a/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx b/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx index 1399881..076d9d8 100644 --- a/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx +++ b/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx @@ -1,32 +1,16 @@ // StudentModeQuiz.tsx import React, { useEffect, useState } from 'react'; -import QuestionComponent from '../QuestionsDisplay/QuestionDisplay'; +import QuestionDisplay from '../QuestionsDisplay/QuestionDisplay'; import '../../pages/Student/JoinRoom/joinRoom.css'; import { QuestionType } from '../../Types/QuestionType'; import { Button } from '@mui/material'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; -import { Question } from 'gift-pegjs'; -import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService'; -import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; -interface StudentModeQuizProps { - questions: QuestionType[]; - answers: AnswerSubmissionToBackendType[]; - submitAnswer: (_answer: AnswerType, _idQuestion: number) => void; - disconnectWebSocket: () => void; -} - -const StudentModeQuiz: React.FC = ({ - questions, - answers, - submitAnswer, - disconnectWebSocket -}) => { - const { setShowAnswer } = useQuizContext(); // Access setShowAnswer from context +const StudentModeQuiz: React.FC = () => { + const { questions, answers, setIsQuestionSent, disconnectWebSocket, setShowAnswer } = useQuizContext(); // Access setShowAnswer from context const [questionInfos, setQuestion] = useState(questions[0]); - const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false); const previousQuestion = () => { setQuestion(questions[Number(questionInfos.question?.id) - 2]); @@ -35,7 +19,7 @@ const StudentModeQuiz: React.FC = ({ useEffect(() => { const savedAnswer = answers[Number(questionInfos.question.id) - 1]?.answer; console.log(`StudentModeQuiz: useEffect: savedAnswer: ${savedAnswer}`); - setIsAnswerSubmitted(savedAnswer !== undefined); + setIsQuestionSent(savedAnswer !== undefined); setShowAnswer(savedAnswer !== undefined); // Update showAnswer in context }, [questionInfos.question, answers, setShowAnswer]); @@ -43,13 +27,6 @@ const StudentModeQuiz: React.FC = ({ setQuestion(questions[Number(questionInfos.question?.id)]); }; - const handleOnSubmitAnswer = (answer: AnswerType) => { - const idQuestion = Number(questionInfos.question.id) || -1; - submitAnswer(answer, idQuestion); - setIsAnswerSubmitted(true); - setShowAnswer(true); // Update showAnswer in context when an answer is submitted - }; - return (
@@ -63,12 +40,7 @@ const StudentModeQuiz: React.FC = ({
- +
) : ( - + )} Rétroaction @@ -111,12 +93,8 @@ const TeacherModeQuiz: React.FC = ({ >Question :
- diff --git a/client/src/pages/Student/JoinRoom/JoinRoom.tsx b/client/src/pages/Student/JoinRoom/JoinRoom.tsx index 4ae4819..4fe7c5f 100644 --- a/client/src/pages/Student/JoinRoom/JoinRoom.tsx +++ b/client/src/pages/Student/JoinRoom/JoinRoom.tsx @@ -15,23 +15,38 @@ import LoadingButton from '@mui/lab/LoadingButton'; import LoginContainer from 'src/components/LoginContainer/LoginContainer' -import ApiService from '../../../services/ApiService' -import { QuizProvider } from './QuizProvider'; +import { useQuizContext } from './QuizContext'; + export type AnswerType = Array; const JoinRoom: React.FC = () => { - const [roomName, setRoomName] = useState(''); - const [username, setUsername] = useState(ApiService.getUsername()); + const { + setQuestions, + setAnswers, + questions, + username, + setUsername, + setDisconnectWebSocket, + roomName, + setRoomName, + index, + updateIndex, + + } = useQuizContext(); + const [socket, setSocket] = useState(null); const [isWaitingForTeacher, setIsWaitingForTeacher] = useState(false); - const [question, setQuestion] = useState(); const [quizMode, setQuizMode] = useState(); - const [questions, setQuestions] = useState([]); - const [answers, setAnswers] = useState([]); const [connectionError, setConnectionError] = useState(''); const [isConnecting, setIsConnecting] = useState(false); + + useEffect(() => { + // Set the disconnectWebSocket function in the context + setDisconnectWebSocket(() => disconnect); + }, [socket]); + useEffect(() => { handleCreateSocket(); return () => { @@ -42,6 +57,7 @@ const JoinRoom: React.FC = () => { useEffect(() => { console.log(`JoinRoom: useEffect: questions: ${JSON.stringify(questions)}`); setAnswers(questions ? Array(questions.length).fill({} as AnswerSubmissionToBackendType) : []); + console.log(`JoinRoom: useEffect: answers: ${JSON.stringify(questions)}`); }, [questions]); @@ -58,13 +74,13 @@ const JoinRoom: React.FC = () => { console.log('JoinRoom: on(next-question): Received next-question:', question); setQuizMode('teacher'); setIsWaitingForTeacher(false); - setQuestion(question); + updateIndex(Number(question.question.id)); }); socket.on('launch-teacher-mode', (questions: QuestionType[]) => { console.log('on(launch-teacher-mode): Received launch-teacher-mode:', questions); setQuizMode('teacher'); setIsWaitingForTeacher(true); - setQuestions([]); // clear out from last time (in case quiz is repeated) + updateIndex(null); setQuestions(questions); // wait for next-question }); @@ -75,7 +91,7 @@ const JoinRoom: React.FC = () => { setIsWaitingForTeacher(false); setQuestions([]); // clear out from last time (in case quiz is repeated) setQuestions(questions); - setQuestion(questions[0]); + updateIndex(0); }); socket.on('end-quiz', () => { disconnect(); @@ -102,10 +118,10 @@ const JoinRoom: React.FC = () => { }; const disconnect = () => { -// localStorage.clear(); + // localStorage.clear(); webSocketService.disconnect(); setSocket(null); - setQuestion(undefined); + updateIndex(null); setIsWaitingForTeacher(false); setQuizMode(''); setRoomName(''); @@ -127,25 +143,6 @@ const JoinRoom: React.FC = () => { } }; - const handleOnSubmitAnswer = (answer: AnswerType, idQuestion: number) => { - console.info(`JoinRoom: handleOnSubmitAnswer: answer: ${answer}, idQuestion: ${idQuestion}`); - const answerData: AnswerSubmissionToBackendType = { - roomName: roomName, - answer: answer, - username: username, - idQuestion: idQuestion - }; - // localStorage.setItem(`Answer${idQuestion}`, JSON.stringify(answer)); - setAnswers((prevAnswers) => { - console.log(`JoinRoom: handleOnSubmitAnswer: prevAnswers: ${JSON.stringify(prevAnswers)}`); - const newAnswers = [...prevAnswers]; // Create a copy of the previous answers array - newAnswers[idQuestion - 1] = answerData; // Update the specific answer - return newAnswers; // Return the new array - }); - console.log(`JoinRoom: handleOnSubmitAnswer: answers: ${JSON.stringify(answers)}`); - webSocketService.submitAnswer(answerData); - }; - const handleReturnKey = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && username && roomName) { handleSocket(); @@ -176,28 +173,16 @@ const JoinRoom: React.FC = () => { } switch (quizMode) { + + case 'student': return ( - - - + ); case 'teacher': return ( - question && ( - - - + index != null && ( + ) ); default: diff --git a/client/src/pages/Student/JoinRoom/QuizContext.tsx b/client/src/pages/Student/JoinRoom/QuizContext.tsx index fc882be..cea3c0e 100644 --- a/client/src/pages/Student/JoinRoom/QuizContext.tsx +++ b/client/src/pages/Student/JoinRoom/QuizContext.tsx @@ -1,19 +1,55 @@ import React, { Dispatch, SetStateAction, useContext } from 'react'; - +import { QuestionType } from '../../../Types/QuestionType'; +import { AnswerSubmissionToBackendType } from '../../../services/WebsocketService'; +import { AnswerType } from './JoinRoom'; export const QuizContext = React.createContext<{ showAnswer: boolean; setShowAnswer: Dispatch>; - }>({ + questions: QuestionType[]; + setQuestions: Dispatch>; + answers: AnswerSubmissionToBackendType[]; + setAnswers: Dispatch>; + answer : AnswerType; + setAnswer: Dispatch>; + index: number | null; // Add index to the context + updateIndex: (questionId: number | null) => void; // Add a function to update the index + submitAnswer: (answer: AnswerType, idQuestion?: number) => void; // Updated submitAnswer signature + isQuestionSent: boolean; + setIsQuestionSent: Dispatch>; + roomName: string; + setRoomName: Dispatch>; + username: string; // Username of the user + setUsername: Dispatch>; // Setter for username + disconnectWebSocket: () => void; // Function to disconnect the WebSocket + setDisconnectWebSocket: Dispatch void>>; // Setter for disconnectWebSocket + +}>({ showAnswer: false, setShowAnswer: () => {}, + questions: [], + setQuestions: () => {}, + answers: [], + setAnswers: () => {}, + answer: [], // Default value for answer + setAnswer: () => {}, // Default no-op function + index: null, // Default value for index + updateIndex: () => {}, // Default no-op function + submitAnswer: () => {}, // Default no-op function + isQuestionSent: false, + setIsQuestionSent: () => {}, + username: '', // Default value for username + setUsername: () => {}, // Default no-op function + roomName: '', // Default value for roomName + setRoomName: () => {}, // Default no-op function + disconnectWebSocket: () => {}, // Default no-op function + setDisconnectWebSocket: () => {}, // Default no-op function +}); - }); - export const useQuizContext = () => { - const context = useContext(QuizContext); - if (!context) { - throw new Error('useQuizContext must be used within a QuizProvider'); - } - return context; -}; + const context = useContext(QuizContext); + if (!context) { + throw new Error('useQuizContext must be used within a QuizProvider'); + } + return context; +}; \ No newline at end of file diff --git a/client/src/pages/Student/JoinRoom/QuizProvider.tsx b/client/src/pages/Student/JoinRoom/QuizProvider.tsx index 152e495..073c024 100644 --- a/client/src/pages/Student/JoinRoom/QuizProvider.tsx +++ b/client/src/pages/Student/JoinRoom/QuizProvider.tsx @@ -1,19 +1,96 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { QuizContext } from './QuizContext'; +import { QuestionType } from '../../../Types/QuestionType'; +import { AnswerSubmissionToBackendType } from '../../../services/WebsocketService'; +import { AnswerType } from './JoinRoom'; +import webSocketService from '../../../services/WebsocketService'; +import ApiService from '../../../services/ApiService' + export const QuizProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + // State for showing answers const [showAnswer, setShowAnswer] = useState(false); - console.log('QuizProvider: showAnswer:', showAnswer); + // State for managing the list of questions + const [questions, setQuestions] = useState([]); - useEffect(() => { - console.log('QuizProvider: showAnswer:', showAnswer); - }, [showAnswer]); + // State for managing the list of answers + const [answers, setAnswers] = useState([]); + + // Calculate the index based on the current question's ID + const [index, setIndex] = useState(null); + + const [answer, setAnswer] = useState([]); // Initialize answer as an empty array + + const [isQuestionSent, setIsQuestionSent] = useState(false); + + const [username, setUsername] = useState(ApiService.getUsername()); + + const [roomName, setRoomName] = useState(''); // Add roomName state + + const [disconnectWebSocket, setDisconnectWebSocket] = useState<() => void>(() => () => {}); + + + const updateIndex = (questionId: number | null) => { + const questionIndex = questions.findIndex((q) => q.question.id === String(questionId)); + setIndex(questionIndex >= 0 ? questionIndex : null); + }; + + // Function to handle answer submission + const submitAnswer = (answer: AnswerType, idQuestion?: number) => { + if (!idQuestion) { + setAnswer(answer); + setIsQuestionSent(true); + console.log('index',index); + } else { + console.info(`QuizProvider: submitAnswer: answer: ${answer}, idQuestion: ${idQuestion}`); + const answerData: AnswerSubmissionToBackendType = { + roomName: roomName, + answer: answer, + username: username, + idQuestion: idQuestion, + }; + + // Update the answers state + setAnswers((prevAnswers) => { + const newAnswers = [...prevAnswers]; // Create a copy of the previous answers array + newAnswers[idQuestion - 1] = answerData; // Update the specific answer + return newAnswers; // Return the new array + }); + + console.log(`QuizProvider: submitAnswer: answers: ${JSON.stringify(answers)}`); + + // Submit the answer to the WebSocket service + webSocketService.submitAnswer(answerData); + } + }; return ( - + {children} ); -}; +}; \ No newline at end of file diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 8c3d699..75bc570 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { Socket } from 'socket.io-client'; -import { BaseQuestion, parse, Question } from 'gift-pegjs'; +import { BaseQuestion, parse /*Question*/ } from 'gift-pegjs'; import LiveResultsComponent from 'src/components/LiveResults/LiveResults'; import webSocketService, { AnswerReceptionFromBackendType @@ -20,17 +20,24 @@ import ApiService from '../../../services/ApiService'; import { QuestionType } from 'src/Types/QuestionType'; import { Button } from '@mui/material'; import { checkIfIsCorrect } from './useRooms'; +import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; const ManageRoom: React.FC = () => { + const { + questions, + setQuestions, + index, + updateIndex, + } = useQuizContext(); + + const navigate = useNavigate(); const [socket, setSocket] = useState(null); const [students, setStudents] = useState([]); const { quizId = '', roomName = '' } = useParams<{ quizId: string, roomName: string }>(); - const [quizQuestions, setQuizQuestions] = useState(); const [quiz, setQuiz] = useState(null); const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher'); const [connectingError, setConnectingError] = useState(''); - const [currentQuestion, setCurrentQuestion] = useState(undefined); const [quizStarted, setQuizStarted] = useState(false); const [formattedRoomName, setFormattedRoomName] = useState(""); const [newlyConnectedUser, setNewlyConnectedUser] = useState(null); @@ -51,12 +58,12 @@ const ManageRoom: React.FC = () => { if (quizMode === 'teacher') { webSocketService.nextQuestion({ roomName: formattedRoomName, - questions: quizQuestions, - questionIndex: Number(currentQuestion?.question.id) - 1, + questions: questions, + questionIndex: Number(index), isLaunch: true // started late }); } else if (quizMode === 'student') { - webSocketService.launchStudentModeQuiz(formattedRoomName, quizQuestions); + webSocketService.launchStudentModeQuiz(formattedRoomName, questions); } else { console.error('Invalid quiz mode:', quizMode); } @@ -126,8 +133,8 @@ const ManageRoom: React.FC = () => { webSocketService.endQuiz(formattedRoomName); webSocketService.disconnect(); setSocket(null); - setQuizQuestions(undefined); - setCurrentQuestion(undefined); + setQuestions([]); + updateIndex(null); setStudents(new Array()); } }; @@ -187,7 +194,7 @@ const ManageRoom: React.FC = () => { console.log( `Received answer from ${username} for question ${idQuestion}: ${answer}` ); - if (!quizQuestions) { + if (!questions) { console.log('Quiz questions not found (cannot update answers without them).'); return; } @@ -218,7 +225,7 @@ const ManageRoom: React.FC = () => { isCorrect: checkIfIsCorrect( answer, idQuestion, - quizQuestions! + questions! ) } : ans; @@ -227,7 +234,7 @@ const ManageRoom: React.FC = () => { const newAnswer = { idQuestion, answer, - isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!) + isCorrect: checkIfIsCorrect(answer, idQuestion, questions!) }; updatedAnswers = [...student.answers, newAnswer]; } @@ -243,30 +250,30 @@ const ManageRoom: React.FC = () => { }); setSocket(socket); } - }, [socket, currentQuestion, quizQuestions]); + }, [socket, index, questions]); const nextQuestion = () => { - if (!quizQuestions || !currentQuestion || !quiz?.content) return; + if (!questions || !index || !quiz?.content) return; - const nextQuestionIndex = Number(currentQuestion?.question.id); + const nextQuestionIndex = index; - if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return; + if (nextQuestionIndex === undefined || nextQuestionIndex > questions.length - 1) return; - setCurrentQuestion(quizQuestions[nextQuestionIndex]); + updateIndex(nextQuestionIndex); webSocketService.nextQuestion({roomName: formattedRoomName, - questions: quizQuestions, + questions: questions, questionIndex: nextQuestionIndex, isLaunch: false}); }; const previousQuestion = () => { - if (!quizQuestions || !currentQuestion || !quiz?.content) return; + if (!questions || !index || !quiz?.content) return; - const prevQuestionIndex = Number(currentQuestion?.question.id) - 2; // -2 because question.id starts at index 1 + const prevQuestionIndex = index - 1; // -2 because question.id starts at index 1 if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return; - setCurrentQuestion(quizQuestions[prevQuestionIndex]); - webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: prevQuestionIndex, isLaunch: false}); + updateIndex(prevQuestionIndex); + webSocketService.nextQuestion({roomName: formattedRoomName, questions: questions, questionIndex: prevQuestionIndex, isLaunch: false}); }; const initializeQuizQuestion = () => { @@ -280,33 +287,33 @@ const ManageRoom: React.FC = () => { }); if (parsedQuestions.length === 0) return null; - setQuizQuestions(parsedQuestions); + setQuestions(parsedQuestions); return parsedQuestions; }; const launchTeacherMode = () => { - const quizQuestions = initializeQuizQuestion(); - console.log('launchTeacherMode - quizQuestions:', quizQuestions); + const questions = initializeQuizQuestion(); + console.log('launchTeacherMode - quizQuestions:', questions); - if (!quizQuestions) { + if (!questions) { console.log('Error launching quiz (launchTeacherMode). No questions found.'); return; } - setCurrentQuestion(quizQuestions[0]); - webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: 0, isLaunch: true}); + updateIndex(0); + webSocketService.nextQuestion({roomName: formattedRoomName, questions: questions, questionIndex: 0, isLaunch: true}); }; const launchStudentMode = () => { - const quizQuestions = initializeQuizQuestion(); - console.log('launchStudentMode - quizQuestions:', quizQuestions); + const questions = initializeQuizQuestion(); + console.log('launchStudentMode - quizQuestions:', questions); - if (!quizQuestions) { + if (!questions) { console.log('Error launching quiz (launchStudentMode). No questions found.'); return; } - setQuizQuestions(quizQuestions); - webSocketService.launchStudentModeQuiz(formattedRoomName, quizQuestions); + setQuestions(questions); + webSocketService.launchStudentModeQuiz(formattedRoomName, questions); }; const launchQuiz = () => { @@ -328,10 +335,10 @@ const ManageRoom: React.FC = () => { }; const showSelectedQuestion = (questionIndex: number) => { - if (quiz?.content && quizQuestions) { - setCurrentQuestion(quizQuestions[questionIndex]); + if (quiz?.content && questions) { + updateIndex(questionIndex); if (quizMode === 'teacher') { - webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex, isLaunch: false}); + webSocketService.nextQuestion({roomName: formattedRoomName, questions: questions, questionIndex, isLaunch: false}); } } }; @@ -398,13 +405,13 @@ const ManageRoom: React.FC = () => { {/* the following breaks the css (if 'room' classes are nested) */}
- {quizQuestions ? ( + {questions.length > 0 ? (
{quiz?.title}
- {!isNaN(Number(currentQuestion?.question.id)) && ( + {index && ( - Question {Number(currentQuestion?.question.id)}/ - {quizQuestions?.length} + Question {index+1}/ + {questions?.length} )} @@ -421,18 +428,14 @@ const ManageRoom: React.FC = () => {
- {currentQuestion && ( - + {index && ( + )} @@ -448,7 +451,7 @@ const ManageRoom: React.FC = () => { @@ -458,8 +461,8 @@ const ManageRoom: React.FC = () => { onClick={nextQuestion} variant="contained" disabled={ - Number(currentQuestion?.question.id) >= - quizQuestions.length + index !== null && + index >= questions.length } > Prochaine question