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 = () => { )}