import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Calendar } from "react-native-big-calendar"; import { ActivityIndicator, ScrollView, StyleSheet, View, ViewStyle } from "react-native"; import { useGetEvents } from "@/hooks/firebase/useGetEvents"; import { useAtom, useSetAtom } from "jotai"; import { editVisibleAtom, eventForEditAtom, isAllDayAtom, modeAtom, selectedDateAtom, selectedNewEventDateAtom, } from "@/components/pages/calendar/atoms"; import { useAuthContext } from "@/contexts/AuthContext"; import { CalendarEvent } from "@/components/pages/calendar/interfaces"; import { Text } from "react-native-ui-lib"; import { addDays, compareAsc, isWithinInterval, subDays } from "date-fns"; import {useCalSync} from "@/hooks/useCalSync"; import {useSyncEvents} from "@/hooks/useSyncOnScroll"; interface EventCalendarProps { calendarHeight: number; // WAS USED FOR SCROLLABLE CALENDARS, PERFORMANCE WAS NOT OPTIMAL calendarWidth: number; } const getTotalMinutes = () => { const date = new Date(); return Math.abs(date.getUTCHours() * 60 + date.getUTCMinutes() - 200); }; export const EventCalendar: React.FC = React.memo( ({ calendarHeight }) => { const { data: events, isLoading } = useGetEvents(); const { profileData } = useAuthContext(); const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom); const [mode, setMode] = useAtom(modeAtom); const setEditVisible = useSetAtom(editVisibleAtom); const [isAllDay, setIsAllDay] = useAtom(isAllDayAtom); const setEventForEdit = useSetAtom(eventForEditAtom); const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom); const {isSyncing} = useSyncEvents() const [offsetMinutes, setOffsetMinutes] = useState(getTotalMinutes()); useCalSync() const todaysDate = new Date(); const handlePressEvent = useCallback( (event: CalendarEvent) => { if (mode === "day" || mode === "week") { setEditVisible(true); // console.log({event}); setEventForEdit(event); } else { setMode("day"); setSelectedDate(event.start); } }, [setEditVisible, setEventForEdit, mode] ); const handlePressCell = useCallback( (date: Date) => { if (mode === "day" || mode === "week") { setSelectedNewEndDate(date); } else { setMode("day"); setSelectedDate(date); } }, [mode, setSelectedNewEndDate, setSelectedDate] ); const handlePressDayHeader = useCallback( (date: Date) => { if (mode === "day") { setIsAllDay(true); setSelectedNewEndDate(date); setEditVisible(true); } if (mode === 'week') { setSelectedDate(date) setMode("day") } }, [mode, setSelectedNewEndDate] ); const handleSwipeEnd = useCallback( (date: Date) => { setSelectedDate(date); }, [setSelectedDate] ); const memoizedEventCellStyle = useCallback( (event: CalendarEvent) => ({ backgroundColor: event.eventColor , fontSize: 14}), [] ); const memoizedWeekStartsOn = useMemo( () => (profileData?.firstDayOfWeek === "Mondays" ? 1 : 0), [profileData] ); // console.log({memoizedWeekStartsOn, profileData: profileData?.firstDayOfWeek, const isSameDate = useCallback((date1: Date, date2: Date) => { return ( date1.getDate() === date2.getDate() && date1.getMonth() === date2.getMonth() && date1.getFullYear() === date2.getFullYear() ); }, []); const dayHeaderColor = useMemo(() => { return isSameDate(todaysDate, selectedDate) ? "white" : "#4d4d4d"; }, [selectedDate, mode]); const dateStyle = useMemo(() => { if (mode === "week") return undefined; return isSameDate(todaysDate, selectedDate) && mode === "day" ? styles.dayHeader : styles.otherDayHeader; }, [selectedDate, mode]); const memoizedHeaderContentStyle = useMemo(() => { if (mode === "day") { return styles.dayModeHeader; } else if (mode === "week") { return styles.weekModeHeader; } else if (mode === "month") { return styles.monthModeHeader; } else { return {}; } }, [mode]); const { enrichedEvents, filteredEvents } = useMemo(() => { const startTime = Date.now(); // Start timer const startOffset = mode === "month" ? 40 : mode === "week" ? 10 : 1; const endOffset = mode === "month" ? 40 : mode === "week" ? 10 : 1; const filteredEvents = events?.filter( (event) => event.start && event.end && isWithinInterval(event.start, { start: subDays(selectedDate, startOffset), end: addDays(selectedDate, endOffset), }) && isWithinInterval(event.end, { start: subDays(selectedDate, startOffset), end: addDays(selectedDate, endOffset), }) ) ?? []; const enrichedEvents = filteredEvents.reduce((acc, event) => { const dateKey = event.start.toISOString().split("T")[0]; acc[dateKey] = acc[dateKey] || []; acc[dateKey].push({ ...event, overlapPosition: false, overlapCount: 0, }); acc[dateKey].sort((a, b) => compareAsc(a.start, b.start)); return acc; }, {} as Record); const endTime = Date.now(); // console.log("memoizedEvents computation time:", endTime - startTime, "ms"); return { enrichedEvents, filteredEvents }; }, [events, selectedDate, mode]); const renderCustomDateForMonth = (date: Date) => { const circleStyle = useMemo( () => ({ width: 30, height: 30, justifyContent: "center", alignItems: "center", borderRadius: 15, }), [] ); const defaultStyle = useMemo( () => ({ ...circleStyle, }), [circleStyle] ); const currentDateStyle = useMemo( () => ({ ...circleStyle, backgroundColor: "#4184f2", }), [circleStyle] ); const renderDate = useCallback( (date: Date) => { const isCurrentDate = isSameDate(todaysDate, date); const appliedStyle = isCurrentDate ? currentDateStyle : defaultStyle; return ( {date.getDate()} ); }, [todaysDate, currentDateStyle, defaultStyle] // dependencies ); return renderDate(date); }; useEffect(() => { setOffsetMinutes(getTotalMinutes()); }, [events, mode]); if (isLoading) { return ( {isSyncing && Syncing...} ); } // console.log(enrichedEvents, filteredEvents) return ( <> {isSyncing && ( {isSyncing && Syncing...} )} ); } ); const styles = StyleSheet.create({ segmentslblStyle: { fontSize: 12, fontFamily: "Manrope_600SemiBold", }, calHeader: { borderWidth: 0, }, dayModeHeader: { alignSelf: "flex-start", justifyContent: "space-between", alignContent: "center", width: 38, right: 42, height: 13, }, weekModeHeader: {}, monthModeHeader: {}, loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center", position: "absolute", width: "100%", height: "100%", zIndex: 100, backgroundColor: "rgba(255, 255, 255, 0.9)", }, dayHeader: { backgroundColor: "#4184f2", aspectRatio: 1, borderRadius: 100, alignItems: "center", justifyContent: "center", }, otherDayHeader: { backgroundColor: "transparent", color: "#919191", aspectRatio: 1, borderRadius: 100, alignItems: "center", justifyContent: "center", }, hourStyle: { color: "#5f6368", fontSize: 12, fontFamily: "Manrope_500Medium", }, });