feat: improve calendar view mode handling with validation and defaulting
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
|||||||
format, parseISO,
|
format, parseISO,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { useCalendarStore } from "@/stores/calendar-store";
|
import { useCalendarStore } from "@/stores/calendar-store";
|
||||||
|
import { isCalendarViewMode } from "@/stores/calendar-store";
|
||||||
import { useAuthStore } from "@/stores/auth-store";
|
import { useAuthStore } from "@/stores/auth-store";
|
||||||
import { useEmailStore } from "@/stores/email-store";
|
import { useEmailStore } from "@/stores/email-store";
|
||||||
import { useSettingsStore } from "@/stores/settings-store";
|
import { useSettingsStore } from "@/stores/settings-store";
|
||||||
@@ -64,6 +65,7 @@ export default function CalendarPage() {
|
|||||||
} = useCalendarStore();
|
} = useCalendarStore();
|
||||||
const { firstDayOfWeek, timeFormat } = useSettingsStore();
|
const { firstDayOfWeek, timeFormat } = useSettingsStore();
|
||||||
const { identities } = useIdentityStore();
|
const { identities } = useIdentityStore();
|
||||||
|
const normalizedViewMode = isCalendarViewMode(viewMode) ? viewMode : "month";
|
||||||
|
|
||||||
const currentUserEmails = useMemo(() =>
|
const currentUserEmails = useMemo(() =>
|
||||||
identities.map(id => id.email).filter(Boolean),
|
identities.map(id => id.email).filter(Boolean),
|
||||||
@@ -133,7 +135,7 @@ export default function CalendarPage() {
|
|||||||
|
|
||||||
const dateRange = useMemo(() => {
|
const dateRange = useMemo(() => {
|
||||||
const d = selectedDate;
|
const d = selectedDate;
|
||||||
switch (viewMode) {
|
switch (normalizedViewMode) {
|
||||||
case "month": {
|
case "month": {
|
||||||
const ms = startOfMonth(d);
|
const ms = startOfMonth(d);
|
||||||
const me = endOfMonth(d);
|
const me = endOfMonth(d);
|
||||||
@@ -160,17 +162,17 @@ export default function CalendarPage() {
|
|||||||
end: format(addDays(d, 30), "yyyy-MM-dd'T'23:59:59"),
|
end: format(addDays(d, 30), "yyyy-MM-dd'T'23:59:59"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [selectedDate, viewMode, firstDayOfWeek]);
|
}, [selectedDate, normalizedViewMode, firstDayOfWeek]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (client && calendars.length > 0) {
|
if (client && calendars.length > 0 && dateRange) {
|
||||||
fetchEvents(client, dateRange.start, dateRange.end);
|
fetchEvents(client, dateRange.start, dateRange.end);
|
||||||
}
|
}
|
||||||
}, [client, calendars.length, dateRange, fetchEvents]);
|
}, [client, calendars.length, dateRange, fetchEvents]);
|
||||||
|
|
||||||
const navigatePrev = useCallback(() => {
|
const navigatePrev = useCallback(() => {
|
||||||
let next: Date;
|
let next: Date;
|
||||||
switch (viewMode) {
|
switch (normalizedViewMode) {
|
||||||
case "month": next = subMonths(selectedDate, 1); break;
|
case "month": next = subMonths(selectedDate, 1); break;
|
||||||
case "week": next = subWeeks(selectedDate, 1); break;
|
case "week": next = subWeeks(selectedDate, 1); break;
|
||||||
case "day": next = subDays(selectedDate, 1); break;
|
case "day": next = subDays(selectedDate, 1); break;
|
||||||
@@ -178,11 +180,11 @@ export default function CalendarPage() {
|
|||||||
}
|
}
|
||||||
setSelectedDate(next);
|
setSelectedDate(next);
|
||||||
setMiniMonth(next);
|
setMiniMonth(next);
|
||||||
}, [viewMode, selectedDate, setSelectedDate]);
|
}, [normalizedViewMode, selectedDate, setSelectedDate]);
|
||||||
|
|
||||||
const navigateNext = useCallback(() => {
|
const navigateNext = useCallback(() => {
|
||||||
let next: Date;
|
let next: Date;
|
||||||
switch (viewMode) {
|
switch (normalizedViewMode) {
|
||||||
case "month": next = addMonths(selectedDate, 1); break;
|
case "month": next = addMonths(selectedDate, 1); break;
|
||||||
case "week": next = addWeeks(selectedDate, 1); break;
|
case "week": next = addWeeks(selectedDate, 1); break;
|
||||||
case "day": next = addDays(selectedDate, 1); break;
|
case "day": next = addDays(selectedDate, 1); break;
|
||||||
@@ -190,7 +192,7 @@ export default function CalendarPage() {
|
|||||||
}
|
}
|
||||||
setSelectedDate(next);
|
setSelectedDate(next);
|
||||||
setMiniMonth(next);
|
setMiniMonth(next);
|
||||||
}, [viewMode, selectedDate, setSelectedDate]);
|
}, [normalizedViewMode, selectedDate, setSelectedDate]);
|
||||||
|
|
||||||
const goToToday = useCallback(() => {
|
const goToToday = useCallback(() => {
|
||||||
setSelectedDate(new Date());
|
setSelectedDate(new Date());
|
||||||
@@ -222,10 +224,10 @@ export default function CalendarPage() {
|
|||||||
setSelectedDate(date);
|
setSelectedDate(date);
|
||||||
setMiniMonth(date);
|
setMiniMonth(date);
|
||||||
// On mobile month view, tapping a date switches to day view
|
// On mobile month view, tapping a date switches to day view
|
||||||
if (isMobile && viewMode === "month") {
|
if (isMobile && normalizedViewMode === "month") {
|
||||||
setViewMode("day");
|
setViewMode("day");
|
||||||
}
|
}
|
||||||
}, [setSelectedDate, isMobile, viewMode, setViewMode]);
|
}, [setSelectedDate, isMobile, normalizedViewMode, setViewMode]);
|
||||||
|
|
||||||
const handleMiniMonthChange = useCallback((date: Date) => {
|
const handleMiniMonthChange = useCallback((date: Date) => {
|
||||||
setMiniMonth(date);
|
setMiniMonth(date);
|
||||||
@@ -625,7 +627,7 @@ export default function CalendarPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const viewContent = (() => {
|
const viewContent = (() => {
|
||||||
switch (viewMode) {
|
switch (normalizedViewMode) {
|
||||||
case "month":
|
case "month":
|
||||||
return (
|
return (
|
||||||
<CalendarMonthView
|
<CalendarMonthView
|
||||||
@@ -764,7 +766,7 @@ export default function CalendarPage() {
|
|||||||
<div className="flex flex-col flex-1 min-w-0">
|
<div className="flex flex-col flex-1 min-w-0">
|
||||||
<CalendarToolbar
|
<CalendarToolbar
|
||||||
selectedDate={selectedDate}
|
selectedDate={selectedDate}
|
||||||
viewMode={viewMode}
|
viewMode={normalizedViewMode}
|
||||||
onPrev={navigatePrev}
|
onPrev={navigatePrev}
|
||||||
onNext={navigateNext}
|
onNext={navigateNext}
|
||||||
onToday={goToToday}
|
onToday={goToToday}
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import { normalizeAllDayDuration } from '@/lib/calendar-utils';
|
|||||||
|
|
||||||
export type CalendarViewMode = 'month' | 'week' | 'day' | 'agenda';
|
export type CalendarViewMode = 'month' | 'week' | 'day' | 'agenda';
|
||||||
|
|
||||||
|
const CALENDAR_VIEW_MODES: CalendarViewMode[] = ['month', 'week', 'day', 'agenda'];
|
||||||
|
|
||||||
|
export function isCalendarViewMode(value: unknown): value is CalendarViewMode {
|
||||||
|
return typeof value === 'string' && CALENDAR_VIEW_MODES.includes(value as CalendarViewMode);
|
||||||
|
}
|
||||||
|
|
||||||
export interface ICalSubscription {
|
export interface ICalSubscription {
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
@@ -71,6 +77,10 @@ const initialState = {
|
|||||||
icalSubscriptions: [] as ICalSubscription[],
|
icalSubscriptions: [] as ICalSubscription[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getSafeCalendarViewMode(value: unknown): CalendarViewMode {
|
||||||
|
return isCalendarViewMode(value) ? value : 'month';
|
||||||
|
}
|
||||||
|
|
||||||
export const useCalendarStore = create<CalendarStore>()(
|
export const useCalendarStore = create<CalendarStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
@@ -299,7 +309,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
setSelectedDate: (date) => set({ selectedDate: date }),
|
setSelectedDate: (date) => set({ selectedDate: date }),
|
||||||
setViewMode: (mode) => set({ viewMode: mode }),
|
setViewMode: (mode) => set({ viewMode: getSafeCalendarViewMode(mode) }),
|
||||||
|
|
||||||
updateCalendar: async (client, calendarId, updates) => {
|
updateCalendar: async (client, calendarId, updates) => {
|
||||||
set({ error: null });
|
set({ error: null });
|
||||||
@@ -575,6 +585,17 @@ export const useCalendarStore = create<CalendarStore>()(
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'calendar-storage',
|
name: 'calendar-storage',
|
||||||
|
merge: (persistedState, currentState) => {
|
||||||
|
const mergedState = {
|
||||||
|
...currentState,
|
||||||
|
...(persistedState as Partial<CalendarStore> | undefined),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...mergedState,
|
||||||
|
viewMode: getSafeCalendarViewMode(mergedState.viewMode),
|
||||||
|
};
|
||||||
|
},
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
selectedCalendarIds: state.selectedCalendarIds,
|
selectedCalendarIds: state.selectedCalendarIds,
|
||||||
viewMode: state.viewMode,
|
viewMode: state.viewMode,
|
||||||
|
|||||||
Reference in New Issue
Block a user