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 { Get } from "../../../scripts/pocketbase/Get";
|
||||||
import { Authentication } from "../../../scripts/pocketbase/Authentication";
|
import { Authentication } from "../../../scripts/pocketbase/Authentication";
|
||||||
import { DataSyncService } from "../../../scripts/database/DataSyncService";
|
import { DataSyncService } from "../../../scripts/database/DataSyncService";
|
||||||
|
import { DexieService } from "../../../scripts/database/DexieService";
|
||||||
import { Collections } from "../../../schemas/pocketbase/schema";
|
import { Collections } from "../../../schemas/pocketbase/schema";
|
||||||
import type { Event, AttendeeEntry } from "../../../schemas/pocketbase";
|
import type { Event, AttendeeEntry } from "../../../schemas/pocketbase";
|
||||||
|
|
||||||
|
@ -19,6 +20,33 @@ declare global {
|
||||||
[key: string]: any;
|
[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 EventLoad = () => {
|
||||||
const [events, setEvents] = useState<{
|
const [events, setEvents] = useState<{
|
||||||
|
@ -31,6 +59,44 @@ const EventLoad = () => {
|
||||||
past: [],
|
past: [],
|
||||||
});
|
});
|
||||||
const [loading, setLoading] = useState(true);
|
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(() => {
|
useEffect(() => {
|
||||||
loadEvents();
|
loadEvents();
|
||||||
|
@ -142,11 +208,46 @@ const EventLoad = () => {
|
||||||
try {
|
try {
|
||||||
const get = Get.getInstance();
|
const get = Get.getInstance();
|
||||||
const dataSync = DataSyncService.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
|
// 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
|
// Get events from IndexedDB
|
||||||
|
console.log("Fetching events from IndexedDB...");
|
||||||
const allEvents = await dataSync.getData<Event>(
|
const allEvents = await dataSync.getData<Event>(
|
||||||
Collections.EVENTS,
|
Collections.EVENTS,
|
||||||
false, // Don't force sync again
|
false, // Don't force sync again
|
||||||
|
@ -154,43 +255,88 @@ const EventLoad = () => {
|
||||||
"-start_date"
|
"-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
|
// Split events into upcoming, ongoing, and past based on start and end dates
|
||||||
|
console.log("Categorizing events...");
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const { upcoming, ongoing, past } = allEvents.reduce(
|
const { upcoming, ongoing, past } = eventsToProcess.reduce(
|
||||||
(acc, event) => {
|
(acc, event) => {
|
||||||
// Convert UTC dates to local time
|
try {
|
||||||
const startDate = new Date(event.start_date);
|
// Convert UTC dates to local time
|
||||||
const endDate = new Date(event.end_date);
|
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
|
// Set both dates and now to midnight for date-only comparison
|
||||||
const startLocal = new Date(
|
const startLocal = new Date(
|
||||||
startDate.getFullYear(),
|
startDate.getFullYear(),
|
||||||
startDate.getMonth(),
|
startDate.getMonth(),
|
||||||
startDate.getDate(),
|
startDate.getDate(),
|
||||||
startDate.getHours(),
|
startDate.getHours(),
|
||||||
startDate.getMinutes()
|
startDate.getMinutes()
|
||||||
);
|
);
|
||||||
const endLocal = new Date(
|
const endLocal = new Date(
|
||||||
endDate.getFullYear(),
|
endDate.getFullYear(),
|
||||||
endDate.getMonth(),
|
endDate.getMonth(),
|
||||||
endDate.getDate(),
|
endDate.getDate(),
|
||||||
endDate.getHours(),
|
endDate.getHours(),
|
||||||
endDate.getMinutes()
|
endDate.getMinutes()
|
||||||
);
|
);
|
||||||
const nowLocal = new Date(
|
const nowLocal = new Date(
|
||||||
now.getFullYear(),
|
now.getFullYear(),
|
||||||
now.getMonth(),
|
now.getMonth(),
|
||||||
now.getDate(),
|
now.getDate(),
|
||||||
now.getHours(),
|
now.getHours(),
|
||||||
now.getMinutes()
|
now.getMinutes()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (startLocal > nowLocal) {
|
if (startLocal > nowLocal) {
|
||||||
acc.upcoming.push(event);
|
acc.upcoming.push(event);
|
||||||
} else if (endLocal < nowLocal) {
|
} 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);
|
acc.past.push(event);
|
||||||
} else {
|
|
||||||
acc.ongoing.push(event);
|
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
@ -201,6 +347,8 @@ const EventLoad = () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(`Categorized events: ${upcoming.length} upcoming, ${ongoing.length} ongoing, ${past.length} past`);
|
||||||
|
|
||||||
// Sort events
|
// Sort events
|
||||||
upcoming.sort((a, b) => new Date(a.start_date).getTime() - new Date(b.start_date).getTime());
|
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());
|
ongoing.sort((a, b) => new Date(a.end_date).getTime() - new Date(b.end_date).getTime());
|
||||||
|
@ -214,6 +362,76 @@ const EventLoad = () => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load events:", 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);
|
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 (
|
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 */}
|
{/* Ongoing Events */}
|
||||||
{events.ongoing.length > 0 && (
|
{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">
|
<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