2026-02-24 20:44:16 +00:00
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
|
|
|
|
|
|
|
|
interface UseExamTimerProps {
|
|
|
|
|
initialTime: number; // in seconds
|
|
|
|
|
onTimeUp?: () => void;
|
|
|
|
|
onTick?: (timeRemaining: number) => void;
|
|
|
|
|
autoStart?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const useExamTimer = ({
|
|
|
|
|
initialTime,
|
|
|
|
|
onTimeUp,
|
|
|
|
|
onTick,
|
|
|
|
|
autoStart = false
|
|
|
|
|
}: UseExamTimerProps) => {
|
|
|
|
|
const [timeRemaining, setTimeRemaining] = useState(initialTime);
|
|
|
|
|
const [isRunning, setIsRunning] = useState(autoStart);
|
|
|
|
|
const [isPaused, setIsPaused] = useState(false);
|
|
|
|
|
|
|
|
|
|
const start = useCallback(() => {
|
|
|
|
|
setIsRunning(true);
|
|
|
|
|
setIsPaused(false);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const pause = useCallback(() => {
|
|
|
|
|
setIsPaused(true);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const resume = useCallback(() => {
|
|
|
|
|
setIsPaused(false);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const reset = useCallback(() => {
|
|
|
|
|
setTimeRemaining(initialTime);
|
|
|
|
|
setIsRunning(false);
|
|
|
|
|
setIsPaused(false);
|
|
|
|
|
}, [initialTime]);
|
|
|
|
|
|
|
|
|
|
const stop = useCallback(() => {
|
|
|
|
|
setIsRunning(false);
|
|
|
|
|
setIsPaused(false);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-05-11 12:19:27 +00:00
|
|
|
let interval: ReturnType<typeof setInterval>;
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
if (isRunning && !isPaused && timeRemaining > 0) {
|
|
|
|
|
interval = setInterval(() => {
|
|
|
|
|
setTimeRemaining((prev) => {
|
|
|
|
|
const newTime = prev - 1;
|
|
|
|
|
onTick?.(newTime);
|
|
|
|
|
|
|
|
|
|
if (newTime <= 0) {
|
|
|
|
|
setIsRunning(false);
|
|
|
|
|
onTimeUp?.();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return newTime;
|
|
|
|
|
});
|
|
|
|
|
}, 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (interval) {
|
|
|
|
|
clearInterval(interval);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, [isRunning, isPaused, timeRemaining, onTimeUp, onTick]);
|
|
|
|
|
|
|
|
|
|
const formatTime = useCallback((seconds: number) => {
|
|
|
|
|
const hours = Math.floor(seconds / 3600);
|
|
|
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
|
const secs = seconds % 60;
|
|
|
|
|
|
|
|
|
|
if (hours > 0) {
|
|
|
|
|
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
|
|
|
}
|
|
|
|
|
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
timeRemaining,
|
|
|
|
|
formattedTime: formatTime(timeRemaining),
|
|
|
|
|
isRunning,
|
|
|
|
|
isPaused,
|
|
|
|
|
start,
|
|
|
|
|
pause,
|
|
|
|
|
resume,
|
|
|
|
|
reset,
|
|
|
|
|
stop,
|
|
|
|
|
progress: ((initialTime - timeRemaining) / initialTime) * 100
|
|
|
|
|
};
|
|
|
|
|
};
|