import { useMemo } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer, } from "recharts"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Skeleton } from "@/components/ui/skeleton"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { ChevronDown, Loader2 } from "lucide-react"; import { usePlacementResults, useLearningPath, useSpeakingStatus } from "@/hooks/queries/usePlacement"; import type { CEFRLevel } from "@/types"; import { cn } from "@/lib/utils"; function cefrBadgeClass(level: CEFRLevel): string { switch (level) { case "c2": case "c1": return "bg-violet-600 hover:bg-violet-600 text-white border-0"; case "b2": case "b1": return "bg-emerald-600 hover:bg-emerald-600 text-white border-0"; case "a2": case "a1": case "pre_a1": return "bg-amber-600 hover:bg-amber-600 text-white border-0"; default: return ""; } } const CEFR_LABEL: Record = { pre_a1: "Pre-A1", a1: "A1", a2: "A2", b1: "B1", b2: "B2", c1: "C1", c2: "C2", }; function formatCefr(level: CEFRLevel): string { return CEFR_LABEL[level] ?? level; } export default function PlacementResults() { const [searchParams] = useSearchParams(); const sessionId = searchParams.get("session") || ""; const navigate = useNavigate(); const results = usePlacementResults(sessionId); const learningPath = useLearningPath(sessionId); const speakingPending = results.data?.speaking_status === "pending"; const speakingPoll = useSpeakingStatus(sessionId, speakingPending); const radarData = useMemo(() => { if (!results.data?.dimensions.length) return []; return results.data.dimensions.map((d) => ({ dimension: d.dimension, score: d.score, fullMark: 100, })); }, [results.data]); if (!sessionId) { return (

Missing session.

); } return (

Your placement results

Personalized snapshot before your course begins.

{results.isLoading && (
)} {results.error && (

{results.error.message}

)} {results.data && ( <>

Overall level

{formatCefr(results.data.overall_cefr)}

CEFR {formatCefr(results.data.overall_cefr)}
Skill profile Relative strength across dimensions {radarData.length > 0 ? ( ) : (

No dimension data.

)}
Detailed scores Gap to target highlights where to focus first Dimension Score CEFR Gap {results.data.dimensions.map((row) => ( {row.dimension} {row.score.toFixed(1)} {row.cefr_level ? ( {formatCefr(row.cefr_level)} ) : ( "—" )} {row.gap_to_target} ))}
Speaking: {(speakingPoll.data?.status === "scored" || results.data.speaking_status === "scored") && ( Scored )} {speakingPending && speakingPoll.data?.status !== "scored" && ( Pending )}
Learning path preview Aligned modules based on your gaps {learningPath.isLoading && (
)} {learningPath.error && (

{learningPath.error.message}

)} {learningPath.data && ( <>

{learningPath.data.course_title}

~{learningPath.data.estimated_duration_weeks} weeks · {learningPath.data.module_count} modules ·{" "} {learningPath.data.study_hours_per_week} h/week suggested

Skill gap modules

    {[...learningPath.data.modules] .sort((a, b) => { const pr = { high: 0, medium: 1, low: 2 }; return pr[a.priority] - pr[b.priority]; }) .map((m) => (
  • {m.name}
    {m.priority} {m.estimated_hours}h {m.difficulty}
  • ))}
View full course plan {learningPath.data.modules.map((m) => (

{m.name} — {m.resource_types.join(", ")}

))}
)}
)}
); }