/** * Page de rapport de correction — Sprint 3.6b. * * Sections toujours visibles : score + jauge, revelation, diagnostic, conseil_nclc. * Sections conditionnelles via isSectionVisible(plan, section) : * detailed_report → criteres (avec exemple/suggestion/astuce/codes) * tips → exercices, modele * * Les exercices et la production modèle peuvent être dans l'état `pending` * (jobs fire-and-forget côté backend — cf. correctionController 3.6a) ou * `error` : JobStatusFallback affiche le message approprié (refresh manuel * uniquement ; polling traqué en FTD-24). * * 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 { useEffect } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { Lock } from 'lucide-react' import { usePlan } from '@/features/dashboard/hooks/usePlan' import { isSectionVisible, groupErreursByCritere, critereCodeFromNom } from '@/entities/report/lib' import type { Report } from '@/entities/report/types' import { useRapport } from '../hooks/useRapport' import { useSimulation } from '../hooks/useSimulation' import { Card } from '@/shared/ui/Card' import { Button } from '@/shared/ui/Button' import { ScoreHero } from '../components/rapport/ScoreHero' import { RevelationCards } from '../components/rapport/RevelationCards' import { DiagnosticCallout } from '../components/rapport/DiagnosticCallout' import { CritereCard } from '../components/rapport/CritereCard' import { ConseilNclcCallout } from '../components/rapport/ConseilNclcCallout' import { ExerciceInteractive } from '../components/rapport/ExerciceInteractive' import { ProductionModeleSection } from '../components/rapport/ProductionModeleSection' import { JobStatusFallback } from '../components/rapport/JobStatusFallback' function isReportNotReady(err: unknown): boolean { return ( typeof err === 'object' && err !== null && 'code' in err && (err as { code: unknown }).code === 'REPORT_NOT_READY' ) } // ── Floutage section ──────────────────────────────────────────────────── 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 (
) } // ── Squelette ─────────────────────────────────────────────────────────── function RapportSkeleton() { return (
) } // ── Sections thématiques ───────────────────────────────────────────────── function CriteresSection({ rapport }: { rapport: Report }) { const grouped = groupErreursByCritere(rapport.erreurs_codes) return (

Détail par critère

{rapport.criteres.map((c) => { const code = critereCodeFromNom(c.nom) const codes = code ? grouped[code] : [] return })}
) } function ExercicesSection({ rapport }: { rapport: Report }) { if (rapport.exercices_status !== 'ready' || !rapport.exercices) { return (

Mes exercices personnalisés

) } return (

Mes exercices personnalisés

{rapport.exercices.map((ex, i) => ( ))}
) } function ModeleSection({ rapport }: { rapport: Report }) { if (rapport.modele_status !== 'ready' || !rapport.modele) { return (

Version restructurée NCLC 9+

) } return (

Version restructurée NCLC 9+

) } // ── Page principale ────────────────────────────────────────────────────── export function RapportPage() { const { id = '' } = useParams<{ id: string }>() const navigate = useNavigate() const { rapport, isLoading, isError, error } = useRapport(id) const isInProgress = isError && isReportNotReady(error) const { reset } = useSimulation() const { data: planData, isLoading: isPlanLoading, isError: isPlanError } = usePlan() // FTD-21 — si la simulation n'est pas encore corrigée, rediriger vers /simulation/ee. useEffect(() => { if (isInProgress) { navigate('/simulation/ee', { replace: true }) } }, [isInProgress, navigate]) const onUpgrade = () => navigate('/plan') // Quitter le rapport proprement : reset du flow (step, production, mutations) // avant de naviguer — sinon step='done' resterait sticky et empêcherait le // retour au TaskSelector ou à /sujets. function goToSimulations() { reset() navigate('/simulation/ee') } return (
{/* Breadcrumb */} {(isLoading || isPlanLoading) && } {isInProgress && (

Votre simulation est en cours.

)} {(isError || isPlanError) && !isInProgress && !isLoading && !isPlanLoading && (

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

)} {rapport && planData && ( <> {/* Action de sortie — reset + nouvelle simulation */}
)}
) }