Compare commits

..

No commits in common. "6d714d1d77fcfd542afe8de0f5e48e01e674db1e" and "128ab6662e3a2d0ed08076b5b4adf9e9b8d34e90" have entirely different histories.

12 changed files with 484 additions and 507 deletions

View file

@ -13,7 +13,6 @@ import ResetPassword from './pages/AuthManager/providers/SimpleLogin/ResetPasswo
import ManageRoom from './pages/Teacher/ManageRoom/ManageRoom'; import ManageRoom from './pages/Teacher/ManageRoom/ManageRoom';
import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz'; import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz';
// Pages espace étudiant // Pages espace étudiant
import JoinRoom from './pages/Student/JoinRoom/JoinRoom'; import JoinRoom from './pages/Student/JoinRoom/JoinRoom';
@ -26,7 +25,6 @@ import Footer from './components/Footer/Footer';
import ApiService from './services/ApiService'; import ApiService from './services/ApiService';
import OAuthCallback from './pages/AuthManager/callback/AuthCallback'; import OAuthCallback from './pages/AuthManager/callback/AuthCallback';
import { QuizProvider } from './pages/Student/JoinRoom/QuizProvider';
const App: React.FC = () => { const App: React.FC = () => {
const [isAuthenticated, setIsAuthenticated] = useState(ApiService.isLoggedIn()); const [isAuthenticated, setIsAuthenticated] = useState(ApiService.isLoggedIn());
@ -80,13 +78,13 @@ const App: React.FC = () => {
/> />
<Route <Route
path="/teacher/manage-room/:quizId/:roomName" path="/teacher/manage-room/:quizId/:roomName"
element={isTeacherAuthenticated ? <QuizProvider><ManageRoom /></QuizProvider> : <Navigate to="/login" />} element={isTeacherAuthenticated ? <ManageRoom /> : <Navigate to="/login" />}
/> />
{/* Pages espace étudiant */} {/* Pages espace étudiant */}
<Route <Route
path="/student/join-room" path="/student/join-room"
element={( !isRoomRequireAuthentication || isAuthenticated ) ? <QuizProvider><JoinRoom /></QuizProvider> : <Navigate to="/login" />} element={( !isRoomRequireAuthentication || isAuthenticated ) ? <JoinRoom /> : <Navigate to="/login" />}
/> />
{/* Pages authentification */} {/* Pages authentification */}

View file

@ -5,52 +5,55 @@ import { Button } from '@mui/material';
import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate';
import { MultipleChoiceQuestion } from 'gift-pegjs'; import { MultipleChoiceQuestion } from 'gift-pegjs';
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext';
import { QuizContext } from 'src/pages/Student/JoinRoom/QuizContext';
const MultipleChoiceQuestionDisplay: React.FC = () => { interface Props {
const { questions, index, answer, submitAnswer } = useQuizContext(); question: MultipleChoiceQuestion;
console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(answer)); handleOnSubmitAnswer?: (answer: AnswerType) => void;
showAnswer?: boolean;
passedAnswer?: AnswerType;
}
const question = questions[Number(index)].question as MultipleChoiceQuestion; const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props;
console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(passedAnswer));
const [actualAnswer, setActualAnswer] = useState<AnswerType>(() => { const [answer, setAnswer] = useState<AnswerType>(() => {
if (answer && answer.length > 0) { if (passedAnswer && passedAnswer.length > 0) {
return answer; return passedAnswer;
} }
return []; return [];
}); });
let disableButton = false; let disableButton = false;
if (submitAnswer === undefined) { if (handleOnSubmitAnswer === undefined) {
disableButton = true; disableButton = true;
} }
useEffect(() => { useEffect(() => {
console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(answer)); console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(passedAnswer));
if (answer !== undefined) { if (passedAnswer !== undefined) {
setActualAnswer(answer); setAnswer(passedAnswer);
} else { } else {
setActualAnswer([]); setAnswer([]);
} }
}, [answer, index]); }, [passedAnswer, question.id]);
const handleOnClickAnswer = (choice: string) => { const handleOnClickAnswer = (choice: string) => {
setActualAnswer((answer) => { setAnswer((prevAnswer) => {
console.log(`handleOnClickAnswer -- setAnswer(): prevAnswer: ${answer}, choice: ${choice}`); console.log(`handleOnClickAnswer -- setAnswer(): prevAnswer: ${prevAnswer}, choice: ${choice}`);
const correctAnswersCount = question.choices.filter((c) => c.isCorrect).length; const correctAnswersCount = question.choices.filter((c) => c.isCorrect).length;
if (correctAnswersCount === 1) { if (correctAnswersCount === 1) {
// If only one correct answer, replace the current selection // If only one correct answer, replace the current selection
return answer.includes(choice) ? [] : [choice]; return prevAnswer.includes(choice) ? [] : [choice];
} else { } else {
// Allow multiple selections if there are multiple correct answers // Allow multiple selections if there are multiple correct answers
if (answer.includes(choice)) { if (prevAnswer.includes(choice)) {
// Remove the choice if it's already selected // Remove the choice if it's already selected
return answer.filter((selected) => selected !== choice); return prevAnswer.filter((selected) => selected !== choice);
} else { } else {
// Add the choice if it's not already selected // Add the choice if it's not already selected
return [...answer, choice]; return [...prevAnswer, choice];
} }
} }
}); });
@ -60,74 +63,70 @@ const MultipleChoiceQuestionDisplay: React.FC = () => {
const alphabet = alpha.map((x) => String.fromCharCode(x)); const alphabet = alpha.map((x) => String.fromCharCode(x));
return ( return (
<QuizContext.Consumer> <div className="question-container">
{({ showAnswer }) => ( <div className="question content">
<div className="question-container"> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
<div className="question content"> </div>
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} /> <div className="choices-wrapper mb-1">
</div> {question.choices.map((choice, i) => {
<div className="choices-wrapper mb-1"> console.log(`answer: ${answer}, choice: ${choice.formattedText.text}`);
{question.choices.map((choice, i) => { const selected = answer.includes(choice.formattedText.text) ? 'selected' : '';
console.log(`answer: ${actualAnswer}, choice: ${choice.formattedText.text}`); return (
const selected = actualAnswer.includes(choice.formattedText.text) ? 'selected' : ''; <div key={choice.formattedText.text + i} className="choice-container">
return ( <Button
<div key={choice.formattedText.text + i} className="choice-container"> variant="text"
<Button className="button-wrapper"
variant="text" disabled={disableButton}
className="button-wrapper" onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)}
disabled={disableButton} >
onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)} {showAnswer ? (
> <div>{choice.isCorrect ? '✅' : '❌'}</div>
{showAnswer ? ( ) : (
<div>{choice.isCorrect ? '✅' : '❌'}</div> ''
) : ( )}
'' <div className={`circle ${selected}`}>{alphabet[i]}</div>
)} <div className={`answer-text ${selected}`}>
<div className={`circle ${selected}`}>{alphabet[i]}</div> <div
<div className={`answer-text ${selected}`}> dangerouslySetInnerHTML={{
<div __html: FormattedTextTemplate(choice.formattedText),
dangerouslySetInnerHTML={{ }}
__html: FormattedTextTemplate(choice.formattedText), />
}}
/>
</div>
{choice.formattedFeedback && showAnswer && (
<div className="feedback-container mb-1 mt-1/2">
<div
dangerouslySetInnerHTML={{
__html: FormattedTextTemplate(choice.formattedFeedback),
}}
/>
</div>
)}
</Button>
</div> </div>
); {choice.formattedFeedback && showAnswer && (
})} <div className="feedback-container mb-1 mt-1/2">
</div> <div
{question.formattedGlobalFeedback && showAnswer && ( dangerouslySetInnerHTML={{
<div className="global-feedback mb-2"> __html: FormattedTextTemplate(choice.formattedFeedback),
<div }}
dangerouslySetInnerHTML={{ />
__html: FormattedTextTemplate(question.formattedGlobalFeedback), </div>
}} )}
/> </Button>
</div> </div>
)} );
{!showAnswer && submitAnswer && ( })}
<Button </div>
variant="contained" {question.formattedGlobalFeedback && showAnswer && (
onClick={() => <div className="global-feedback mb-2">
actualAnswer.length > 0 && submitAnswer && submitAnswer(actualAnswer) <div
} dangerouslySetInnerHTML={{
disabled={answer.length === 0} __html: FormattedTextTemplate(question.formattedGlobalFeedback),
> }}
Répondre />
</Button>
)}
</div> </div>
)} )}
</QuizContext.Consumer> {!showAnswer && handleOnSubmitAnswer && (
<Button
variant="contained"
onClick={() =>
answer.length > 0 && handleOnSubmitAnswer && handleOnSubmitAnswer(answer)
}
disabled={answer.length === 0}
>
Répondre
</Button>
)}
</div>
); );
}; };

