"use client" import { useMemo, useState } from "react" import { useRouter } from "next/navigation" import { toast } from "sonner" import { RadioGroup, RadioGroupItem } from "@/shared/components/ui/radio-group" import { Badge } from "@/shared/components/ui/badge" import { Button } from "@/shared/components/ui/button" import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/shared/components/ui/card" import { Checkbox } from "@/shared/components/ui/checkbox" import { Label } from "@/shared/components/ui/label" import { Textarea } from "@/shared/components/ui/textarea" import { ScrollArea } from "@/shared/components/ui/scroll-area" import { Clock, CheckCircle2, Save, FileText } from "lucide-react" import type { StudentHomeworkTakeData } from "../types" import { saveHomeworkAnswerAction, startHomeworkSubmissionAction, submitHomeworkAction } from "../actions" const isRecord = (v: unknown): v is Record => typeof v === "object" && v !== null type Option = { id: string; text: string } const getQuestionText = (content: unknown): string => { if (!isRecord(content)) return "" return typeof content.text === "string" ? content.text : "" } const getOptions = (content: unknown): Option[] => { if (!isRecord(content)) return [] const raw = content.options if (!Array.isArray(raw)) return [] const out: Option[] = [] for (const item of raw) { if (!isRecord(item)) continue const id = typeof item.id === "string" ? item.id : "" const text = typeof item.text === "string" ? item.text : "" if (!id || !text) continue out.push({ id, text }) } return out } const toAnswerShape = (questionType: string, v: unknown) => { if (questionType === "text") return { answer: typeof v === "string" ? v : "" } if (questionType === "judgment") return { answer: typeof v === "boolean" ? v : false } if (questionType === "single_choice") return { answer: typeof v === "string" ? v : "" } if (questionType === "multiple_choice") return { answer: Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : [] } return { answer: v } } const parseSavedAnswer = (saved: unknown, questionType: string) => { if (isRecord(saved) && "answer" in saved) return toAnswerShape(questionType, saved.answer) return toAnswerShape(questionType, saved) } type HomeworkTakeViewProps = { assignmentId: string initialData: StudentHomeworkTakeData } export function HomeworkTakeView({ assignmentId, initialData }: HomeworkTakeViewProps) { const router = useRouter() const [submissionId, setSubmissionId] = useState(initialData.submission?.id ?? null) const [submissionStatus, setSubmissionStatus] = useState(initialData.submission?.status ?? "not_started") const [isBusy, setIsBusy] = useState(false) const initialAnswersByQuestionId = useMemo(() => { const map = new Map() for (const q of initialData.questions) { map.set(q.questionId, parseSavedAnswer(q.savedAnswer, q.questionType)) } return map }, [initialData.questions]) const [answersByQuestionId, setAnswersByQuestionId] = useState(() => { const obj: Record = {} for (const [k, v] of initialAnswersByQuestionId.entries()) obj[k] = v return obj }) const isStarted = submissionStatus === "started" const canEdit = isStarted && Boolean(submissionId) const showQuestions = submissionStatus !== "not_started" const handleStart = async () => { setIsBusy(true) const fd = new FormData() fd.set("assignmentId", assignmentId) const res = await startHomeworkSubmissionAction(null, fd) if (res.success && res.data) { setSubmissionId(res.data) setSubmissionStatus("started") toast.success("Started") router.refresh() } else { toast.error(res.message || "Failed to start") } setIsBusy(false) } const handleSaveQuestion = async (questionId: string) => { if (!submissionId) return // setIsBusy(true) // Don't block UI for individual saves const payload = answersByQuestionId[questionId]?.answer ?? null const fd = new FormData() fd.set("submissionId", submissionId) fd.set("questionId", questionId) fd.set("answerJson", JSON.stringify({ answer: payload })) const res = await saveHomeworkAnswerAction(null, fd) if (res.success) toast.success("Saved") else toast.error(res.message || "Failed to save") // setIsBusy(false) } const handleSubmit = async () => { if (!submissionId) return setIsBusy(true) // Save all first for (const q of initialData.questions) { const payload = answersByQuestionId[q.questionId]?.answer ?? null const fd = new FormData() fd.set("submissionId", submissionId) fd.set("questionId", q.questionId) fd.set("answerJson", JSON.stringify({ answer: payload })) const res = await saveHomeworkAnswerAction(null, fd) if (!res.success) { toast.error(res.message || "Failed to save") setIsBusy(false) return } } const submitFd = new FormData() submitFd.set("submissionId", submissionId) const submitRes = await submitHomeworkAction(null, submitFd) if (submitRes.success) { toast.success("Submitted") setSubmissionStatus("submitted") router.push("/student/learning/assignments") } else { toast.error(submitRes.message || "Failed to submit") } setIsBusy(false) } return (

Questions

{submissionStatus === "not_started" ? "Not Started" : submissionStatus} {initialData.questions.length} Questions
{!canEdit ? ( ) : (
Auto-saving enabled
)}
{!isStarted && (

Ready to start?

Click the "Start Assignment" button above to begin. The timer will start once you confirm.

)} {showQuestions && initialData.questions.map((q, idx) => { const text = getQuestionText(q.questionContent) const options = getOptions(q.questionContent) const value = answersByQuestionId[q.questionId]?.answer return (
Question {idx + 1} {q.questionType.replace("_", " ").replace(/\b\w/g, l => l.toUpperCase())} • {q.maxScore} points
{text || "—"}
{q.questionType === "text" ? (