From 3f8e01487fcec731186204c4ca60f41175d0d414 Mon Sep 17 00:00:00 2001 From: chark1es Date: Sat, 1 Mar 2025 17:43:56 -0800 Subject: [PATCH] have events use indexdb --- .../dashboard/EventsSection/EventLoad.tsx | 320 ++++++++++++++++-- 1 file changed, 288 insertions(+), 32 deletions(-) diff --git a/src/components/dashboard/EventsSection/EventLoad.tsx b/src/components/dashboard/EventsSection/EventLoad.tsx index 96cebe6..8082998 100644 --- a/src/components/dashboard/EventsSection/EventLoad.tsx +++ b/src/components/dashboard/EventsSection/EventLoad.tsx @@ -3,6 +3,7 @@ import { Icon } from "@iconify/react"; import { Get } from "../../../scripts/pocketbase/Get"; import { Authentication } from "../../../scripts/pocketbase/Authentication"; import { DataSyncService } from "../../../scripts/database/DataSyncService"; +import { DexieService } from "../../../scripts/database/DexieService"; import { Collections } from "../../../schemas/pocketbase/schema"; import type { Event, AttendeeEntry } from "../../../schemas/pocketbase"; @@ -19,6 +20,33 @@ declare global { [key: string]: any; } } +// Helper function to validate event data integrity +const isValidEvent = (event: any): boolean => { + if (!event || typeof event !== 'object') return false; + + // Check required fields + if (!event.id || !event.event_name) return false; + + // Validate date fields + try { + const startDate = new Date(event.start_date); + const endDate = new Date(event.end_date); + + // Check if dates are valid + if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { + console.warn(`Event ${event.id} has invalid date format`, { + start: event.start_date, + end: event.end_date + }); + return false; + } + + return true; + } catch (error) { + console.warn(`Error validating event ${event?.id || 'unknown'}:`, error); + return false; + } +}; const EventLoad = () => { const [events, setEvents] = useState<{ @@ -31,6 +59,44 @@ const EventLoad = () => { past: [], }); const [loading, setLoading] = useState(true); + const [syncStatus, setSyncStatus] = useState<'idle' | 'syncing' | 'success' | 'error'>('idle'); + const [errorMessage, setErrorMessage] = useState(null); + const [refreshing, setRefreshing] = useState(false); + + // Function to clear the events cache and force a fresh sync + const refreshEvents = async () => { + try { + setRefreshing(true); + + // Get DexieService instance + const dexieService = DexieService.getInstance(); + const db = dexieService.getDB(); + + // Clear events table + if (db && db.events) { + console.log("Clearing events cache..."); + await db.events.clear(); + console.log("Events cache cleared successfully"); + } + + // Reset sync timestamp for events by updating it to 0 + // First get the current record + const currentInfo = await dexieService.getLastSync(Collections.EVENTS); + // Then update it with a timestamp of 0 (forcing a fresh sync) + await dexieService.updateLastSync(Collections.EVENTS); + console.log("Events sync timestamp reset"); + + // Reload events + setLoading(true); + await loadEvents(); + + } catch (error) { + console.error("Error refreshing events:", error); + setErrorMessage("Failed to refresh events. Please try again."); + } finally { + setRefreshing(false); + } + }; useEffect(() => { loadEvents(); @@ -142,11 +208,46 @@ const EventLoad = () => { try { const get = Get.getInstance(); const dataSync = DataSyncService.getInstance(); + const auth = Authentication.getInstance(); + + console.log("Starting to load events..."); + + // Check if user is authenticated + if (!auth.isAuthenticated()) { + console.error("User not authenticated, cannot load events"); + setLoading(false); + return; + } // Force sync to ensure we have the latest data - await dataSync.syncCollection(Collections.EVENTS, "published = true", "-start_date"); + console.log("Syncing events collection..."); + let syncSuccess = false; + let retryCount = 0; + const maxRetries = 3; + + while (!syncSuccess && retryCount < maxRetries) { + try { + if (retryCount > 0) { + console.log(`Retry attempt ${retryCount} of ${maxRetries}...`); + // Add a small delay between retries + await new Promise(resolve => setTimeout(resolve, 1000 * retryCount)); + } + + await dataSync.syncCollection(Collections.EVENTS, "published = true", "-start_date"); + console.log("Events collection synced successfully"); + syncSuccess = true; + } catch (syncError) { + retryCount++; + console.error(`Error syncing events collection (attempt ${retryCount}/${maxRetries}):`, syncError); + + if (retryCount >= maxRetries) { + console.warn("Max retry attempts reached, continuing with local data"); + } + } + } // Get events from IndexedDB + console.log("Fetching events from IndexedDB..."); const allEvents = await dataSync.getData( Collections.EVENTS, false, // Don't force sync again @@ -154,43 +255,88 @@ const EventLoad = () => { "-start_date" ); + console.log(`Retrieved ${allEvents.length} events from IndexedDB`); + + // Filter out invalid events + const validEvents = allEvents.filter(event => isValidEvent(event)); + console.log(`Filtered out ${allEvents.length - validEvents.length} invalid events`); + + // If no valid events found in IndexedDB, try fetching directly from PocketBase as fallback + let eventsToProcess = validEvents; + if (allEvents.length === 0) { + console.log("No events found in IndexedDB, trying direct PocketBase fetch..."); + try { + const pbEvents = await get.getAll( + Collections.EVENTS, + "published = true", + "-start_date" + ); + console.log(`Retrieved ${pbEvents.length} events directly from PocketBase`); + + // Filter out invalid events from PocketBase results + const validPbEvents = pbEvents.filter(event => isValidEvent(event)); + console.log(`Filtered out ${pbEvents.length - validPbEvents.length} invalid events from PocketBase`); + + eventsToProcess = validPbEvents; + + // Store these events in IndexedDB for future use + if (validPbEvents.length > 0) { + const dexieService = DexieService.getInstance(); + const db = dexieService.getDB(); + if (db && db.events) { + console.log(`Storing ${validPbEvents.length} valid PocketBase events in IndexedDB...`); + await db.events.bulkPut(validPbEvents); + } + } + } catch (pbError) { + console.error("Error fetching events from PocketBase:", pbError); + } + } + // Split events into upcoming, ongoing, and past based on start and end dates + console.log("Categorizing events..."); const now = new Date(); - const { upcoming, ongoing, past } = allEvents.reduce( + const { upcoming, ongoing, past } = eventsToProcess.reduce( (acc, event) => { - // Convert UTC dates to local time - const startDate = new Date(event.start_date); - const endDate = new Date(event.end_date); + try { + // Convert UTC dates to local time + const startDate = new Date(event.start_date); + const endDate = new Date(event.end_date); - // Set both dates and now to midnight for date-only comparison - const startLocal = new Date( - startDate.getFullYear(), - startDate.getMonth(), - startDate.getDate(), - startDate.getHours(), - startDate.getMinutes() - ); - const endLocal = new Date( - endDate.getFullYear(), - endDate.getMonth(), - endDate.getDate(), - endDate.getHours(), - endDate.getMinutes() - ); - const nowLocal = new Date( - now.getFullYear(), - now.getMonth(), - now.getDate(), - now.getHours(), - now.getMinutes() - ); + // Set both dates and now to midnight for date-only comparison + const startLocal = new Date( + startDate.getFullYear(), + startDate.getMonth(), + startDate.getDate(), + startDate.getHours(), + startDate.getMinutes() + ); + const endLocal = new Date( + endDate.getFullYear(), + endDate.getMonth(), + endDate.getDate(), + endDate.getHours(), + endDate.getMinutes() + ); + const nowLocal = new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), + now.getHours(), + now.getMinutes() + ); - if (startLocal > nowLocal) { - acc.upcoming.push(event); - } else if (endLocal < nowLocal) { + if (startLocal > nowLocal) { + acc.upcoming.push(event); + } else if (endLocal < nowLocal) { + acc.past.push(event); + } else { + acc.ongoing.push(event); + } + } catch (dateError) { + console.error("Error processing event dates:", dateError, event); + // If we can't process dates properly, put in past events as fallback acc.past.push(event); - } else { - acc.ongoing.push(event); } return acc; }, @@ -201,6 +347,8 @@ const EventLoad = () => { } ); + console.log(`Categorized events: ${upcoming.length} upcoming, ${ongoing.length} ongoing, ${past.length} past`); + // Sort events upcoming.sort((a, b) => new Date(a.start_date).getTime() - new Date(b.start_date).getTime()); ongoing.sort((a, b) => new Date(a.end_date).getTime() - new Date(b.end_date).getTime()); @@ -214,6 +362,76 @@ const EventLoad = () => { setLoading(false); } catch (error) { console.error("Failed to load events:", error); + + // Attempt to diagnose the error + if (error instanceof Error) { + console.error(`Error type: ${error.name}, Message: ${error.message}`); + console.error(`Stack trace: ${error.stack}`); + + // Check for network-related errors + if (error.message.includes('network') || error.message.includes('fetch') || error.message.includes('connection')) { + console.error("Network-related error detected"); + + // Try to load from IndexedDB only as a last resort + try { + console.log("Attempting to load events from IndexedDB only..."); + const dexieService = DexieService.getInstance(); + const db = dexieService.getDB(); + if (db && db.events) { + const allCachedEvents = await db.events.filter(event => event.published === true).toArray(); + console.log(`Found ${allCachedEvents.length} cached events in IndexedDB`); + + // Filter out invalid events + const cachedEvents = allCachedEvents.filter(event => isValidEvent(event)); + console.log(`Filtered out ${allCachedEvents.length - cachedEvents.length} invalid cached events`); + + if (cachedEvents.length > 0) { + // Process these events + const now = new Date(); + const { upcoming, ongoing, past } = cachedEvents.reduce( + (acc, event) => { + try { + const startDate = new Date(event.start_date); + const endDate = new Date(event.end_date); + + if (startDate > now) { + acc.upcoming.push(event); + } else if (endDate < now) { + acc.past.push(event); + } else { + acc.ongoing.push(event); + } + } catch (e) { + acc.past.push(event); + } + return acc; + }, + { + upcoming: [] as Event[], + ongoing: [] as Event[], + past: [] as Event[], + } + ); + + // Sort and set events + upcoming.sort((a, b) => new Date(a.start_date).getTime() - new Date(b.start_date).getTime()); + ongoing.sort((a, b) => new Date(a.end_date).getTime() - new Date(b.end_date).getTime()); + past.sort((a, b) => new Date(b.end_date).getTime() - new Date(a.end_date).getTime()); + + setEvents({ + upcoming: upcoming.slice(0, 50), + ongoing: ongoing.slice(0, 50), + past: past.slice(0, 50) + }); + console.log("Successfully loaded events from cache"); + } + } + } catch (cacheError) { + console.error("Failed to load events from cache:", cacheError); + } + } + } + setLoading(false); } }; @@ -260,8 +478,46 @@ const EventLoad = () => { ); } + // Check if there are no events at all + const noEvents = events.ongoing.length === 0 && events.upcoming.length === 0 && events.past.length === 0; + return ( <> + {/* No Events Message */} + {noEvents && ( +
+
+ +

No Events Found

+

+ There are currently no events to display. This could be due to: +

+
    +
  • No events have been published yet
  • +
  • There might be a connection issue with the event database
  • +
  • The events data might be temporarily unavailable
  • +
+ +
+
+ )} + {/* Ongoing Events */} {events.ongoing.length > 0 && (