feat(rapport): Sprint 3.6b — RapportPage enrichie, exercices dynamiques, production modèle, sélecteur NCLC

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hermann_Kitio 2026-04-22 20:14:38 +03:00
parent 8390e8b873
commit f51caa1b75
22 changed files with 1357 additions and 297 deletions

View file

@ -17,6 +17,7 @@ import { Button } from '@/shared/ui/Button'
import { formatTache } from '@/entities/production/lib'
import { hasAccess, type Plan } from '@/entities/user/lib'
import type { SujetData, Tache } from '@/entities/production/types'
import type { NclcCible } from '@/entities/report/types'
import type { ApiError } from '@/shared/types/api'
import { countWords, getSimulationConfig } from '../lib/simulationConfig'
import { useTimer } from '../hooks/useTimer'
@ -28,6 +29,7 @@ import { SpecialCharsKeyboard } from './SpecialCharsKeyboard'
import { TimerDisplay } from './TimerDisplay'
import { WordCountBar } from './WordCountBar'
import { IdeesSuggestions } from './IdeesSuggestions'
import { NclcCibleSelector } from './NclcCibleSelector'
const MIN_WORDS_IDEES = 30
const LS_SIMULATION_ID_KEY = 'expria_simulation_id'
@ -66,7 +68,7 @@ interface Props {
step: SimulationStep
isSubmitting: boolean
error: ApiError | null
onSubmit: (texte: string) => void
onSubmit: (texte: string, nclcCible: NclcCible) => void
onBack: () => void
onChangeSujet: () => void
}
@ -89,6 +91,7 @@ export function SimulationForm({
const [texte, setTexte] = useState(() => initialContenu ?? '')
const [fieldError, setFieldError] = useState<string | null>(null)
const [isIdeesOpen, setIsIdeesOpen] = useState(false)
const [nclcCible, setNclcCible] = useState<NclcCible>(9)
const config = getSimulationConfig(tache)
const wordCount = countWords(texte)
@ -150,8 +153,8 @@ export function SimulationForm({
if (wordCount < config.motsMin) return
hasAutoSubmittedRef.current = true
onSubmit(texte)
}, [timer.isExpired, wordCount, config.motsMin, isSubmitting, texte, onSubmit])
onSubmit(texte, nclcCible)
}, [timer.isExpired, wordCount, config.motsMin, isSubmitting, texte, nclcCible, onSubmit])
function handleInsert(char: string) {
const el = textareaRef.current
@ -185,7 +188,7 @@ export function SimulationForm({
return
}
onSubmit(parsed.data.texte)
onSubmit(parsed.data.texte, nclcCible)
}
const apiError = mapCorrectError(error)
@ -308,6 +311,12 @@ export function SimulationForm({
className="w-full resize-none overflow-y-hidden rounded-md border border-line bg-surface p-3 text-sm text-ink-1 placeholder:text-ink-5 focus:border-expria focus:outline-none focus:shadow-focus disabled:cursor-not-allowed disabled:opacity-50"
/>
<WordCountBar count={wordCount} config={config} />
<NclcCibleSelector
value={nclcCible}
onChange={setNclcCible}
disabled={isSubmitting}
/>
{autosave.savedAt && !fieldError && (
<p className="text-xs text-ink-4" aria-live="polite">
Sauvegardé à{' '}