mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
[FEATURE] Ajout des rétroactions pour les questions à choix multiples et réponses courtes
Fixes #291
This commit is contained in:
parent
0f52440946
commit
459b30f63c
1 changed files with 129 additions and 77 deletions
|
|
@ -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 className="question-title">
|
||||||
|
Question :
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="question-title">
|
|
||||||
Question :
|
|
||||||
</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>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue