have events use indexdb
This commit is contained in:
parent
5d92589bac
commit
3f8e01487f
1 changed files with 288 additions and 32 deletions
|
@ -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">
|
||||
|
|
Loading…
Reference in a new issue