learning_ai_clock/web/src/lib/routine-store.ts

154 lines
5.2 KiB
TypeScript

// ── Zustand Store for Routines ────────────────────────────────
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import type { Routine, CreateRoutineParams } from './routines';
import {
createRoutine,
startRoutine,
pauseRoutine,
resumeRoutine,
completeCurrentStep,
skipCurrentStep,
cancelRoutine,
instantiateTemplate,
getBuiltInTemplates,
shouldStepComplete,
} from './routines';
import { enqueueRoutineChange, enqueueRoutineDeleteChange, isSyncEnabled } from './platform-sync';
export interface RoutineStore {
routines: Routine[];
templates: Routine[];
// CRUD
addRoutine: (params: CreateRoutineParams) => Routine;
addTemplate: (params: CreateRoutineParams) => Routine;
removeRoutine: (id: string) => void;
removeTemplate: (id: string) => void;
// State transitions
start: (id: string) => void;
pause: (id: string) => void;
resume: (id: string) => void;
completeStep: (id: string) => void;
skipStep: (id: string) => void;
cancel: (id: string) => void;
// Template
startFromTemplate: (templateId: string) => Routine | null;
// Tick — auto-advance steps
tickRoutines: (now: number) => void;
// Helpers
getRoutine: (id: string) => Routine | undefined;
getActiveRoutine: () => Routine | undefined;
}
function updateRoutine(routines: Routine[], id: string, updater: (r: Routine) => Routine): Routine[] {
return routines.map((r) => (r.id === id ? updater(r) : r));
}
export const useRoutineStore = create<RoutineStore>()(
persist(
(set, get) => ({
routines: [],
templates: getBuiltInTemplates(),
addRoutine: (params) => {
const routine = createRoutine(params);
set((s) => ({ routines: [...s.routines, routine] }));
if (isSyncEnabled()) enqueueRoutineChange(routine, 'create');
return routine;
},
addTemplate: (params) => {
const template = createRoutine({ ...params, isTemplate: true });
set((s) => ({ templates: [...s.templates, template] }));
return template;
},
removeRoutine: (id) => {
set((s) => ({ routines: s.routines.filter((r) => r.id !== id) }));
if (isSyncEnabled()) enqueueRoutineDeleteChange(id);
},
removeTemplate: (id) => {
set((s) => ({ templates: s.templates.filter((r) => r.id !== id) }));
},
start: (id) => {
set((s) => ({ routines: updateRoutine(s.routines, id, startRoutine) }));
const updated = get().routines.find((r) => r.id === id);
if (updated && isSyncEnabled()) enqueueRoutineChange(updated, 'update');
},
pause: (id) => {
set((s) => ({ routines: updateRoutine(s.routines, id, pauseRoutine) }));
const updated = get().routines.find((r) => r.id === id);
if (updated && isSyncEnabled()) enqueueRoutineChange(updated, 'update');
},
resume: (id) => {
set((s) => ({ routines: updateRoutine(s.routines, id, resumeRoutine) }));
const updated = get().routines.find((r) => r.id === id);
if (updated && isSyncEnabled()) enqueueRoutineChange(updated, 'update');
},
completeStep: (id) => {
set((s) => ({ routines: updateRoutine(s.routines, id, completeCurrentStep) }));
const updated = get().routines.find((r) => r.id === id);
if (updated && isSyncEnabled()) enqueueRoutineChange(updated, 'update');
},
skipStep: (id) => {
set((s) => ({ routines: updateRoutine(s.routines, id, skipCurrentStep) }));
const updated = get().routines.find((r) => r.id === id);
if (updated && isSyncEnabled()) enqueueRoutineChange(updated, 'update');
},
cancel: (id) => {
set((s) => ({ routines: updateRoutine(s.routines, id, cancelRoutine) }));
const updated = get().routines.find((r) => r.id === id);
if (updated && isSyncEnabled()) enqueueRoutineChange(updated, 'update');
},
startFromTemplate: (templateId) => {
const template = get().templates.find((t) => t.id === templateId);
if (!template) return null;
const routine = startRoutine(instantiateTemplate(template));
set((s) => ({ routines: [...s.routines, routine] }));
if (isSyncEnabled()) enqueueRoutineChange(routine, 'create');
return routine;
},
tickRoutines: (now) => {
const { routines } = get();
let changed = false;
const updated = routines.map((r) => {
if (r.status === 'active' && shouldStepComplete(r, now)) {
changed = true;
return completeCurrentStep(r);
}
return r;
});
if (changed) set({ routines: updated });
},
getRoutine: (id) => get().routines.find((r) => r.id === id),
getActiveRoutine: () => get().routines.find((r) => r.status === 'active' || r.status === 'paused'),
}),
{
name: 'chronomind-routines',
storage: createJSONStorage(() => {
if (typeof window === 'undefined') {
return { getItem: () => null, setItem: () => {}, removeItem: () => {} };
}
return localStorage;
}),
partialize: (state) => ({ routines: state.routines, templates: state.templates }),
}
)
);