import { useState } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { AlertTriangle, ArrowLeft, CheckCircle2, Sparkles, XCircle, } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "@/components/ui/sonner"; import { useApproveExamReview, useExamReviewDetail, useRejectExamReview, } from "@/hooks/queries/useExamReview"; import type { ExamReviewQuestion } from "@/types/exam-review"; const FAILING_THRESHOLD = 0.7; function QuestionCard({ q, index }: { q: ExamReviewQuestion; index: number }) { const failing = (q.quality_score ?? 0) < FAILING_THRESHOLD; return (
Q{index + 1} · {q.skill} {q.question_type} {q.difficulty} {q.ai_generated && ( AI )}
{q.stem.length > 220 ? `${q.stem.slice(0, 220)}…` : q.stem}
= 0.85 ? "default" : failing ? "destructive" : "secondary" } className="tabular-nums shrink-0" > {((q.quality_score ?? 0) * 100).toFixed(0)}%
{q.quality_report && Object.keys(q.quality_report).length > 0 && (
            {JSON.stringify(q.quality_report, null, 2)}
          
)}
); } export default function ExamReviewDetail() { const { examId } = useParams<{ examId: string }>(); const navigate = useNavigate(); const parsedId = examId ? Number(examId) : undefined; const { data: exam, isLoading } = useExamReviewDetail(parsedId); const approve = useApproveExamReview(); const reject = useRejectExamReview(); const [approveNotes, setApproveNotes] = useState(""); const [rejectNotes, setRejectNotes] = useState(""); const [approveOpen, setApproveOpen] = useState(false); const [rejectOpen, setRejectOpen] = useState(false); if (isLoading || !exam || !parsedId) { return (
); } const handleApprove = async () => { try { await approve.mutateAsync({ examId: parsedId, notes: approveNotes }); toast.success("Exam approved and published."); setApproveOpen(false); navigate("/admin/exam/review-queue"); } catch (err) { toast.error(err instanceof Error ? err.message : "Approve failed"); } }; const handleReject = async () => { if (!rejectNotes.trim()) { toast.error("Rejection notes are required."); return; } try { await reject.mutateAsync({ examId: parsedId, notes: rejectNotes }); toast.success("Exam rejected and returned to draft."); setRejectOpen(false); navigate("/admin/exam/review-queue"); } catch (err) { toast.error(err instanceof Error ? err.message : "Reject failed"); } }; const summary = exam.summary; const alreadyReviewed = exam.status !== "pending_review"; return (

{exam.title}

{exam.status.replace("_", " ")} {exam.subject_name && · {exam.subject_name}} {exam.teacher_name && · Created by {exam.teacher_name}}
{!alreadyReviewed && (
Reject this exam?