fix: quiz bugs fix

grading ui improvs
This commit is contained in:
swve 2024-08-28 20:54:15 +02:00
parent 62fd5e0a3a
commit 494620d2d6

View file

@ -16,7 +16,7 @@ type QuizSchema = {
text: string; text: string;
fileID: string; fileID: string;
type: 'text' | 'image' | 'audio' | 'video'; type: 'text' | 'image' | 'audio' | 'video';
correct: boolean; assigned_right_answer: boolean;
}[]; }[];
}; };
@ -25,6 +25,7 @@ type QuizSubmitSchema = {
submissions: { submissions: {
questionUUID: string; questionUUID: string;
optionUUID: string; optionUUID: string;
answer: boolean
}[]; }[];
}; };
@ -34,6 +35,12 @@ type TaskQuizObjectProps = {
assignmentTaskUUID?: string; assignmentTaskUUID?: string;
}; };
type Submission = {
questionUUID: string;
optionUUID: string;
answer: boolean;
};
function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectProps) { function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectProps) {
const session = useLHSession() as any; const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token; const access_token = session?.data?.tokens?.access_token;
@ -44,7 +51,7 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
/* TEACHER VIEW CODE */ /* TEACHER VIEW CODE */
const [questions, setQuestions] = useState<QuizSchema[]>([ const [questions, setQuestions] = useState<QuizSchema[]>([
{ questionText: '', questionUUID: 'question_' + uuidv4(), options: [{ text: '', fileID: '', type: 'text', correct: false, optionUUID: 'option_' + uuidv4() }] }, { questionText: '', questionUUID: 'question_' + uuidv4(), options: [{ text: '', fileID: '', type: 'text', assigned_right_answer: false, optionUUID: 'option_' + uuidv4() }] },
]); ]);
const handleQuestionChange = (index: number, value: string) => { const handleQuestionChange = (index: number, value: string) => {
@ -61,7 +68,7 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
const addOption = (qIndex: number) => { const addOption = (qIndex: number) => {
const updatedQuestions = [...questions]; const updatedQuestions = [...questions];
updatedQuestions[qIndex].options.push({ text: '', fileID: '', type: 'text', correct: false, optionUUID: 'option_' + uuidv4() }); updatedQuestions[qIndex].options.push({ text: '', fileID: '', type: 'text', assigned_right_answer: false, optionUUID: 'option_' + uuidv4() });
setQuestions(updatedQuestions); setQuestions(updatedQuestions);
}; };
@ -72,7 +79,7 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
}; };
const addQuestion = () => { const addQuestion = () => {
setQuestions([...questions, { questionText: '', questionUUID: 'question_' + uuidv4(), options: [{ text: '', fileID: '', type: 'text', correct: false, optionUUID: 'option_' + uuidv4() }] }]); setQuestions([...questions, { questionText: '', questionUUID: 'question_' + uuidv4(), options: [{ text: '', fileID: '', type: 'text', assigned_right_answer: false, optionUUID: 'option_' + uuidv4() }] }]);
}; };
const removeQuestion = (qIndex: number) => { const removeQuestion = (qIndex: number) => {
@ -81,12 +88,12 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
setQuestions(updatedQuestions); setQuestions(updatedQuestions);
}; };
const toggleCorrectOption = (qIndex: number, oIndex: number) => { const toggleOption = (qIndex: number, oIndex: number) => {
const updatedQuestions = [...questions]; const updatedQuestions = [...questions];
// Find the option to toggle // Find the option to toggle
const optionToToggle = updatedQuestions[qIndex].options[oIndex]; const optionToToggle = updatedQuestions[qIndex].options[oIndex];
// Toggle the 'correct' property of the option // Toggle the 'correct' property of the option
optionToToggle.correct = !optionToToggle.correct; optionToToggle.assigned_right_answer = !optionToToggle.assigned_right_answer;
setQuestions(updatedQuestions); setQuestions(updatedQuestions);
}; };
@ -123,20 +130,24 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
async function chooseOption(qIndex: number, oIndex: number) { async function chooseOption(qIndex: number, oIndex: number) {
const updatedSubmissions = [...userSubmissions.submissions]; const updatedSubmissions = [...userSubmissions.submissions];
const questionUUID = questions[qIndex].questionUUID; const question = questions[qIndex];
const optionUUID = questions[qIndex].options[oIndex].optionUUID; const option = question?.options[oIndex];
// Check if this question already has a submission with the selected option if (!question || !option) return;
const existingSubmissionIndex = updatedSubmissions.findIndex(
const questionUUID = question.questionUUID;
const optionUUID = option.optionUUID;
if (!questionUUID || !optionUUID) return;
const submissionIndex = updatedSubmissions.findIndex(
(submission) => submission.questionUUID === questionUUID && submission.optionUUID === optionUUID (submission) => submission.questionUUID === questionUUID && submission.optionUUID === optionUUID
); );
if (existingSubmissionIndex === -1 && optionUUID && questionUUID) { if (submissionIndex === -1) {
// If the selected option is not already chosen, add it to the submissions updatedSubmissions.push({ questionUUID, optionUUID, answer: true });
updatedSubmissions.push({ questionUUID, optionUUID });
} else { } else {
// If the selected option is already chosen, remove it from the submissions updatedSubmissions[submissionIndex].answer = !updatedSubmissions[submissionIndex].answer;
updatedSubmissions.splice(existingSubmissionIndex, 1);
} }
setUserSubmissions({ setUserSubmissions({
@ -176,12 +187,34 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
const submitFC = async () => { const submitFC = async () => {
// Ensure all questions and options have submissions
const updatedSubmissions: Submission[] = questions.flatMap(question => {
return question.options.map(option => {
const existingSubmission = userSubmissions.submissions.find(
submission => submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID
);
return existingSubmission || {
questionUUID: question.questionUUID || '',
optionUUID: option.optionUUID || '',
answer: false // Mark unsubmitted options as false
};
});
});
// Update userSubmissions with the complete set of submissions
const updatedUserSubmissions: QuizSubmitSchema = {
...userSubmissions,
submissions: updatedSubmissions
};
// Save the quiz to the server // Save the quiz to the server
const values = { const values = {
task_submission: userSubmissions, task_submission: updatedUserSubmissions,
grade: 0, grade: 0,
task_submission_grade_feedback: '', task_submission_grade_feedback: '',
}; };
if (assignmentTaskUUID) { if (assignmentTaskUUID) {
const res = await handleAssignmentTaskSubmission(values, assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token); const res = await handleAssignmentTaskSubmission(values, assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
if (res) { if (res) {
@ -190,6 +223,7 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
}); });
toast.success('Task saved successfully'); toast.success('Task saved successfully');
setShowSavingDisclaimer(false); setShowSavingDisclaimer(false);
setUserSubmissions(updatedUserSubmissions);
} else { } else {
toast.error('Error saving task, please retry later.'); toast.error('Error saving task, please retry later.');
} }
@ -214,30 +248,22 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
async function gradeFC() { async function gradeFC() {
if (assignmentTaskUUID) { if (assignmentTaskUUID) {
// Ensure maxPoints is defined const maxPoints = assignmentTaskOutsideProvider?.max_grade_value || 100;
const maxPoints = assignmentTaskOutsideProvider?.max_grade_value || 100; // Default to 100 if not defined const totalOptions = questions.reduce((total, question) => total + question.options.length, 0);
let correctAnswers = 0;
// Ensure userSubmissions.questions are set questions.forEach((question) => {
const totalQuestions = questions.length; question.options.forEach((option) => {
let correctQuestions = 0; const submission = userSubmissions.submissions.find(
let incorrectQuestions = 0; (sub) => sub.questionUUID === question.questionUUID && sub.optionUUID === option.optionUUID
);
userSubmissions.submissions.forEach((submission) => { if (submission?.answer === option.assigned_right_answer) {
const question = questions.find((q) => q.questionUUID === submission.questionUUID); correctAnswers++;
const option = question?.options.find((o) => o.optionUUID === submission.optionUUID);
if (option?.correct) {
correctQuestions++;
} else {
incorrectQuestions++;
} }
}); });
});
// Calculate grade with penalties for incorrect answers const finalGrade = Math.round((correctAnswers / totalOptions) * maxPoints);
const pointsPerQuestion = maxPoints / totalQuestions;
const rawGrade = (correctQuestions - incorrectQuestions) * pointsPerQuestion;
// Ensure the grade is within the valid range
const finalGrade = Math.max(0, Math.min(rawGrade, maxPoints));
// Save the grade to the server // Save the grade to the server
const values = { const values = {
@ -337,15 +363,15 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
{view === 'teacher' && ( {view === 'teacher' && (
<> <>
<div <div
className={`w-fit flex-none flex text-xs px-2 py-0.5 space-x-1 items-center h-fit rounded-lg ${option.correct ? 'bg-lime-200 text-lime-600' : 'bg-rose-200/60 text-rose-500' className={`w-fit flex-none flex text-xs px-2 py-0.5 space-x-1 items-center h-fit rounded-lg ${option.assigned_right_answer ? 'bg-lime-200 text-lime-600' : 'bg-rose-200/60 text-rose-500'
} hover:bg-lime-300 text-sm transition-all ease-linear cursor-pointer`} } hover:bg-lime-300 text-sm transition-all ease-linear cursor-pointer`}
onClick={() => toggleCorrectOption(qIndex, oIndex)} onClick={() => toggleOption(qIndex, oIndex)}
> >
{option.correct ? <Check size={12} className="mx-auto" /> : <X size={12} className="mx-auto" />} {option.assigned_right_answer ? <Check size={12} className="mx-auto" /> : <X size={12} className="mx-auto" />}
{option.correct ? ( {option.assigned_right_answer ? (
<p className="mx-auto font-bold text-xs">Correct</p> <p className="mx-auto font-bold text-xs">True</p>
) : ( ) : (
<p className="mx-auto font-bold text-xs">Incorrect</p> <p className="mx-auto font-bold text-xs">False</p>
)} )}
</div> </div>
<div <div
@ -359,30 +385,38 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
{view === 'grading' && ( {view === 'grading' && (
<> <>
<div <div
className={`w-fit flex-none flex text-xs px-2 py-0.5 space-x-1 items-center h-fit rounded-lg ${option.correct ? 'bg-lime-200 text-lime-600' : 'bg-rose-200/60 text-rose-500' className={`w-fit flex-none flex text-xs px-2 py-0.5 space-x-1 items-center h-fit rounded-lg ${option.assigned_right_answer ? 'bg-lime-200 text-lime-600' : 'bg-rose-200/60 text-rose-500'
} hover:bg-lime-300 text-sm transition-all ease-linear cursor-pointer`} } hover:bg-lime-300 text-sm transition-all ease-linear cursor-pointer`}
> >
{option.correct ? <Check size={12} className="mx-auto" /> : <X size={12} className="mx-auto" />} {option.assigned_right_answer ? <Check size={12} className="mx-auto" /> : <X size={12} className="mx-auto" />}
{option.correct ? ( {option.assigned_right_answer ? (
<p className="mx-auto font-bold text-xs">Marked as Correct</p> <p className="mx-auto font-bold text-xs">Marked as True</p>
) : ( ) : (
<p className="mx-auto font-bold text-xs">Marked as Incorrect</p> <p className="mx-auto font-bold text-xs">Marked as False</p>
)} )}
</div> </div>
</> </>
)} )}
{view === 'student' && ( {view === 'student' && (
<div className={`w-[20px] flex-none flex items-center h-[20px] rounded-lg ${userSubmissions.submissions.find( <div
className={`w-[20px] flex-none flex items-center h-[20px] rounded-lg ${
userSubmissions.submissions.find(
(submission) => (submission) =>
submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID submission.questionUUID === question.questionUUID &&
submission.optionUUID === option.optionUUID &&
submission.answer
) )
? "bg-green-200/60 text-green-500 hover:bg-green-300" // Selected state colors ? "bg-green-200/60 text-green-500 hover:bg-green-300"
: "bg-slate-200/60 text-slate-500 hover:bg-slate-300" // Default state colors : "bg-slate-200/60 text-slate-500 hover:bg-slate-300"
} text-sm transition-all ease-linear cursor-pointer`}> } text-sm transition-all ease-linear cursor-pointer`}
onClick={() => chooseOption(qIndex, oIndex)}
>
{userSubmissions.submissions.find( {userSubmissions.submissions.find(
(submission) => (submission) =>
submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID submission.questionUUID === question.questionUUID &&
submission.optionUUID === option.optionUUID &&
submission.answer
) ? ( ) ? (
<Check size={12} className="mx-auto" /> <Check size={12} className="mx-auto" />
) : ( ) : (
@ -391,22 +425,30 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
</div> </div>
)} )}
{view === 'grading' && ( {view === 'grading' && (
<div className={`w-[20px] flex-none flex items-center h-[20px] rounded-lg ${userSubmissions.submissions.find( <>
<div className={`w-[20px] flex-none flex items-center h-[20px] rounded-lg ${
userSubmissions.submissions.find(
(submission) => (submission) =>
submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID submission.questionUUID === question.questionUUID &&
submission.optionUUID === option.optionUUID &&
submission.answer
) )
? "bg-green-200/60 text-green-500 hover:bg-green-300" // Selected state colors ? "bg-green-200/60 text-green-500"
: "bg-slate-200/60 text-slate-500 hover:bg-slate-300" // Default state colors : "bg-slate-200/60 text-slate-500"
} text-sm transition-all ease-linear cursor-pointer`}> } text-sm`}>
{userSubmissions.submissions.find( {userSubmissions.submissions.find(
(submission) => (submission) =>
submission.questionUUID === question.questionUUID && submission.optionUUID === option.optionUUID submission.questionUUID === question.questionUUID &&
submission.optionUUID === option.optionUUID &&
submission.answer
) ? ( ) ? (
<Check size={12} className="mx-auto" /> <Check size={12} className="mx-auto" />
) : ( ) : (
<X size={12} className="mx-auto" /> <X size={12} className="mx-auto" />
)} )}
</div> </div>
</>
)} )}
</div> </div>