102 lines
3.1 KiB
TypeScript
102 lines
3.1 KiB
TypeScript
/**
|
|
* Page de simulation Expression Écrite.
|
|
*
|
|
* Orchestre les 3 étapes du flux : sélection de tâche → saisie → rapport.
|
|
* Le choix du sujet est délégué à la page /sujets (refonte UX 2026-04-21).
|
|
*
|
|
* Règle D : quotas et permissions passent par canSimulate() — jamais de plan === '...'
|
|
* Règle H : aucune logique métier — tout est dans useSimulation() et les entités.
|
|
*/
|
|
|
|
import { usePlan } from '@/features/dashboard/hooks/usePlan'
|
|
import { Button } from '@/shared/ui/Button'
|
|
import { useSimulation } from '../hooks/useSimulation'
|
|
import { TaskSelector } from '../components/TaskSelector'
|
|
import { SimulationForm } from '../components/SimulationForm'
|
|
|
|
function SimulationSkeleton() {
|
|
return (
|
|
<div className="space-y-4" aria-busy="true" aria-label="Chargement…">
|
|
<div className="h-6 w-40 animate-pulse rounded bg-canvas-2" />
|
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
|
{Array.from({ length: 6 }).map((_, i) => (
|
|
<div key={i} className="h-24 animate-pulse rounded-lg bg-canvas-2" />
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function SimulationPage() {
|
|
const {
|
|
data: planData,
|
|
isLoading: isPlanLoading,
|
|
isError: isPlanError,
|
|
refetch: refetchPlan,
|
|
} = usePlan()
|
|
|
|
const {
|
|
step,
|
|
production,
|
|
sujet,
|
|
isCreating,
|
|
isCorrecting,
|
|
correctError,
|
|
selectTask,
|
|
submitText,
|
|
goToSubjectPicker,
|
|
reset,
|
|
} = useSimulation()
|
|
|
|
// Le reset sticky (step='done' ou 'choosing-subject' au retour) est déclenché
|
|
// explicitement par les callers qui ramènent vers /simulation/ee :
|
|
// - RapportPage.goToSimulations : reset() avant navigate
|
|
// - SujetsPage bouton « ← Retour » : reset() avant navigate
|
|
// Un useEffect réactif ici annulerait les transitions légitimes de
|
|
// createMutation.onSuccess (idle → choosing-subject → navigate /sujets).
|
|
|
|
return (
|
|
<main className="mx-auto max-w-2xl px-4 py-6">
|
|
{isPlanLoading && <SimulationSkeleton />}
|
|
|
|
{isPlanError && (
|
|
<div className="space-y-3 text-center">
|
|
<p className="text-sm text-danger">
|
|
Impossible de charger vos informations. Réessayez dans quelques instants.
|
|
</p>
|
|
<Button variant="secondary" size="sm" onClick={() => refetchPlan()}>
|
|
Réessayer
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
{planData && step === 'idle' && (
|
|
<TaskSelector
|
|
type="EE"
|
|
plan={planData.plan}
|
|
simulationsUsed={planData.simulations_used}
|
|
isLoading={isCreating}
|
|
onSelect={selectTask}
|
|
/>
|
|
)}
|
|
|
|
{planData &&
|
|
(step === 'task-selected' || step === 'correcting') &&
|
|
production && (
|
|
<SimulationForm
|
|
tache={production.tache}
|
|
sujet={sujet}
|
|
plan={planData.plan}
|
|
simulationId={production.id}
|
|
initialContenu={production.contenu}
|
|
step={step}
|
|
isSubmitting={isCorrecting}
|
|
error={correctError}
|
|
onSubmit={submitText}
|
|
onBack={reset}
|
|
onChangeSujet={goToSubjectPicker}
|
|
/>
|
|
)}
|
|
</main>
|
|
)
|
|
}
|