/** * 원형 카운트다운 타이머 (PRD §3.1) — duration(ms) 후 onTimeout * key를 바꿔 리셋한다. * paused=true 일 때는 일시정지 (남은 시간 보존, 재개 시 이어서). */ import { useEffect, useRef, useState } from 'react'; interface Props { duration: number; // ms onTimeout: () => void; size?: number; paused?: boolean; } export default function CircularTimer({ duration, onTimeout, size = 64, paused = false }: Props) { const [remaining, setRemaining] = useState(duration); const cbRef = useRef(onTimeout); cbRef.current = onTimeout; const startRef = useRef(Date.now()); const accumulatedRef = useRef(0); // 누적 경과 시간 useEffect(() => { // duration이 바뀌면 (새 단계) 완전 리셋 setRemaining(duration); startRef.current = Date.now(); accumulatedRef.current = 0; }, [duration]); useEffect(() => { if (paused) { // 일시정지: 현재까지 경과한 시간을 누적에 더하고 interval 정지 accumulatedRef.current += Date.now() - startRef.current; return; } // 재개: 새 시작 시점 기록 startRef.current = Date.now(); const id = window.setInterval(() => { const elapsed = accumulatedRef.current + (Date.now() - startRef.current); const left = duration - elapsed; setRemaining(Math.max(0, left)); if (left <= 0) { window.clearInterval(id); cbRef.current(); } }, 50); return () => window.clearInterval(id); }, [paused, duration]); const ratio = Math.max(0, Math.min(1, remaining / duration)); const r = size / 2 - 5; const circ = 2 * Math.PI * r; return ( {(remaining / 1000).toFixed(1)} ); }