mirror of
https://github.com/urosran/cally.git
synced 2025-07-10 07:07:16 +00:00
164 lines
4.9 KiB
TypeScript
164 lines
4.9 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;
|
|
}
|
|
|
|
const createEventHash = (event: FormattedEvent): string => {
|
|
const startTime = 'date' in event.start ? event.start.date : event.start.dateTime;
|
|
const endTime = 'date' in event.end ? event.end.date : event.end.dateTime;
|
|
const str = `${startTime}-${endTime}-${event.title}`;
|
|
|
|
let hash = 0;
|
|
for (let i = 0; i < str.length; i++) {
|
|
const char = str.charCodeAt(i);
|
|
hash = ((hash << 5) - hash) + char;
|
|
hash = hash & hash;
|
|
}
|
|
return hash.toString(36);
|
|
};
|
|
|
|
|
|
// Precompute time constants
|
|
const DAY_IN_MS = 24 * 60 * 60 * 1000;
|
|
const PERIOD_IN_MS = 180 * 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[]> => {
|
|
if (!events.length) return [];
|
|
|
|
const currentRangeKey = getDateRangeKey(selectedDate.getTime());
|
|
const isTablet = Device.deviceType === DeviceType.TABLET;
|
|
const userId = selectedUser?.uid;
|
|
|
|
const uniqueEvents = new Map<string, FormattedEvent>();
|
|
const processedHashes = new Set<string>();
|
|
|
|
const CHUNK_SIZE = 100;
|
|
|
|
for (let i = 0; i < events.length; i += CHUNK_SIZE) {
|
|
const chunk = events.slice(i, i + CHUNK_SIZE);
|
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
|
|
for (const event of chunk) {
|
|
try {
|
|
if (isTablet && userId &&
|
|
!event.attendees?.includes(userId) &&
|
|
event.creatorId !== userId) {
|
|
continue;
|
|
}
|
|
|
|
const startDate = getValidDate(event.start, event.startDate);
|
|
if (!startDate) continue;
|
|
|
|
const rangeKey = getDateRangeKey(startDate.getTime());
|
|
if (Math.abs(rangeKey - currentRangeKey) > 1) continue;
|
|
|
|
const endDate = getValidDate(event.end, event.endDate);
|
|
if (!endDate) continue;
|
|
|
|
const formattedEvent = event.allDay ? {
|
|
id: event.id,
|
|
title: event.title,
|
|
start: { date: format(startDate, 'yyyy-MM-dd') },
|
|
end: { date: format(endDate, 'yyyy-MM-dd') },
|
|
color: event.eventColor
|
|
} : {
|
|
id: event.id,
|
|
title: event.title,
|
|
start: {
|
|
dateTime: startDate.toISOString(),
|
|
timeZone: TIME_ZONE
|
|
},
|
|
end: {
|
|
dateTime: endDate.toISOString(),
|
|
timeZone: TIME_ZONE
|
|
},
|
|
color: event.eventColor
|
|
};
|
|
|
|
const hash = createEventHash(formattedEvent);
|
|
if (!processedHashes.has(hash)) {
|
|
processedHashes.add(hash);
|
|
uniqueEvents.set(event.id, formattedEvent);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error processing event:', event.id, error);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Array.from(uniqueEvents.values());
|
|
};
|
|
|
|
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
|
|
});
|
|
}; |