/** * useAutoSave.ts — S800: auto-salvataggio automatico per mobile * * Salva automaticamente in tre scenari: * 1. Intervallo regolare (default 30s) — solo se isDirty * 2. App va in background (visibilitychange → hidden) — CRITICO su iOS: l'app * può essere killata dal sistema operativo in qualsiasi momento * 3. beforeunload — best-effort (non garantito su mobile) * * onSave deve essere una funzione stabile (useCallback) per evitare * re-registrazione continua degli event listener. * * Safari-safe: nessun uso di sendBeacon o XHR synchronous in beforeunload. */ import { useEffect, useRef } from "react"; interface AutoSaveOptions { /** true se ci sono modifiche non salvate */ isDirty: boolean; /** Funzione di salvataggio — deve essere stabile (useCallback) */ onSave: () => Promise; /** Intervallo in ms — default 30000 (30s) */ interval?: number; /** Se false, l'auto-save è disabilitato */ enabled?: boolean; } export function useAutoSave({ isDirty, onSave, interval = 30_000, enabled = true, }: AutoSaveOptions): void { const onSaveRef = useRef(onSave); const isDirtyRef = useRef(isDirty); // Mantieni refs aggiornate senza rimontare i listener useEffect(() => { onSaveRef.current = onSave; }, [onSave]); useEffect(() => { isDirtyRef.current = isDirty; }, [isDirty]); useEffect(() => { if (!enabled) return; // 1. Salvataggio periodico const id = setInterval(() => { if (isDirtyRef.current) void onSaveRef.current(); }, interval); // 2. App va in background — critico su iOS (viene killata se non salva) const onVisibility = () => { if (document.hidden && isDirtyRef.current) { void onSaveRef.current(); } }; document.addEventListener("visibilitychange", onVisibility, { passive: true }); // 3. beforeunload — best-effort const onUnload = () => { if (isDirtyRef.current) void onSaveRef.current(); }; window.addEventListener("beforeunload", onUnload); return () => { clearInterval(id); document.removeEventListener("visibilitychange", onVisibility); window.removeEventListener("beforeunload", onUnload); }; }, [enabled, interval]); }