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

Fixes #291
This commit is contained in:
JubaAzul 2025-03-13 18:50:29 -04:00
parent 112062c0b2
commit 61a5cf1330
6 changed files with 168 additions and 50 deletions

View file

@ -16,19 +16,29 @@ interface Props {
const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => { const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props; const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props;
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || ''); const [answer, setAnswer] = useState<AnswerType>(passedAnswer || '');
const [isGoodAnswer, setisGoodAnswer] = useState<boolean>(false);
let disableButton = false; let disableButton = false;
if(handleOnSubmitAnswer === undefined){ if (handleOnSubmitAnswer === undefined) {
disableButton = true; disableButton = true;
} }
useEffect(() => { useEffect(() => {
if (passedAnswer !== undefined) { if (passedAnswer !== undefined) {
setAnswer(passedAnswer); setAnswer(passedAnswer);
} }
}, [passedAnswer]); }, [passedAnswer]);
useEffect(() => {
checkAnswer();
}, [answer]);
const checkAnswer = () => {
const isCorrect = question.choices.some((choice) => choice.formattedText.text === answer as string);
setisGoodAnswer(isCorrect);
};
const handleOnClickAnswer = (choice: string) => { const handleOnClickAnswer = (choice: string) => {
setAnswer(choice); setAnswer(choice);
}; };
@ -36,7 +46,19 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
const alphabet = alpha.map((x) => String.fromCharCode(x)); const alphabet = alpha.map((x) => String.fromCharCode(x));
return ( return (
<div className="question-container">
<div className="question-wrapper">
{showAnswer && (
<div>
<div className='question-feedback-validation'>
{isGoodAnswer ? '✅ Correct! ' : '❌ Incorrect!'}
</div>
<div className="question-title">
Question :
</div>
</div>
)}
<div className="question content"> <div className="question content">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} /> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
</div> </div>
@ -51,17 +73,17 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
className="button-wrapper" className="button-wrapper"
disabled={disableButton} disabled={disableButton}
onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)}> onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)}>
{showAnswer? (<div> {(choice.isCorrect ? '✅' : '❌')}</div>) {showAnswer ? (<div> {(choice.isCorrect ? '✅' : '❌')}</div>)
:``} : ``}
<div className={`circle ${selected}`}>{alphabet[i]}</div> <div className={`circle ${selected}`}>{alphabet[i]}</div>
<div className={`answer-text ${selected}`}> <div className={`answer-text ${selected}`}>
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedText) }} /> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedText) }} />
</div> </div>
{choice.formattedFeedback && showAnswer && ( {choice.formattedFeedback && showAnswer && (
<div className="feedback-container mb-1 mt-1/2"> <div className="feedback-container mb-1 mt-1/2">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedFeedback) }} /> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedFeedback) }} />
</div> </div>
)} )}
</Button> </Button>
</div> </div>
@ -70,8 +92,8 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
</div> </div>
{question.formattedGlobalFeedback && showAnswer && ( {question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2"> <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} /> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
</div> </div>
)} )}
{!showAnswer && handleOnSubmitAnswer && ( {!showAnswer && handleOnSubmitAnswer && (

View file

@ -18,6 +18,7 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } =
props; props;
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || ''); const [answer, setAnswer] = useState<AnswerType>(passedAnswer || '');
const [isGoodAnswer, setisGoodAnswer] = useState<boolean>(false);
const correctAnswers = question.choices; const correctAnswers = question.choices;
let correctAnswer = ''; let correctAnswer = '';
@ -27,6 +28,14 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
} }
}, [passedAnswer]); }, [passedAnswer]);
useEffect(() => {
checkAnswer();
}, [answer]);
const checkAnswer = () => {
const isCorrect = correctAnswer === answer;
setisGoodAnswer(isCorrect);
};
//const isSingleAnswer = correctAnswers.length === 1; //const isSingleAnswer = correctAnswers.length === 1;
if (isSimpleNumericalAnswer(correctAnswers[0])) { if (isSimpleNumericalAnswer(correctAnswers[0])) {
@ -45,21 +54,40 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
return ( return (
<div className="question-wrapper"> <div className="question-wrapper">
{showAnswer && (
<div>
<div className='question-feedback-validation'>
{isGoodAnswer ? '✅ Correct! ' : '❌ Incorrect!'}
</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-2"> <div className="correct-answer-text mb-1">
<strong>La bonne réponse est: </strong> <div>
{correctAnswer}</div> <div className="question-title">
<span> Réponse(s) accepté(es):
<strong>Votre réponse est: </strong>{answer.toString()} </div>
</span> <div className="accepted-answers">
{correctAnswer}
</div>
</div>
<div>
<div className="question-title">
Votre réponse est: </div>
<div className="accepted-answers">{answer}</div>
</div>
</div>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2"> {question.formattedGlobalFeedback && <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} /> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
</div>} </div>}
</> </>
) : ( ) : (
<> <>

View file

@ -17,34 +17,59 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props; const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props;
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || ''); const [answer, setAnswer] = useState<AnswerType>(passedAnswer || '');
const [isGoodAnswer, setisGoodAnswer] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
if (passedAnswer !== undefined) { if (passedAnswer !== undefined) {
setAnswer(passedAnswer); setAnswer(passedAnswer);
} }
}, [passedAnswer]); }, [passedAnswer]);
console.log("Answer" , answer);
useEffect(() => {
checkAnswer();
}, [answer]);
const checkAnswer = () => {
const isCorrect = question.choices.some((choice) => choice.text.toLowerCase() === (answer as String).toLowerCase());
setisGoodAnswer(isCorrect);
};
return ( return (
<div className="question-wrapper"> <div className="question-wrapper">
<div className="question content"> {showAnswer && (
<div>
<div className='question-feedback-validation'>
{isGoodAnswer ? '✅ Correct! ' : '❌ Incorrect!'}
</div>
<div className="question-title">
Question :
</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">
<span> <div>
<strong>La bonne réponse est: </strong> <div className="question-title">
Réponse(s) accepté(es):
{question.choices.map((choice) => (
<div key={choice.text} className="mb-1">
{choice.text}
</div> </div>
))} {question.choices.map((choice) => (
</span> <div key={choice.text} className="accepted-answers">
<span> {choice.text}
<strong>Votre réponse est: </strong>{answer} </div>
</span> ))}
</div>
<div>
<div className="question-title">
Votre réponse est: </div>
<div className="accepted-answers">{answer}</div>
</div>
</div> </div>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2"> {question.formattedGlobalFeedback && <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} /> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />

View file

@ -47,7 +47,19 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
const selectedTrue = answer ? 'selected' : ''; const selectedTrue = answer ? 'selected' : '';
const selectedFalse = answer !== undefined && !answer ? 'selected' : ''; const selectedFalse = answer !== undefined && !answer ? 'selected' : '';
return ( return (
<div className="question-container"> <div className="question-wrapper">
{showAnswer && (
<div>
<div className='question-feedback-validation'>
{question.isTrue? '✅ Correct! ' : '❌ Incorrect!'}
</div>
<div className="question-title">
Question :
</div>
</div>
)}
<div className="question content"> <div className="question content">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} /> <div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
</div> </div>

View file

@ -2,7 +2,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center;
width: 100%; width: 100%;
} }
@ -22,7 +21,6 @@
.question-wrapper { .question-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center;
justify-content: center; justify-content: center;
} }
@ -136,12 +134,12 @@
/* height: 1em; */ /* height: 1em; */
} }
.global-feedback { .global-feedback {
position: relative; position: relative;
padding: 0 1rem; padding: 0 1rem;
background-color: hsl(43, 100%, 94%); background-color: hsl(43, 100%, 94%);
color: hsl(43, 95%, 9%); color: hsl(43, 95%, 9%);
margin-top: 2rem;
border: hsl(36, 84%, 93%) 1px solid; border: hsl(36, 84%, 93%) 1px solid;
border-radius: 6px; border-radius: 6px;
box-shadow: 0px 2px 5px hsl(0, 0%, 74%); box-shadow: 0px 2px 5px hsl(0, 0%, 74%);
@ -169,3 +167,39 @@
.choices-wrapper { .choices-wrapper {
width: 90%; width: 90%;
} }
.correct-answer-text {
text-align: left; /* Align text to the left */
width: 100%; /* Ensure it takes the full width of its container */
}
.accepted-answers {
display: inline-block; /* Display elements side by side */
border-radius: 6px; /* Add rounded corners */
padding: 0.5rem; /* Add padding for spacing */
margin: 0.5rem; /* Add margin for spacing between elements */
background-color: hsl(0, 0%, 87%); /* Optional: Add a background color for emphasis */
}
.question-feedback-validation{
font-size: 1.8rem; /* Increase the font size */
font-weight: bold; /* Make the text bold */
width: 100%;
height: 100%;
margin: 0 auto;
}
.question-title{
margin-bottom: 0.5rem;
margin-top: 1.5rem;
width: 100%;
font-weight: bold;
}
.question-content{
margin-bottom: 1rem;
}

View file

@ -4,7 +4,7 @@ import QuestionComponent from '../QuestionsDisplay/QuestionDisplay';
import '../../pages/Student/JoinRoom/joinRoom.css'; import '../../pages/Student/JoinRoom/joinRoom.css';
import { QuestionType } from '../../Types/QuestionType'; import { QuestionType } from '../../Types/QuestionType';
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'; import { Dialog, DialogContent, DialogActions, Button } from '@mui/material';
import { Question } from 'gift-pegjs'; import { Question } from 'gift-pegjs';
import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService'; import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService';
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
@ -94,7 +94,6 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
open={isFeedbackDialogOpen} open={isFeedbackDialogOpen}
onClose={handleFeedbackDialogClose} onClose={handleFeedbackDialogClose}
> >
<DialogTitle>Rétroaction</DialogTitle>
<DialogContent> <DialogContent>
<div style={{ <div style={{
wordWrap: 'break-word', wordWrap: 'break-word',
@ -102,8 +101,6 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
maxHeight: '400px', maxHeight: '400px',
overflowY: 'auto', overflowY: 'auto',
}}> }}>
<div style={{ textAlign: 'left', fontWeight: 'bold', marginTop: '10px' }}
>Question : </div>
</div> </div>
<QuestionComponent <QuestionComponent