diff --git a/src/components/events/Calendar.jsx b/src/components/events/Calendar.jsx new file mode 100644 index 0000000..25c4245 --- /dev/null +++ b/src/components/events/Calendar.jsx @@ -0,0 +1,358 @@ +import React, { useState, useEffect } from "react"; + +const Calendar = ({ CALENDAR_API_KEY, EVENT_CALENDAR_ID }) => { + const [currentDate, setCurrentDate] = useState(new Date()); + const [events, setEvents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [hoveredEvent, setHoveredEvent] = useState(null); + const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); + + const getDaysInMonth = (date) => { + const year = date.getFullYear(); + const month = date.getMonth(); + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + const daysInMonth = lastDay.getDate(); + const startingDay = firstDay.getDay(); + const endingDay = lastDay.getDay(); + + const days = []; + // Add empty slots for days before the first of the month + for (let i = 0; i < startingDay; i++) { + days.push(null); + } + // Add all days of the month + for (let i = 1; i <= daysInMonth; i++) { + days.push(new Date(year, month, i)); + } + // Add empty slots for remaining days in the last week + const remainingDays = 6 - endingDay; + for (let i = 0; i < remainingDays; i++) { + days.push(null); + } + return days; + }; + + // Format date to match event dates + const formatDate = (date) => { + if (!date) return ""; + return date.toISOString().split("T")[0]; + }; + + // Get events for a specific day + const getEventsForDay = (day) => { + if (!day) return []; + const dayStr = formatDate(day); + return events.filter((event) => { + const eventDate = event.start.dateTime + ? new Date(event.start.dateTime).toISOString().split("T")[0] + : event.start.date; + return eventDate === dayStr; + }); + }; + + // Format time for display + const formatEventTime = (dateTimeStr) => { + if (!dateTimeStr) return ""; + const date = new Date(dateTimeStr); + return date.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + }); + }; + + useEffect(() => { + const calendarId = EVENT_CALENDAR_ID; + const userTimeZone = "America/Los_Angeles"; + + const loadGapiAndListEvents = async () => { + try { + console.log("Starting to load events..."); + + if (typeof window.gapi === "undefined") { + console.log("Loading GAPI script..."); + await new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.src = "https://apis.google.com/js/api.js"; + document.body.appendChild(script); + script.onload = () => { + console.log("GAPI script loaded"); + window.gapi.load("client", resolve); + }; + script.onerror = () => { + console.error("Failed to load GAPI script"); + reject(new Error("Failed to load the Google API script.")); + }; + }); + } + + console.log("Initializing GAPI client..."); + await window.gapi.client.init({ + apiKey: CALENDAR_API_KEY, + discoveryDocs: [ + "https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest", + ], + }); + + // Get first and last day of current month + const firstDay = new Date( + currentDate.getFullYear(), + currentDate.getMonth(), + 1, + ); + const lastDay = new Date( + currentDate.getFullYear(), + currentDate.getMonth() + 1, + 0, + ); + + console.log("Fetching events..."); + const response = await window.gapi.client.calendar.events.list({ + calendarId: calendarId, + timeZone: userTimeZone, + singleEvents: true, + timeMin: firstDay.toISOString(), + timeMax: lastDay.toISOString(), + orderBy: "startTime", + }); + + console.log("Response received:", response); + + if (response.result.items) { + setEvents(response.result.items); + } + } catch (error) { + console.error("Detailed Error: ", error); + setError(error.message || "Failed to load events"); + } finally { + setLoading(false); + } + }; + + if (!CALENDAR_API_KEY) { + setError("API key is missing"); + setLoading(false); + return; + } + + setLoading(true); + loadGapiAndListEvents(); + }, [CALENDAR_API_KEY, currentDate]); + + const monthNames = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + + const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + + const changeMonth = (increment) => { + setCurrentDate( + new Date( + currentDate.getFullYear(), + currentDate.getMonth() + increment, + 1, + ), + ); + }; + + const handleEventMouseEnter = (event, e) => { + const target = e.target; + setTooltipPosition({ + x: e.clientX, + y: e.clientY, + }); + setHoveredEvent({ event, target }); + }; + + const handleEventMouseLeave = () => { + setHoveredEvent(null); + }; + + const handleMouseMove = (e) => { + if (hoveredEvent) { + // Check if the mouse is still over the event element + const rect = hoveredEvent.target.getBoundingClientRect(); + const isStillHovering = + e.clientX >= rect.left && + e.clientX <= rect.right && + e.clientY >= rect.top && + e.clientY <= rect.bottom; + + if (!isStillHovering) { + setHoveredEvent(null); + } else { + setTooltipPosition({ + x: e.clientX, + y: e.clientY, + }); + } + } + }; + + useEffect(() => { + const handleScroll = () => { + if (hoveredEvent) { + const rect = hoveredEvent.target.getBoundingClientRect(); + const mouseX = tooltipPosition.x - 15; // Subtract the offset added to the tooltip + const mouseY = tooltipPosition.y - 15; + + const isStillHovering = + mouseX >= rect.left && + mouseX <= rect.right && + mouseY >= rect.top && + mouseY <= rect.bottom; + + if (!isStillHovering) { + setHoveredEvent(null); + } + } + }; + + window.addEventListener("scroll", handleScroll, true); + return () => window.removeEventListener("scroll", handleScroll, true); + }, [hoveredEvent, tooltipPosition]); + + if (!CALENDAR_API_KEY) { + return ( +
+ {hoveredEvent.event.description} +
+ )} ++ Start: {formatEventTime(hoveredEvent.event.start.dateTime)} +
+End: {formatEventTime(hoveredEvent.event.end.dateTime)}
+ > + ) : ( +All day event
+ )} + {hoveredEvent.event.location && ( ++ Location: {hoveredEvent.event.location} +
+ )} +