"use client"; // Hub d'apprentissage : annonces actives, carte par cours (progression, série, // éléments dus) et « prochaines meilleures activités » recommandées. import Link from "next/link"; import { useEffect, useState } from "react"; import { ArrowRight, BellRing, BookOpenCheck, Flame, GraduationCap, Layers, Megaphone, Pin, } from "lucide-react"; import { PageHeader } from "@/components/app-shell"; import { Badge, Card, EmptyState, ProgressBar, Skeleton } from "@/components/ui"; import { Markdown } from "@/components/chat/markdown"; import { ErrorBanner, fetchJson, fmtDate } from "./shared"; type Course = { code: string; title: string; color: string }; type Announcement = { id: number; title: string; body: string; course_code: string | null; pinned: number; created_at: string }; type Recommendation = { kind: string; label: string; reason: string; href: string; courseCode: string; priority: number }; type Overview = { globalScore: number; streak: number; practicedCount: number; concepts: unknown[]; stats: { cardsDue: number; errorsToReview: number; quizAccuracy: number | null; studyMinutes: number }; recommendations: Recommendation[]; }; export function LearnHub({ courses, displayName }: { courses: Course[]; displayName: string }) { const [announcements, setAnnouncements] = useState(null); const [overviews, setOverviews] = useState>({}); const [error, setError] = useState(null); useEffect(() => { fetchJson<{ announcements: Announcement[] }>("/api/announcements") .then((d) => setAnnouncements(d.announcements)) .catch((e) => { setAnnouncements([]); setError(e instanceof Error ? e.message : "Erreur de chargement."); }); for (const c of courses) { fetchJson(`/api/learning/${c.code.toLowerCase()}/overview`) .then((d) => setOverviews((o) => ({ ...o, [c.code]: d }))) .catch(() => setOverviews((o) => ({ ...o, [c.code]: null }))); } }, [courses]); const recommendations = courses .flatMap((c) => overviews[c.code]?.recommendations ?? []) .sort((a, b) => b.priority - a.priority) .slice(0, 5); const overviewsLoaded = courses.every((c) => c.code in overviews); return (
{error && } {/* Annonces */} {announcements === null ? (
) : announcements.length > 0 ? (
{announcements.map((a) => (

{a.title}

{!!a.pinned && Épinglée} {a.course_code && {a.course_code}} {fmtDate(a.created_at)}
))}
) : null} {/* Cours */}

Mes cours

{courses.length === 0 ? ( } title="Aucun cours inscrit" description="Votre compte n'est associé à aucun cours actif. Contactez votre professeur pour l'inscription." /> ) : (
{courses.map((c) => { const ov = overviews[c.code]; const loading = !(c.code in overviews); return (

{c.code}

{c.title}

{loading ? (
) : ov ? ( <>
Maîtrise globale estimée {Math.round(ov.globalScore * 100)} %
= 0.6 ? "green" : "brand"} />
{ov.streak > 0 && ( {ov.streak} jour{ov.streak > 1 ? "s" : ""} )} 0 ? "amber" : "neutral"}> {ov.stats.cardsDue} carte{ov.stats.cardsDue > 1 ? "s" : ""} due{ov.stats.cardsDue > 1 ? "s" : ""} {ov.stats.errorsToReview > 0 && ( {ov.stats.errorsToReview} erreur{ov.stats.errorsToReview > 1 ? "s" : ""} à revoir )}
) : (

Progression indisponible pour le moment.

)}
); })}
)}
{/* Recommandations */}

Recommandé pour vous

{!overviewsLoaded ? (
) : recommendations.length === 0 ? ( } title="Rien d'urgent" description="Commencez une activité — flashcards, quiz ou une question au chat — pour obtenir des recommandations personnalisées." /> ) : (
{recommendations.map((r, i) => (

{r.label}

{r.courseCode}

Pourquoi : {r.reason}

))}
)}
); }