have events use indexdb

This commit is contained in:
chark1es 2025-03-01 17:43:56 -08:00
parent 5d92589bac
commit 3f8e01487f

View file

@ -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<string | null>(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<Event>(
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<Event>(
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 && (
<div className="card bg-base-100 shadow-xl border border-base-200 mx-4 sm:mx-6 p-8">
<div className="text-center">
<Icon icon="heroicons:calendar" className="w-16 h-16 mx-auto text-base-content/30 mb-4" />
<h3 className="text-xl font-bold mb-2">No Events Found</h3>
<p className="text-base-content/70 mb-4">
There are currently no events to display. This could be due to:
</p>
<ul className="list-disc text-left max-w-md mx-auto text-base-content/70 mb-6">
<li className="mb-1">No events have been published yet</li>
<li className="mb-1">There might be a connection issue with the event database</li>
<li className="mb-1">The events data might be temporarily unavailable</li>
</ul>
<button
onClick={refreshEvents}
className="btn btn-primary"
disabled={refreshing}
>
{refreshing ? (
<>
<Icon icon="heroicons:arrow-path" className="w-5 h-5 mr-2 animate-spin" />
Refreshing...
</>
) : (
<>
<Icon icon="heroicons:arrow-path" className="w-5 h-5 mr-2" />
Refresh Events
</>
)}
</button>
</div>
</div>
)}
{/* Ongoing Events */}
{events.ongoing.length > 0 && (
<div className="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-4 sm:mb-6 mx-4 sm:mx-6">