const { useState, useEffect, useRef, useCallback } = React; const TV_INACT_MS_3 = 30000; function useTvMode3() { const [isTv] = useState(() => { try { return new URLSearchParams(window.location.search).has('tv'); } catch (e) { return false; } }); return isTv; } function useInactivity3(active, onTimeout, ms = TV_INACT_MS_3) { const [progress, setProgress] = useState(1); const last = useRef(Date.now()); useEffect(() => { if (!active) { setProgress(1); return; } let raf = 0; const reset = () => { last.current = Date.now(); }; const events = ['touchstart', 'click', 'mousemove', 'keydown', 'wheel']; events.forEach(e => window.addEventListener(e, reset, { passive: true })); const tick = () => { const elapsed = Date.now() - last.current; setProgress(Math.max(0, 1 - elapsed / ms)); if (elapsed >= ms) onTimeout(); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => { cancelAnimationFrame(raf); events.forEach(e => window.removeEventListener(e, reset)); }; }, [active, ms, onTimeout]); return progress; } function App3() { const [data, setData] = useState(null); const [err, setErr] = useState(null); const isTv = useTvMode3(); const [route, setRoute] = useState(() => ({ screen: isTv ? 'veille' : 'etapes', etapeIndex: null, entreprise: null, activeFilter: null, })); const routeRef = useRef(route); useEffect(() => { routeRef.current = route; }, [route]); useEffect(() => { fetch('data/aluminium.json').then(r => r.json()).then(setData).catch(e => setErr(String(e))); }, []); // Initialise la première entrée d'historique useEffect(() => { if (isTv) return; try { history.replaceState({ screen: 'etapes', etapeIndex: null, entreprise: null, activeFilter: null }, ''); } catch(e) {} }, []); // Retour / avance navigateur useEffect(() => { if (isTv) return; const onPop = (e) => { const s = e.state; if (s?.screen) setRoute(s); else setRoute({ screen: 'etapes', etapeIndex: null, entreprise: null }); }; window.addEventListener('popstate', onPop); return () => window.removeEventListener('popstate', onPop); }, [isTv]); const push = useCallback((newRoute, hash) => { if (!isTv) try { history.pushState(newRoute, '', hash || '#'); } catch(e) {} setRoute(newRoute); }, [isTv]); const goVeille = useCallback(() => setRoute({ screen: 'veille', etapeIndex: null, entreprise: null }), []); const goEtapes = useCallback(() => push({ screen: 'etapes', etapeIndex: null, entreprise: null }, '#'), [push]); const pickEtape = (i) => push({ screen: 'detail', etapeIndex: i, entreprise: null, activeFilter: null }, '#etape-' + i); const onFilterChange = (filterId) => push({ ...routeRef.current, activeFilter: filterId }, '#etape-' + routeRef.current.etapeIndex + '-f'); const pickEnt = (e) => push({ ...routeRef.current, entreprise: e }, '#ent'); const closeEnt = () => push({ ...routeRef.current, entreprise: null }, routeRef.current.etapeIndex != null ? '#etape-' + routeRef.current.etapeIndex : '#'); const inactivityActive = isTv && route.screen !== 'veille'; const progress = useInactivity3(inactivityActive, goVeille); if (err) return
{err}