72 lines
2.2 KiB
TypeScript
72 lines
2.2 KiB
TypeScript
/**
|
|
* 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<Date | null>(null)
|
|
const [isSaving, setIsSaving] = useState(false)
|
|
const lastSavedContenuRef = useRef<string | null>(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 }
|
|
}
|