mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
fix: quiz bugs fix
grading ui improvs
This commit is contained in:
parent
62fd5e0a3a
commit
494620d2d6
1 changed files with 120 additions and 78 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue