feat(simulations): resume session + RapportPage tolère rapport=null (FTD-21)

This commit is contained in:
Hermann_Kitio 2026-04-21 04:52:51 +03:00
parent aaecc3f804
commit ae8d8af1df
2 changed files with 48 additions and 5 deletions

View file

@ -11,11 +11,28 @@
*/ */
import { apiFetch } from '@/shared/lib/api-client' import { apiFetch } from '@/shared/lib/api-client'
import { getSimulationState } from '@/entities/production/api'
import type { CorrectEePayload, CorrectEoPayload, Report } from './types' import type { CorrectEePayload, CorrectEoPayload, Report } from './types'
/** Récupère un rapport existant. Endpoint : `GET /simulations/:id`. */ /**
* Récupère un rapport existant. Endpoint : `GET /simulations/:id`.
*
* FTD-21 : depuis l'assouplissement de `getById` côté backend (tolère `rapport=null`
* pour permettre le resume), on unwrap le champ `rapport` du `SimulationState`.
* Si la simulation est encore en cours (`rapport === null`), on lève une erreur
* typée `REPORT_NOT_READY` que RapportPage catche pour rediriger vers /simulation/ee.
*/
export function getReport(id: string): Promise<Report> { export function getReport(id: string): Promise<Report> {
return apiFetch<Report>(`/simulations/${id}`) return getSimulationState(id).then((state) => {
if (state.rapport === null) {
throw {
error: true,
code: 'REPORT_NOT_READY',
message: 'Simulation en cours — rédaction pas encore corrigée.',
}
}
return { ...state.rapport, simulation_id: state.simulation_id }
})
} }
const CORRECTION_TIMEOUT_MS = 30_000 const CORRECTION_TIMEOUT_MS = 30_000

View file

@ -11,6 +11,7 @@
* Règle H : logique de floutage dans entities/report/lib.ts. * Règle H : logique de floutage dans entities/report/lib.ts.
*/ */
import { useEffect } from 'react'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import { Link, useNavigate, useParams } from 'react-router-dom' import { Link, useNavigate, useParams } from 'react-router-dom'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
@ -23,6 +24,15 @@ import { Badge } from '@/shared/ui/Badge'
import { Button } from '@/shared/ui/Button' import { Button } from '@/shared/ui/Button'
import type { Critere } from '@/entities/report/types' import type { Critere } from '@/entities/report/types'
function isReportNotReady(err: unknown): boolean {
return (
typeof err === 'object' &&
err !== null &&
'code' in err &&
(err as { code: unknown }).code === 'REPORT_NOT_READY'
)
}
// ── Composants internes ────────────────────────────────────────────────────── // ── Composants internes ──────────────────────────────────────────────────────
function RapportSkeleton() { function RapportSkeleton() {
@ -105,7 +115,16 @@ export function RapportPage() {
const { id = '' } = useParams<{ id: string }>() const { id = '' } = useParams<{ id: string }>()
const navigate = useNavigate() const navigate = useNavigate()
const { rapport, isLoading, isError } = useRapport(id) const { rapport, isLoading, isError, error } = useRapport(id)
const isInProgress = isError && isReportNotReady(error)
// FTD-21 — si la simulation n'est pas encore corrigée, rediriger vers /simulation/ee.
// Le SimulationFlowProvider restaurera la session via localStorage si présent.
useEffect(() => {
if (isInProgress) {
navigate('/simulation/ee', { replace: true })
}
}, [isInProgress, navigate])
const { const {
data: planData, data: planData,
@ -137,8 +156,15 @@ export function RapportPage() {
{/* Loading */} {/* Loading */}
{(isLoading || isPlanLoading) && <RapportSkeleton />} {(isLoading || isPlanLoading) && <RapportSkeleton />}
{/* Erreur */} {/* FTD-21 — simulation en cours : message discret avant redirection via useEffect */}
{(isError || isPlanError) && !isLoading && !isPlanLoading && ( {isInProgress && (
<p className="text-center text-sm text-ink-4" aria-live="polite">
Votre simulation est en cours.
</p>
)}
{/* Erreur (hors "en cours" déjà géré au-dessus) */}
{(isError || isPlanError) && !isInProgress && !isLoading && !isPlanLoading && (
<div <div
role="alert" role="alert"
className="space-y-3 rounded-lg border border-danger/30 bg-danger-bg px-4 py-6 text-center" className="space-y-3 rounded-lg border border-danger/30 bg-danger-bg px-4 py-6 text-center"