View file

@ -6,24 +6,27 @@ import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemp
import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs';
import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards'; import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards';
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext';
import { QuizContext } from 'src/pages/Student/JoinRoom/QuizContext';
const NumericalQuestionDisplay: React.FC = () => { interface Props {
const { questions, index, answer, submitAnswer } = useQuizContext(); question: NumericalQuestion;
handleOnSubmitAnswer?: (answer: AnswerType) => void;
showAnswer?: boolean;
passedAnswer?: AnswerType;
}
const question = questions[Number(index)].question as NumericalQuestion; const NumericalQuestionDisplay: React.FC<Props> = (props) => {
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } =
const [actualAnswer, setActualAnswer] = useState<AnswerType>(answer || []); props;
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
const correctAnswers = question.choices; const correctAnswers = question.choices;
let correctAnswer = ''; let correctAnswer = '';
useEffect(() => { useEffect(() => {
if (answer !== null && answer !== undefined) { if (passedAnswer !== null && passedAnswer !== undefined) {
setActualAnswer(answer); setAnswer(passedAnswer);
} }
}, [answer]); }, [passedAnswer]);
//const isSingleAnswer = correctAnswers.length === 1; //const isSingleAnswer = correctAnswers.length === 1;
if (isSimpleNumericalAnswer(correctAnswers[0])) { if (isSimpleNumericalAnswer(correctAnswers[0])) {
@ -41,62 +44,57 @@ const NumericalQuestionDisplay: React.FC = () => {
} }
return ( return (
<QuizContext.Consumer> <div className="question-wrapper">
{({ showAnswer }) => ( <div>
<div className="question-wrapper"> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
<div> </div>
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} /> {showAnswer ? (
<>
<div className="correct-answer-text mb-2">
<strong>La bonne réponse est: </strong>
{correctAnswer}</div>
<span>
<strong>Votre réponse est: </strong>{answer.toString()}
</span>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
</div>}
</>
) : (
<>
<div className="answer-wrapper mb-1">
<TextField
type="number"
id={question.formattedStem.text}
name={question.formattedStem.text}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAnswer([e.target.valueAsNumber]);
}}
inputProps={{ 'data-testid': 'number-input' }}
/>
</div> </div>
{showAnswer ? ( {question.formattedGlobalFeedback && showAnswer && (
<> <div className="global-feedback mb-2">
<div className="correct-answer-text mb-2"> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
<strong>La bonne réponse est: </strong> </div>
{correctAnswer}</div>
<span>
<strong>Votre réponse est: </strong>{actualAnswer.toString()}
</span>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
</div>}
</>
) : (
<>
<div className="answer-wrapper mb-1">
<TextField
type="number"
id={question.formattedStem.text}
name={question.formattedStem.text}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setActualAnswer([e.target.valueAsNumber]);
}}
inputProps={{ 'data-testid': 'number-input' }}
/>
</div>
{question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
</div>
)}
{submitAnswer && (
<Button
variant="contained"
onClick={() =>
actualAnswer !== undefined &&
submitAnswer &&
submitAnswer(actualAnswer)
}
disabled={actualAnswer === undefined || actualAnswer === null || isNaN(actualAnswer[0] as number)}
>
Répondre
</Button>
)}
</>
)} )}
{handleOnSubmitAnswer && (
</div> <Button
variant="contained"
onClick={() =>
answer !== undefined &&
handleOnSubmitAnswer &&
handleOnSubmitAnswer(answer)
}
disabled={answer === undefined || answer === null || isNaN(answer[0] as number)}
>
Répondre
</Button>
)}
</>
)} )}
</QuizContext.Consumer> </div>
); );
}; };

View file

