diff --git a/components/pages/calendar/useCalendarControls.ts b/components/pages/calendar/useCalendarControls.ts index 403258a..37e701f 100644 --- a/components/pages/calendar/useCalendarControls.ts +++ b/components/pages/calendar/useCalendarControls.ts @@ -17,8 +17,21 @@ export const useCalendarControls = (events: any[]) => { const handlePressEvent = useCallback((event: any) => { const foundEvent = events?.find(x => x.id === event.id); - setEditVisible(true); - setEventForEdit(foundEvent!); + + if (foundEvent) { + const processedEvent = { + ...foundEvent, + startDate: foundEvent.startDate?.seconds ? + new Date(foundEvent.startDate.seconds * 1000) : + new Date(foundEvent.start), + endDate: foundEvent.endDate?.seconds ? + new Date(foundEvent.endDate.seconds * 1000) : + new Date(foundEvent.end) + }; + + setEditVisible(true); + setEventForEdit(processedEvent); + } }, [events, setEditVisible, setEventForEdit]); const handlePressCell = useCallback((date: DateOrDateTime) => { diff --git a/firebase/functions/index.js b/firebase/functions/index.js index 2079284..52fb930 100644 --- a/firebase/functions/index.js +++ b/firebase/functions/index.js @@ -313,18 +313,22 @@ exports.checkUpcomingEvents = functions.pubsub .schedule("every 5 minutes") .onRun(async (context) => { const now = Timestamp.now(); + const tenMinutesFromNow = new Date(now.toDate().getTime() + 10 * 60 * 1000); + const eventsSnapshot = await db.collection("Events") .where("startDate", ">=", now) + .where("startDate", "<=", Timestamp.fromDate(tenMinutesFromNow)) .get(); + await Promise.all(eventsSnapshot.docs.map(async (doc) => { const event = doc.data(); if (!event?.startDate) return; const { familyId, title, allDay } = event; try { - const familyDoc = await db.collection("Families").doc(familyId).get(); + const familyDoc = await db.collection("Households").doc(familyId).get(); if (!familyDoc.exists) return; const familySettings = familyDoc.data()?.settings || {}; - const reminderTime = familySettings.defaultReminderTime || 15; + const reminderTime = familySettings.defaultReminderTime || 5; const eventTime = event.startDate.toDate(); const reminderThreshold = new Date(now.toDate().getTime() + reminderTime * 60 * 1000); if (allDay) { @@ -460,6 +464,13 @@ exports.syncNewEventToGoogle = functions.firestore .onCreate(async (snapshot, context) => { const newEvent = snapshot.data(); const eventId = context.params.eventId; + + await snapshot.ref.update({ + reminderSent: false, + eveningReminderSent: false, + notifiedAt: null + }); + if (newEvent.externalOrigin === "google") { logger.info("[GOOGLE_SYNC] Skipping sync for Google-originated event", eventId); return; @@ -1132,19 +1143,19 @@ exports.notifyOnEventUpdate = functions.firestore ───────────────────────────────── */ // We keep the reminder scheduler mostly as-is but ensure that once a notification is sent, the event is updated exports.checkUpcomingEvents = functions.pubsub - .schedule("every 5 minutes") // Run more frequently to catch reminders + .schedule("every 5 minutes") .onRun(async (context) => { const now = Timestamp.now(); const thirtyMinutesFromNow = new Date(now.toDate().getTime() + 30 * 60 * 1000); - // Query only events starting in the next 30 minutes that haven't been reminded + logger.info(`Running check at ${now.toDate().toISOString()}`); + const eventsSnapshot = await db.collection("Events") .where("startDate", ">=", now) - .where("startDate", "<=", thirtyMinutesFromNow) - .where("reminderSent", "==", false) // Only get events that haven't been reminded + .where("startDate", "<=", Timestamp.fromDate(thirtyMinutesFromNow)) .get(); - const batch = db.batch(); // Batch our updates + const batch = db.batch(); const notificationPromises = []; for (const doc of eventsSnapshot.docs) { @@ -1152,64 +1163,65 @@ exports.checkUpcomingEvents = functions.pubsub if (!event?.startDate) continue; const { familyId, title, allDay } = event; + + // Skip if reminder already sent + if (event.reminderSent) { + logger.info(`Reminder already sent for event: ${title}`); + continue; + } + try { const familyDoc = await db.collection("Families").doc(familyId).get(); if (!familyDoc.exists) continue; const familySettings = familyDoc.data()?.settings || {}; - const reminderTime = familySettings.defaultReminderTime || 10; // Default to 10 minutes - const eventTime = event.startDate.toDate(); - const reminderThreshold = new Date(now.toDate().getTime() + reminderTime * 60 * 1000); + const reminderTime = familySettings.defaultReminderTime || 10; - // Check if we're within the reminder window + const eventTime = event.startDate.toDate(); const timeUntilEvent = eventTime.getTime() - now.toDate().getTime(); const minutesUntilEvent = Math.floor(timeUntilEvent / (60 * 1000)); + logger.info(`Checking event: "${title}"`, { + minutesUntilEvent, + reminderTime, + eventTime: eventTime.toISOString() + }); + + // Modified timing logic: Send reminder when we're close to the reminder time + // This ensures we don't miss the window between function executions if (minutesUntilEvent <= reminderTime && minutesUntilEvent > 0) { - notificationPromises.push(async () => { - const pushTokens = await getPushTokensForFamily(familyId); - if (pushTokens.length) { - await sendNotifications(pushTokens, { - title: "Upcoming Event", - body: `In ${minutesUntilEvent} minutes: ${title}`, - data: { type: 'event_reminder', eventId: doc.id } - }); - batch.update(doc.ref, { reminderSent: true }); - } - }); - } + logger.info(`Preparing to send reminder for: ${title}`); + const pushTokens = await getPushTokensForFamily(familyId); - // Handle all-day events separately - if (allDay && !event.eveningReminderSent) { - const eveningBefore = new Date(eventTime); - eveningBefore.setDate(eveningBefore.getDate() - 1); - eveningBefore.setHours(20, 0, 0, 0); - - if (now.toDate() >= eveningBefore) { - notificationPromises.push(async () => { - const pushTokens = await getPushTokensForFamily(familyId); - if (pushTokens.length) { - await sendNotifications(pushTokens, { - title: "Tomorrow's All-Day Event", - body: `Tomorrow: ${title}`, - data: { type: 'event_reminder', eventId: doc.id } - }); - batch.update(doc.ref, { eveningReminderSent: true }); - } + if (pushTokens.length) { + await sendNotifications(pushTokens, { + title: "Upcoming Event", + body: `In ${minutesUntilEvent} minutes: ${title}`, + data: { type: 'event_reminder', eventId: doc.id } }); + + batch.update(doc.ref, { + reminderSent: true, + lastReminderSent: Timestamp.now() + }); + + logger.info(`Reminder sent for: ${title}`); } + } else { + logger.info(`Not yet time for reminder: ${title}`, { + minutesUntilEvent, + reminderTime + }); } } catch (error) { logger.error(`Error processing reminder for event ${doc.id}:`, error); } } - // Execute all notifications - await Promise.all(notificationPromises.map(fn => fn())); - - // Commit all updates in one batch + // Commit batch if there are any operations if (batch._ops.length > 0) { await batch.commit(); + logger.info(`Committed ${batch._ops.length} updates`); } });