/** * Page de rapport de correction. * * Sections toujours visibles : score /20, NCLC, feedback_court. * Sections conditionnelles via isSectionVisible(plan, section) : * detailed_report → criteres, erreurs * tips → modele, idees, exercices * * SEC-05 : textes IA rendus via react-markdown, jamais dangerouslySetInnerHTML. * Règle D : isSectionVisible() obligatoire — jamais if (plan === 'xxx'). * Règle H : logique de floutage dans entities/report/lib.ts. */ import ReactMarkdown from 'react-markdown' import { Link, useNavigate, useParams } from 'react-router-dom' import { useQuery } from '@tanstack/react-query' import { Lock } from 'lucide-react' import { getPlanStatus } from '@/entities/user/api' import { isSectionVisible } from '@/entities/report/lib' import { useRapport } from '../hooks/useRapport' import { Card } from '@/shared/ui/Card' import { Badge } from '@/shared/ui/Badge' import { Button } from '@/shared/ui/Button' import type { Critere, Exercice } from '@/entities/report/types' // ── Composants internes ────────────────────────────────────────────────────── function RapportSkeleton() { return (
) } const PLACEHOLDER_WIDTHS = ['w-3/4', 'w-full', 'w-3/5', 'w-4/5'] as const function BlurredSection({ visible, onUpgrade, children, }: { visible: boolean onUpgrade: () => void children: React.ReactNode }) { if (visible) return <>{children} return (
) } function CritereRow({ critere }: { critere: Critere }) { return (
  • {critere.nom} {critere.note}

    {c}

    }} > {critere.commentaire}
  • ) } function ExerciceCard({ exercice }: { exercice: Exercice }) { return (
  • {exercice.titre}

    {exercice.contenu}
  • ) } // ── Page principale ────────────────────────────────────────────────────────── export function RapportPage() { const { id = '' } = useParams<{ id: string }>() const navigate = useNavigate() const { rapport, isLoading, isError } = useRapport(id) const { data: planData, isLoading: isPlanLoading, isError: isPlanError, } = useQuery({ queryKey: ['plan'], queryFn: getPlanStatus, staleTime: 5 * 60 * 1000, }) const onUpgrade = () => navigate('/plan') return (
    {/* Breadcrumb */} {/* Loading */} {(isLoading || isPlanLoading) && } {/* Erreur */} {(isError || isPlanError) && !isLoading && !isPlanLoading && (

    Impossible de charger ce rapport. Réessayez dans quelques instants.

    )} {rapport && planData && ( <> {/* ── Hero : Score + NCLC — toujours visibles ───────────── */}

    Score

    {rapport.score} /20

    Niveau estimé

    NCLC {rapport.nclc.toFixed(1).replace('.', ',')}
    {/* ── Feedback court — toujours visible ─────────────────── */}

    Retour général

    {rapport.feedback_court}
    {/* ── Critères — detailed_report ────────────────────────── */}

    Détail par critère

      {rapport.criteres.map((c) => ( ))}
    {/* ── Erreurs — detailed_report ─────────────────────────── */}

    Erreurs détectées

      {rapport.erreurs.map((erreur, i) => (
    • {c} }} > {erreur}
    • ))}
    {/* ── Modèle — tips ────────────────────────────────────── */}

    Production modèle

    {rapport.modele}
    {/* ── Idées — tips ─────────────────────────────────────── */}

    Suggestions d'idées

      {rapport.idees.map((idee, i) => (
    1. {i + 1}. {c} }} > {idee}
    2. ))}
    {/* ── Exercices — tips ─────────────────────────────────── */}

    Exercices personnalisés

      {rapport.exercices.map((ex) => ( ))}
    )}
    ) }