From 3f16a758d883685a4743d9f9843d1dfdbdd6937a Mon Sep 17 00:00:00 2001 From: KenChanA Date: Thu, 6 Mar 2025 03:29:49 -0500 Subject: [PATCH 01/12] Display percentage feature in manageRooms Added the percentage display for the following question types: TrueFalse, MultipleChoice, Numerical and ShortAnswer Modified the signature of each question types to optionally receive students in order to display their pickrate for each option --- .../MultipleChoiceQuestionDisplay.tsx | 82 ++++++++++++++-- .../NumericalQuestionDisplay.tsx | 63 +++++++++++- .../QuestionsDisplay/QuestionDisplay.tsx | 16 ++++ .../ShortAnswerQuestionDisplay.tsx | 61 +++++++++++- .../TrueFalseQuestionDisplay.tsx | 95 ++++++++++++++++--- .../QuestionsDisplay/questionStyle.css | 27 ++++++ .../pages/Teacher/ManageRoom/ManageRoom.tsx | 2 + 7 files changed, 323 insertions(+), 23 deletions(-) diff --git a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx index e5e7b6b..bf4f7f3 100644 --- a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx @@ -1,28 +1,57 @@ // MultipleChoiceQuestionDisplay.tsx -import React, { useEffect, useState } from 'react'; +import React, { useState, useEffect } from 'react'; import '../questionStyle.css'; import { Button } from '@mui/material'; import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { MultipleChoiceQuestion } from 'gift-pegjs'; +import { StudentType } from 'src/Types/StudentType'; interface Props { question: MultipleChoiceQuestion; handleOnSubmitAnswer?: (answer: string) => void; showAnswer?: boolean; + students?: StudentType[]; + isDisplayOnly?: boolean; } const MultipleChoiceQuestionDisplay: React.FC = (props) => { - const { question, showAnswer, handleOnSubmitAnswer } = props; + const { question, showAnswer, handleOnSubmitAnswer, students, isDisplayOnly } = props; const [answer, setAnswer] = useState(); - - useEffect(() => { - setAnswer(undefined); - }, [question]); + const [pickRates, setPickRates] = useState([]); + const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); const handleOnClickAnswer = (choice: string) => { setAnswer(choice); }; + const toggleShowCorrectAnswers = () => { + setShowCorrectAnswers(!showCorrectAnswers); + }; + + const calculatePickRates = () => { + if (!students || students.length === 0) { + setPickRates(new Array(question.choices.length).fill(0)); // Fill with 0 for each choice + return; + } + + const rates = question.choices.map(choice => { + const choiceAnswers = students.filter(student => + student.answers.some(ans => + ans.idQuestion === Number(question.id) && ans.answer === choice.formattedText.text + ) + ).length; + return (choiceAnswers / students.length) * 100; + }); + + setPickRates(rates); + }; + + useEffect(() => { + setAnswer(undefined); + calculatePickRates(); + }, [students, question, showCorrectAnswers]); + + const alpha = Array.from(Array(26)).map((_e, i) => i + 65); const alphabet = alpha.map((x) => String.fromCharCode(x)); return ( @@ -33,9 +62,13 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => {
{question.choices.map((choice, i) => { const selected = answer === choice.formattedText.text ? 'selected' : ''; + const rateStyle = showCorrectAnswers ? { + backgroundImage: `linear-gradient(to right, ${choice.isCorrect ? 'lightgreen' : 'lightcoral'} ${pickRates[i]}%, transparent ${pickRates[i]}%)`, + color: 'black' + } : {}; return (
- */} + -
); })} @@ -75,6 +129,18 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { )} + + {isDisplayOnly && ( +
+ +
+ )}
); }; diff --git a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx index ac9c83f..aa9bbc6 100644 --- a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx @@ -1,19 +1,22 @@ // NumericalQuestion.tsx -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards'; +import { StudentType } from 'src/Types/StudentType'; interface Props { question: NumericalQuestion; handleOnSubmitAnswer?: (answer: number) => void; showAnswer?: boolean; + students?: StudentType[]; + isDisplayOnly?: boolean; } const NumericalQuestionDisplay: React.FC = (props) => { - const { question, showAnswer, handleOnSubmitAnswer } = + const { question, showAnswer, handleOnSubmitAnswer, students, isDisplayOnly } = props; const [answer, setAnswer] = useState(); @@ -21,6 +24,34 @@ const NumericalQuestionDisplay: React.FC = (props) => { const correctAnswers = question.choices; let correctAnswer = ''; + const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); + const [correctAnswerRate, setCorrectAnswerRate] = useState(0); + + const toggleShowCorrectAnswers = () => { + setShowCorrectAnswers(!showCorrectAnswers); + }; + + useEffect(() => { + if (showCorrectAnswers && students) { + calculateCorrectAnswerRate(); + } + }, [showCorrectAnswers, students]); + + const calculateCorrectAnswerRate = () => { + if (!students || students.length === 0) { + setCorrectAnswerRate(0); // Safeguard against undefined or empty student array + return; + } + + const totalSubmissions = students.length; + const correctSubmissions = students.filter(student => + student.answers.some(ans => + ans.idQuestion === Number(question.id) && ans.isCorrect + ) + ).length; + setCorrectAnswerRate((correctSubmissions / totalSubmissions) * 100); + }; + //const isSingleAnswer = correctAnswers.length === 1; if (isSimpleNumericalAnswer(correctAnswers[0])) { @@ -82,6 +113,34 @@ const NumericalQuestionDisplay: React.FC = (props) => { )} )} + + + {isDisplayOnly && ( + <> +
+ +
+
+
+ Taux de réponse correcte: +
+
+
+
+ {correctAnswerRate.toFixed(1)}% +
+
+
+ + )} ); }; diff --git a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx index 8dfa1b3..afed654 100644 --- a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx @@ -7,15 +7,21 @@ import NumericalQuestionDisplay from './NumericalQuestionDisplay/NumericalQuesti import ShortAnswerQuestionDisplay from './ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay'; // import useCheckMobileScreen from '../../services/useCheckMobileScreen'; +import { StudentType } from '../../Types/StudentType'; + interface QuestionProps { question: Question; handleOnSubmitAnswer?: (answer: string | number | boolean) => void; showAnswer?: boolean; + students?: StudentType[]; + isDisplayOnly?: boolean; } const QuestionDisplay: React.FC = ({ question, handleOnSubmitAnswer, showAnswer, + students, + isDisplayOnly = false }) => { // const isMobile = useCheckMobileScreen(); // const imgWidth = useMemo(() => { @@ -30,6 +36,8 @@ const QuestionDisplay: React.FC = ({ question={question} handleOnSubmitAnswer={handleOnSubmitAnswer} showAnswer={showAnswer} + students={students} + isDisplayOnly={isDisplayOnly} /> ); break; @@ -39,6 +47,8 @@ const QuestionDisplay: React.FC = ({ question={question} handleOnSubmitAnswer={handleOnSubmitAnswer} showAnswer={showAnswer} + students={students} + isDisplayOnly={isDisplayOnly} /> ); break; @@ -50,6 +60,8 @@ const QuestionDisplay: React.FC = ({ question={question} handleOnSubmitAnswer={handleOnSubmitAnswer} showAnswer={showAnswer} + students={students} + isDisplayOnly={isDisplayOnly} /> ); } else { @@ -58,6 +70,8 @@ const QuestionDisplay: React.FC = ({ question={question} handleOnSubmitAnswer={handleOnSubmitAnswer} showAnswer={showAnswer} + students={students} + isDisplayOnly={isDisplayOnly} /> ); } @@ -69,6 +83,8 @@ const QuestionDisplay: React.FC = ({ question={question} handleOnSubmitAnswer={handleOnSubmitAnswer} showAnswer={showAnswer} + students={students} + isDisplayOnly={isDisplayOnly} /> ); break; diff --git a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx index 50c2261..a73e452 100644 --- a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx @@ -1,18 +1,48 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { ShortAnswerQuestion } from 'gift-pegjs'; +import { StudentType } from 'src/Types/StudentType'; interface Props { question: ShortAnswerQuestion; handleOnSubmitAnswer?: (answer: string) => void; showAnswer?: boolean; + students?: StudentType[]; + isDisplayOnly?: boolean; } const ShortAnswerQuestionDisplay: React.FC = (props) => { - const { question, showAnswer, handleOnSubmitAnswer } = props; + const { question, showAnswer, handleOnSubmitAnswer, students, isDisplayOnly } = props; const [answer, setAnswer] = useState(); + const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); + const [correctAnswerRate, setCorrectAnswerRate] = useState(0); + + const toggleShowCorrectAnswers = () => { + setShowCorrectAnswers(!showCorrectAnswers); + }; + + useEffect(() => { + if (showCorrectAnswers && students) { + calculateCorrectAnswerRate(); + } + }, [showCorrectAnswers, students]); + + const calculateCorrectAnswerRate = () => { + if (!students || students.length === 0) { + setCorrectAnswerRate(0); // Safeguard against undefined or empty student array + return; + } + + const totalSubmissions = students.length; + const correctSubmissions = students.filter(student => + student.answers.some(ans => + ans.idQuestion === Number(question.id) && ans.isCorrect + ) + ).length; + setCorrectAnswerRate((correctSubmissions / totalSubmissions) * 100); + }; return (
@@ -61,6 +91,33 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { )} )} + + {isDisplayOnly && ( + <> +
+ +
+
+
+ Taux de réponse correcte: +
+
+
+
+ {correctAnswerRate.toFixed(1)}% +
+
+
+ + )}
); }; diff --git a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx index 63b3891..686acfd 100644 --- a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx @@ -4,24 +4,63 @@ import '../questionStyle.css'; import { Button } from '@mui/material'; import { TrueFalseQuestion } from 'gift-pegjs'; import { FormattedTextTemplate } from 'src/components/GiftTemplate/templates/TextTypeTemplate'; +import { StudentType } from 'src/Types/StudentType'; interface Props { question: TrueFalseQuestion; handleOnSubmitAnswer?: (answer: boolean) => void; showAnswer?: boolean; + students?: StudentType[]; + isDisplayOnly?: boolean; } const TrueFalseQuestionDisplay: React.FC = (props) => { - const { question, showAnswer, handleOnSubmitAnswer } = - props; + const { question, showAnswer, handleOnSubmitAnswer, students, isDisplayOnly } = props; const [answer, setAnswer] = useState(undefined); + const [pickRates, setPickRates] = useState<{ trueRate: number, falseRate: number }>({ trueRate: 0, falseRate: 0 }); + const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); useEffect(() => { setAnswer(undefined); - }, [question]); + calculatePickRates(); + }, [question, students]); const selectedTrue = answer ? 'selected' : ''; const selectedFalse = answer !== undefined && !answer ? 'selected' : ''; + + const toggleShowCorrectAnswers = () => { + setShowCorrectAnswers(!showCorrectAnswers); + }; + + // Calcul le pick rate de chaque réponse + const calculatePickRates = () => { + if (!students) { + setPickRates({ trueRate: 0, falseRate: 0 }); + return; + } + + const totalAnswers = students.length; + + const trueAnswers = students.filter(student => + student.answers.some(ans => + ans.idQuestion === Number(question.id) && ans.answer === true + )).length; + + const falseAnswers = students.filter(student => + student.answers.some(ans => + ans.idQuestion === Number(question.id) && ans.answer === false + )).length; + + if (totalAnswers > 0) { + setPickRates({ + trueRate: (trueAnswers / totalAnswers) * 100, + falseRate: (falseAnswers / totalAnswers) * 100 + }); + } else { + setPickRates({ trueRate: 0, falseRate: 0 }); + } + }; + return (
@@ -29,23 +68,45 @@ const TrueFalseQuestionDisplay: React.FC = (props) => {
+ +
{/* selected TRUE, show True feedback if it exists */} {showAnswer && answer && question.trueFormattedFeedback && ( @@ -75,6 +136,18 @@ const TrueFalseQuestionDisplay: React.FC = (props) => { Répondre )} + + {isDisplayOnly && ( +
+ +
+ )}
); }; diff --git a/client/src/components/QuestionsDisplay/questionStyle.css b/client/src/components/QuestionsDisplay/questionStyle.css index cdf611f..bd2dd74 100644 --- a/client/src/components/QuestionsDisplay/questionStyle.css +++ b/client/src/components/QuestionsDisplay/questionStyle.css @@ -150,3 +150,30 @@ .choices-wrapper { width: 90%; } + +.progress-bar-container { + position: relative; + width: 100%; + height: 20px; + background-color: #FEFEFE; + border-radius: 8px; + overflow: hidden; + border: 1px solid black; +} + +.progress-bar-fill { + height: 100%; + background-color: lightgreen; + width: 0%; + transition: width 0.6s ease; +} + +.progress-bar-text { + position: absolute; + width: 100%; + text-align: center; + top: 0; + line-height: 20px; + color: Black; +} + diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 7af12f9..f148dd3 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -485,6 +485,8 @@ const ManageRoom: React.FC = () => { )} From 8db658e7a0384472aae9252f9d5e8a7fbb570802 Mon Sep 17 00:00:00 2001 From: KenChanA Date: Sun, 16 Mar 2025 21:25:21 -0400 Subject: [PATCH 02/12] Changed questions display --- client/package-lock.json | 19 +++ client/package.json | 1 + .../LiveResultsTableHeader.tsx | 13 +- .../MultipleChoiceQuestionDisplay.tsx | 11 +- .../NumericalQuestionDisplay.tsx | 14 +- .../QuestionsDisplay/QuestionDisplay.tsx | 2 +- .../ShortAnswerQuestionDisplay.tsx | 120 ++++++++++-------- .../TrueFalseQuestionDisplay.tsx | 11 +- .../QuestionsDisplay/questionStyle.css | 7 +- client/src/main.tsx | 1 + 10 files changed, 122 insertions(+), 77 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index e2c6890..a5e49d7 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -19,6 +19,7 @@ "@mui/material": "^6.4.6", "@types/uuid": "^9.0.7", "axios": "^1.8.1", + "bootstrap": "^5.3.3", "dompurify": "^3.2.3", "esbuild": "^0.25.0", "gift-pegjs": "^2.0.0-beta.1", @@ -5536,6 +5537,24 @@ "devOptional": true, "license": "MIT" }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", diff --git a/client/package.json b/client/package.json index b6d62e4..a09f051 100644 --- a/client/package.json +++ b/client/package.json @@ -23,6 +23,7 @@ "@mui/material": "^6.4.6", "@types/uuid": "^9.0.7", "axios": "^1.8.1", + "bootstrap": "^5.3.3", "dompurify": "^3.2.3", "esbuild": "^0.25.0", "gift-pegjs": "^2.0.0-beta.1", diff --git a/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx index d8d4159..732dd13 100644 --- a/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx +++ b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import { TableCell, TableHead, TableRow } from "@mui/material"; interface LiveResultsHeaderProps { @@ -10,6 +10,12 @@ const LiveResultsTableHeader: React.FC = ({ maxQuestions, showSelectedQuestion, }) => { + const [selectedQuestionIndex, setSelectedQuestionIndex] = useState(null); + + const handleQuestionClick = (index: number) => { + setSelectedQuestionIndex(index); + showSelectedQuestion(index); + }; return ( @@ -25,9 +31,10 @@ const LiveResultsTableHeader: React.FC = ({ cursor: 'pointer', borderStyle: 'solid', borderWidth: 1, - borderColor: 'rgba(224, 224, 224, 1)' + borderColor: 'rgba(224, 224, 224, 1)', + backgroundColor: selectedQuestionIndex === index ? '#dedede' : 'transparent' }} - onClick={() => showSelectedQuestion(index)} + onClick={() => handleQuestionClick(index)} >
{`Q${index + 1}`}
diff --git a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx index a34d244..6b37089 100644 --- a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx @@ -67,7 +67,7 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { const alphabet = alpha.map((x) => String.fromCharCode(x)); return ( -
+
@@ -76,7 +76,7 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { {question.choices.map((choice, i) => { const selected = answer === choice.formattedText.text ? 'selected' : ''; const rateStyle = showCorrectAnswers ? { - backgroundImage: `linear-gradient(to right, ${choice.isCorrect ? 'lightgreen' : 'lightcoral'} ${pickRates[i]}%, transparent ${pickRates[i]}%)`, + backgroundImage: `linear-gradient(to right, ${choice.isCorrect ? 'royalblue' : 'orange'} ${pickRates[i]}%, transparent ${pickRates[i]}%)`, color: 'black' } : {}; return ( @@ -114,12 +114,7 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => {
)} - {showCorrectAnswers &&
{choice.isCorrect ? '✅' : '❌'}
} - {showCorrectAnswers && ( -
- {pickRates[i].toFixed(1)}% -
- )} + {showCorrectAnswers &&
{choice.isCorrect ? '✅' : '❌'} {pickRates[i].toFixed(1)}%
}
); diff --git a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx index bd7b19e..959d561 100644 --- a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx @@ -26,6 +26,10 @@ const NumericalQuestionDisplay: React.FC = (props) => { const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); const [correctAnswerRate, setCorrectAnswerRate] = useState(0); + const [submissionCounts, setSubmissionCounts] = useState({ + correctSubmissions: 0, + totalSubmissions: 0 + }); const toggleShowCorrectAnswers = () => { setShowCorrectAnswers(!showCorrectAnswers); @@ -42,7 +46,7 @@ const NumericalQuestionDisplay: React.FC = (props) => { const calculateCorrectAnswerRate = () => { if (!students || students.length === 0) { - setCorrectAnswerRate(0); // Safeguard against undefined or empty student array + setSubmissionCounts({ correctSubmissions: 0, totalSubmissions: 0 }); return; } @@ -52,6 +56,12 @@ const NumericalQuestionDisplay: React.FC = (props) => { ans.idQuestion === Number(question.id) && ans.isCorrect ) ).length; + + setSubmissionCounts({ + correctSubmissions, + totalSubmissions + }); + setCorrectAnswerRate((correctSubmissions / totalSubmissions) * 100); }; @@ -139,7 +149,7 @@ const NumericalQuestionDisplay: React.FC = (props) => { visibility: showCorrectAnswers ? 'visible' : 'hidden' }}>
- Taux de réponse correcte: + Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions}
diff --git a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx index 6036124..8ace5fd 100644 --- a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx @@ -24,7 +24,7 @@ const QuestionDisplay: React.FC = ({ handleOnSubmitAnswer, showAnswer, students, - isDisplayOnly = false + isDisplayOnly = false, answer, }) => { // const isMobile = useCheckMobileScreen(); diff --git a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx index 7d5b091..044ffbf 100644 --- a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx @@ -20,6 +20,10 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { const [answer, setAnswer] = useState(passedAnswer || ''); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); const [correctAnswerRate, setCorrectAnswerRate] = useState(0); + const [submissionCounts, setSubmissionCounts] = useState({ + correctSubmissions: 0, + totalSubmissions: 0 + }); const toggleShowCorrectAnswers = () => { setShowCorrectAnswers(!showCorrectAnswers); @@ -39,7 +43,7 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { const calculateCorrectAnswerRate = () => { if (!students || students.length === 0) { - setCorrectAnswerRate(0); // Safeguard against undefined or empty student array + setSubmissionCounts({ correctSubmissions: 0, totalSubmissions: 0 }); return; } @@ -49,63 +53,69 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { ans.idQuestion === Number(question.id) && ans.isCorrect ) ).length; + + setSubmissionCounts({ + correctSubmissions, + totalSubmissions + }); + setCorrectAnswerRate((correctSubmissions / totalSubmissions) * 100); }; return ( -
-
-
-
- {showAnswer ? ( - <> -
- - La bonne réponse est: - - {question.choices.map((choice) => ( -
- {choice.text} -
- ))} -
- - Votre réponse est: {answer} - -
- {question.formattedGlobalFeedback &&
-
-
} - - ) : ( - <> -
- { - setAnswer(e.target.value); - }} - disabled={showAnswer} - aria-label="short-answer-input" - /> -
- {handleOnSubmitAnswer && ( - - )} - - )} +
+
+
+
+ {showAnswer ? ( + <> +
+ + La bonne réponse est: + + {question.choices.map((choice) => ( +
+ {choice.text} +
+ ))} +
+ + Votre réponse est: {answer} + +
+ {question.formattedGlobalFeedback &&
+
+
} + + ) : ( + <> +
+ { + setAnswer(e.target.value); + }} + disabled={showAnswer} + aria-label="short-answer-input" + /> +
+ {handleOnSubmitAnswer && ( + + )} + + )} {isDisplayOnly && ( <> @@ -122,7 +132,7 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { visibility: showCorrectAnswers ? 'visible' : 'hidden' }}>
- Taux de réponse correcte: + Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions}
diff --git a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx index 6e2d1ef..62f41b4 100644 --- a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx @@ -32,7 +32,6 @@ const TrueFalseQuestionDisplay: React.FC = (props) => { }; useEffect(() => { - console.log("passedAnswer", passedAnswer); if (passedAnswer === true || passedAnswer === false) { setAnswer(passedAnswer); } else { @@ -96,15 +95,14 @@ const TrueFalseQuestionDisplay: React.FC = (props) => {
V
Vrai
{showCorrectAnswers && ( <> -
{question.isTrue ? '✅' : '❌'}
-
{pickRates.trueRate.toFixed(1)}%
+
{question.isTrue ? '✅' : '❌'} {pickRates.trueRate.toFixed(1)}%
)} @@ -125,7 +123,7 @@ const TrueFalseQuestionDisplay: React.FC = (props) => {
Faux @@ -133,8 +131,7 @@ const TrueFalseQuestionDisplay: React.FC = (props) => { {showCorrectAnswers && ( <> -
{!question.isTrue ? '✅' : '❌'}
-
{pickRates.falseRate.toFixed(1)}%
+
{!question.isTrue ? '✅' : '❌'} {pickRates.falseRate.toFixed(1)}%
)} diff --git a/client/src/components/QuestionsDisplay/questionStyle.css b/client/src/components/QuestionsDisplay/questionStyle.css index 87e983e..6df5d71 100644 --- a/client/src/components/QuestionsDisplay/questionStyle.css +++ b/client/src/components/QuestionsDisplay/questionStyle.css @@ -182,7 +182,7 @@ .progress-bar-fill { height: 100%; - background-color: lightgreen; + background-color: royalblue; width: 0%; transition: width 0.6s ease; } @@ -196,3 +196,8 @@ color: Black; } +.pick-rate{ + color: rgba(0,0,0,1); + min-width: 70px; +} + diff --git a/client/src/main.tsx b/client/src/main.tsx index e73c979..a04f569 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -6,6 +6,7 @@ import { BrowserRouter } from 'react-router-dom'; import { ThemeProvider, createTheme } from '@mui/material'; import '@fortawesome/fontawesome-free/css/all.min.css'; +import 'bootstrap/dist/css/bootstrap.min.css'; import './cssReset.css'; import './index.css'; From 490f4dab76e6e2d63366ce240bf29b37a40d21f0 Mon Sep 17 00:00:00 2001 From: KenChanA Date: Sun, 16 Mar 2025 23:21:59 -0400 Subject: [PATCH 03/12] Minor fixes --- .../MultipleChoiceQuestionDisplay.tsx | 26 +-- .../ShortAnswerQuestionDisplay.tsx | 159 +++++++++--------- .../TrueFalseQuestionDisplay.tsx | 33 ++-- .../QuestionsDisplay/questionStyle.css | 2 +- 4 files changed, 116 insertions(+), 104 deletions(-) diff --git a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx index 6b37089..ea07695 100644 --- a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx @@ -19,7 +19,7 @@ interface Props { const MultipleChoiceQuestionDisplay: React.FC = (props) => { const { question, showAnswer, handleOnSubmitAnswer, students, isDisplayOnly, passedAnswer } = props; const [answer, setAnswer] = useState(passedAnswer || ''); - const [pickRates, setPickRates] = useState([]); + const [pickRates, setPickRates] = useState<{ percentages: number[], counts: number[], totalCount: number }>({ percentages: [], counts: [], totalCount: 0 }); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); let disableButton = false; @@ -37,20 +37,26 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { const calculatePickRates = () => { if (!students || students.length === 0) { - setPickRates(new Array(question.choices.length).fill(0)); // Fill with 0 for each choice + setPickRates({ percentages: new Array(question.choices.length).fill(0), counts: new Array(question.choices.length).fill(0), totalCount: 0 }); return; } - - const rates = question.choices.map(choice => { - const choiceAnswers = students.filter(student => + + const rates: number[] = []; + const counts: number[] = []; + let totalResponses = 0; + + question.choices.forEach(choice => { + const choiceCount = students.filter(student => student.answers.some(ans => ans.idQuestion === Number(question.id) && ans.answer === choice.formattedText.text ) ).length; - return (choiceAnswers / students.length) * 100; + totalResponses += choiceCount; + rates.push((choiceCount / students.length) * 100); + counts.push(choiceCount); }); - - setPickRates(rates); + + setPickRates({ percentages: rates, counts: counts, totalCount: totalResponses }); }; useEffect(() => { @@ -76,7 +82,7 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { {question.choices.map((choice, i) => { const selected = answer === choice.formattedText.text ? 'selected' : ''; const rateStyle = showCorrectAnswers ? { - backgroundImage: `linear-gradient(to right, ${choice.isCorrect ? 'royalblue' : 'orange'} ${pickRates[i]}%, transparent ${pickRates[i]}%)`, + backgroundImage: `linear-gradient(to right, ${choice.isCorrect ? 'lightgreen' : 'lightcoral'} ${pickRates.percentages[i]}%, transparent ${pickRates.percentages[i]}%)`, color: 'black' } : {}; return ( @@ -114,7 +120,7 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => {
)} - {showCorrectAnswers &&
{choice.isCorrect ? '✅' : '❌'} {pickRates[i].toFixed(1)}%
} + {showCorrectAnswers &&
{choice.isCorrect ? '✅' : '❌'} {`${pickRates.counts[i]}/${pickRates.totalCount} (${pickRates.percentages[i].toFixed(1)}%)`}
}
); diff --git a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx index 044ffbf..2fd4a7d 100644 --- a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx @@ -63,87 +63,90 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { }; return ( -
-
-
-
- {showAnswer ? ( - <> -
- - La bonne réponse est: - - {question.choices.map((choice) => ( -
- {choice.text} -
- ))} -
- - Votre réponse est: {answer} - -
- {question.formattedGlobalFeedback &&
-
-
} - - ) : ( - <> -
- { - setAnswer(e.target.value); - }} - disabled={showAnswer} - aria-label="short-answer-input" - /> -
- {handleOnSubmitAnswer && ( - - )} - - )} + <>
+
+
+
+
+
+ {showAnswer ? ( + <> +
+ + La bonne réponse est: - {isDisplayOnly && ( - <> -
- -
-
-
- Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions} -
-
-
-
- {correctAnswerRate.toFixed(1)}% + {question.choices.map((choice) => ( +
+ {choice.text} +
+ ))} + + + Votre réponse est: {answer} +
+ {question.formattedGlobalFeedback &&
+
+
} + + ) : ( + <> +
+ { + setAnswer(e.target.value); + } } + disabled={showAnswer} + aria-label="short-answer-input" /> +
+ {handleOnSubmitAnswer && ( +
+ +
+ )} + + )} +
+ {isDisplayOnly && ( + <> +
+ + {showCorrectAnswers && ( +
+
+ Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions} +
+
+
+
+ {correctAnswerRate.toFixed(1)}% +
+
+
+ )}
-
- - )} -
+ + )} +
+
); }; diff --git a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx index 62f41b4..1f77ef7 100644 --- a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx @@ -19,7 +19,13 @@ interface Props { const TrueFalseQuestionDisplay: React.FC = (props) => { const { question, showAnswer, handleOnSubmitAnswer, students, passedAnswer, isDisplayOnly } = props; const [answer, setAnswer] = useState(undefined); - const [pickRates, setPickRates] = useState<{ trueRate: number, falseRate: number }>({ trueRate: 0, falseRate: 0 }); + const [pickRates, setPickRates] = useState<{ trueRate: number, falseRate: number, trueCount: number, falseCount: number, totalCount: number }>({ + trueRate: 0, + falseRate: 0, + trueCount: 0, + falseCount: 0, + totalCount: 0 + }); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); let disableButton = false; @@ -54,36 +60,33 @@ const TrueFalseQuestionDisplay: React.FC = (props) => { // Calcul le pick rate de chaque réponse const calculatePickRates = () => { if (!students) { - setPickRates({ trueRate: 0, falseRate: 0 }); + setPickRates({ trueRate: 0, falseRate: 0, trueCount: 0, falseCount: 0, totalCount: 0 }); return; } const totalAnswers = students.length; - const trueAnswers = students.filter(student => student.answers.some(ans => ans.idQuestion === Number(question.id) && ans.answer === true )).length; - const falseAnswers = students.filter(student => student.answers.some(ans => ans.idQuestion === Number(question.id) && ans.answer === false )).length; - if (totalAnswers > 0) { - setPickRates({ - trueRate: (trueAnswers / totalAnswers) * 100, - falseRate: (falseAnswers / totalAnswers) * 100 - }); - } else { - setPickRates({ trueRate: 0, falseRate: 0 }); - } + setPickRates({ + trueRate: (trueAnswers / totalAnswers) * 100, + falseRate: (falseAnswers / totalAnswers) * 100, + trueCount: trueAnswers, + falseCount: falseAnswers, + totalCount: totalAnswers + }); }; return (
-
+
{showCorrectAnswers && ( <> -
{question.isTrue ? '✅' : '❌'} {pickRates.trueRate.toFixed(1)}%
+
{question.isTrue ? '✅' : '❌'} {pickRates.trueRate}/{pickRates.totalCount} ({pickRates.trueRate.toFixed(1)}%)
)} @@ -131,7 +134,7 @@ const TrueFalseQuestionDisplay: React.FC = (props) => { {showCorrectAnswers && ( <> -
{!question.isTrue ? '✅' : '❌'} {pickRates.falseRate.toFixed(1)}%
+
{!question.isTrue ? '✅' : '❌'} {pickRates.falseCount}/{pickRates.totalCount} ({pickRates.falseRate.toFixed(1)}%)
)} diff --git a/client/src/components/QuestionsDisplay/questionStyle.css b/client/src/components/QuestionsDisplay/questionStyle.css index 6df5d71..2babc91 100644 --- a/client/src/components/QuestionsDisplay/questionStyle.css +++ b/client/src/components/QuestionsDisplay/questionStyle.css @@ -198,6 +198,6 @@ .pick-rate{ color: rgba(0,0,0,1); - min-width: 70px; + min-width: 120px; } From 7e597348a3cd7824e9a554486c8ce787f7c58c85 Mon Sep 17 00:00:00 2001 From: KenChanA Date: Mon, 17 Mar 2025 00:52:11 -0400 Subject: [PATCH 04/12] Moved buttons around --- .../MultipleChoiceQuestionDisplay.tsx | 166 +++++++++--------- .../TrueFalseQuestionDisplay.tsx | 2 +- 2 files changed, 85 insertions(+), 83 deletions(-) diff --git a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx index ea07695..d951017 100644 --- a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx @@ -72,91 +72,93 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { const alpha = Array.from(Array(26)).map((_e, i) => i + 65); const alphabet = alpha.map((x) => String.fromCharCode(x)); return ( - -
-
-
-
-
- - {question.choices.map((choice, i) => { - const selected = answer === choice.formattedText.text ? 'selected' : ''; - const rateStyle = showCorrectAnswers ? { - backgroundImage: `linear-gradient(to right, ${choice.isCorrect ? 'lightgreen' : 'lightcoral'} ${pickRates.percentages[i]}%, transparent ${pickRates.percentages[i]}%)`, - color: 'black' - } : {}; - return ( -
- {/* */} +
- {choice.formattedFeedback && showAnswer && ( -
-
-
- )} - */} - -
- ); - })} -
- {question.formattedGlobalFeedback && showAnswer && ( -
-
-
- )} - - {!showAnswer && handleOnSubmitAnswer && ( - -
+ {question.formattedGlobalFeedback && showAnswer && ( +
+
+
+ )} - - )} - - {isDisplayOnly && ( -
- + {!showAnswer && handleOnSubmitAnswer && ( + + + )}
- )} + {isDisplayOnly && ( +
+ +
+ )} +
); }; diff --git a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx index 1f77ef7..796614b 100644 --- a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx @@ -105,7 +105,7 @@ const TrueFalseQuestionDisplay: React.FC = (props) => {
{showCorrectAnswers && ( <> -
{question.isTrue ? '✅' : '❌'} {pickRates.trueRate}/{pickRates.totalCount} ({pickRates.trueRate.toFixed(1)}%)
+
{question.isTrue ? '✅' : '❌'} {pickRates.trueCount}/{pickRates.totalCount} ({pickRates.trueRate.toFixed(1)}%)
)} From b9d79fceace60e5e476d6356bf1ad1422b4ef74e Mon Sep 17 00:00:00 2001 From: KenChanA Date: Tue, 1 Apr 2025 00:29:56 -0400 Subject: [PATCH 05/12] Fixed bootstrap format, added toggle button --- .../MultipleChoiceQuestionDisplay.tsx | 37 ++-- .../NumericalQuestionDisplay.tsx | 157 +++++++-------- .../QuestionsDisplay/QuestionDisplay.tsx | 12 +- .../ShortAnswerQuestionDisplay.tsx | 57 ++---- .../TrueFalseQuestionDisplay.tsx | 185 ++++++++---------- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 17 +- 6 files changed, 215 insertions(+), 250 deletions(-) diff --git a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx index 9e055ff..1d7ac9b 100644 --- a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx @@ -14,18 +14,22 @@ interface Props { passedAnswer?: AnswerType; students?: StudentType[]; isDisplayOnly?: boolean; + showResults?: boolean; } const MultipleChoiceQuestionDisplay: React.FC = (props) => { - const { question, showAnswer, handleOnSubmitAnswer, students, isDisplayOnly, passedAnswer } = props; + const { question, showAnswer, handleOnSubmitAnswer, students, showResults, passedAnswer } = props; const [answer, setAnswer] = useState(() => { if (passedAnswer && passedAnswer.length > 0) { return passedAnswer; } return []; }); - const [pickRates, setPickRates] = useState<{ percentages: number[], counts: number[], totalCount: number }>({ percentages: [], counts: [], totalCount: 0 }); - const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); + const [pickRates, setPickRates] = useState<{ percentages: number[], counts: number[], totalCount: number }>({ + percentages: [], + counts: [], + totalCount: 0 + }); let disableButton = false; if (handleOnSubmitAnswer === undefined) { @@ -52,10 +56,6 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { } }); }; - - const toggleShowCorrectAnswers = () => { - setShowCorrectAnswers(!showCorrectAnswers); - }; const calculatePickRates = () => { if (!students || students.length === 0) { @@ -88,11 +88,12 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { setAnswer([]); calculatePickRates(); } - }, [passedAnswer, students, question.id, showCorrectAnswers]); + }, [passedAnswer, students, question.id]); const alpha = Array.from(Array(26)).map((_e, i) => i + 65); const alphabet = alpha.map((x) => String.fromCharCode(x)); + return (
@@ -103,7 +104,7 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => {
{question.choices.map((choice, i) => { const selected = answer.includes(choice.formattedText.text) ? 'selected' : ''; - const rateStyle = showCorrectAnswers ? { + const rateStyle = showResults ? { backgroundImage: `linear-gradient(to right, ${choice.isCorrect ? 'lightgreen' : 'lightcoral'} ${pickRates.percentages[i]}%, transparent ${pickRates.percentages[i]}%)`, color: 'black' } : {}; @@ -134,7 +135,12 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { />
)} - {showCorrectAnswers &&
{choice.isCorrect ? '✅' : '❌'} {`${pickRates.counts[i]}/${pickRates.totalCount} (${pickRates.percentages[i].toFixed(1)}%)`}
} + {showResults && pickRates.percentages.length > i && ( +
+ {choice.isCorrect ? '✅' : '❌'} + {`${pickRates.counts[i]}/${pickRates.totalCount} (${pickRates.percentages[i].toFixed(1)}%)`} +
+ )}
); @@ -162,17 +168,6 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { )}
- {isDisplayOnly && ( -
- -
- )}
); diff --git a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx index 069eaa2..36f0a5a 100644 --- a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx @@ -14,35 +14,29 @@ interface Props { showAnswer?: boolean; passedAnswer?: AnswerType; students?: StudentType[]; - isDisplayOnly?: boolean; + showResults?: boolean; } const NumericalQuestionDisplay: React.FC = (props) => { - const { question, showAnswer, handleOnSubmitAnswer, students, passedAnswer, isDisplayOnly } = + const { question, showAnswer, handleOnSubmitAnswer, students, showResults, passedAnswer } = props; const [answer, setAnswer] = useState(passedAnswer || []); const correctAnswers = question.choices; let correctAnswer = ''; - - const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); const [correctAnswerRate, setCorrectAnswerRate] = useState(0); const [submissionCounts, setSubmissionCounts] = useState({ correctSubmissions: 0, totalSubmissions: 0 }); - const toggleShowCorrectAnswers = () => { - setShowCorrectAnswers(!showCorrectAnswers); - }; - useEffect(() => { if (passedAnswer !== null && passedAnswer !== undefined) { setAnswer(passedAnswer); } - if (showCorrectAnswers && students) { + if (showResults && students) { calculateCorrectAnswerRate(); } - }, [passedAnswer, showCorrectAnswers, students]); + }, [passedAnswer, showResults, students]); const calculateCorrectAnswerRate = () => { if (!students || students.length === 0) { @@ -82,85 +76,78 @@ 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' }} - /> -
- {question.formattedGlobalFeedback && showAnswer && ( -
-
-
- )} - {handleOnSubmitAnswer && ( - - )} - - )} - - - {isDisplayOnly && ( - <> -
- -
-
+ <> +
+
+
- Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions} +
-
-
-
- {correctAnswerRate.toFixed(1)}% + {showAnswer ? ( + <> +
+ La bonne réponse est: + {correctAnswer}
+ + Votre réponse est: {answer.toString()} + + {question.formattedGlobalFeedback &&
+
+
} + + + ) : ( + <> +
+ ) => { + setAnswer([e.target.valueAsNumber]); + }} + inputProps={{ 'data-testid': 'number-input' }} + /> +
+ {question.formattedGlobalFeedback && showAnswer && ( +
+
+
+ )} + {handleOnSubmitAnswer && ( +
+ +
+ )} + + )} +
+ {showResults && ( +
+
+ Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions} +
+
+
+
+ {correctAnswerRate.toFixed(1)}% +
-
- - )} -
+ )} +
+
+ ); }; diff --git a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx index 8ace5fd..2979eff 100644 --- a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx @@ -15,7 +15,7 @@ interface QuestionProps { handleOnSubmitAnswer?: (answer: AnswerType) => void; showAnswer?: boolean; students?: StudentType[]; - isDisplayOnly?: boolean; + showResults?: boolean; answer?: AnswerType; } @@ -24,7 +24,7 @@ const QuestionDisplay: React.FC = ({ handleOnSubmitAnswer, showAnswer, students, - isDisplayOnly = false, + showResults, answer, }) => { // const isMobile = useCheckMobileScreen(); @@ -41,7 +41,7 @@ const QuestionDisplay: React.FC = ({ handleOnSubmitAnswer={handleOnSubmitAnswer} showAnswer={showAnswer} students={students} - isDisplayOnly={isDisplayOnly} + showResults={showResults} passedAnswer={answer} /> ); @@ -54,7 +54,7 @@ const QuestionDisplay: React.FC = ({ handleOnSubmitAnswer={handleOnSubmitAnswer} showAnswer={showAnswer} students={students} - isDisplayOnly={isDisplayOnly} + showResults={showResults} passedAnswer={answer} /> ); @@ -68,7 +68,7 @@ const QuestionDisplay: React.FC = ({ showAnswer={showAnswer} passedAnswer={answer} students={students} - isDisplayOnly={isDisplayOnly} + showResults={showResults} /> ); } @@ -80,7 +80,7 @@ const QuestionDisplay: React.FC = ({ handleOnSubmitAnswer={handleOnSubmitAnswer} showAnswer={showAnswer} students={students} - isDisplayOnly={isDisplayOnly} + showResults={showResults} passedAnswer={answer} /> ); diff --git a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx index 8ee9c15..b28ffbb 100644 --- a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx @@ -12,33 +12,28 @@ interface Props { showAnswer?: boolean; passedAnswer?: AnswerType; students?: StudentType[]; - isDisplayOnly?: boolean; + showResults?: boolean; } const ShortAnswerQuestionDisplay: React.FC = (props) => { - const { question, showAnswer, handleOnSubmitAnswer, students, passedAnswer, isDisplayOnly } = props; + const { question, showAnswer, handleOnSubmitAnswer, students, showResults, passedAnswer } = props; const [answer, setAnswer] = useState(passedAnswer || []); - const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); const [correctAnswerRate, setCorrectAnswerRate] = useState(0); const [submissionCounts, setSubmissionCounts] = useState({ correctSubmissions: 0, totalSubmissions: 0 }); - const toggleShowCorrectAnswers = () => { - setShowCorrectAnswers(!showCorrectAnswers); - }; - useEffect(() => { if (passedAnswer !== undefined) { setAnswer(passedAnswer); } - if (showCorrectAnswers && students) { + if (showResults && students) { calculateCorrectAnswerRate(); } - }, [passedAnswer, showCorrectAnswers, students, answer]); + }, [passedAnswer, showResults, students, answer]); console.log("Answer", answer); const calculateCorrectAnswerRate = () => { @@ -67,7 +62,9 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => {
-
+
+
+
{showAnswer ? ( <>
@@ -104,7 +101,7 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { />
{handleOnSubmitAnswer && ( -
+
- {isDisplayOnly && ( - <> -
- - {showCorrectAnswers && ( -
-
- Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions} -
-
-
-
- {correctAnswerRate.toFixed(1)}% -
-
-
- )} + {showResults && ( +
+
+ Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions}
- - )} +
+
+
+ {correctAnswerRate.toFixed(1)}% +
+
+
+ )}
diff --git a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx index 356b376..a961b6e 100644 --- a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx @@ -13,11 +13,11 @@ interface Props { showAnswer?: boolean; passedAnswer?: AnswerType; students?: StudentType[]; - isDisplayOnly?: boolean; + showResults?: boolean; } const TrueFalseQuestionDisplay: React.FC = (props) => { - const { question, showAnswer, handleOnSubmitAnswer, students, passedAnswer, isDisplayOnly } = props; + const { question, showAnswer, handleOnSubmitAnswer, students, passedAnswer, showResults } = props; const [pickRates, setPickRates] = useState<{ trueRate: number, falseRate: number, trueCount: number, falseCount: number, totalCount: number }>({ trueRate: 0, falseRate: 0, @@ -25,7 +25,6 @@ const TrueFalseQuestionDisplay: React.FC = (props) => { falseCount: 0, totalCount: 0 }); - const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); const [answer, setAnswer] = useState(() => { @@ -61,10 +60,6 @@ const TrueFalseQuestionDisplay: React.FC = (props) => { const selectedTrue = answer ? 'selected' : ''; const selectedFalse = answer !== undefined && !answer ? 'selected' : ''; - const toggleShowCorrectAnswers = () => { - setShowCorrectAnswers(!showCorrectAnswers); - }; - // Calcul le pick rate de chaque réponse const calculatePickRates = () => { if (!students) { @@ -75,12 +70,14 @@ const TrueFalseQuestionDisplay: React.FC = (props) => { const totalAnswers = students.length; const trueAnswers = students.filter(student => student.answers.some(ans => - ans.idQuestion === Number(question.id) && ans.answer === true - )).length; + ans.idQuestion === Number(question.id) && ans.answer.some(a => a === true) + ) + ).length; const falseAnswers = students.filter(student => student.answers.some(ans => - ans.idQuestion === Number(question.id) && ans.answer === false - )).length; + ans.idQuestion === Number(question.id) && ans.answer.some(a => a === false) + ) + ).length; setPickRates({ trueRate: (trueAnswers / totalAnswers) * 100, @@ -92,99 +89,91 @@ const TrueFalseQuestionDisplay: React.FC = (props) => { }; return ( -
-
-
-
-
- + + + +
+ {question.formattedGlobalFeedback && showAnswer && ( +
+
)} - - )} - - {showAnswer && !answer && question.falseFormattedFeedback && ( -
-
-
- )} - - - +
- {question.formattedGlobalFeedback && showAnswer && ( -
-
-
- )} - {!showAnswer && handleOnSubmitAnswer && ( - - )} - - {isDisplayOnly && ( -
- -
- )}
); }; diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 901e133..0d9e59f 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -18,7 +18,7 @@ import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay'; import ApiService from '../../../services/ApiService'; import { QuestionType } from 'src/Types/QuestionType'; -import { Button } from '@mui/material'; +import { Button, FormControlLabel, Switch } from '@mui/material'; import { checkIfIsCorrect } from './useRooms'; const ManageRoom: React.FC = () => { @@ -34,6 +34,7 @@ const ManageRoom: React.FC = () => { const [quizStarted, setQuizStarted] = useState(false); const [formattedRoomName, setFormattedRoomName] = useState(""); const [newlyConnectedUser, setNewlyConnectedUser] = useState(null); + const [showResults, setShowResults] = useState(false); // Handle the newly connected user in useEffect, because it needs state info // not available in the socket.on() callback @@ -407,7 +408,17 @@ const ManageRoom: React.FC = () => { {quizQuestions?.length} )} - + Afficher les résultats
} + control={ + ) => + setShowResults(e.target.checked) + } + /> + } + /> {quizMode === 'teacher' && (
{/* { showAnswer={false} question={currentQuestion?.question as Question} students={students} - isDisplayOnly={true} + showResults={showResults} /> )} From 0f236205132c42eb360dee575fe59ff980f72b6b Mon Sep 17 00:00:00 2001 From: KenChanA Date: Thu, 3 Apr 2025 16:49:54 -0400 Subject: [PATCH 06/12] Added accordions for LiveResult and QuestionDisplay --- client/package-lock.json | 165 +++++++++++++++++- client/package.json | 3 +- .../components/LiveResults/LiveResults.tsx | 88 +++++----- .../QuestionsDisplay/QuestionDisplay.tsx | 47 +++-- .../ShortAnswerQuestionDisplay.tsx | 2 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 15 +- 6 files changed, 249 insertions(+), 71 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index a5e49d7..9423d8a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -19,7 +19,7 @@ "@mui/material": "^6.4.6", "@types/uuid": "^9.0.7", "axios": "^1.8.1", - "bootstrap": "^5.3.3", + "bootstrap": "^5.3.4", "dompurify": "^3.2.3", "esbuild": "^0.25.0", "gift-pegjs": "^2.0.0-beta.1", @@ -29,6 +29,7 @@ "marked": "^14.1.2", "nanoid": "^5.1.2", "react": "^18.3.1", + "react-bootstrap": "^2.10.9", "react-dom": "^18.3.1", "react-modal": "^3.16.3", "react-router-dom": "^6.26.2", @@ -3845,6 +3846,20 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-aria/ssr": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", + "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -3854,6 +3869,56 @@ "node": ">=14.0.0" } }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.34.8", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", @@ -4337,6 +4402,14 @@ "devOptional": true, "license": "Apache-2.0" }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@swc/types": { "version": "0.1.19", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.19.tgz", @@ -4756,6 +4829,11 @@ "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", "license": "MIT" }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -5538,9 +5616,9 @@ "license": "MIT" }, "node_modules/bootstrap": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", - "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.4.tgz", + "integrity": "sha512-q2oK3ZPDTa5I44FTyY3H76+SDTJREvOBxtX1HNLHcxMni50jMvUtOh+dgFdgpsAHtJ9bfNAWr6d6VezJHJ/7tg==", "funding": [ { "type": "github", @@ -5802,6 +5880,11 @@ "dev": true, "license": "MIT" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -7969,6 +8052,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -11130,6 +11221,23 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/prop-types-extra/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -11219,6 +11327,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-bootstrap": { + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.9.tgz", + "integrity": "sha512-TJUCuHcxdgYpOqeWmRApM/Dy0+hVsxNRFvq2aRFQuxhNi/+ivOxC5OdWIeHS3agxvzJ4Ev4nDw2ZdBl9ymd/JQ==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -12482,6 +12620,11 @@ } } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -12650,6 +12793,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", diff --git a/client/package.json b/client/package.json index a09f051..017db7e 100644 --- a/client/package.json +++ b/client/package.json @@ -23,7 +23,7 @@ "@mui/material": "^6.4.6", "@types/uuid": "^9.0.7", "axios": "^1.8.1", - "bootstrap": "^5.3.3", + "bootstrap": "^5.3.4", "dompurify": "^3.2.3", "esbuild": "^0.25.0", "gift-pegjs": "^2.0.0-beta.1", @@ -33,6 +33,7 @@ "marked": "^14.1.2", "nanoid": "^5.1.2", "react": "^18.3.1", + "react-bootstrap": "^2.10.9", "react-dom": "^18.3.1", "react-modal": "^3.16.3", "react-router-dom": "^6.26.2", diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index f165e10..95b6edb 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -1,5 +1,7 @@ // LiveResults.tsx import React, { useState } from 'react'; + +import { Accordion } from 'react-bootstrap'; import { Socket } from 'socket.io-client'; import { QuestionType } from '../../Types/QuestionType'; import './liveResult.css'; @@ -24,50 +26,56 @@ interface LiveResultsProps { const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); + const [isOpen, setIsOpen] = useState(true); + return ( + + + setIsOpen(!isOpen)}> +
Résultats du quiz
+
+ +
+ + Afficher les noms
} + control={ + ) => + setShowUsernames(e.target.checked) + } + /> + } + /> + Afficher les réponses
} + control={ + ) => + setShowCorrectAnswers(e.target.checked) + } + /> + } + /> + +
- -
-
-
Résultats du quiz
- - Afficher les noms
} - control={ - ) => - setShowUsernames(e.target.checked) - } +
+ + - } - /> - Afficher les réponses
} - control={ - ) => - setShowCorrectAnswers(e.target.checked) - } - /> - } - /> - -
- -
- - -
-
+
+ + + ); }; diff --git a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx index 2979eff..fc5d18f 100644 --- a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx @@ -1,6 +1,9 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Question } from 'gift-pegjs'; +import { Accordion } from 'react-bootstrap'; +import { FormControlLabel, Switch } from '@mui/material'; + import TrueFalseQuestionDisplay from './TrueFalseQuestionDisplay/TrueFalseQuestionDisplay'; import MultipleChoiceQuestionDisplay from './MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay'; import NumericalQuestionDisplay from './NumericalQuestionDisplay/NumericalQuestionDisplay'; @@ -24,13 +27,15 @@ const QuestionDisplay: React.FC = ({ handleOnSubmitAnswer, showAnswer, students, - showResults, answer, }) => { // const isMobile = useCheckMobileScreen(); // const imgWidth = useMemo(() => { // return isMobile ? '100%' : '20%'; // }, [isMobile]); + + const [isOpen, setIsOpen] = useState(true); + const [showResults, setShowResults] = useState(false); let questionTypeComponent = null; switch (question?.type) { @@ -87,15 +92,35 @@ const QuestionDisplay: React.FC = ({ break; } return ( -
- {questionTypeComponent ? ( - <> - {questionTypeComponent} - - ) : ( -
Question de type inconnue
- )} -
+ + + setIsOpen(!isOpen)}> + {isOpen ? 'Masquer les questions' : 'Afficher les questions'} + + + Afficher les résultats
} + control={ + ) => + setShowResults(e.target.checked) + } + /> + } + /> +
+ {questionTypeComponent ? ( + <> + {questionTypeComponent} + + ) : ( +
Question de type inconnue
+ )} +
+ + + ); }; diff --git a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx index b28ffbb..3310169 100644 --- a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx @@ -101,7 +101,7 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { />
{handleOnSubmitAnswer && ( -
+
} - control={ - ) => - setShowResults(e.target.checked) - } - /> - } - /> {quizMode === 'teacher' && (
{/* { showAnswer={false} question={currentQuestion?.question as Question} students={students} - showResults={showResults} /> )} From 7abd8b8019505d8c8d68b4b0093789a81687cae2 Mon Sep 17 00:00:00 2001 From: KenChanA Date: Sat, 5 Apr 2025 23:10:46 -0400 Subject: [PATCH 07/12] Fixed question display during quiz --- .../components/LiveResults/LiveResults.tsx | 2 +- .../QuestionsDisplay/QuestionDisplay.tsx | 72 +++++++++++-------- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 1 + 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 95b6edb..4369a4b 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -33,7 +33,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti setIsOpen(!isOpen)}> -
Résultats du quiz
+
{isOpen ? 'Résultats du quiz' : 'Masquer les résultats'}
diff --git a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx index fc5d18f..1ed7887 100644 --- a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx @@ -19,6 +19,7 @@ interface QuestionProps { showAnswer?: boolean; students?: StudentType[]; showResults?: boolean; + showAnswerToggle?: boolean; answer?: AnswerType; } @@ -26,6 +27,7 @@ const QuestionDisplay: React.FC = ({ question, handleOnSubmitAnswer, showAnswer, + showAnswerToggle = false, students, answer, }) => { @@ -92,35 +94,49 @@ const QuestionDisplay: React.FC = ({ break; } return ( - - - setIsOpen(!isOpen)}> - {isOpen ? 'Masquer les questions' : 'Afficher les questions'} - - - Afficher les résultats
} - control={ - ) => - setShowResults(e.target.checked) - } + <> + {showAnswerToggle ? ( + + + setIsOpen(!isOpen)}> + {isOpen ? 'Afficher les questions' : 'Masquer les questions'} + + + Afficher les résultats
} + control={ + ) => + setShowResults(e.target.checked) + } + /> + } /> - } - /> -
- {questionTypeComponent ? ( - <> - {questionTypeComponent} - - ) : ( -
Question de type inconnue
- )} -
- - - +
+ {questionTypeComponent ? ( + <> + {questionTypeComponent} + + ) : ( +
Question de type inconnue
+ )} +
+ + + + ) : ( +
+ {questionTypeComponent ? ( + <> + {questionTypeComponent} + + ) : ( +
Question de type inconnue
+ )} +
+ )} + ); }; diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 27bf7e7..eeac06f 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -423,6 +423,7 @@ const ManageRoom: React.FC = () => { {currentQuestion && ( From 4ee0fd5b177843472d7325fbcd453ad7dda9c370 Mon Sep 17 00:00:00 2001 From: KenChanA Date: Sun, 6 Apr 2025 01:11:42 -0400 Subject: [PATCH 08/12] Added active students in LiveResultsTable --- client/src/Types/StudentType.tsx | 1 + .../TableComponents/LiveResultsTableBody.tsx | 7 ++++++- client/src/pages/Teacher/ManageRoom/ManageRoom.tsx | 7 ++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/client/src/Types/StudentType.tsx b/client/src/Types/StudentType.tsx index 41a4a63..872bdee 100644 --- a/client/src/Types/StudentType.tsx +++ b/client/src/Types/StudentType.tsx @@ -11,4 +11,5 @@ export interface StudentType { id: string; room?: string; answers: Answer[]; + isActive?: boolean; } diff --git a/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx index a0c67f7..adaba37 100644 --- a/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx +++ b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx @@ -25,7 +25,12 @@ const LiveResultsTableFooter: React.FC = ({ return ( {students.map((student) => ( - + { socket.on('user-disconnected', (userId: string) => { console.log(`Student left: id = ${userId}`); - setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId)); + //setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId)); + setStudents(prevStudents => + prevStudents.map(student => + student.id === userId ? { ...student, isActive: false } : student + ) + ); }); setSocket(socket); From 377c363522c39cf735b854ea6de39a7a80a8c352 Mon Sep 17 00:00:00 2001 From: KenChanA Date: Wed, 23 Apr 2025 16:16:50 -0400 Subject: [PATCH 09/12] Quick fix accordion header --- client/src/components/LiveResults/LiveResults.tsx | 10 ++++++---- .../components/QuestionsDisplay/QuestionDisplay.tsx | 12 ++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 4369a4b..e77a25e 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -26,14 +26,16 @@ interface LiveResultsProps { const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); - const [isOpen, setIsOpen] = useState(true); + const [activeKey, setActiveKey] = useState('0'); + const toggleAccordion = () => { + setActiveKey(activeKey === '0' ? null : '0'); return ( - + - setIsOpen(!isOpen)}> -
{isOpen ? 'Résultats du quiz' : 'Masquer les résultats'}
+ +
{activeKey === '0' ? 'Résultats du quiz' : 'Masquer les résultats'}
diff --git a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx index 1ed7887..edf99f2 100644 --- a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx @@ -36,8 +36,12 @@ const QuestionDisplay: React.FC = ({ // return isMobile ? '100%' : '20%'; // }, [isMobile]); - const [isOpen, setIsOpen] = useState(true); const [showResults, setShowResults] = useState(false); + const [activeKey, setActiveKey] = useState('0'); + + const toggleAccordion = () => { + setActiveKey(activeKey === '0' ? null : '0'); + }; let questionTypeComponent = null; switch (question?.type) { @@ -96,10 +100,10 @@ const QuestionDisplay: React.FC = ({ return ( <> {showAnswerToggle ? ( - + - setIsOpen(!isOpen)}> - {isOpen ? 'Afficher les questions' : 'Masquer les questions'} + + {activeKey === '0' ? 'Afficher les questions' : 'Masquer les questions'} Date: Wed, 23 Apr 2025 16:17:06 -0400 Subject: [PATCH 10/12] + --- client/src/components/LiveResults/LiveResults.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index e77a25e..cad75b5 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -30,6 +30,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti const toggleAccordion = () => { setActiveKey(activeKey === '0' ? null : '0'); + }; return ( From 22dc7a5399469cd84e97505b941cc14478a7130f Mon Sep 17 00:00:00 2001 From: KenChanA Date: Wed, 23 Apr 2025 21:41:06 -0400 Subject: [PATCH 11/12] Removed accordion and updated versions --- .../components/LiveResults/LiveResults.tsx | 89 ++++++++----------- .../QuestionsDisplay/QuestionDisplay.tsx | 56 ++++-------- package-lock.json | 31 ++++++- package.json | 4 +- 4 files changed, 87 insertions(+), 93 deletions(-) diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index cad75b5..a35a672 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -1,7 +1,6 @@ // LiveResults.tsx import React, { useState } from 'react'; -import { Accordion } from 'react-bootstrap'; import { Socket } from 'socket.io-client'; import { QuestionType } from '../../Types/QuestionType'; import './liveResult.css'; @@ -26,59 +25,47 @@ interface LiveResultsProps { const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); - const [activeKey, setActiveKey] = useState('0'); - - const toggleAccordion = () => { - setActiveKey(activeKey === '0' ? null : '0'); - }; return ( - - - -
{activeKey === '0' ? 'Résultats du quiz' : 'Masquer les résultats'}
-
- -
- - Afficher les noms
} - control={ - ) => - setShowUsernames(e.target.checked) - } - /> - } - /> - Afficher les réponses
} - control={ - ) => - setShowCorrectAnswers(e.target.checked) - } - /> - } - /> - -
- -
- - +
+ + Afficher les noms
} + control={ + ) => + setShowUsernames(e.target.checked) + } /> -
- - - + } + /> + Afficher les réponses
} + control={ + ) => + setShowCorrectAnswers(e.target.checked) + } + /> + } + /> + +
+ +
+ + +
+
); }; diff --git a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx index edf99f2..6b31ee2 100644 --- a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import { Question } from 'gift-pegjs'; -import { Accordion } from 'react-bootstrap'; import { FormControlLabel, Switch } from '@mui/material'; import TrueFalseQuestionDisplay from './TrueFalseQuestionDisplay/TrueFalseQuestionDisplay'; @@ -37,11 +36,6 @@ const QuestionDisplay: React.FC = ({ // }, [isMobile]); const [showResults, setShowResults] = useState(false); - const [activeKey, setActiveKey] = useState('0'); - - const toggleAccordion = () => { - setActiveKey(activeKey === '0' ? null : '0'); - }; let questionTypeComponent = null; switch (question?.type) { @@ -99,38 +93,21 @@ const QuestionDisplay: React.FC = ({ } return ( <> - {showAnswerToggle ? ( - - - - {activeKey === '0' ? 'Afficher les questions' : 'Masquer les questions'} - - - Afficher les résultats
} - control={ - ) => - setShowResults(e.target.checked) - } - /> - } - /> -
- {questionTypeComponent ? ( - <> - {questionTypeComponent} - - ) : ( -
Question de type inconnue
- )} -
- - - - ) : ( -
+ {showAnswerToggle && ( + Afficher les résultats
} + control={ + ) => + setShowResults(e.target.checked) + } + /> + } + /> + )} + +
{questionTypeComponent ? ( <> {questionTypeComponent} @@ -138,8 +115,7 @@ const QuestionDisplay: React.FC = ({ ) : (
Question de type inconnue
)} -
- )} +
); }; diff --git a/package-lock.json b/package-lock.json index 144d2f8..fb5535d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,18 @@ "packages": { "": { "dependencies": { - "axios-mock-adapter": "^2.1.0" + "@popperjs/core": "^2.11.8", + "axios-mock-adapter": "^2.1.0", + "bootstrap": "^5.3.5" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" } }, "node_modules/asynckit": { @@ -37,6 +48,24 @@ "axios": ">= 0.17.0" } }, + "node_modules/bootstrap": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.5.tgz", + "integrity": "sha512-ct1CHKtiobRimyGzmsSldEtM03E8fcEX4Tb3dGXz1V8faRwM50+vfHwTzOxB3IlKO7m+9vTH3s/3C6T2EAPeTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", diff --git a/package.json b/package.json index a8332a4..ca7551a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,7 @@ { "dependencies": { - "axios-mock-adapter": "^2.1.0" + "@popperjs/core": "^2.11.8", + "axios-mock-adapter": "^2.1.0", + "bootstrap": "^5.3.5" } } From 7b69071672046d5aac418f4e40a7ae60dfe32968 Mon Sep 17 00:00:00 2001 From: KenChanA Date: Thu, 24 Apr 2025 18:01:54 -0400 Subject: [PATCH 12/12] Added tests --- .../MultipleChoiceQuestionDisplay.test.tsx | 28 ++++++++++ .../NumericalQuestionDisplay.test.tsx | 33 ++++++++++++ .../QuestionsDisplay/Question.test.tsx | 9 +++- .../ShortAnswerQuestionDisplay.test.tsx | 51 +++++++++++++++++++ .../TrueFalseQuestionDisplay.test.tsx | 39 ++++++++++++++ .../components/LiveResults/LiveResults.tsx | 1 + 6 files changed, 159 insertions(+), 2 deletions(-) diff --git a/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx index 45e9b0a..415524e 100644 --- a/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx @@ -209,5 +209,33 @@ describe('MultipleChoiceQuestionDisplay', () => { expect(wrongAnswer1?.textContent).not.toContain('❌'); }); + it('calculates and displays pick rates correctly when showResults is true', () => { + const question = parse(`::MCQ:: What is 2+2? { + =Four + ~Three + ~Five + }`)[0] as MultipleChoiceQuestion; + + const mockStudents = [ + { id: '1', name: 'Alice', answers: [{ idQuestion: 1, answer: ['Four'], isCorrect: true }] }, + { id: '2', name: 'Bob', answers: [{ idQuestion: 1, answer: ['Three'], isCorrect: false }] }, + { id: '3', name: 'Charlie', answers: [{ idQuestion: 1, answer: ['Four'], isCorrect: true }] } + ]; + + render( + + ); + + // Expect pick rate for "Four" to be 2/3 + expect(screen.getByText('✅2/3 (66.7%)')).toBeInTheDocument(); + + // Expect pick rate for "Three" to be 1/3 + expect(screen.getByText('❌1/3 (33.3%)')).toBeInTheDocument(); + }); + }); diff --git a/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx index 5c32547..697ddca 100644 --- a/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx @@ -81,4 +81,37 @@ describe('NumericalQuestion Component', () => { expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith([7]); mockHandleOnSubmitAnswer.mockClear(); }); + + it('calculates and displays correct answer rate when showResults is true', () => { + const mockStudents = [ + { + id: '1', + name: 'Alice', + answers: [{ idQuestion: 1, answer: [7], isCorrect: true }] + }, + { + id: '2', + name: 'Bob', + answers: [{ idQuestion: 1, answer: [3], isCorrect: false }] + }, + { + id: '3', + name: 'Charlie', + answers: [{ idQuestion: 1, answer: [6], isCorrect: true }] + } + ]; + + render( + + + + ); + + expect(screen.getByText('Taux de réponse correcte: 2/3')).toBeInTheDocument(); + expect(screen.getByText('66.7%')).toBeInTheDocument(); + }); }); diff --git a/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx b/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx index 142e563..83afa57 100644 --- a/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx @@ -25,8 +25,8 @@ describe('Questions Component', () => { showAnswer: false }; - const renderComponent = (question: Question) => { - render(); + const renderComponent = (question: Question, showAnswerToggle = false) => { + render(); }; // describe('question type parsing', () => { @@ -122,6 +122,11 @@ describe('Questions Component', () => { expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['User Input']); }); + + it('shows "Afficher les résultats" toggle when showAnswerToggle is true', () => { + renderComponent(sampleTrueFalseQuestion, true); + expect(screen.getByText('Afficher les résultats')).toBeInTheDocument(); + }); }); diff --git a/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx index 57e9da5..687f6a4 100644 --- a/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx @@ -3,6 +3,7 @@ import { render, screen, fireEvent, within } from '@testing-library/react'; import '@testing-library/jest-dom'; import { parse, ShortAnswerQuestion } from 'gift-pegjs'; import ShortAnswerQuestionDisplay from 'src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay'; +import { MemoryRouter } from 'react-router-dom'; describe('ShortAnswerQuestion Component', () => { const mockHandleSubmitAnswer = jest.fn(); @@ -64,4 +65,54 @@ describe('ShortAnswerQuestion Component', () => { expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['User Input']); mockHandleSubmitAnswer.mockClear(); }); + + + it('calculates and displays correct answer rate when showResults is true', () => { + const mockStudents = [ + { + id: '1', + name: 'Alice', + answers: [{ idQuestion: 1, answer: ['Paris'], isCorrect: true }] + }, + { + id: '2', + name: 'Bob', + answers: [{ idQuestion: 1, answer: ['Lyon'], isCorrect: false }] + }, + { + id: '3', + name: 'Charlie', + answers: [{ idQuestion: 1, answer: ['Paris'], isCorrect: true }] + } + ]; + + + const question: ShortAnswerQuestion = { + id: '1', + type: 'Short', + hasEmbeddedAnswers: false, + formattedStem: { + text: 'What is the capital of France?', + format: 'html' + }, + choices: [{ text: 'Paris', isCorrect: true }], + formattedGlobalFeedback: { + text: '', + format: 'html' + } + }; + + render( + + + + ); + + expect(screen.getByText('Taux de réponse correcte: 2/3')).toBeInTheDocument(); + expect(screen.getByText('66.7%')).toBeInTheDocument(); + }); }); diff --git a/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx index f79d1da..87ce9cb 100644 --- a/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx @@ -134,4 +134,43 @@ describe('TrueFalseQuestion Component', () => { expect(wrongAnswer1).toBeInTheDocument(); expect(wrongAnswer1?.textContent).not.toContain('❌'); }); + + it('calculates and displays pick rates correctly when showResults is true', () => { + const mockStudents = [ + { + id: '1', + name: 'Alice', + answers: [{ idQuestion: 1, answer: [true], isCorrect: true }] + }, + { + id: '2', + name: 'Bob', + answers: [{ idQuestion: 1, answer: [false], isCorrect: false }] + } + ]; + + render( + + + + ); + + const pickRateDivs = screen.getAllByText((_, element) => + element !== null && + (element as HTMLElement).classList.contains('pick-rate') && + (element as HTMLElement).textContent!.includes('1/2') + ); + expect(pickRateDivs.length).toBe(2); + + const percentDivs = screen.getAllByText((_, element) => + element !== null && + (element as HTMLElement).classList.contains('pick-rate') && + (element as HTMLElement).textContent!.includes('50.0%') + ); + expect(percentDivs.length).toBe(2); + }); }); diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index a35a672..17ef726 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -29,6 +29,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti return (
+
Résultats du quiz
Afficher les noms
}