mirror of
https://github.com/urosran/cally.git
synced 2025-07-15 09:45:20 +00:00
154 lines
4.7 KiB
TypeScript
154 lines
4.7 KiB
TypeScript
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<FormattedEvent[]> => {
|
|
// 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
|
|
});
|
|
}; |