mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Fixed bootstrap format, added toggle button
This commit is contained in:
parent
198eb786fc
commit
b9d79fceac
6 changed files with 215 additions and 250 deletions
|
|
@ -14,18 +14,22 @@ interface Props {
|
||||||
passedAnswer?: AnswerType;
|
passedAnswer?: AnswerType;
|
||||||
students?: StudentType[];
|
students?: StudentType[];
|
||||||
isDisplayOnly?: boolean;
|
isDisplayOnly?: boolean;
|
||||||
|
showResults?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
const { question, showAnswer, handleOnSubmitAnswer, students, isDisplayOnly, passedAnswer } = props;
|
const { question, showAnswer, handleOnSubmitAnswer, students, showResults, passedAnswer } = props;
|
||||||
const [answer, setAnswer] = useState<AnswerType>(() => {
|
const [answer, setAnswer] = useState<AnswerType>(() => {
|
||||||
if (passedAnswer && passedAnswer.length > 0) {
|
if (passedAnswer && passedAnswer.length > 0) {
|
||||||
return passedAnswer;
|
return passedAnswer;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
const [pickRates, setPickRates] = useState<{ percentages: number[], counts: number[], totalCount: number }>({ percentages: [], counts: [], totalCount: 0 });
|
const [pickRates, setPickRates] = useState<{ percentages: number[], counts: number[], totalCount: number }>({
|
||||||
const [showCorrectAnswers, setShowCorrectAnswers] = useState(false);
|
percentages: [],
|
||||||
|
counts: [],
|
||||||
|
totalCount: 0
|
||||||
|
});
|
||||||
|
|
||||||
let disableButton = false;
|
let disableButton = false;
|
||||||
if (handleOnSubmitAnswer === undefined) {
|
if (handleOnSubmitAnswer === undefined) {
|
||||||
|
|
@ -53,10 +57,6 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleShowCorrectAnswers = () => {
|
|
||||||
setShowCorrectAnswers(!showCorrectAnswers);
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculatePickRates = () => {
|
const calculatePickRates = () => {
|
||||||
if (!students || students.length === 0) {
|
if (!students || students.length === 0) {
|
||||||
setPickRates({ percentages: new Array(question.choices.length).fill(0), counts: new Array(question.choices.length).fill(0), totalCount: 0 });
|
setPickRates({ percentages: new Array(question.choices.length).fill(0), counts: new Array(question.choices.length).fill(0), totalCount: 0 });
|
||||||
|
|
@ -88,11 +88,12 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
setAnswer([]);
|
setAnswer([]);
|
||||||
calculatePickRates();
|
calculatePickRates();
|
||||||
}
|
}
|
||||||
}, [passedAnswer, students, question.id, showCorrectAnswers]);
|
}, [passedAnswer, students, question.id]);
|
||||||
|
|
||||||
|
|
||||||
const alpha = Array.from(Array(26)).map((_e, i) => i + 65);
|
const alpha = Array.from(Array(26)).map((_e, i) => i + 65);
|
||||||
const alphabet = alpha.map((x) => String.fromCharCode(x));
|
const alphabet = alpha.map((x) => String.fromCharCode(x));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row justify-content-center">
|
<div className="row justify-content-center">
|
||||||
|
|
@ -103,7 +104,7 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
<div className="choices-wrapper mb-1">
|
<div className="choices-wrapper mb-1">
|
||||||
{question.choices.map((choice, i) => {
|
{question.choices.map((choice, i) => {
|
||||||
const selected = answer.includes(choice.formattedText.text) ? 'selected' : '';
|
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]}%)`,
|
backgroundImage: `linear-gradient(to right, ${choice.isCorrect ? 'lightgreen' : 'lightcoral'} ${pickRates.percentages[i]}%, transparent ${pickRates.percentages[i]}%)`,
|
||||||
color: 'black'
|
color: 'black'
|
||||||
} : {};
|
} : {};
|
||||||
|
|
@ -134,7 +135,12 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showCorrectAnswers && <div className="pick-rate">{choice.isCorrect ? '✅' : '❌'} {`${pickRates.counts[i]}/${pickRates.totalCount} (${pickRates.percentages[i].toFixed(1)}%)`}</div>}
|
{showResults && pickRates.percentages.length > i && (
|
||||||
|
<div className="pick-rate">
|
||||||
|
{choice.isCorrect ? '✅' : '❌'}
|
||||||
|
{`${pickRates.counts[i]}/${pickRates.totalCount} (${pickRates.percentages[i].toFixed(1)}%)`}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -162,17 +168,6 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isDisplayOnly && (
|
|
||||||
<div className="col-auto d-flex align-items-center justify-content-end">
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
onClick={toggleShowCorrectAnswers}
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
{showCorrectAnswers ? "Masquer les résultats" : "Afficher les résultats"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,35 +14,29 @@ interface Props {
|
||||||
showAnswer?: boolean;
|
showAnswer?: boolean;
|
||||||
passedAnswer?: AnswerType;
|
passedAnswer?: AnswerType;
|
||||||
students?: StudentType[];
|
students?: StudentType[];
|
||||||
isDisplayOnly?: boolean;
|
showResults?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
const { question, showAnswer, handleOnSubmitAnswer, students, passedAnswer, isDisplayOnly } =
|
const { question, showAnswer, handleOnSubmitAnswer, students, showResults, passedAnswer } =
|
||||||
props;
|
props;
|
||||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
|
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
|
||||||
const correctAnswers = question.choices;
|
const correctAnswers = question.choices;
|
||||||
let correctAnswer = '';
|
let correctAnswer = '';
|
||||||
|
|
||||||
const [showCorrectAnswers, setShowCorrectAnswers] = useState(false);
|
|
||||||
const [correctAnswerRate, setCorrectAnswerRate] = useState<number>(0);
|
const [correctAnswerRate, setCorrectAnswerRate] = useState<number>(0);
|
||||||
const [submissionCounts, setSubmissionCounts] = useState({
|
const [submissionCounts, setSubmissionCounts] = useState({
|
||||||
correctSubmissions: 0,
|
correctSubmissions: 0,
|
||||||
totalSubmissions: 0
|
totalSubmissions: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleShowCorrectAnswers = () => {
|
|
||||||
setShowCorrectAnswers(!showCorrectAnswers);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (passedAnswer !== null && passedAnswer !== undefined) {
|
if (passedAnswer !== null && passedAnswer !== undefined) {
|
||||||
setAnswer(passedAnswer);
|
setAnswer(passedAnswer);
|
||||||
}
|
}
|
||||||
if (showCorrectAnswers && students) {
|
if (showResults && students) {
|
||||||
calculateCorrectAnswerRate();
|
calculateCorrectAnswerRate();
|
||||||
}
|
}
|
||||||
}, [passedAnswer, showCorrectAnswers, students]);
|
}, [passedAnswer, showResults, students]);
|
||||||
|
|
||||||
const calculateCorrectAnswerRate = () => {
|
const calculateCorrectAnswerRate = () => {
|
||||||
if (!students || students.length === 0) {
|
if (!students || students.length === 0) {
|
||||||
|
|
@ -82,85 +76,78 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="question-wrapper">
|
<>
|
||||||
<div>
|
<div className="container question-wrapper">
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
<div className="row justify-content-center">
|
||||||
</div>
|
<div className="col-auto">
|
||||||
{showAnswer ? (
|
|
||||||
<>
|
|
||||||
<div className="correct-answer-text mb-2">
|
|
||||||
<strong>La bonne réponse est: </strong>
|
|
||||||
{correctAnswer}</div>
|
|
||||||
<span>
|
|
||||||
<strong>Votre réponse est: </strong>{answer.toString()}
|
|
||||||
</span>
|
|
||||||
{question.formattedGlobalFeedback && <div className="global-feedback mb-2">
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="answer-wrapper mb-1">
|
|
||||||
<TextField
|
|
||||||
type="number"
|
|
||||||
id={question.formattedStem.text}
|
|
||||||
name={question.formattedStem.text}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setAnswer([e.target.valueAsNumber]);
|
|
||||||
}}
|
|
||||||
inputProps={{ 'data-testid': 'number-input' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{question.formattedGlobalFeedback && showAnswer && (
|
|
||||||
<div className="global-feedback mb-2">
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{handleOnSubmitAnswer && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={() =>
|
|
||||||
answer !== undefined &&
|
|
||||||
handleOnSubmitAnswer &&
|
|
||||||
handleOnSubmitAnswer(answer)
|
|
||||||
}
|
|
||||||
disabled={answer === undefined || answer === null || isNaN(answer[0] as number)}
|
|
||||||
>
|
|
||||||
Répondre
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
{isDisplayOnly && (
|
|
||||||
<>
|
|
||||||
<div style={{ marginTop: '10px' }}>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
onClick={toggleShowCorrectAnswers}
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
{showCorrectAnswers ? "Masquer les résultats" : "Afficher les résultats"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div style={{
|
|
||||||
visibility: showCorrectAnswers ? 'visible' : 'hidden'
|
|
||||||
}}>
|
|
||||||
<div>
|
<div>
|
||||||
Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions}
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="progress-bar-container">
|
{showAnswer ? (
|
||||||
<div className="progress-bar-fill" style={{ width: `${correctAnswerRate}%` }}></div>
|
<>
|
||||||
<div className="progress-bar-text">
|
<div className="correct-answer-text mb-2">
|
||||||
{correctAnswerRate.toFixed(1)}%
|
<strong>La bonne réponse est: </strong>
|
||||||
|
{correctAnswer}</div>
|
||||||
|
<span>
|
||||||
|
<strong>Votre réponse est: </strong>{answer.toString()}
|
||||||
|
</span>
|
||||||
|
{question.formattedGlobalFeedback && <div className="global-feedback mb-2">
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="answer-wrapper mb-1">
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
id={question.formattedStem.text}
|
||||||
|
name={question.formattedStem.text}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setAnswer([e.target.valueAsNumber]);
|
||||||
|
}}
|
||||||
|
inputProps={{ 'data-testid': 'number-input' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{question.formattedGlobalFeedback && showAnswer && (
|
||||||
|
<div className="global-feedback mb-2">
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{handleOnSubmitAnswer && (
|
||||||
|
<div className="col-auto d-flex flex-column align-items-center">
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() =>
|
||||||
|
answer !== undefined &&
|
||||||
|
handleOnSubmitAnswer &&
|
||||||
|
handleOnSubmitAnswer(answer)
|
||||||
|
}
|
||||||
|
disabled={answer === undefined || answer === null || isNaN(answer[0] as number)}
|
||||||
|
>
|
||||||
|
Répondre
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{showResults && (
|
||||||
|
<div className="col-auto">
|
||||||
|
<div>
|
||||||
|
Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions}
|
||||||
|
</div>
|
||||||
|
<div className="progress-bar-container">
|
||||||
|
<div className="progress-bar-fill" style={{ width: `${correctAnswerRate}%` }}></div>
|
||||||
|
<div className="progress-bar-text">
|
||||||
|
{correctAnswerRate.toFixed(1)}%
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ interface QuestionProps {
|
||||||
handleOnSubmitAnswer?: (answer: AnswerType) => void;
|
handleOnSubmitAnswer?: (answer: AnswerType) => void;
|
||||||
showAnswer?: boolean;
|
showAnswer?: boolean;
|
||||||
students?: StudentType[];
|
students?: StudentType[];
|
||||||
isDisplayOnly?: boolean;
|
showResults?: boolean;
|
||||||
answer?: AnswerType;
|
answer?: AnswerType;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -24,7 +24,7 @@ const QuestionDisplay: React.FC<QuestionProps> = ({
|
||||||
handleOnSubmitAnswer,
|
handleOnSubmitAnswer,
|
||||||
showAnswer,
|
showAnswer,
|
||||||
students,
|
students,
|
||||||
isDisplayOnly = false,
|
showResults,
|
||||||
answer,
|
answer,
|
||||||
}) => {
|
}) => {
|
||||||
// const isMobile = useCheckMobileScreen();
|
// const isMobile = useCheckMobileScreen();
|
||||||
|
|
@ -41,7 +41,7 @@ const QuestionDisplay: React.FC<QuestionProps> = ({
|
||||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||||
showAnswer={showAnswer}
|
showAnswer={showAnswer}
|
||||||
students={students}
|
students={students}
|
||||||
isDisplayOnly={isDisplayOnly}
|
showResults={showResults}
|
||||||
passedAnswer={answer}
|
passedAnswer={answer}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
@ -54,7 +54,7 @@ const QuestionDisplay: React.FC<QuestionProps> = ({
|
||||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||||
showAnswer={showAnswer}
|
showAnswer={showAnswer}
|
||||||
students={students}
|
students={students}
|
||||||
isDisplayOnly={isDisplayOnly}
|
showResults={showResults}
|
||||||
passedAnswer={answer}
|
passedAnswer={answer}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
@ -68,7 +68,7 @@ const QuestionDisplay: React.FC<QuestionProps> = ({
|
||||||
showAnswer={showAnswer}
|
showAnswer={showAnswer}
|
||||||
passedAnswer={answer}
|
passedAnswer={answer}
|
||||||
students={students}
|
students={students}
|
||||||
isDisplayOnly={isDisplayOnly}
|
showResults={showResults}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +80,7 @@ const QuestionDisplay: React.FC<QuestionProps> = ({
|
||||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||||
showAnswer={showAnswer}
|
showAnswer={showAnswer}
|
||||||
students={students}
|
students={students}
|
||||||
isDisplayOnly={isDisplayOnly}
|
showResults={showResults}
|
||||||
passedAnswer={answer}
|
passedAnswer={answer}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -12,33 +12,28 @@ interface Props {
|
||||||
showAnswer?: boolean;
|
showAnswer?: boolean;
|
||||||
passedAnswer?: AnswerType;
|
passedAnswer?: AnswerType;
|
||||||
students?: StudentType[];
|
students?: StudentType[];
|
||||||
isDisplayOnly?: boolean;
|
showResults?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
const { question, showAnswer, handleOnSubmitAnswer, students, passedAnswer, isDisplayOnly } = props;
|
const { question, showAnswer, handleOnSubmitAnswer, students, showResults, passedAnswer } = props;
|
||||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
|
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
|
||||||
const [showCorrectAnswers, setShowCorrectAnswers] = useState(false);
|
|
||||||
const [correctAnswerRate, setCorrectAnswerRate] = useState<number>(0);
|
const [correctAnswerRate, setCorrectAnswerRate] = useState<number>(0);
|
||||||
const [submissionCounts, setSubmissionCounts] = useState({
|
const [submissionCounts, setSubmissionCounts] = useState({
|
||||||
correctSubmissions: 0,
|
correctSubmissions: 0,
|
||||||
totalSubmissions: 0
|
totalSubmissions: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleShowCorrectAnswers = () => {
|
|
||||||
setShowCorrectAnswers(!showCorrectAnswers);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (passedAnswer !== undefined) {
|
if (passedAnswer !== undefined) {
|
||||||
setAnswer(passedAnswer);
|
setAnswer(passedAnswer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showCorrectAnswers && students) {
|
if (showResults && students) {
|
||||||
calculateCorrectAnswerRate();
|
calculateCorrectAnswerRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [passedAnswer, showCorrectAnswers, students, answer]);
|
}, [passedAnswer, showResults, students, answer]);
|
||||||
console.log("Answer", answer);
|
console.log("Answer", answer);
|
||||||
|
|
||||||
const calculateCorrectAnswerRate = () => {
|
const calculateCorrectAnswerRate = () => {
|
||||||
|
|
@ -67,7 +62,9 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
<div className="container question-wrapper">
|
<div className="container question-wrapper">
|
||||||
<div className="row justify-content-center">
|
<div className="row justify-content-center">
|
||||||
<div className="col-auto">
|
<div className="col-auto">
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
<div>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
||||||
|
</div>
|
||||||
{showAnswer ? (
|
{showAnswer ? (
|
||||||
<>
|
<>
|
||||||
<div className="correct-answer-text mb-1">
|
<div className="correct-answer-text mb-1">
|
||||||
|
|
@ -104,7 +101,7 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{handleOnSubmitAnswer && (
|
{handleOnSubmitAnswer && (
|
||||||
<div className="col-auto d-flex flex-column align-items-center">
|
<div className="">
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|
@ -121,32 +118,18 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isDisplayOnly && (
|
{showResults && (
|
||||||
<>
|
<div className="col-auto">
|
||||||
<div className="col-auto d-flex flex-column align-items-center">
|
<div>
|
||||||
<Button
|
Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions}
|
||||||
style={{ marginTop: '10px' }}
|
|
||||||
variant="outlined"
|
|
||||||
onClick={toggleShowCorrectAnswers}
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
{showCorrectAnswers ? "Masquer les résultats" : "Afficher les résultats"}
|
|
||||||
</Button>
|
|
||||||
{showCorrectAnswers && (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
Taux de réponse correcte: {submissionCounts.correctSubmissions}/{submissionCounts.totalSubmissions}
|
|
||||||
</div>
|
|
||||||
<div className="progress-bar-container">
|
|
||||||
<div className="progress-bar-fill" style={{ width: `${correctAnswerRate}%` }}></div>
|
|
||||||
<div className="progress-bar-text">
|
|
||||||
{correctAnswerRate.toFixed(1)}%
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
<div className="progress-bar-container">
|
||||||
|
<div className="progress-bar-fill" style={{ width: `${correctAnswerRate}%` }}></div>
|
||||||
|
<div className="progress-bar-text">
|
||||||
|
{correctAnswerRate.toFixed(1)}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ interface Props {
|
||||||
showAnswer?: boolean;
|
showAnswer?: boolean;
|
||||||
passedAnswer?: AnswerType;
|
passedAnswer?: AnswerType;
|
||||||
students?: StudentType[];
|
students?: StudentType[];
|
||||||
isDisplayOnly?: boolean;
|
showResults?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
const TrueFalseQuestionDisplay: React.FC<Props> = (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 }>({
|
const [pickRates, setPickRates] = useState<{ trueRate: number, falseRate: number, trueCount: number, falseCount: number, totalCount: number }>({
|
||||||
trueRate: 0,
|
trueRate: 0,
|
||||||
falseRate: 0,
|
falseRate: 0,
|
||||||
|
|
@ -25,7 +25,6 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
falseCount: 0,
|
falseCount: 0,
|
||||||
totalCount: 0
|
totalCount: 0
|
||||||
});
|
});
|
||||||
const [showCorrectAnswers, setShowCorrectAnswers] = useState(false);
|
|
||||||
|
|
||||||
const [answer, setAnswer] = useState<boolean | undefined>(() => {
|
const [answer, setAnswer] = useState<boolean | undefined>(() => {
|
||||||
|
|
||||||
|
|
@ -61,10 +60,6 @@ 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' : '';
|
||||||
|
|
||||||
const toggleShowCorrectAnswers = () => {
|
|
||||||
setShowCorrectAnswers(!showCorrectAnswers);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calcul le pick rate de chaque réponse
|
// Calcul le pick rate de chaque réponse
|
||||||
const calculatePickRates = () => {
|
const calculatePickRates = () => {
|
||||||
if (!students) {
|
if (!students) {
|
||||||
|
|
@ -75,12 +70,14 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
const totalAnswers = students.length;
|
const totalAnswers = students.length;
|
||||||
const trueAnswers = students.filter(student =>
|
const trueAnswers = students.filter(student =>
|
||||||
student.answers.some(ans =>
|
student.answers.some(ans =>
|
||||||
ans.idQuestion === Number(question.id) && ans.answer === true
|
ans.idQuestion === Number(question.id) && ans.answer.some(a => a === true)
|
||||||
)).length;
|
)
|
||||||
|
).length;
|
||||||
const falseAnswers = students.filter(student =>
|
const falseAnswers = students.filter(student =>
|
||||||
student.answers.some(ans =>
|
student.answers.some(ans =>
|
||||||
ans.idQuestion === Number(question.id) && ans.answer === false
|
ans.idQuestion === Number(question.id) && ans.answer.some(a => a === false)
|
||||||
)).length;
|
)
|
||||||
|
).length;
|
||||||
|
|
||||||
setPickRates({
|
setPickRates({
|
||||||
trueRate: (trueAnswers / totalAnswers) * 100,
|
trueRate: (trueAnswers / totalAnswers) * 100,
|
||||||
|
|
@ -92,99 +89,91 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="question-container">
|
<div className="container">
|
||||||
<div className="question content">
|
<div className="row justify-content-center">
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
<div className="col-auto question-container">
|
||||||
</div>
|
<div className="question content">
|
||||||
<div className="choices-wrapper mb-1">
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
||||||
<Button
|
|
||||||
className="button-wrapper"
|
|
||||||
onClick={() => !showAnswer && handleOnClickAnswer(true)}
|
|
||||||
fullWidth
|
|
||||||
disabled={disableButton}
|
|
||||||
>
|
|
||||||
{showAnswer ? (<div> {(question.isTrue ? '✅' : '❌')}</div>) : ``}
|
|
||||||
<div className={`circle ${selectedTrue}`}>V</div>
|
|
||||||
<div className={`answer-text ${selectedTrue}`}
|
|
||||||
style={showCorrectAnswers ? {
|
|
||||||
backgroundImage: `linear-gradient(to right, ${question.isTrue ? 'royalblue' : 'orange'} ${pickRates.trueRate}%, transparent ${pickRates.trueRate}%)`
|
|
||||||
} : {}}
|
|
||||||
>
|
|
||||||
Vrai
|
|
||||||
</div>
|
</div>
|
||||||
{showCorrectAnswers && (
|
<div className="choices-wrapper mb-1">
|
||||||
<>
|
<Button
|
||||||
<div className="pick-rate">{question.isTrue ? '✅' : '❌'} {pickRates.trueCount}/{pickRates.totalCount} ({pickRates.trueRate.toFixed(1)}%)</div>
|
className="button-wrapper"
|
||||||
</>
|
onClick={() => !showAnswer && handleOnClickAnswer(true)}
|
||||||
)}
|
fullWidth
|
||||||
|
disabled={disableButton}
|
||||||
|
>
|
||||||
|
{showAnswer ? (<div> {(question.isTrue ? '✅' : '❌')}</div>) : ``}
|
||||||
|
<div className={`circle ${selectedTrue}`}>V</div>
|
||||||
|
<div className={`answer-text ${selectedTrue}`}
|
||||||
|
style={showResults ? {
|
||||||
|
backgroundImage: `linear-gradient(to right, ${question.isTrue ? 'lightgreen' : 'lightcoral'} ${pickRates.trueRate}%, transparent ${pickRates.trueRate}%)`
|
||||||
|
} : {}}
|
||||||
|
>
|
||||||
|
Vrai
|
||||||
|
</div>
|
||||||
|
{showResults && (
|
||||||
|
<>
|
||||||
|
<div className="pick-rate">{question.isTrue ? '✅' : '❌'} {pickRates.trueCount}/{pickRates.totalCount} ({pickRates.trueRate.toFixed(1)}%)</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{showAnswer && answer && question.trueFormattedFeedback && (
|
{showAnswer && answer && question.trueFormattedFeedback && (
|
||||||
<div className="true-feedback mb-2">
|
<div className="true-feedback mb-2">
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.trueFormattedFeedback) }} />
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.trueFormattedFeedback) }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={`button-wrapper ${selectedFalse}`}
|
||||||
|
onClick={() => !showResults && handleOnClickAnswer(false)}
|
||||||
|
fullWidth
|
||||||
|
disabled={disableButton}
|
||||||
|
|
||||||
|
>
|
||||||
|
{showAnswer ? (<div> {(!question.isTrue ? '✅' : '❌')}</div>) : ``}
|
||||||
|
<div className={`circle ${selectedFalse}`}>F</div>
|
||||||
|
<div
|
||||||
|
className={`answer-text ${selectedFalse}`}
|
||||||
|
style={showResults ? {
|
||||||
|
backgroundImage: `linear-gradient(to right, ${!question.isTrue ? 'lightgreen' : 'lightcoral'} ${pickRates.falseRate}%, transparent ${pickRates.falseRate}%)`,
|
||||||
|
} : {}}
|
||||||
|
>
|
||||||
|
Faux
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showResults && (
|
||||||
|
<>
|
||||||
|
<div className="pick-rate">{!question.isTrue ? '✅' : '❌'} {pickRates.falseCount}/{pickRates.totalCount} ({pickRates.falseRate.toFixed(1)}%)</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showAnswer && !answer && question.falseFormattedFeedback && (
|
||||||
|
<div className="false-feedback mb-2">
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.falseFormattedFeedback) }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{question.formattedGlobalFeedback && showAnswer && (
|
||||||
|
<div className="global-feedback mb-2">
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Button>
|
{!showAnswer && handleOnSubmitAnswer && (
|
||||||
<Button
|
<Button
|
||||||
className={`button-wrapper ${selectedFalse}`}
|
variant="contained"
|
||||||
onClick={() => !showCorrectAnswers && handleOnClickAnswer(false)}
|
onClick={() =>
|
||||||
fullWidth
|
answer !== undefined && handleOnSubmitAnswer && handleOnSubmitAnswer([answer])
|
||||||
disabled={disableButton}
|
}
|
||||||
|
disabled={answer === undefined}
|
||||||
>
|
>
|
||||||
{showAnswer ? (<div> {(!question.isTrue ? '✅' : '❌')}</div>) : ``}
|
Répondre
|
||||||
<div className={`circle ${selectedFalse}`}>F</div>
|
</Button>
|
||||||
<div
|
|
||||||
className={`answer-text ${selectedFalse}`}
|
|
||||||
style={showCorrectAnswers ? {
|
|
||||||
backgroundImage: `linear-gradient(to right, ${!question.isTrue ? 'royalblue' : 'orange'} ${pickRates.falseRate}%, transparent ${pickRates.falseRate}%)`,
|
|
||||||
} : {}}
|
|
||||||
>
|
|
||||||
Faux
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showCorrectAnswers && (
|
|
||||||
<>
|
|
||||||
<div className="pick-rate">{!question.isTrue ? '✅' : '❌'} {pickRates.falseCount}/{pickRates.totalCount} ({pickRates.falseRate.toFixed(1)}%)</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
{showAnswer && !answer && question.falseFormattedFeedback && (
|
|
||||||
<div className="false-feedback mb-2">
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.falseFormattedFeedback) }} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{question.formattedGlobalFeedback && showAnswer && (
|
|
||||||
<div className="global-feedback mb-2">
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!showAnswer && handleOnSubmitAnswer && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={() =>
|
|
||||||
answer !== undefined && handleOnSubmitAnswer && handleOnSubmitAnswer([answer])
|
|
||||||
}
|
|
||||||
disabled={answer === undefined}
|
|
||||||
>
|
|
||||||
Répondre
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isDisplayOnly && (
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
onClick={toggleShowCorrectAnswers}
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
{showCorrectAnswers ? "Masquer les résultats" : "Afficher les résultats"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
|
||||||
import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
||||||
import ApiService from '../../../services/ApiService';
|
import ApiService from '../../../services/ApiService';
|
||||||
import { QuestionType } from 'src/Types/QuestionType';
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
import { Button } from '@mui/material';
|
import { Button, FormControlLabel, Switch } from '@mui/material';
|
||||||
import { checkIfIsCorrect } from './useRooms';
|
import { checkIfIsCorrect } from './useRooms';
|
||||||
|
|
||||||
const ManageRoom: React.FC = () => {
|
const ManageRoom: React.FC = () => {
|
||||||
|
|
@ -34,6 +34,7 @@ const ManageRoom: React.FC = () => {
|
||||||
const [quizStarted, setQuizStarted] = useState<boolean>(false);
|
const [quizStarted, setQuizStarted] = useState<boolean>(false);
|
||||||
const [formattedRoomName, setFormattedRoomName] = useState("");
|
const [formattedRoomName, setFormattedRoomName] = useState("");
|
||||||
const [newlyConnectedUser, setNewlyConnectedUser] = useState<StudentType | null>(null);
|
const [newlyConnectedUser, setNewlyConnectedUser] = useState<StudentType | null>(null);
|
||||||
|
const [showResults, setShowResults] = useState<boolean>(false);
|
||||||
|
|
||||||
// Handle the newly connected user in useEffect, because it needs state info
|
// Handle the newly connected user in useEffect, because it needs state info
|
||||||
// not available in the socket.on() callback
|
// not available in the socket.on() callback
|
||||||
|
|
@ -407,7 +408,17 @@ const ManageRoom: React.FC = () => {
|
||||||
{quizQuestions?.length}
|
{quizQuestions?.length}
|
||||||
</strong>
|
</strong>
|
||||||
)}
|
)}
|
||||||
|
<FormControlLabel
|
||||||
|
label={<div className="text-sm">Afficher les résultats</div>}
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
value={showResults}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setShowResults(e.target.checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
{quizMode === 'teacher' && (
|
{quizMode === 'teacher' && (
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
{/* <QuestionNavigation
|
{/* <QuestionNavigation
|
||||||
|
|
@ -426,7 +437,7 @@ const ManageRoom: React.FC = () => {
|
||||||
showAnswer={false}
|
showAnswer={false}
|
||||||
question={currentQuestion?.question as Question}
|
question={currentQuestion?.question as Question}
|
||||||
students={students}
|
students={students}
|
||||||
isDisplayOnly={true}
|
showResults={showResults}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue