/** * Calendar plugin UI store (zustand). * * Holds view-mode state (day/week/month/range), navigation dates, the list of * calendars the user owns / can write to, the active calendar selection, * which calendars are visible in the grid, and the currently selected entry * for the right-hand detail panel. * * Server state (entries, kanban board) lives in the API hooks layer - this * store only carries view/UI selection state. */ import { create } from 'zustand'; import type { Calendar, CalendarEntry } from '@/api/calendar'; export type CalendarViewMode = 'day' | 'week' | 'month' | 'range'; export interface CalendarState { /** First day of the visible month (local midnight). */ visibleMonth: Date; /** First day (Monday) of the visible week. */ visibleWeek: Date; /** The visible day in day-view mode. */ visibleDay: Date; /** Start of the date-range view (inclusive). */ rangeStart: Date | null; /** End of the date-range view (inclusive). */ rangeEnd: Date | null; /** Current view mode. */ viewMode: CalendarViewMode; /** Selected calendar id (null = first available). */ activeCalendarId: string | null; /** Cached calendar list (filled by fetchCalendars()). */ calendars: Calendar[]; /** Currently selected entry for the detail panel. */ selectedEntry: CalendarEntry | null; /** Set of calendar ids whose entries are shown in the grid (empty = all). */ visibleCalendarIds: Set; setVisibleMonth: (date: Date) => void; setVisibleWeek: (date: Date) => void; setVisibleDay: (date: Date) => void; setRangeStart: (date: Date | null) => void; setRangeEnd: (date: Date | null) => void; setViewMode: (mode: CalendarViewMode) => void; goToNextMonth: () => void; goToPrevMonth: () => void; goToNextWeek: () => void; goToPrevWeek: () => void; goToNextDay: () => void; goToPrevDay: () => void; goToToday: () => void; setActiveCalendarId: (id: string | null) => void; setCalendars: (calendars: Calendar[]) => void; setSelectedEntry: (entry: CalendarEntry | null) => void; toggleCalendarVisibility: (id: string) => void; } function startOfMonth(d: Date): Date { return new Date(d.getFullYear(), d.getMonth(), 1); } /** Return the Monday of the week containing `d`. */ function startOfWeek(d: Date): Date { const dayOfWeek = (d.getDay() + 6) % 7; // 0 = Monday const start = new Date(d); start.setDate(d.getDate() - dayOfWeek); start.setHours(0, 0, 0, 0); return start; } function startOfDay(d: Date): Date { return new Date(d.getFullYear(), d.getMonth(), d.getDate()); } const today = new Date(); export const useCalendarStore = create((set) => ({ visibleMonth: startOfMonth(today), visibleWeek: startOfWeek(today), visibleDay: startOfDay(today), rangeStart: startOfDay(today), rangeEnd: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30), viewMode: 'month', activeCalendarId: null, calendars: [], selectedEntry: null, visibleCalendarIds: new Set(), setVisibleMonth: (date) => set({ visibleMonth: startOfMonth(date) }), setVisibleWeek: (date) => set({ visibleWeek: startOfWeek(date) }), setVisibleDay: (date) => set({ visibleDay: startOfDay(date) }), setRangeStart: (date) => set({ rangeStart: date ? startOfDay(date) : null }), setRangeEnd: (date) => set({ rangeEnd: date ? startOfDay(date) : null }), setViewMode: (mode) => set({ viewMode: mode }), goToNextMonth: () => set((s) => { const next = new Date(s.visibleMonth); next.setMonth(next.getMonth() + 1); return { visibleMonth: next }; }), goToPrevMonth: () => set((s) => { const next = new Date(s.visibleMonth); next.setMonth(next.getMonth() - 1); return { visibleMonth: next }; }), goToNextWeek: () => set((s) => { const next = new Date(s.visibleWeek); next.setDate(next.getDate() + 7); return { visibleWeek: next }; }), goToPrevWeek: () => set((s) => { const next = new Date(s.visibleWeek); next.setDate(next.getDate() - 7); return { visibleWeek: next }; }), goToNextDay: () => set((s) => { const next = new Date(s.visibleDay); next.setDate(next.getDate() + 1); return { visibleDay: next }; }), goToPrevDay: () => set((s) => { const next = new Date(s.visibleDay); next.setDate(next.getDate() - 1); return { visibleDay: next }; }), goToToday: () => set({ visibleMonth: startOfMonth(new Date()), visibleWeek: startOfWeek(new Date()), visibleDay: startOfDay(new Date()), }), setActiveCalendarId: (id) => set({ activeCalendarId: id }), setCalendars: (calendars) => set((s) => { // Initialize visibleCalendarIds with all calendar IDs if empty const visibleCalendarIds = s.visibleCalendarIds.size === 0 ? new Set(calendars.map((c) => c.id)) : s.visibleCalendarIds; return { calendars, visibleCalendarIds }; }), setSelectedEntry: (entry) => set({ selectedEntry: entry }), toggleCalendarVisibility: (id) => set((s) => { const next = new Set(s.visibleCalendarIds); if (next.has(id)) { next.delete(id); } else { next.add(id); } return { visibleCalendarIds: next }; }), }));