@ -1,41 +1,74 @@
import React from 'react'; import React from 'react';
import { Question } from 'gift-pegjs'; import { Question } from 'gift-pegjs';
import TrueFalseQuestionDisplay from './TrueFalseQuestionDisplay/TrueFalseQuestionDisplay'; import TrueFalseQuestionDisplay from './TrueFalseQuestionDisplay/TrueFalseQuestionDisplay';
import MultipleChoiceQuestionDisplay from './MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay'; import MultipleChoiceQuestionDisplay from './MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay';
import NumericalQuestionDisplay from './NumericalQuestionDisplay/NumericalQuestionDisplay'; import NumericalQuestionDisplay from './NumericalQuestionDisplay/NumericalQuestionDisplay';
import ShortAnswerQuestionDisplay from './ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay'; import ShortAnswerQuestionDisplay from './ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay';
import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; 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<QuestionProps> = ({
question,
handleOnSubmitAnswer,
showAnswer,
answer,
}) => {
// const isMobile = useCheckMobileScreen();
// const imgWidth = useMemo(() => {
// return isMobile ? '100%' : '20%';
// }, [isMobile]);
const QuestionDisplay: React.FC = () => {
const { questions, index } = useQuizContext();
const question = questions[Number(index)].question as Question;
let questionTypeComponent = null; let questionTypeComponent = null;
switch (question?.type) { switch (question?.type) {
case 'TF': case 'TF':
questionTypeComponent = ( questionTypeComponent = (
<TrueFalseQuestionDisplay <TrueFalseQuestionDisplay
question={question}
handleOnSubmitAnswer={handleOnSubmitAnswer}
// showAnswer={showAnswer}
passedAnswer={answer}
/> />
); );
break; break;
case 'MC': case 'MC':
questionTypeComponent = ( questionTypeComponent = (
<MultipleChoiceQuestionDisplay <MultipleChoiceQuestionDisplay
question={question}
handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswer}
passedAnswer={answer}
/> />
); );
break; break;
case 'Numerical': case 'Numerical':
if (question.choices) { if (question.choices) {
questionTypeComponent = ( questionTypeComponent = (
<NumericalQuestionDisplay <NumericalQuestionDisplay
/> question={question}
); handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswer}
passedAnswer={answer}
/>
);
} }
break; break;
case 'Short': case 'Short':
questionTypeComponent = ( questionTypeComponent = (
<ShortAnswerQuestionDisplay <ShortAnswerQuestionDisplay
question={question}
handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswer}
passedAnswer={answer}
/> />
); );
break; break;

View file

@ -4,82 +4,82 @@ import { Button, TextField } from '@mui/material';
import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate';
import { ShortAnswerQuestion } from 'gift-pegjs'; import { ShortAnswerQuestion } from 'gift-pegjs';
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; 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 { questions, index, answer, submitAnswer } = useQuizContext(); const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
const [actualAnswer, setActualAnswer] = useState<AnswerType>(answer || []);
const question = questions[Number(index)].question as ShortAnswerQuestion;
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props;
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
useEffect(() => { useEffect(() => {
if (answer !== undefined) { if (passedAnswer !== undefined) {
setActualAnswer(answer); setAnswer(passedAnswer);
} }
}, [answer]); }, [passedAnswer]);
console.log("Answer", actualAnswer); console.log("Answer" , answer);
return ( return (
<QuizContext.Consumer> <div className="question-wrapper">
{({ showAnswer }) => ( <div className="question content">
<div className="question-wrapper"> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
<div className="question content"> </div>
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} /> {showAnswer ? (
<>
<div className="correct-answer-text mb-1">
<span>
<strong>La bonne réponse est: </strong>
{question.choices.map((choice) => (
<div key={choice.text} className="mb-1">
{choice.text}
</div>
))}
</span>
<span>
<strong>Votre réponse est: </strong>{answer}
</span>
</div> </div>
{showAnswer ? ( {question.formattedGlobalFeedback && <div className="global-feedback mb-2">
<> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
<div className="correct-answer-text mb-1"> </div>}
<span> </>
<strong>La bonne réponse est: </strong> ) : (
<>
{question.choices.map((choice) => ( <div className="answer-wrapper mb-1">
<div key={choice.text} className="mb-1"> <TextField
{choice.text} type="text"
</div> id={question.formattedStem.text}
))} name={question.formattedStem.text}
</span> onChange={(e) => {
<span> setAnswer([e.target.value]);
<strong>Votre réponse est: </strong>{actualAnswer} }}
</span> disabled={showAnswer}
</div> aria-label="short-answer-input"
{question.formattedGlobalFeedback && <div className="global-feedback mb-2"> />
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} /> </div>
</div>} {handleOnSubmitAnswer && (
</> <Button
) : ( variant="contained"
<> onClick={() =>
<div className="answer-wrapper mb-1"> answer !== undefined &&
<TextField handleOnSubmitAnswer &&
type="text" handleOnSubmitAnswer(answer)
id={question.formattedStem.text} }
name={question.formattedStem.text} disabled={answer === null || answer === undefined || answer.length === 0}
onChange={(e) => { >
setActualAnswer([e.target.value]); Répondre
}} </Button>
disabled={showAnswer}
aria-label="short-answer-input"
/>
</div>
{submitAnswer && (
<Button
variant="contained"
onClick={() =>
actualAnswer !== undefined &&
submitAnswer &&
submitAnswer(actualAnswer)
}
disabled={actualAnswer === null || actualAnswer === undefined || actualAnswer.length === 0}
>
Répondre
</Button>
)}
</>
)} )}
</div> </>
)} )}
</QuizContext.Consumer> </div>
); );
}; };

View file

