import { DeviceType } from 'expo-device'; import * as Device from 'expo-device'; import { format } from 'date-fns'; import { useQuery } from "@tanstack/react-query"; interface EventTimestamp { seconds: number; nanoseconds: number; } interface Event { id: string; title: string; start: Date | EventTimestamp; startDate?: EventTimestamp; end: Date | EventTimestamp; endDate?: EventTimestamp; allDay: boolean; eventColor: string; attendees?: string[]; creatorId?: string; } interface FormattedEvent { id: string; title: string; start: { date: string } | { dateTime: string; timeZone: string }; end: { date: string } | { dateTime: string; timeZone: string }; color: string; } // Precompute time constants const DAY_IN_MS = 24 * 60 * 60 * 1000; const PERIOD_IN_MS = 45 * DAY_IN_MS; const TIME_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone; // Memoize date range calculations const getDateRangeKey = (timestamp: number) => Math.floor(timestamp / PERIOD_IN_MS); // Optimized date validation and conversion const getValidDate = (date: any, timestamp?: EventTimestamp): Date | null => { try { if (timestamp?.seconds) { return new Date(timestamp.seconds * 1000); } if (date instanceof Date) { return date; } if (typeof date === 'string') { return new Date(date); } return null; } catch { return null; } }; // Batch process events const processEvents = async ( events: Event[], selectedDate: Date, selectedUser: { uid: string } | null ): Promise => { // Early return if no events if (!events.length) return []; // Pre-calculate constants const currentRangeKey = getDateRangeKey(selectedDate.getTime()); const isTablet = Device.deviceType === DeviceType.TABLET; const userId = selectedUser?.uid; // Process in chunks to avoid blocking the main thread const CHUNK_SIZE = 100; const results: FormattedEvent[] = []; for (let i = 0; i < events.length; i += CHUNK_SIZE) { const chunk = events.slice(i, i + CHUNK_SIZE); // Process chunk and await to give UI thread a chance to breathe await new Promise(resolve => setTimeout(resolve, 0)); for (const event of chunk) { try { // Quick user filter if (isTablet && userId && !event.attendees?.includes(userId) && event.creatorId !== userId) { continue; } // Validate dates first const startDate = getValidDate(event.start, event.startDate); if (!startDate) continue; const rangeKey = getDateRangeKey(startDate.getTime()); // Skip events outside our range if (Math.abs(rangeKey - currentRangeKey) > 1) continue; const endDate = getValidDate(event.end, event.endDate); if (!endDate) continue; if (event.allDay) { const dateStr = format(startDate, 'yyyy-MM-dd'); const endDateStr = format(endDate, 'yyyy-MM-dd'); results.push({ id: event.id, title: event.title, start: { date: dateStr }, end: { date: endDateStr }, color: event.eventColor }); } else { results.push({ id: event.id, title: event.title, start: { dateTime: startDate.toISOString(), timeZone: TIME_ZONE }, end: { dateTime: endDate.toISOString(), timeZone: TIME_ZONE }, color: event.eventColor }); } } catch (error) { console.error('Error processing event:', event.id, error); continue; } } } return results; }; export const useFormattedEvents = ( events: Event[], selectedDate: Date, selectedUser: { uid: string } | null ) => { return useQuery({ queryKey: ['formattedEvents', events, selectedDate, selectedUser?.uid], queryFn: () => processEvents(events, selectedDate, selectedUser), enabled: events.length > 0, staleTime: Infinity, placeholderData: (previousData) => previousData, gcTime: Infinity }); };