feat(simulations): SimulationFlowProvider + SujetsPage + SujetCard

This commit is contained in:
Hermann_Kitio 2026-04-21 02:14:19 +03:00
parent 7902eec042
commit 782439b309
3 changed files with 278 additions and 0 deletions

View file

@ -0,0 +1,117 @@
/**
* Provider de flux simulation partage l'état entre /simulation/ee et /sujets.
*
* Hérite de la state machine de useSimulation mais déplace la source de vérité
* hors du hook pour qu'elle survive aux navigations React Router (Option A cf.
* plan de refonte UX "page /sujets avec cartes").
*
* Règle H : aucune logique métier les mutations s'appuient sur entities/.
*/
import { createContext, useContext, useState, type ReactNode } from 'react'
import { useMutation } from '@tanstack/react-query'
import { createSimulation } from '@/entities/production/api'
import { correctEe } from '@/entities/report/api'
import type {
CreateSimulationPayload,
Production,
SujetData,
Tache,
} from '@/entities/production/types'
import type { Report } from '@/entities/report/types'
import type { ApiError } from '@/shared/types/api'
export type SimulationStep =
| 'idle'
| 'choosing-subject'
| 'task-selected'
| 'correcting'
| 'done'
const TACHES_SANS_CATALOGUE: Tache[] = ['EO_T1']
interface FlowValue {
step: SimulationStep
production: Production | null
sujet: SujetData | null
report: Report | null
isCreating: boolean
isCorrecting: boolean
createError: ApiError | null
correctError: ApiError | null
selectTask: (payload: CreateSimulationPayload) => void
submitText: (texte: string) => void
changeSubject: (sujet: SujetData) => void
setStep: (step: SimulationStep) => void
reset: () => void
}
const SimulationFlowContext = createContext<FlowValue | null>(null)
export function SimulationFlowProvider({ children }: { children: ReactNode }) {
const [step, setStep] = useState<SimulationStep>('idle')
const [production, setProduction] = useState<Production | null>(null)
const createMutation = useMutation({
mutationFn: createSimulation,
onSuccess: (data) => {
setProduction(data)
setStep(TACHES_SANS_CATALOGUE.includes(data.tache) ? 'task-selected' : 'choosing-subject')
},
})
const correctMutation = useMutation({
mutationFn: correctEe,
onMutate: () => setStep('correcting'),
onSuccess: () => setStep('done'),
onError: () => setStep('task-selected'),
})
function selectTask(payload: CreateSimulationPayload): void {
createMutation.mutate(payload)
}
function submitText(texte: string): void {
if (!production) return
correctMutation.mutate({ simulationId: production.id, contenu: texte, tache: production.tache })
}
function changeSubject(sujet: SujetData): void {
setProduction((p) => (p ? { ...p, sujet } : p))
}
function reset(): void {
setStep('idle')
setProduction(null)
createMutation.reset()
correctMutation.reset()
}
const value: FlowValue = {
step,
production,
sujet: production?.sujet ?? null,
report: (correctMutation.data ?? null) as Report | null,
isCreating: createMutation.isPending,
isCorrecting: correctMutation.isPending,
createError: createMutation.error as ApiError | null,
correctError: correctMutation.error as ApiError | null,
selectTask,
submitText,
changeSubject,
setStep,
reset,
}
return (
<SimulationFlowContext.Provider value={value}>{children}</SimulationFlowContext.Provider>
)
}
export function useSimulationFlow(): FlowValue {
const ctx = useContext(SimulationFlowContext)
if (!ctx) {
throw new Error('useSimulationFlow doit être utilisé dans un <SimulationFlowProvider>.')
}
return ctx
}