@ -4,43 +4,51 @@ import '../questionStyle.css';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import { TrueFalseQuestion } from 'gift-pegjs'; import { TrueFalseQuestion } from 'gift-pegjs';
import { FormattedTextTemplate } from 'src/components/GiftTemplate/templates/TextTypeTemplate'; 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 { 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 = () => { const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
const { question,
const { questions, index, answer, submitAnswer } = useQuizContext(); // showAnswer,
handleOnSubmitAnswer, passedAnswer } =
props;
const question = questions[Number(index)].question as TrueFalseQuestion; const [answer, setAnswer] = useState<boolean | undefined>(() => {
if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) {
const [actualAnswer, setActualAnswer] = useState<boolean | undefined>(() => { return passedAnswer[0];
if (answer && (answer[0] === true || answer[0] === false)) {
return answer[0];
} }
return undefined; return undefined;
}); });
let disableButton = false; let disableButton = false;
if (submitAnswer === undefined) { if (handleOnSubmitAnswer === undefined) {
disableButton = true; disableButton = true;
} }
useEffect(() => { useEffect(() => {
console.log("passedAnswer", answer); console.log("passedAnswer", passedAnswer);
if (answer && (answer[0] === true || answer[0] === false)) { if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) {
setActualAnswer(answer[0]); setAnswer(passedAnswer[0]);
} else { } else {
setActualAnswer(undefined); setAnswer(undefined);
} }
}, [answer, index]); }, [passedAnswer, question.id]);
const handleOnClickAnswer = (choice: boolean) => { const handleOnClickAnswer = (choice: boolean) => {
setActualAnswer(choice); setAnswer(choice);
}; };
const selectedTrue = actualAnswer ? 'selected' : ''; const selectedTrue = answer ? 'selected' : '';
const selectedFalse = actualAnswer !== undefined && !actualAnswer ? 'selected' : ''; const selectedFalse = answer !== undefined && !answer ? 'selected' : '';
return ( return (
<QuizContext.Consumer> <QuizContext.Consumer>
@ -68,7 +76,7 @@ const TrueFalseQuestionDisplay: React.FC = () => {
<div className={`circle ${selectedTrue}`}>V</div> <div className={`circle ${selectedTrue}`}>V</div>
<div className={`answer-text ${selectedTrue}`}>Vrai</div> <div className={`answer-text ${selectedTrue}`}>Vrai</div>
{showAnswer && actualAnswer && question.trueFormattedFeedback && ( {showAnswer && answer && question.trueFormattedFeedback && (
<div className="true-feedback mb-2"> <div className="true-feedback mb-2">
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
@ -94,7 +102,7 @@ const TrueFalseQuestionDisplay: React.FC = () => {
<div className={`circle ${selectedFalse}`}>F</div> <div className={`circle ${selectedFalse}`}>F</div>
<div className={`answer-text ${selectedFalse}`}>Faux</div> <div className={`answer-text ${selectedFalse}`}>Faux</div>
{showAnswer && !actualAnswer && question.falseFormattedFeedback && ( {showAnswer && !answer && question.falseFormattedFeedback && (
<div className="false-feedback mb-2"> <div className="false-feedback mb-2">
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
@ -118,15 +126,15 @@ const TrueFalseQuestionDisplay: React.FC = () => {
/> />
</div> </div>
)} )}
{!showAnswer && submitAnswer && ( {!showAnswer && handleOnSubmitAnswer && (
<Button <Button
variant="contained" variant="contained"
onClick={() => onClick={() =>
actualAnswer !== undefined && answer !== undefined &&
submitAnswer && handleOnSubmitAnswer &&
submitAnswer([actualAnswer]) handleOnSubmitAnswer([answer])
} }
disabled={actualAnswer === undefined} disabled={answer === undefined}
> >
Répondre Répondre
</Button> </Button>

View file

@ -1,16 +1,32 @@
// StudentModeQuiz.tsx // StudentModeQuiz.tsx
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import QuestionDisplay from '../QuestionsDisplay/QuestionDisplay'; import QuestionComponent from '../QuestionsDisplay/QuestionDisplay';
import '../../pages/Student/JoinRoom/joinRoom.css'; import '../../pages/Student/JoinRoom/joinRoom.css';
import { QuestionType } from '../../Types/QuestionType'; import { QuestionType } from '../../Types/QuestionType';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; 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'; import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext';
const StudentModeQuiz: React.FC = () => { interface StudentModeQuizProps {
const { questions, answers, setIsQuestionSent, disconnectWebSocket, setShowAnswer } = useQuizContext(); // Access setShowAnswer from context questions: QuestionType[];
answers: AnswerSubmissionToBackendType[];
submitAnswer: (_answer: AnswerType, _idQuestion: number) => void;
disconnectWebSocket: () => void;
}
const StudentModeQuiz: React.FC<StudentModeQuizProps> = ({
questions,
answers,
submitAnswer,
disconnectWebSocket
}) => {
const { setShowAnswer } = useQuizContext(); // Access setShowAnswer from context
const [questionInfos, setQuestion] = useState<QuestionType>(questions[0]); const [questionInfos, setQuestion] = useState<QuestionType>(questions[0]);
const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false);
const previousQuestion = () => { const previousQuestion = () => {
setQuestion(questions[Number(questionInfos.question?.id) - 2]); setQuestion(questions[Number(questionInfos.question?.id) - 2]);
@ -19,7 +35,7 @@ const StudentModeQuiz: React.FC = () => {
useEffect(() => { useEffect(() => {
const savedAnswer = answers[Number(questionInfos.question.id) - 1]?.answer; const savedAnswer = answers[Number(questionInfos.question.id) - 1]?.answer;
console.log(`StudentModeQuiz: useEffect: savedAnswer: ${savedAnswer}`); console.log(`StudentModeQuiz: useEffect: savedAnswer: ${savedAnswer}`);
setIsQuestionSent(savedAnswer !== undefined); setIsAnswerSubmitted(savedAnswer !== undefined);
setShowAnswer(savedAnswer !== undefined); // Update showAnswer in context setShowAnswer(savedAnswer !== undefined); // Update showAnswer in context
}, [questionInfos.question, answers, setShowAnswer]); }, [questionInfos.question, answers, setShowAnswer]);
@ -27,6 +43,13 @@ const StudentModeQuiz: React.FC = () => {
setQuestion(questions[Number(questionInfos.question?.id)]); 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 ( return (
<div className="room"> <div className="room">
<div className="roomHeader"> <div className="roomHeader">
@ -40,7 +63,12 @@ const StudentModeQuiz: React.FC = () => {
</div> </div>
<div className="overflow-auto"> <div className="overflow-auto">
<div className="question-component-container"> <div className="question-component-container">
<QuestionDisplay/> <QuestionComponent
handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question as Question}
showAnswer={isAnswerSubmitted} // Local state for showing answers
answer={answers[Number(questionInfos.question.id) - 1]?.answer}
/>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', marginTop: '1rem' }}> <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', marginTop: '1rem' }}>
<div> <div>
<Button <Button

View file

@ -1,55 +1,64 @@
// TeacherModeQuiz.tsx // TeacherModeQuiz.tsx
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import QuestionDisplay from '../QuestionsDisplay/QuestionDisplay'; import QuestionComponent from '../QuestionsDisplay/QuestionDisplay';
import '../../pages/Student/JoinRoom/joinRoom.css'; import '../../pages/Student/JoinRoom/joinRoom.css';
import { QuestionType } from '../../Types/QuestionType';
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material';
import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext'; import { Question } from 'gift-pegjs';
import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService';
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
// import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
interface TeacherModeQuizProps {
questionInfos: QuestionType;
answers: AnswerSubmissionToBackendType[];
submitAnswer: (_answer: AnswerType, _idQuestion: number) => void;
disconnectWebSocket: () => void;
}
const TeacherModeQuiz: React.FC = () => { const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
const { questionInfos,
questions, answers,
answers, submitAnswer,
setShowAnswer, disconnectWebSocket
disconnectWebSocket, }) => {
index,
isQuestionSent,
setIsQuestionSent,
answer,
setAnswer,
} = useQuizContext();
const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false); const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false);
const [isFeedbackDialogOpen, setIsFeedbackDialogOpen] = useState(false);
const [answer, setAnswer] = useState<AnswerType>();
// arrive here the first time after waiting for next question // arrive here the first time after waiting for next question
useEffect(() => { useEffect(() => {
console.log(`QuestionDisplay: questions: ${JSON.stringify(questions)}`);
console.log(`TeacherModeQuiz: useEffect: answers: ${JSON.stringify(answers)}`); console.log(`TeacherModeQuiz: useEffect: answers: ${JSON.stringify(answers)}`);
console.log(`TeacherModeQuiz: useEffect: questionInfos.question.id: ${index} answer: ${answer}`); console.log(`TeacherModeQuiz: useEffect: questionInfos.question.id: ${questionInfos.question.id} answer: ${answer}`);
const oldAnswer = answers[Number(index) -1 ]?.answer; const oldAnswer = answers[Number(questionInfos.question.id) -1 ]?.answer;
console.log(`TeacherModeQuiz: useEffect: oldAnswer: ${oldAnswer}`); console.log(`TeacherModeQuiz: useEffect: oldAnswer: ${oldAnswer}`);
setAnswer(oldAnswer); setAnswer(oldAnswer);
setShowAnswer(false); setIsFeedbackDialogOpen(false);
setIsQuestionSent(false); }, [questionInfos.question, answers]);
}, [questions[Number(index)].question, answers]);
// handle showing the feedback dialog // handle showing the feedback dialog
useEffect(() => { useEffect(() => {
console.log(`TeacherModeQuiz: useEffect: answer: ${answer}`); console.log(`TeacherModeQuiz: useEffect: answer: ${answer}`);
setIsAnswerSubmitted(answer !== undefined); setIsAnswerSubmitted(answer !== undefined);
setIsQuestionSent(answer !== undefined); setIsFeedbackDialogOpen(answer !== undefined);
}, [answer]); }, [answer]);
useEffect(() => { useEffect(() => {
console.log(`TeacherModeQuiz: useEffect: isAnswerSubmitted: ${isAnswerSubmitted}`); console.log(`TeacherModeQuiz: useEffect: isAnswerSubmitted: ${isAnswerSubmitted}`);
setIsQuestionSent(isAnswerSubmitted); setIsFeedbackDialogOpen(isAnswerSubmitted);
setShowAnswer(isAnswerSubmitted);
}, [isAnswerSubmitted]); }, [isAnswerSubmitted]);
const handleOnSubmitAnswer = (answer: AnswerType) => {
const idQuestion = Number(questionInfos.question.id) || -1;
submitAnswer(answer, idQuestion);
// setAnswer(answer);
setIsFeedbackDialogOpen(true);
};
const handleFeedbackDialogClose = () => { const handleFeedbackDialogClose = () => {
setIsQuestionSent(false); setIsFeedbackDialogOpen(false);
setIsAnswerSubmitted(true); setIsAnswerSubmitted(true);
}; };
@ -62,7 +71,7 @@ const TeacherModeQuiz: React.FC = () => {
message={`Êtes-vous sûr de vouloir quitter?`} /> message={`Êtes-vous sûr de vouloir quitter?`} />
<div className='centerTitle'> <div className='centerTitle'>
<div className='title'>Question {index}</div> <div className='title'>Question {questionInfos.question.id}</div>
</div> </div>
<div className='dumb'></div> <div className='dumb'></div>
@ -74,11 +83,15 @@ const TeacherModeQuiz: React.FC = () => {
En attente pour la prochaine question... En attente pour la prochaine question...
</div> </div>
) : ( ) : (
<QuestionDisplay/> <QuestionComponent
handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question as Question}
answer={answer}
/>
)} )}
<Dialog <Dialog
open={isQuestionSent} open={isFeedbackDialogOpen}
onClose={handleFeedbackDialogClose} onClose={handleFeedbackDialogClose}
> >
<DialogTitle>Rétroaction</DialogTitle> <DialogTitle>Rétroaction</DialogTitle>
@ -93,8 +106,12 @@ const TeacherModeQuiz: React.FC = () => {
>Question : </div> >Question : </div>
</div> </div>
<QuestionDisplay <QuestionComponent
//showAnswer={true} handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question as Question}
showAnswer={true}
answer={answer}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>

View file

@ -15,38 +15,23 @@ import LoadingButton from '@mui/lab/LoadingButton';
import LoginContainer from 'src/components/LoginContainer/LoginContainer' import LoginContainer from 'src/components/LoginContainer/LoginContainer'
import { useQuizContext } from './QuizContext'; import ApiService from '../../../services/ApiService'
import { QuizProvider } from './QuizProvider';
export type AnswerType = Array<string | number | boolean>; export type AnswerType = Array<string | number | boolean>;
const JoinRoom: React.FC = () => { const JoinRoom: React.FC = () => {
const { const [roomName, setRoomName] = useState('');
setQuestions, const [username, setUsername] = useState(ApiService.getUsername());
setAnswers,
questions,
username,
setUsername,
setDisconnectWebSocket,
roomName,
setRoomName,
index,
updateIndex,
} = useQuizContext();
const [socket, setSocket] = useState<Socket | null>(null); const [socket, setSocket] = useState<Socket | null>(null);
const [isWaitingForTeacher, setIsWaitingForTeacher] = useState(false); const [isWaitingForTeacher, setIsWaitingForTeacher] = useState(false);
const [question, setQuestion] = useState<QuestionType>();
const [quizMode, setQuizMode] = useState<string>(); const [quizMode, setQuizMode] = useState<string>();
const [questions, setQuestions] = useState<QuestionType[]>([]);
const [answers, setAnswers] = useState<AnswerSubmissionToBackendType[]>([]);
const [connectionError, setConnectionError] = useState<string>(''); const [connectionError, setConnectionError] = useState<string>('');
const [isConnecting, setIsConnecting] = useState<boolean>(false); const [isConnecting, setIsConnecting] = useState<boolean>(false);
useEffect(() => {
// Set the disconnectWebSocket function in the context
setDisconnectWebSocket(() => disconnect);
}, [socket]);
useEffect(() => { useEffect(() => {
handleCreateSocket(); handleCreateSocket();
return () => { return () => {
@ -57,7 +42,6 @@ const JoinRoom: React.FC = () => {
useEffect(() => { useEffect(() => {
console.log(`JoinRoom: useEffect: questions: ${JSON.stringify(questions)}`); console.log(`JoinRoom: useEffect: questions: ${JSON.stringify(questions)}`);
setAnswers(questions ? Array(questions.length).fill({} as AnswerSubmissionToBackendType) : []); setAnswers(questions ? Array(questions.length).fill({} as AnswerSubmissionToBackendType) : []);
console.log(`JoinRoom: useEffect: answers: ${JSON.stringify(questions)}`);
}, [questions]); }, [questions]);
@ -74,13 +58,13 @@ const JoinRoom: React.FC = () => {
console.log('JoinRoom: on(next-question): Received next-question:', question); console.log('JoinRoom: on(next-question): Received next-question:', question);
setQuizMode('teacher'); setQuizMode('teacher');
setIsWaitingForTeacher(false); setIsWaitingForTeacher(false);
updateIndex(Number(question.question.id)); setQuestion(question);
}); });
socket.on('launch-teacher-mode', (questions: QuestionType[]) => { socket.on('launch-teacher-mode', (questions: QuestionType[]) => {
console.log('on(launch-teacher-mode): Received launch-teacher-mode:', questions); console.log('on(launch-teacher-mode): Received launch-teacher-mode:', questions);
setQuizMode('teacher'); setQuizMode('teacher');
setIsWaitingForTeacher(true); setIsWaitingForTeacher(true);
updateIndex(null); setQuestions([]); // clear out from last time (in case quiz is repeated)
setQuestions(questions); setQuestions(questions);
// wait for next-question // wait for next-question
}); });
@ -91,7 +75,7 @@ const JoinRoom: React.FC = () => {
setIsWaitingForTeacher(false); setIsWaitingForTeacher(false);
setQuestions([]); // clear out from last time (in case quiz is repeated) setQuestions([]); // clear out from last time (in case quiz is repeated)
setQuestions(questions); setQuestions(questions);
updateIndex(0); setQuestion(questions[0]);
}); });
socket.on('end-quiz', () => { socket.on('end-quiz', () => {
disconnect(); disconnect();
@ -118,10 +102,10 @@ const JoinRoom: React.FC = () => {
}; };
const disconnect = () => { const disconnect = () => {
// localStorage.clear(); // localStorage.clear();
webSocketService.disconnect(); webSocketService.disconnect();
setSocket(null); setSocket(null);
updateIndex(null); setQuestion(undefined);
setIsWaitingForTeacher(false); setIsWaitingForTeacher(false);
setQuizMode(''); setQuizMode('');
setRoomName(''); setRoomName('');
@ -143,6 +127,25 @@ 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<HTMLInputElement>) => { const handleReturnKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && username && roomName) { if (e.key === 'Enter' && username && roomName) {
handleSocket(); handleSocket();
@ -173,16 +176,26 @@ const JoinRoom: React.FC = () => {
} }
switch (quizMode) { switch (quizMode) {
case 'student': case 'student':
return ( return (
<StudentModeQuiz /> <QuizProvider>
<StudentModeQuiz
questions={questions}
answers={answers}
submitAnswer={handleOnSubmitAnswer}
disconnectWebSocket={disconnect}
/>
</QuizProvider>
); );
case 'teacher': case 'teacher':
return ( return (
index != null && ( question && (
<TeacherModeQuiz /> <TeacherModeQuiz
questionInfos={question}
answers={answers}
submitAnswer={handleOnSubmitAnswer}
disconnectWebSocket={disconnect}
/>
) )
); );
default: default:

View file

@ -1,55 +1,18 @@
import React, { Dispatch, SetStateAction, useContext } from 'react'; 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<{ export const QuizContext = React.createContext<{
showAnswer: boolean; showAnswer: boolean;
setShowAnswer: Dispatch<SetStateAction<boolean>>; setShowAnswer: Dispatch<SetStateAction<boolean>>;
questions: QuestionType[]; }>({
setQuestions: Dispatch<SetStateAction<QuestionType[]>>;
answers: AnswerSubmissionToBackendType[];
setAnswers: Dispatch<SetStateAction<AnswerSubmissionToBackendType[]>>;
answer : AnswerType;
setAnswer: Dispatch<SetStateAction<AnswerType>>;
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<SetStateAction<boolean>>;
roomName: string;
setRoomName: Dispatch<SetStateAction<string>>;
username: string; // Username of the user
setUsername: Dispatch<SetStateAction<string>>; // Setter for username
disconnectWebSocket: () => void; // Function to disconnect the WebSocket
setDisconnectWebSocket: Dispatch<SetStateAction<() => void>>; // Setter for disconnectWebSocket
}>({
showAnswer: false, showAnswer: false,
setShowAnswer: () => {}, 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 = () => { export const useQuizContext = () => {
const context = useContext(QuizContext); const context = useContext(QuizContext);
if (!context) { if (!context) {
throw new Error('useQuizContext must be used within a QuizProvider'); throw new Error('useQuizContext must be used within a QuizProvider');
} }
return context; return context;
}; };

View file

@ -1,96 +1,19 @@
import React, { useState } from 'react'; import React, { useEffect, useState } from 'react';
import { QuizContext } from './QuizContext'; 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 }) => { export const QuizProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
// State for showing answers // State for showing answers
const [showAnswer, setShowAnswer] = useState(false); const [showAnswer, setShowAnswer] = useState(false);
// State for managing the list of questions console.log('QuizProvider: showAnswer:', showAnswer);
const [questions, setQuestions] = useState<QuestionType[]>([]);
// State for managing the list of answers useEffect(() => {
const [answers, setAnswers] = useState<AnswerSubmissionToBackendType[]>([]); console.log('QuizProvider: showAnswer:', showAnswer);
}, [showAnswer]);
// Calculate the index based on the current question's ID
const [index, setIndex] = useState<number | null>(null);
const [answer, setAnswer] = useState<AnswerType>([]); // Initialize answer as an empty array
const [isQuestionSent, setIsQuestionSent] = useState(false);
const [username, setUsername] = useState<string>(ApiService.getUsername());
const [roomName, setRoomName] = useState<string>(''); // 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 ( return (
<QuizContext.Provider <QuizContext.Provider value={{ showAnswer, setShowAnswer }}>
value={{
showAnswer,
setShowAnswer,
questions,
setQuestions,
answers,
setAnswers,
answer,
setAnswer,
index, // Expose the index in the context
updateIndex, // Expose a function to update the index
submitAnswer, // Expose submitAnswer in the context
isQuestionSent,
setIsQuestionSent,
username,
setUsername,
roomName,
setRoomName,
disconnectWebSocket,
setDisconnectWebSocket,
}}
>
{children} {children}
</QuizContext.Provider> </QuizContext.Provider>
); );
}; };

View file

@ -1,7 +1,7 @@
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 { BaseQuestion, parse /*Question*/ } from 'gift-pegjs'; import { BaseQuestion, parse, Question } from 'gift-pegjs';
import LiveResultsComponent from 'src/components/LiveResults/LiveResults'; import LiveResultsComponent from 'src/components/LiveResults/LiveResults';
import webSocketService, { import webSocketService, {
AnswerReceptionFromBackendType AnswerReceptionFromBackendType
@ -20,24 +20,17 @@ import ApiService from '../../../services/ApiService';
import { QuestionType } from 'src/Types/QuestionType'; import { QuestionType } from 'src/Types/QuestionType';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import { checkIfIsCorrect } from './useRooms'; import { checkIfIsCorrect } from './useRooms';
import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext';
const ManageRoom: React.FC = () => { const ManageRoom: React.FC = () => {
const {
questions,
setQuestions,
index,
updateIndex,
} = useQuizContext();
const navigate = useNavigate(); const navigate = useNavigate();
const [socket, setSocket] = useState<Socket | null>(null); const [socket, setSocket] = useState<Socket | null>(null);
const [students, setStudents] = useState<StudentType[]>([]); const [students, setStudents] = useState<StudentType[]>([]);
const { quizId = '', roomName = '' } = useParams<{ quizId: string, roomName: string }>(); const { quizId = '', roomName = '' } = useParams<{ quizId: string, roomName: string }>();
const [quizQuestions, setQuizQuestions] = useState<QuestionType[] | undefined>();
const [quiz, setQuiz] = useState<QuizType | null>(null); const [quiz, setQuiz] = useState<QuizType | null>(null);
const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher'); const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher');
const [connectingError, setConnectingError] = useState<string>(''); const [connectingError, setConnectingError] = useState<string>('');
const [currentQuestion, setCurrentQuestion] = useState<QuestionType | undefined>(undefined);
const [quizStarted, setQuizStarted] = useState<boolean>(false); const [quizStarted, setQuizStarted] = useState<boolean>(false);
const [formattedRoomName, setFormattedRoomName] = useState(""); const [formattedRoomName, setFormattedRoomName] = useState("");
const [newlyConnectedUser, setNewlyConnectedUser] = useState<StudentType | null>(null); const [newlyConnectedUser, setNewlyConnectedUser] = useState<StudentType | null>(null);
@ -58,12 +51,12 @@ const ManageRoom: React.FC = () => {
if (quizMode === 'teacher') { if (quizMode === 'teacher') {
webSocketService.nextQuestion({ webSocketService.nextQuestion({
roomName: formattedRoomName, roomName: formattedRoomName,
questions: questions, questions: quizQuestions,
questionIndex: Number(index), questionIndex: Number(currentQuestion?.question.id) - 1,
isLaunch: true // started late isLaunch: true // started late
}); });
} else if (quizMode === 'student') { } else if (quizMode === 'student') {
webSocketService.launchStudentModeQuiz(formattedRoomName, questions); webSocketService.launchStudentModeQuiz(formattedRoomName, quizQuestions);
} else { } else {
console.error('Invalid quiz mode:', quizMode); console.error('Invalid quiz mode:', quizMode);
} }
@ -133,8 +126,8 @@ const ManageRoom: React.FC = () => {
webSocketService.endQuiz(formattedRoomName); webSocketService.endQuiz(formattedRoomName);
webSocketService.disconnect(); webSocketService.disconnect();
setSocket(null); setSocket(null);
setQuestions([]); setQuizQuestions(undefined);
updateIndex(null); setCurrentQuestion(undefined);
setStudents(new Array<StudentType>()); setStudents(new Array<StudentType>());
} }
}; };
@ -194,7 +187,7 @@ const ManageRoom: React.FC = () => {
console.log( console.log(
`Received answer from ${username} for question ${idQuestion}: ${answer}` `Received answer from ${username} for question ${idQuestion}: ${answer}`
); );
if (!questions) { if (!quizQuestions) {
console.log('Quiz questions not found (cannot update answers without them).'); console.log('Quiz questions not found (cannot update answers without them).');
return; return;
} }
@ -225,7 +218,7 @@ const ManageRoom: React.FC = () => {
isCorrect: checkIfIsCorrect( isCorrect: checkIfIsCorrect(
answer, answer,
idQuestion, idQuestion,
questions! quizQuestions!
) )
} }
: ans; : ans;
@ -234,7 +227,7 @@ const ManageRoom: React.FC = () => {
const newAnswer = { const newAnswer = {
idQuestion, idQuestion,
answer, answer,
isCorrect: checkIfIsCorrect(answer, idQuestion, questions!) isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!)
}; };
updatedAnswers = [...student.answers, newAnswer]; updatedAnswers = [...student.answers, newAnswer];
} }
@ -250,30 +243,30 @@ const ManageRoom: React.FC = () => {
}); });
setSocket(socket); setSocket(socket);
} }
}, [socket, index, questions]); }, [socket, currentQuestion, quizQuestions]);
const nextQuestion = () => { const nextQuestion = () => {
if (!questions || !index || !quiz?.content) return; if (!quizQuestions || !currentQuestion || !quiz?.content) return;
const nextQuestionIndex = index; const nextQuestionIndex = Number(currentQuestion?.question.id);
if (nextQuestionIndex === undefined || nextQuestionIndex > questions.length - 1) return; if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return;
updateIndex(nextQuestionIndex); setCurrentQuestion(quizQuestions[nextQuestionIndex]);
webSocketService.nextQuestion({roomName: formattedRoomName, webSocketService.nextQuestion({roomName: formattedRoomName,
questions: questions, questions: quizQuestions,
questionIndex: nextQuestionIndex, questionIndex: nextQuestionIndex,
isLaunch: false}); isLaunch: false});
}; };
const previousQuestion = () => { const previousQuestion = () => {
if (!questions || !index || !quiz?.content) return; if (!quizQuestions || !currentQuestion || !quiz?.content) return;
const prevQuestionIndex = index - 1; // -2 because question.id starts at index 1 const prevQuestionIndex = Number(currentQuestion?.question.id) - 2; // -2 because question.id starts at index 1
if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return; if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return;
updateIndex(prevQuestionIndex); setCurrentQuestion(quizQuestions[prevQuestionIndex]);
webSocketService.nextQuestion({roomName: formattedRoomName, questions: questions, questionIndex: prevQuestionIndex, isLaunch: false}); webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: prevQuestionIndex, isLaunch: false});
}; };
const initializeQuizQuestion = () => { const initializeQuizQuestion = () => {
@ -287,33 +280,33 @@ const ManageRoom: React.FC = () => {
}); });
if (parsedQuestions.length === 0) return null; if (parsedQuestions.length === 0) return null;
setQuestions(parsedQuestions); setQuizQuestions(parsedQuestions);
return parsedQuestions; return parsedQuestions;
}; };
const launchTeacherMode = () => { const launchTeacherMode = () => {
const questions = initializeQuizQuestion(); const quizQuestions = initializeQuizQuestion();
console.log('launchTeacherMode - quizQuestions:', questions); console.log('launchTeacherMode - quizQuestions:', quizQuestions);
if (!questions) { if (!quizQuestions) {
console.log('Error launching quiz (launchTeacherMode). No questions found.'); console.log('Error launching quiz (launchTeacherMode). No questions found.');
return; return;
} }
updateIndex(0); setCurrentQuestion(quizQuestions[0]);
webSocketService.nextQuestion({roomName: formattedRoomName, questions: questions, questionIndex: 0, isLaunch: true}); webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: 0, isLaunch: true});
}; };
const launchStudentMode = () => { const launchStudentMode = () => {
const questions = initializeQuizQuestion(); const quizQuestions = initializeQuizQuestion();
console.log('launchStudentMode - quizQuestions:', questions); console.log('launchStudentMode - quizQuestions:', quizQuestions);
if (!questions) { if (!quizQuestions) {
console.log('Error launching quiz (launchStudentMode). No questions found.'); console.log('Error launching quiz (launchStudentMode). No questions found.');
return; return;
} }
setQuestions(questions); setQuizQuestions(quizQuestions);
webSocketService.launchStudentModeQuiz(formattedRoomName, questions); webSocketService.launchStudentModeQuiz(formattedRoomName, quizQuestions);
}; };
const launchQuiz = () => { const launchQuiz = () => {
@ -335,10 +328,10 @@ const ManageRoom: React.FC = () => {
}; };
const showSelectedQuestion = (questionIndex: number) => { const showSelectedQuestion = (questionIndex: number) => {
if (quiz?.content && questions) { if (quiz?.content && quizQuestions) {
updateIndex(questionIndex); setCurrentQuestion(quizQuestions[questionIndex]);
if (quizMode === 'teacher') { if (quizMode === 'teacher') {
webSocketService.nextQuestion({roomName: formattedRoomName, questions: questions, questionIndex, isLaunch: false}); webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex, isLaunch: false});
} }
} }
}; };
@ -405,13 +398,13 @@ const ManageRoom: React.FC = () => {
{/* the following breaks the css (if 'room' classes are nested) */} {/* the following breaks the css (if 'room' classes are nested) */}
<div className=""> <div className="">
{questions.length > 0 ? ( {quizQuestions ? (
<div style={{ display: 'flex', flexDirection: 'column' }}> <div style={{ display: 'flex', flexDirection: 'column' }}>
<div className="title center-h-align mb-2">{quiz?.title}</div> <div className="title center-h-align mb-2">{quiz?.title}</div>
{index && ( {!isNaN(Number(currentQuestion?.question.id)) && (
<strong className="number of questions"> <strong className="number of questions">
Question {index+1}/ Question {Number(currentQuestion?.question.id)}/
{questions?.length} {quizQuestions?.length}
</strong> </strong>
)} )}
@ -428,14 +421,18 @@ const ManageRoom: React.FC = () => {
<div className="mb-2 flex-column-wrapper"> <div className="mb-2 flex-column-wrapper">
<div className="preview-and-result-container"> <div className="preview-and-result-container">
{index && ( {currentQuestion && (
<QuestionDisplay/> <QuestionDisplay
showAnswer={false}
question={currentQuestion?.question as Question}
/>
)} )}
<LiveResultsComponent <LiveResultsComponent
quizMode={quizMode} quizMode={quizMode}
socket={socket} socket={socket}
questions={questions} questions={quizQuestions}
showSelectedQuestion={showSelectedQuestion} showSelectedQuestion={showSelectedQuestion}
students={students} students={students}
></LiveResultsComponent> ></LiveResultsComponent>
@ -451,7 +448,7 @@ const ManageRoom: React.FC = () => {
<Button <Button
onClick={previousQuestion} onClick={previousQuestion}
variant="contained" variant="contained"
disabled={index !== null && index <= 1} disabled={Number(currentQuestion?.question.id) <= 1}
> >
Question précédente Question précédente
</Button> </Button>
@ -461,8 +458,8 @@ const ManageRoom: React.FC = () => {
onClick={nextQuestion} onClick={nextQuestion}
variant="contained" variant="contained"
disabled={ disabled={
index !== null && Number(currentQuestion?.question.id) >=
index >= questions.length quizQuestions.length
} }
> >
Prochaine question Prochaine question