fix(simulations): resolve FTD-23 autosave after correction + FTD-24 auto-polling pending jobs

- FTD-23: propagate enabled=false to useAutosave when step is done/correcting, preventing 400 PATCH after correction
- FTD-24: add conditional refetchInterval (3s) in useRapport for pending exercices/modele, 2min timeout with retry UI
- 7 new tests (2 regression + 5 polling), 122/122 green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hermann_Kitio 2026-04-23 03:56:57 +03:00
parent bc2a1174d1
commit cab9c8c92b
8 changed files with 379 additions and 20 deletions

View file

@ -99,7 +99,8 @@ export function SimulationForm({
const timer = useTimer(config.dureeMinutes, !isSubmitting)
const idees = useIdees()
const autosave = useAutosave(simulationId, texte, !isSubmitting)
const autosaveEnabled = !isSubmitting && step !== 'done' && step !== 'correcting'
const autosave = useAutosave(simulationId, texte, autosaveEnabled)
// FTD-21 — marquer la simulation en cours pour le resume au refresh.
useEffect(() => {

View file

@ -1,38 +1,58 @@
/**
* JobStatusFallback Sprint 3.6b.
* JobStatusFallback Sprint 3.6b + FTD-24.
*
* Affiche un fallback visuel pour les sections générées en asynchrone par le
* backend (exercices, production modèle) :
* - 'pending' "Génération en cours…" avec spinner (refresh manuel côté user)
* - 'error' "Indisponible pour le moment"
*
* FTD-24 tracera le polling automatique (laissé pour une session ultérieure).
* - 'pending' "Génération en cours…" avec spinner. Polling automatique
* géré par useRapport (cf. FTD-24).
* - 'pending' + hasTimedOut message "La génération prend plus de temps
* que prévu" + bouton Réessayer.
* - 'error' "Indisponible pour le moment".
*
* Règle L : tokens Direction H exclusivement.
*/
import { Loader2 } from 'lucide-react'
import { Card } from '@/shared/ui/Card'
import { Button } from '@/shared/ui/Button'
import type { JobStatus } from '@/entities/report/types'
interface Props {
status: JobStatus
pendingLabel?: string
errorLabel?: string
hasTimedOut?: boolean
onRetry?: () => void
}
export function JobStatusFallback({
status,
pendingLabel = 'Génération en cours…',
errorLabel = 'Indisponible pour le moment.',
hasTimedOut = false,
onRetry,
}: Props) {
if (status === 'pending') {
if (hasTimedOut) {
return (
<Card variant="default" className="space-y-3 p-4">
<p className="text-sm text-ink-2" role="alert">
La génération prend plus de temps que prévu.
</p>
{onRetry && (
<Button variant="secondary" size="sm" onClick={onRetry}>
Réessayer
</Button>
)}
</Card>
)
}
return (
<Card variant="default" className="flex items-center gap-3 p-4">
<Loader2 className="size-4 animate-spin text-ink-4" aria-hidden="true" />
<p className="text-sm text-ink-3" aria-live="polite">
{pendingLabel}{' '}
<span className="text-ink-4">Rafraîchissez la page dans quelques instants.</span>
{pendingLabel}
</p>
</Card>
)