import { useMemo } from "react"; import { useSearchParams } from "react-router-dom"; import { useAuth } from "@/contexts/AuthContext"; import { useGapAnalysis, useAutoGenerateCourse } from "@/hooks/queries/useCourseGeneration"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; import { Skeleton } from "@/components/ui/skeleton"; import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; export default function GapAnalysis() { const [searchParams] = useSearchParams(); const { user } = useAuth(); const fromParam = searchParams.get("from"); const source = fromParam === "placement" ? "placement" : "exam"; const examId = Number(searchParams.get("examId")); const sessionId = Number(searchParams.get("sessionId")); const sourceId = source === "exam" ? examId : sessionId; const entityStudent = searchParams.get("entity") === "1"; const enabled = source === "exam" ? Number.isFinite(examId) && examId > 0 : Number.isFinite(sessionId) && sessionId > 0; const { data, isLoading, isError } = useGapAnalysis(source, sourceId); const generate = useAutoGenerateCourse(); const chartData = useMemo(() => { if (!data?.skill_gaps?.length) return []; return [...data.skill_gaps] .sort((a, b) => b.gap - a.gap) .map((g) => ({ skill: g.skill, gap: g.gap })); }, [data]); const canGenerate = user?.user_type === "student" && !entityStudent; return (

Skill gap analysis

{source === "exam" ? `From exam ${examId || "—"}` : `Placement session ${sessionId || "—"}`}

{canGenerate ? ( ) : null}
{entityStudent ? ( Entity-managed students Gap analysis is view-only for your organization. Contact your coordinator to assign a course. ) : null} {!enabled ? ( Missing source Add ?from=exam&examId=… or{" "} ?from=placement&sessionId=… to the URL. ) : null} {isLoading ? ( ) : isError || !data ? (

Could not load gap analysis.

) : ( <> Skills ranked by gap Larger bars need more attention
Gap table Skill Current Target Gap Priority Est. Hours {data.skill_gaps.map((g) => ( {g.skill} {g.current_band ?? g.current_level} {g.target_band ?? g.target_level} {g.gap} {g.priority} {g.estimated_hours} ))}

Question-type weaknesses

{Object.entries(data.question_type_weaknesses).map(([skill, rows]) => ( {skill} Type Accuracy Correct Total {rows.map((r) => ( {r.question_type} {Math.round(r.accuracy * 100)}% {r.correct} {r.total} ))}
))}

Topic weaknesses

{Object.entries(data.topic_weaknesses).map(([cat, topics]) => ( {cat} Topic Errors Total {topics.map((t) => ( {t.topic} {t.errors} {t.total} ))}
))}
)}
); }