9f89cb17a0
5.1: Toolbar already has PluginToolbar with navigation, view mode, actions — no changes needed 5.2: Calendar visibility toggle fix: - calendarStore.setCalendars now initializes visibleCalendarIds with all calendar IDs - CalendarTree.tsx visibility check simplified to visibleCalendarIds.has(cal.id) - No more empty-set-means-all-visible confusion tsc clean
161 lines
5.2 KiB
TypeScript
161 lines
5.2 KiB
TypeScript
/**
|
|
* 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<string>;
|
|
|
|
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<CalendarState>((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<string>(),
|
|
|
|
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 };
|
|
}),
|
|
}));
|