From 5df438f474030444dadf5fd760322a6589bed5b7 Mon Sep 17 00:00:00 2001 From: shafin-r Date: Wed, 28 Jan 2026 21:23:31 +0600 Subject: [PATCH] feat(test): add short answer input on questions fix(test): fix navigation upon test completion --- src/pages/student/practice/Test.tsx | 146 ++++++++++++++++------------ src/types/sheet.ts | 2 + 2 files changed, 84 insertions(+), 64 deletions(-) diff --git a/src/pages/student/practice/Test.tsx b/src/pages/student/practice/Test.tsx index fca3964..6d2ec79 100644 --- a/src/pages/student/practice/Test.tsx +++ b/src/pages/student/practice/Test.tsx @@ -16,7 +16,7 @@ import { } from "lucide-react"; import { api } from "../../../utils/api"; import { useAuthStore } from "../../../stores/authStore"; -import type { Option, PracticeSheet } from "../../../types/sheet"; +import type { Option, PracticeSheet, Question } from "../../../types/sheet"; import { Button } from "../../../components/ui/button"; import { useSatExam } from "../../../stores/useSatExam"; import { useSatTimer } from "../../../hooks/useSatTimer"; @@ -33,7 +33,9 @@ export const Test = () => { const [practiceSheet, setPracticeSheet] = useState( null, ); - const [answers, setAnswers] = useState([]); + const [answer, setAnswer] = useState(""); + const [answers, setAnswers] = useState>({}); + const [isSubmitting, setIsSubmitting] = useState(false); const [sessionId, setSessionId] = useState(null); const { sheetId } = useParams<{ sheetId: string }>(); @@ -47,7 +49,6 @@ export const Test = () => { const currentQuestion = currentModule?.questions[questionIndex]; const resetExam = useSatExam((s) => s.resetExam); - const startSatExam = useSatExam((s) => s.startExam); const nextQuestion = useSatExam((s) => s.nextQuestion); const prevQuestion = useSatExam((s) => s.prevQuestion); const finishExam = useSatExam((s) => s.finishExam); @@ -110,47 +111,57 @@ export const Test = () => { }; const handleNext = async () => { - if (!currentQuestion || !selectedOption || !sessionId) return; + if (!currentQuestion || !sessionId) return; - const selected = currentQuestion.options.find( - (opt) => opt.id === selectedOption, - ); + const userAnswer = answers[currentQuestion.id] ?? ""; - if (!selected) return; + let answerText = ""; - const answerPayload: SubmitAnswer = { + // ✅ MCQ case + if (currentQuestion.options?.length) { + const selected = currentQuestion.options.find( + (opt) => opt.id === userAnswer, + ); + answerText = selected?.text ?? ""; + } + // ✅ Text input case + else { + answerText = userAnswer; + } + + const payload: SubmitAnswer = { question_id: currentQuestion.id, - answer_text: selected.text, + answer_text: answerText, // ✅ empty string if skipped time_spent_seconds: 3, }; - setIsSubmitting(true); - - await api.submitAnswer(token!, sessionId, answerPayload); + try { + setIsSubmitting(true); + await api.submitAnswer(token!, sessionId, payload); + } catch (err) { + console.error("Failed to submit answer:", err); + } finally { + setIsSubmitting(false); + } const isLastQuestion = questionIndex === currentModule!.questions.length - 1; - // ✅ normal question flow + // ✅ Move to next question if (!isLastQuestion) { nextQuestion(); - setIsSubmitting(false); return; } - // ✅ ask backend for next module + // ✅ Module finished → ask backend for next module const next = await api.fetchNextModule(token!, sessionId); - if (next?.finished) { + if (next?.status === "COMPLETED") { finishExam(); } else { await loadSessionQuestions(sessionId); - - // ✅ IMPORTANT: start break AFTER module loads useSatExam.getState().startBreak(); } - - setIsSubmitting(false); }; useEffect(() => { @@ -173,54 +184,61 @@ export const Test = () => { if (!user) return; }, [sheetId]); - const [selectedOption, setSelectedOption] = useState(null); + useEffect(() => { + setAnswer(""); + }, [questionIndex, currentModule?.module_id]); - const isLastQuestion = - questionIndex === (currentModule?.questions.length ?? 0) - 1; + // const isLastQuestion = + // questionIndex === (currentModule?.questions.length ?? 0) - 1; const isFirstQuestion = questionIndex === 0; - useEffect(() => { - setSelectedOption(null); - }, [questionIndex, currentModule?.module_id]); + const renderAnswerInput = (question?: Question) => { + if (!question) return null; - const renderOptions = (options?: Option[]) => { - if (!options || !Array.isArray(options)) { - return

No options available.

; + // ✅ MCQ + if (question.options && question.options.length > 0) { + return ( +
+ {question.options.map((option, index) => { + const isSelected = answer === option.id; + + return ( + + ); + })} +
+ ); } - const handleOptionClick = (option: Option) => { - setSelectedOption(option.id); - }; - + // ✅ SHORT ANSWER (text input) return ( -
- {options.map((option, index) => { - const isSelected = selectedOption === option.id; - - return ( - - ); - })} +
+