Merge pull request #150 from R0m1k3/claude/fix-pomodoro-tab-switch-01WFQEsfPro5ynAgg4VAU3EV

fix: permettre au timer Pomodoro de continuer en arrière-plan
This commit is contained in:
LogiFlow authored and GitHub committed 2025-11-29 09:01:55 +01:00
commit 4be735a46b
1 file changed
+39 -12
+39 -12
View File
@@ -37,6 +37,10 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) {
const saved = localStorage.getItem('pomodoroSessionsCompleted'); const saved = localStorage.getItem('pomodoroSessionsCompleted');
return saved ? parseInt(saved) : 0; return saved ? parseInt(saved) : 0;
}); });
const [endTime, setEndTime] = useState<number | null>(() => {
const saved = localStorage.getItem('pomodoroEndTime');
return saved ? parseInt(saved) : null;
});
const intervalRef = useRef<NodeJS.Timeout | null>(null); const intervalRef = useRef<NodeJS.Timeout | null>(null);
// Save state to localStorage whenever it changes and dispatch custom event // Save state to localStorage whenever it changes and dispatch custom event
@@ -60,6 +64,15 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) {
window.dispatchEvent(new CustomEvent('pomodoroStateChange')); window.dispatchEvent(new CustomEvent('pomodoroStateChange'));
}, [sessionsCompleted]); }, [sessionsCompleted]);
useEffect(() => {
if (endTime !== null) {
localStorage.setItem('pomodoroEndTime', endTime.toString());
} else {
localStorage.removeItem('pomodoroEndTime');
}
window.dispatchEvent(new CustomEvent('pomodoroStateChange'));
}, [endTime]);
// Listen for localStorage changes from other instances // Listen for localStorage changes from other instances
useEffect(() => { useEffect(() => {
const handleStorageChange = () => { const handleStorageChange = () => {
@@ -67,6 +80,7 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) {
const savedTimeLeft = localStorage.getItem('pomodoroTimeLeft'); const savedTimeLeft = localStorage.getItem('pomodoroTimeLeft');
const savedIsRunning = localStorage.getItem('pomodoroIsRunning'); const savedIsRunning = localStorage.getItem('pomodoroIsRunning');
const savedSessions = localStorage.getItem('pomodoroSessionsCompleted'); const savedSessions = localStorage.getItem('pomodoroSessionsCompleted');
const savedEndTime = localStorage.getItem('pomodoroEndTime');
if (savedMode && savedMode !== mode) { if (savedMode && savedMode !== mode) {
setMode(savedMode); setMode(savedMode);
@@ -80,11 +94,15 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) {
if (savedSessions && parseInt(savedSessions) !== sessionsCompleted) { if (savedSessions && parseInt(savedSessions) !== sessionsCompleted) {
setSessionsCompleted(parseInt(savedSessions)); setSessionsCompleted(parseInt(savedSessions));
} }
const newEndTime = savedEndTime ? parseInt(savedEndTime) : null;
if (newEndTime !== endTime) {
setEndTime(newEndTime);
}
}; };
window.addEventListener('pomodoroStateChange', handleStorageChange); window.addEventListener('pomodoroStateChange', handleStorageChange);
return () => window.removeEventListener('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 // Function to play notification sound using Web Audio API
const playNotificationSound = () => { const playNotificationSound = () => {
@@ -137,6 +155,7 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) {
const handleTimerComplete = useCallback(() => { const handleTimerComplete = useCallback(() => {
setIsRunning(false); setIsRunning(false);
setEndTime(null);
// Play notification sound // Play notification sound
playNotificationSound(); playNotificationSound();
@@ -163,18 +182,19 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) {
} }
}, [mode, sessionsCompleted]); }, [mode, sessionsCompleted]);
// Manage timer interval // Manage timer interval - using timestamps for accuracy across tab switches
useEffect(() => { useEffect(() => {
if (isRunning) { if (isRunning && endTime) {
intervalRef.current = setInterval(() => { intervalRef.current = setInterval(() => {
setTimeLeft(prev => { const now = Date.now();
if (prev <= 1) { const remaining = Math.max(0, Math.ceil((endTime - now) / 1000));
handleTimerComplete();
return 0; setTimeLeft(remaining);
}
return prev - 1; if (remaining <= 0) {
}); handleTimerComplete();
}, 1000); }
}, 100); // Check every 100ms for better accuracy
} else { } else {
if (intervalRef.current) { if (intervalRef.current) {
clearInterval(intervalRef.current); clearInterval(intervalRef.current);
@@ -188,18 +208,24 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) {
intervalRef.current = null; intervalRef.current = null;
} }
}; };
}, [isRunning, handleTimerComplete]); }, [isRunning, endTime, handleTimerComplete]);
const handleStart = () => { const handleStart = () => {
const now = Date.now();
const newEndTime = now + (timeLeft * 1000);
setEndTime(newEndTime);
setIsRunning(true); setIsRunning(true);
}; };
const handlePause = () => { const handlePause = () => {
setIsRunning(false); setIsRunning(false);
setEndTime(null);
// timeLeft is already updated by the interval
}; };
const handleReset = () => { const handleReset = () => {
setIsRunning(false); setIsRunning(false);
setEndTime(null);
setTimeLeft(TIMER_DURATIONS[mode]); setTimeLeft(TIMER_DURATIONS[mode]);
}; };
@@ -207,6 +233,7 @@ export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) {
setMode(newMode); setMode(newMode);
setTimeLeft(TIMER_DURATIONS[newMode]); setTimeLeft(TIMER_DURATIONS[newMode]);
setIsRunning(false); setIsRunning(false);
setEndTime(null);
}; };
const formatTime = (seconds: number): string => { const formatTime = (seconds: number): string => {