/** * FTD-21 — autosave du contenu d'une simulation en cours. * * Debounce 30 s sur chaque changement de `contenu`. Flush immédiat au * `beforeunload` (best-effort : la promesse peut ne pas aboutir si la page * se ferme avant la réponse — le texte reste en `localStorage` côté texte * utilisateur + state parent). * * Dédoublonnage : aucun appel réseau si le contenu n'a pas changé depuis * le dernier save réussi. * * Règle H : pas de logique métier — wrap autour de `entities/production/api.autosaveContenu`. */ import { useCallback, useEffect, useRef, useState } from 'react' import { autosaveContenu } from '@/entities/production/api' const DEBOUNCE_MS = 30_000 export interface UseAutosaveResult { savedAt: Date | null isSaving: boolean } export function useAutosave( simulationId: string | null, contenu: string, enabled: boolean, ): UseAutosaveResult { const [savedAt, setSavedAt] = useState(null) const [isSaving, setIsSaving] = useState(false) const lastSavedContenuRef = useRef(null) const latestRef = useRef({ simulationId, contenu, enabled }) latestRef.current = { simulationId, contenu, enabled } const flush = useCallback(async () => { const { simulationId, contenu, enabled } = latestRef.current if (!enabled || !simulationId || !contenu) return if (lastSavedContenuRef.current === contenu) return setIsSaving(true) try { await autosaveContenu(simulationId, contenu) lastSavedContenuRef.current = contenu setSavedAt(new Date()) } catch { // best-effort — pas d'UI d'erreur, le texte reste en mémoire côté client } finally { setIsSaving(false) } }, []) useEffect(() => { if (!enabled || !simulationId || !contenu) return if (lastSavedContenuRef.current === contenu) return const timer = setTimeout(() => { void flush() }, DEBOUNCE_MS) return () => clearTimeout(timer) }, [simulationId, contenu, enabled, flush]) useEffect(() => { const handler = () => { void flush() } window.addEventListener('beforeunload', handler) return () => window.removeEventListener('beforeunload', handler) }, [flush]) return { savedAt, isSaving } }