import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import dayjs from 'dayjs'; import { FASTING_PLANS, FastingPlan, getPlanById } from '@/constants/Fasting'; import { calculateFastingWindow, getFastingPhase } from '@/utils/fasting'; import type { RootState } from './index'; export type FastingScheduleOrigin = 'manual' | 'recommended' | 'quick-start'; export type FastingSchedule = { planId: string; startISO: string; endISO: string; createdAtISO: string; updatedAtISO: string; origin: FastingScheduleOrigin; }; type FastingState = { activeSchedule: FastingSchedule | null; history: FastingSchedule[]; }; const initialState: FastingState = { activeSchedule: null, history: [], }; const fastingSlice = createSlice({ name: 'fasting', initialState, reducers: { scheduleFastingPlan: ( state, action: PayloadAction<{ planId: string; start: Date; origin?: FastingScheduleOrigin }> ) => { const plan = getPlanById(action.payload.planId); if (!plan) return; const { start, end } = calculateFastingWindow(action.payload.start, plan.fastingHours); const nowISO = new Date().toISOString(); state.activeSchedule = { planId: plan.id, startISO: start.toISOString(), endISO: end.toISOString(), createdAtISO: nowISO, updatedAtISO: nowISO, origin: action.payload.origin ?? 'manual', }; }, rescheduleActivePlan: ( state, action: PayloadAction<{ start: Date; origin?: FastingScheduleOrigin }> ) => { if (!state.activeSchedule) return; const plan = getPlanById(state.activeSchedule.planId); if (!plan) return; const { start, end } = calculateFastingWindow(action.payload.start, plan.fastingHours); state.activeSchedule = { ...state.activeSchedule, startISO: start.toISOString(), endISO: end.toISOString(), updatedAtISO: new Date().toISOString(), origin: action.payload.origin ?? state.activeSchedule.origin, }; }, setRecommendedSchedule: ( state, action: PayloadAction<{ planId: string; recommendedStart: Date }> ) => { const plan = getPlanById(action.payload.planId); if (!plan) return; const { start, end } = calculateFastingWindow(action.payload.recommendedStart, plan.fastingHours); const nowISO = new Date().toISOString(); state.activeSchedule = { planId: plan.id, startISO: start.toISOString(), endISO: end.toISOString(), createdAtISO: nowISO, updatedAtISO: nowISO, origin: 'recommended', }; }, completeActiveSchedule: (state) => { if (!state.activeSchedule) return; const phase = getFastingPhase( new Date(state.activeSchedule.startISO), new Date(state.activeSchedule.endISO) ); if (phase === 'fasting') { // Allow manual completion only when fasting window已经结束 state.activeSchedule = { ...state.activeSchedule, endISO: dayjs().toISOString(), updatedAtISO: new Date().toISOString(), }; } state.history.unshift(state.activeSchedule); state.activeSchedule = null; }, clearActiveSchedule: (state) => { state.activeSchedule = null; }, }, }); export const { scheduleFastingPlan, rescheduleActivePlan, setRecommendedSchedule, completeActiveSchedule, clearActiveSchedule, } = fastingSlice.actions; export default fastingSlice.reducer; export const selectFastingRoot = (state: RootState) => state.fasting; export const selectActiveFastingSchedule = (state: RootState) => state.fasting.activeSchedule; export const selectFastingPlans = () => FASTING_PLANS; export const selectActiveFastingPlan = (state: RootState): FastingPlan | undefined => { const schedule = state.fasting.activeSchedule; if (!schedule) return undefined; return getPlanById(schedule.planId); };