import { useMemo, useState } from "react"; import { useParams } from "react-router-dom"; import { useCourseDetail, useTrackProgress } from "@/hooks/queries/useCourseGeneration"; import type { ModuleConfig, ModuleResource } from "@/types"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { ScrollArea } from "@/components/ui/scroll-area"; import { BookOpen, Check, ChevronDown, FileText, Headphones, Lock, Sparkles, Video, } from "lucide-react"; import { cn } from "@/lib/utils"; function ResourceIcon({ type }: { type: ModuleResource["type"] }) { switch (type) { case "pdf": return ; case "video": return ; case "audio": return ; default: return ; } } export default function CourseDelivery() { const { courseId: courseIdParam } = useParams(); const courseId = Number(courseIdParam); const { data: course, isLoading } = useCourseDetail(courseId); const track = useTrackProgress(); const [activeResource, setActiveResource] = useState<{ module: ModuleConfig; resource: ModuleResource } | null>(null); const bySkill = useMemo(() => { const map = new Map(); course?.modules.forEach((m) => { const k = m.skill || "General"; if (!map.has(k)) map.set(k, []); map.get(k)!.push(m); }); return map; }, [course]); const overallProgress = useMemo(() => { if (!course?.modules.length) return 0; const sum = course.modules.reduce((s, m) => s + (m.progress_percent ?? 0), 0); return Math.round(sum / course.modules.length); }, [course]); const weeksLeft = course ? Math.max(0, course.duration_weeks - 2) : 0; const markResource = (mod: ModuleConfig, res: ModuleResource) => { track.mutate({ courseId, progress: { resource_id: res.id, completion_percent: 100, time_spent_ms: 60000, }, }); setActiveResource({ module: mod, resource: res }); }; if (isLoading) { return ( ); } return ( {course?.title ?? `Course #${courseId}`} {course?.exam_type ?? "IELTS"} · Target {course?.target_band ?? course?.target_level ?? "—"} {course?.progression_model ?? "linear"} Overall progress {overallProgress}% About {weeksLeft} weeks remaining at your current pace {Array.from(bySkill.entries()).map(([skill, mods]) => ( {skill} {mods.map((mod) => ( {mod.title} {mod.status === "locked" ? : null} {mod.id === course?.modules.find((m) => m.status === "in_progress")?.id ? ( Recommended Next ) : null} {mod.status === "skipped" ? Skipped : null} {mod.estimated_hours}h · {mod.cefr_target ?? "CEFR target"} {mod.resources.map((r) => ( markResource(mod, r)} className="flex w-full items-center justify-between rounded-md border bg-background px-3 py-2 text-left text-sm hover:bg-muted/50" > {r.title} {r.completed ? : null} ))} {mod.status === "in_progress" || mod.progress_percent ? "Continue" : "Start"} ))} ))} Resource viewer {activeResource ? activeResource.resource.title : "Select a resource to open the viewer"} {!activeResource ? ( PDF, video, audio, and exercise viewers render here. ) : ( {activeResource.resource.type === "pdf" && "PDF viewer placeholder"} {activeResource.resource.type === "video" && "Video player placeholder"} {activeResource.resource.type === "audio" && "Audio player placeholder"} {activeResource.resource.type === "exercise" && "Interactive exercise placeholder"} {activeResource.resource.type === "ai_generated" && ( AI-generated content placeholder )} Module: {activeResource.module.title}. Completion: {activeResource.module.completion_criteria}. )} ); }
About {weeksLeft} weeks remaining at your current pace
PDF, video, audio, and exercise viewers render here.