feat(web): wire TimerChain[] into Zustand store with auto-start
- chains[] persisted alongside timers in localStorage - addChain/removeChain with linkedTimerId bookkeeping - removeTimer auto-cleans chain references - complete() auto-starts next timer in chain via getNextInChain - 373/373 tests pass
This commit is contained in:
parent
233fde8f99
commit
ad4bc946a8
@ -2,6 +2,8 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import type { Timer, CreateAlarmParams, CreateCountdownParams, CreatePomodoroParams, CreateEventParams } from './timer-engine';
|
||||
import type { TimerChain } from './linked-timers';
|
||||
import { buildChain, getNextInChain, findChainForTimer } from './linked-timers';
|
||||
import {
|
||||
createAlarm,
|
||||
createCountdown,
|
||||
@ -23,6 +25,7 @@ import { trackTimerCreated, trackTimerCompleted, trackCascadeFired } from './ana
|
||||
|
||||
export interface TimerStore {
|
||||
timers: Timer[];
|
||||
chains: TimerChain[];
|
||||
now: number; // current time for reactivity
|
||||
|
||||
// CRUD
|
||||
@ -32,6 +35,11 @@ export interface TimerStore {
|
||||
addEvent: (params: CreateEventParams) => Timer;
|
||||
removeTimer: (id: string) => void;
|
||||
|
||||
// Chain operations
|
||||
addChain: (name: string, timerIds: string[], delayMs?: number) => TimerChain;
|
||||
removeChain: (chainId: string) => void;
|
||||
getChainForTimer: (timerId: string) => TimerChain | null;
|
||||
|
||||
// State transitions
|
||||
pause: (id: string) => void;
|
||||
resume: (id: string) => void;
|
||||
@ -58,6 +66,7 @@ export const useTimerStore = create<TimerStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
timers: [],
|
||||
chains: [],
|
||||
now: Date.now(),
|
||||
|
||||
addAlarm: (params) => {
|
||||
@ -89,7 +98,41 @@ export const useTimerStore = create<TimerStore>()(
|
||||
},
|
||||
|
||||
removeTimer: (id) => {
|
||||
set((s) => ({ timers: s.timers.filter((t) => t.id !== id) }));
|
||||
set((s) => ({
|
||||
timers: s.timers.filter((t) => t.id !== id),
|
||||
chains: s.chains
|
||||
.map((c) => ({
|
||||
...c,
|
||||
timerIds: c.timerIds.filter((tid) => tid !== id),
|
||||
links: c.links.filter((l) => l.fromId !== id && l.toId !== id),
|
||||
}))
|
||||
.filter((c) => c.timerIds.length > 1),
|
||||
}));
|
||||
},
|
||||
|
||||
addChain: (name, timerIds, delayMs = 0) => {
|
||||
const chain = buildChain(name, timerIds, delayMs);
|
||||
set((s) => ({
|
||||
chains: [...s.chains, chain],
|
||||
timers: s.timers.map((t) =>
|
||||
timerIds.includes(t.id) ? { ...t, linkedTimerId: chain.id } : t
|
||||
),
|
||||
}));
|
||||
return chain;
|
||||
},
|
||||
|
||||
removeChain: (chainId) => {
|
||||
const chain = get().chains.find((c) => c.id === chainId);
|
||||
set((s) => ({
|
||||
chains: s.chains.filter((c) => c.id !== chainId),
|
||||
timers: s.timers.map((t) =>
|
||||
chain && chain.timerIds.includes(t.id) ? { ...t, linkedTimerId: null } : t
|
||||
),
|
||||
}));
|
||||
},
|
||||
|
||||
getChainForTimer: (timerId) => {
|
||||
return findChainForTimer(get().chains, timerId);
|
||||
},
|
||||
|
||||
pause: (id) => {
|
||||
@ -118,6 +161,23 @@ export const useTimerStore = create<TimerStore>()(
|
||||
trackTimerCompleted(timer.type, timer.targetTime - timer.createdAt);
|
||||
}
|
||||
set((s) => ({ timers: updateTimer(s.timers, id, completeTimer) }));
|
||||
// Auto-start next timer in chain
|
||||
const { chains, timers } = get();
|
||||
const link = getNextInChain(chains, id);
|
||||
if (link) {
|
||||
const nextTimer = timers.find((t) => t.id === link.toId);
|
||||
if (nextTimer && nextTimer.state === 'idle') {
|
||||
const now = Date.now();
|
||||
const targetTime = now + (nextTimer.duration ?? 0) + link.delay;
|
||||
set((s) => ({
|
||||
timers: s.timers.map((t) =>
|
||||
t.id === link.toId
|
||||
? { ...t, state: 'active' as const, startedAt: now + link.delay, targetTime }
|
||||
: t
|
||||
),
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
advancePom: (id) => {
|
||||
@ -201,7 +261,7 @@ export const useTimerStore = create<TimerStore>()(
|
||||
}
|
||||
return localStorage;
|
||||
}),
|
||||
partialize: (state) => ({ timers: state.timers }),
|
||||
partialize: (state) => ({ timers: state.timers, chains: state.chains }),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user