154 lines
5.2 KiB
TypeScript
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 }),
|
|
}
|
|
)
|
|
);
|