From 8e57a1c15be4bb2d18e0f2753f4f47a552679abb Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Nov 2025 07:54:56 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20permettre=20au=20timer=20Pomodoro=20de?= =?UTF-8?q?=20continuer=20en=20arri=C3=A8re-plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correction du problème où le timer Pomodoro se mettait en pause lors du changement d'onglet. Le timer utilise maintenant des timestamps réels au lieu de setInterval pour calculer le temps restant, ce qui permet de continuer à fonctionner même quand l'onglet n'est pas actif. Changements: - Ajout d'un état endTime pour stocker le timestamp de fin - Modification de la logique du timer pour calculer le temps restant basé sur endTime - Le timer vérifie maintenant toutes les 100ms pour plus de précision - Persistance de endTime dans localStorage pour la synchronisation --- src/components/PomodoroTimer.tsx | 78 +++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/src/components/PomodoroTimer.tsx b/src/components/PomodoroTimer.tsx index 9f829f5..34b2ae4 100644 --- a/src/components/PomodoroTimer.tsx +++ b/src/components/PomodoroTimer.tsx @@ -37,6 +37,10 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) { const saved = localStorage.getItem('pomodoroSessionsCompleted'); return saved ? parseInt(saved) : 0; }); + const [endTime, setEndTime] = useState(() => { + const saved = localStorage.getItem('pomodoroEndTime'); + return saved ? parseInt(saved) : null; + }); const intervalRef = useRef(null); // Save state to localStorage whenever it changes and dispatch custom event @@ -60,6 +64,42 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) { window.dispatchEvent(new CustomEvent('pomodoroStateChange')); }, [sessionsCompleted]); + useEffect(() => { + if (endTime !== null) { + localStorage.setItem('pomodoroEndTime', endTime.toString()); + } else { + localStorage.removeItem('pomodoroEndTime'); + } + window.dispatchEvent(new CustomEvent('pomodoroStateChange')); + }, [endTime]); + + // Restore timer state on mount if it was running + useEffect(() => { + // Clean up invalid states on mount + if (isRunning && endTime === null) { + // If running but no endTime, stop the timer + setIsRunning(false); + setTimeLeft(TIMER_DURATIONS[mode]); + } else if (endTime !== null && isRunning) { + // Recalculate remaining time based on endTime + const now = Date.now(); + const remaining = Math.max(0, Math.ceil((endTime - now) / 1000)); + + if (remaining > 0) { + setTimeLeft(remaining); + } else { + // Timer expired while page was closed/reloaded + setIsRunning(false); + setEndTime(null); + setTimeLeft(0); + } + } else if (endTime !== null && !isRunning) { + // If paused with an endTime, clean up the endTime + setEndTime(null); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // Run only on mount + // Listen for localStorage changes from other instances useEffect(() => { const handleStorageChange = () => { @@ -67,6 +107,7 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) { const savedTimeLeft = localStorage.getItem('pomodoroTimeLeft'); const savedIsRunning = localStorage.getItem('pomodoroIsRunning'); const savedSessions = localStorage.getItem('pomodoroSessionsCompleted'); + const savedEndTime = localStorage.getItem('pomodoroEndTime'); if (savedMode && savedMode !== mode) { setMode(savedMode); @@ -80,11 +121,15 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) { if (savedSessions && parseInt(savedSessions) !== sessionsCompleted) { setSessionsCompleted(parseInt(savedSessions)); } + const newEndTime = savedEndTime ? parseInt(savedEndTime) : null; + if (newEndTime !== endTime) { + setEndTime(newEndTime); + } }; window.addEventListener('pomodoroStateChange', handleStorageChange); return () => window.removeEventListener('pomodoroStateChange', handleStorageChange); - }, [mode, timeLeft, isRunning, sessionsCompleted]); + }, [mode, timeLeft, isRunning, sessionsCompleted, endTime]); // Function to play notification sound using Web Audio API const playNotificationSound = () => { @@ -137,6 +182,7 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) { const handleTimerComplete = useCallback(() => { setIsRunning(false); + setEndTime(null); // Play notification sound playNotificationSound(); @@ -163,18 +209,19 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) { } }, [mode, sessionsCompleted]); - // Manage timer interval + // Manage timer interval - using timestamps for accuracy across tab switches useEffect(() => { - if (isRunning) { + if (isRunning && endTime) { intervalRef.current = setInterval(() => { - setTimeLeft(prev => { - if (prev <= 1) { - handleTimerComplete(); - return 0; - } - return prev - 1; - }); - }, 1000); + const now = Date.now(); + const remaining = Math.max(0, Math.ceil((endTime - now) / 1000)); + + setTimeLeft(remaining); + + if (remaining <= 0) { + handleTimerComplete(); + } + }, 100); // Check every 100ms for better accuracy } else { if (intervalRef.current) { clearInterval(intervalRef.current); @@ -188,18 +235,24 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) { intervalRef.current = null; } }; - }, [isRunning, handleTimerComplete]); + }, [isRunning, endTime, handleTimerComplete]); const handleStart = () => { + const now = Date.now(); + const newEndTime = now + (timeLeft * 1000); + setEndTime(newEndTime); setIsRunning(true); }; const handlePause = () => { setIsRunning(false); + setEndTime(null); + // timeLeft is already updated by the interval }; const handleReset = () => { setIsRunning(false); + setEndTime(null); setTimeLeft(TIMER_DURATIONS[mode]); }; @@ -207,6 +260,7 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) { setMode(newMode); setTimeLeft(TIMER_DURATIONS[newMode]); setIsRunning(false); + setEndTime(null); }; const formatTime = (seconds: number): string => {