feat(api): 세션·통계·설정 API 연동 기반을 추가
맥락: - 실제 세션 엔진과 통계·설정 저장을 백엔드와 연결할 프론트 API 경계를 먼저 정리할 필요가 있었다. 변경사항: - focus session, stats, preferences API 계층과 타입을 추가하고 메서드 주석에 Backend Codex 지시 사항을 작성했다. - /space를 현재 세션 조회, 시작, 일시정지, 재개, 다시 시작, 완료, 종료 API 흐름에 연결하고 API 실패 시 로컬 미리보기 fallback을 유지했다. - /stats와 /settings를 API 기반 fetch/save 구조로 전환하고 auth/apiClient를 보강했다. - React 19 규칙에 맞게 관련 훅과 HUD/시트 구현을 정리해 lint/build가 통과하도록 보정했다. 검증: - npm run lint - npm run build 세션-상태: 프론트에서 세션·통계·설정 API를 호출할 준비가 된 상태 세션-다음: 백엔드가 주석에 맞춘 엔드포인트와 응답 스키마를 구현하도록 협업 세션-리스크: 실제 서버 응답 필드명이 현재 타입과 다르면 프론트 매핑 조정이 추가로 필요
This commit is contained in:
@@ -8,22 +8,36 @@ import { GoalFlashOverlay } from './GoalFlashOverlay';
|
||||
interface SpaceFocusHudWidgetProps {
|
||||
goal: string;
|
||||
timerLabel: string;
|
||||
timeDisplay?: string;
|
||||
visible: boolean;
|
||||
onGoalUpdate: (nextGoal: string) => void;
|
||||
playbackState?: 'running' | 'paused';
|
||||
sessionPhase?: 'focus' | 'break' | null;
|
||||
isSessionActionPending?: boolean;
|
||||
onPauseRequested?: () => void;
|
||||
onResumeRequested?: () => void;
|
||||
onRestartRequested?: () => void;
|
||||
onGoalUpdate: (nextGoal: string) => void | Promise<void>;
|
||||
onStatusMessage: (payload: HudStatusLinePayload) => void;
|
||||
}
|
||||
|
||||
export const SpaceFocusHudWidget = ({
|
||||
goal,
|
||||
timerLabel,
|
||||
timeDisplay,
|
||||
visible,
|
||||
playbackState = 'running',
|
||||
sessionPhase = 'focus',
|
||||
isSessionActionPending = false,
|
||||
onPauseRequested,
|
||||
onResumeRequested,
|
||||
onRestartRequested,
|
||||
onGoalUpdate,
|
||||
onStatusMessage,
|
||||
}: SpaceFocusHudWidgetProps) => {
|
||||
const reducedMotion = useReducedMotion();
|
||||
const [flashVisible, setFlashVisible] = useState(false);
|
||||
const [sheetOpen, setSheetOpen] = useState(false);
|
||||
const playbackStateRef = useRef<'running' | 'paused'>('running');
|
||||
const playbackStateRef = useRef<'running' | 'paused'>(playbackState);
|
||||
const flashTimerRef = useRef<number | null>(null);
|
||||
const restReminderTimerRef = useRef<number | null>(null);
|
||||
|
||||
@@ -59,13 +73,40 @@ export const SpaceFocusHudWidget = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible || reducedMotion) {
|
||||
setFlashVisible(false);
|
||||
return;
|
||||
const rafId = window.requestAnimationFrame(() => {
|
||||
setFlashVisible(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.cancelAnimationFrame(rafId);
|
||||
};
|
||||
}
|
||||
|
||||
triggerFlash(2000);
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
triggerFlash(2000);
|
||||
}, 0);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
}, [visible, reducedMotion, triggerFlash]);
|
||||
|
||||
useEffect(() => {
|
||||
if (playbackStateRef.current === 'paused' && playbackState === 'running' && visible && !reducedMotion) {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
triggerFlash(1000);
|
||||
}, 0);
|
||||
|
||||
playbackStateRef.current = playbackState;
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
}
|
||||
|
||||
playbackStateRef.current = playbackState;
|
||||
}, [playbackState, reducedMotion, triggerFlash, visible]);
|
||||
|
||||
useEffect(() => {
|
||||
const ENABLE_PERIODIC_FLASH = false;
|
||||
|
||||
@@ -96,21 +137,16 @@ export const SpaceFocusHudWidget = ({
|
||||
<SpaceTimerHudWidget
|
||||
timerLabel={timerLabel}
|
||||
goal={goal}
|
||||
timeDisplay={timeDisplay}
|
||||
isImmersionMode
|
||||
sessionPhase={sessionPhase}
|
||||
playbackState={playbackState}
|
||||
isControlsDisabled={isSessionActionPending}
|
||||
className="pr-[4.2rem]"
|
||||
onGoalCompleteRequest={handleOpenCompleteSheet}
|
||||
onPlaybackStateChange={(state) => {
|
||||
if (reducedMotion) {
|
||||
playbackStateRef.current = state;
|
||||
return;
|
||||
}
|
||||
|
||||
if (playbackStateRef.current === 'paused' && state === 'running') {
|
||||
triggerFlash(1000);
|
||||
}
|
||||
|
||||
playbackStateRef.current = state;
|
||||
}}
|
||||
onStartClick={onResumeRequested}
|
||||
onPauseClick={onPauseRequested}
|
||||
onResetClick={onRestartRequested}
|
||||
/>
|
||||
<GoalCompleteSheet
|
||||
open={sheetOpen}
|
||||
@@ -129,7 +165,7 @@ export const SpaceFocusHudWidget = ({
|
||||
}, 5 * 60 * 1000);
|
||||
}}
|
||||
onConfirm={(nextGoal) => {
|
||||
onGoalUpdate(nextGoal);
|
||||
void onGoalUpdate(nextGoal);
|
||||
setSheetOpen(false);
|
||||
onStatusMessage({
|
||||
message: `이번 한 조각 · ${nextGoal}`,
|
||||
|
||||
Reference in New Issue
Block a user