[FEATURE] Ajout des rétroactions pour les questions à choix multiples et réponses courtes

Fixes #291
This commit is contained in:
JubaAzul 2025-03-21 18:58:18 -04:00
parent 0f52440946
commit 459b30f63c

View file

@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
import '../questionStyle.css'; import '../questionStyle.css';
import { Button, TextField } from '@mui/material'; import { Button, TextField } from '@mui/material';
import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate';
import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; import { MultipleNumericalAnswer, NumericalQuestion } from 'gift-pegjs';
import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards'; import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards';
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
@ -19,105 +19,157 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
props; props;
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || ''); const [answer, setAnswer] = useState<AnswerType>(passedAnswer || '');
const [isGoodAnswer, setisGoodAnswer] = useState<boolean>(false); const [isGoodAnswer, setisGoodAnswer] = useState<boolean>(false);
const [isMultpleAnswer, setIsMultpleAnswer] = useState<boolean>(false); let isMultpleAnswer = false;
const correctAnswers = question.choices; const correctAnswers = question.choices;
const correctAnswersList: number[] = [];
const correctAnswersPhrases: string[] = []; const correctAnswersPhrases: string[] = [];
let correctAnswer = ''; let correctAnswer = '';
useEffect(() => { useEffect(() => {
if (passedAnswer !== null && passedAnswer !== undefined) { if (passedAnswer !== null && passedAnswer !== undefined) {
setAnswer(passedAnswer); setAnswer(passedAnswer);
} }
}, [passedAnswer]); }, [passedAnswer]);
useEffect(() => { useEffect(() => {
checkAnswer(); checkAnswer();
}, [answer]); }, [answer]);
const checkAnswer = () => { const isValidWeight = (weight?: number) => weight === undefined || weight > 0;
if(isMultpleAnswer) {
correctAnswers.forEach((answers) => { const isAnswerCorrect = (answer: number, correctAnswer: any): boolean => {
if(isSimpleNumericalAnswer(answers) && answer === answers.number) { if (isSimpleNumericalAnswer(correctAnswer)) {
setisGoodAnswer(true); return answer === correctAnswer.number;
} else if(isRangeNumericalAnswer(answers) && answer as number >= answers.number - answers.range && answer as number <= answers.number + answers.range) { } else if (isRangeNumericalAnswer(correctAnswer)) {
setisGoodAnswer(true); return (
} else if(isHighLowNumericalAnswer(answers) && answer as number >= answers.numberLow && answer as number <= answers.numberHigh) { answer >= correctAnswer.number - correctAnswer.range &&
setisGoodAnswer(true); answer <= correctAnswer.number + correctAnswer.range
} );
} } else if (isHighLowNumericalAnswer(correctAnswer)) {
) return (
; answer >= correctAnswer.numberLow &&
} else { answer <= correctAnswer.numberHigh
if(isSimpleNumericalAnswer(answers) && answer === answers.number) { );
setisGoodAnswer(true); }
} else if(isRangeNumericalAnswer(answers) && answer as number >= answers.number - answers.range && answer as number <= answers.number + answers.range) { return false;
setisGoodAnswer(true);
} else if(isHighLowNumericalAnswer(answers) && answer as number >= answers.numberLow && answer as number <= answers.numberHigh) {
setisGoodAnswer(true);
}
};
}; };
//const isSingleAnswer = correctAnswers.length === 1; const formatCorrectAnswer = (correctAnswer: any): string => {
if (isSimpleNumericalAnswer(correctAnswer)) {
return `${correctAnswer.number}`;
} else if (isRangeNumericalAnswer(correctAnswer)) {
return `Entre ${correctAnswer.number - correctAnswer.range} et ${correctAnswer.number + correctAnswer.range}`;
} else if (isHighLowNumericalAnswer(correctAnswer)) {
return `Entre ${correctAnswer.numberLow} et ${correctAnswer.numberHigh}`;
}
return '';
};
if (isSimpleNumericalAnswer(correctAnswers[0])) { const checkAnswer = () => {
correctAnswer = `${(correctAnswers[0] as SimpleNumericalAnswer).number}`; if (isMultpleAnswer) {
} else if (isRangeNumericalAnswer(correctAnswers[0])) { correctAnswers.forEach((answers) => {
const choice = correctAnswers[0] as RangeNumericalAnswer; if (
correctAnswer = `Entre ${choice.number - choice.range} et ${choice.number + choice.range}`; isMultipleNumericalAnswer(answers) &&
} else if (isHighLowNumericalAnswer(correctAnswers[0])) { isValidWeight(answers.weight) &&
const choice = correctAnswers[0] as HighLowNumericalAnswer; isAnswerCorrect(answer as number, answers.answer)
correctAnswer = `Entre ${choice.numberLow} et ${choice.numberHigh}`; ) {
} else if (isMultipleNumericalAnswer(correctAnswers[0])) { setisGoodAnswer(true);
setIsMultpleAnswer(true); }
});
} else {
const firstAnswer = correctAnswers[0];
if (isAnswerCorrect(answer as number, firstAnswer)) {
setisGoodAnswer(true);
}
}
};
if (isMultipleNumericalAnswer(correctAnswers[0])) {
correctAnswers.forEach((answers) => { correctAnswers.forEach((answers) => {
if(isSimpleNumericalAnswer(answers)) { if (
correctAnswersPhrases.push(`${(answers as SimpleNumericalAnswer).number}`); isMultipleNumericalAnswer(answers) &&
} else if(isRangeNumericalAnswer(answers)) { isValidWeight(answers.weight)
correctAnswersPhrases.push(`Entre ${answers.number - answers.range} et ${answers.number + answers.range}`); ) {
} else if(isHighLowNumericalAnswer(answers)) { correctAnswersPhrases.push(formatCorrectAnswer(answers.answer));
correctAnswersPhrases.push(`Entre ${answers.numberLow} et ${answers.numberHigh}`);
} }
}); });
} isMultpleAnswer = true;
else { } else {
throw new Error('Unknown numerical answer type'); correctAnswer = formatCorrectAnswer(correctAnswers[0]);
} }
return ( return (
<div className="question-wrapper"> <div className="question-wrapper">
{showAnswer && ( {showAnswer && (
<div> <div>
<div className='question-feedback-validation'> <div className='question-feedback-validation'>
{isGoodAnswer ? '✅ Correct! ' : '❌ Incorrect!'} {isGoodAnswer ? '✅ Correct! ' : '❌ Incorrect!'}
</div> </div>
<div className="question-title"> <div className="question-title">
Question : Question :
</div> </div>
</div> </div>
)} )}
<div> <div>
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} /> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
</div> </div>
{showAnswer ? ( {showAnswer ? (
<> <>
<div className="correct-answer-text mb-1"> <div className="correct-answer-text mb-1">
<div> <div>
<div className="question-title"> <div className="question-title">
Réponse(s) accepté(es): Réponse(s) accepté(es):
</div> </div>
<div className="accepted-answers"> {isMultpleAnswer ? (
{correctAnswer} correctAnswersPhrases.map((phrase) => (
</div> <div key={phrase} className="accepted-answers">
{phrase}
</div>
))
) : (
<div className="accepted-answers">{correctAnswer}</div>
)}
</div> </div>
<div> <div>
<div className="question-title"> <div className="question-title">
Votre réponse est: </div> Votre réponse est: </div>
<div className="accepted-answers">{answer}</div> <span>
<div className="accepted-answers">{answer}</div>
{isMultpleAnswer && (() => {
const highestPriorityAnswer = correctAnswers
.filter((correctAnswer) =>
isAnswerCorrect(answer as number, (correctAnswer as MultipleNumericalAnswer).answer)
) // Filter answers that return true for isAnswerCorrect
.reduce((prev, current) => {
const prevWeight = (prev as MultipleNumericalAnswer).weight ?? -1; // Treat undefined as highest priority
const currentWeight = (current as MultipleNumericalAnswer).weight ?? -1; // Treat undefined as highest priority
// Prioritize undefined weights
if (prevWeight === -1 && currentWeight !== -1) return prev;
if (currentWeight === -1 && prevWeight !== -1) return current;
// Otherwise, return the one with the smallest weight
return currentWeight < prevWeight ? current : prev;
});
return isAnswerCorrect(answer as number, (highestPriorityAnswer as MultipleNumericalAnswer).answer) && (
<div>
{(highestPriorityAnswer as MultipleNumericalAnswer).formattedFeedback && (
<div className="global-feedback">
<div
dangerouslySetInnerHTML={{
__html: FormattedTextTemplate(
(highestPriorityAnswer as MultipleNumericalAnswer).formattedFeedback!
),
}}
/>
</div>
)}
</div>
);
})()}
</span>
</div> </div>
</div> </div>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2"> {question.formattedGlobalFeedback && <div className="global-feedback mb-2">
@ -143,19 +195,19 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
</div> </div>
)} )}
{handleOnSubmitAnswer && ( {handleOnSubmitAnswer && (
<div className="submit-button-container"> <div className="submit-button-container">
<Button <Button
className='submit-button' className='submit-button'
variant="contained" variant="contained"
onClick={() => onClick={() =>
answer !== undefined && answer !== undefined &&
handleOnSubmitAnswer && handleOnSubmitAnswer &&
handleOnSubmitAnswer(answer) handleOnSubmitAnswer(answer)
} }
disabled={answer === "" || isNaN(answer as number)} disabled={answer === "" || isNaN(answer as number)}
> >
Répondre Répondre
</Button> </Button>
</div> </div>
)} )}
</> </>