import Dexie from "dexie"; import type { User, Event, EventRequest, Log, Officer, Reimbursement, Receipt, Sponsor, EventAttendee, } from "../../schemas/pocketbase/schema"; // Check if we're in a browser environment const isBrowser = typeof window !== "undefined" && typeof window.indexedDB !== "undefined"; // Interface for tracking offline changes interface OfflineChange { id: string; collection: string; recordId: string; operation: "create" | "update" | "delete"; data?: any; timestamp: number; synced: boolean; syncAttempts: number; } // Import ThemeSettings interface import type { ThemeSettings } from "./ThemeService"; export class DashboardDatabase extends Dexie { users!: Dexie.Table; events!: Dexie.Table; eventAttendees!: Dexie.Table; eventRequests!: Dexie.Table; logs!: Dexie.Table; officers!: Dexie.Table; reimbursements!: Dexie.Table; receipts!: Dexie.Table; sponsors!: Dexie.Table; offlineChanges!: Dexie.Table; themeSettings!: Dexie.Table; // Store last sync timestamps syncInfo!: Dexie.Table< { id: string; collection: string; lastSync: number }, string >; constructor() { super("IEEEDashboardDB"); this.version(1).stores({ users: "id, email, name", events: "id, event_name, event_code, start_date, end_date, published", eventRequests: "id, name, status, requested_user, created, updated", logs: "id, user, type, created", officers: "id, user, role, type", reimbursements: "id, title, status, submitted_by, created", receipts: "id, created_by, date", sponsors: "id, user, company", syncInfo: "id, collection, lastSync", }); // Add version 2 with offlineChanges table this.version(2).stores({ offlineChanges: "id, collection, recordId, operation, timestamp, synced, syncAttempts", }); // Add version 3 with eventAttendees table and updated events table (no attendees field) this.version(3).stores({ events: "id, event_name, event_code, start_date, end_date, published", eventAttendees: "id, user, event, time_checked_in", }); // Add version 4 with files field in events table this.version(4).stores({ events: "id, event_name, event_code, start_date, end_date, published, files", }); // Add version 5 with themeSettings table this.version(5).stores({ themeSettings: "id, theme, fontSize, updatedAt", }); } // Initialize the database with default values async initialize() { const collections = [ "users", "events", "event_attendees", "event_request", "logs", "officers", "reimbursement", "receipts", "sponsors", ]; for (const collection of collections) { const exists = await this.syncInfo.get(collection); if (!exists) { await this.syncInfo.put({ id: collection, collection, lastSync: 0, }); } } } } // Mock database for server-side rendering class MockDashboardDatabase { // Implement empty methods that won't fail during SSR async initialize() { // Do nothing } } // Singleton pattern export class DexieService { private static instance: DexieService; private db: DashboardDatabase | MockDashboardDatabase; private constructor() { if (isBrowser) { // Only initialize Dexie in browser environments this.db = new DashboardDatabase(); this.db.initialize(); } else { // Use a mock database in non-browser environments // console.log("Running in Node.js environment, using mock database"); this.db = new MockDashboardDatabase() as any; } } public static getInstance(): DexieService { if (!DexieService.instance) { DexieService.instance = new DexieService(); } return DexieService.instance; } // Get the database instance public getDB(): DashboardDatabase { if (!isBrowser) { console.warn( "Attempting to access IndexedDB in a non-browser environment", ); } return this.db as DashboardDatabase; } // Update the last sync timestamp for a collection public async updateLastSync(collection: string): Promise { if (!isBrowser) return; await (this.db as DashboardDatabase).syncInfo.update(collection, { lastSync: Date.now(), }); } // Get the last sync timestamp for a collection public async getLastSync(collection: string): Promise { if (!isBrowser) return 0; const info = await (this.db as DashboardDatabase).syncInfo.get(collection); return info?.lastSync || 0; } // Clear all data (useful for logout) public async clearAllData(): Promise { if (!isBrowser) return; const db = this.db as DashboardDatabase; await db.users.clear(); await db.events.clear(); await db.eventAttendees.clear(); await db.eventRequests.clear(); await db.logs.clear(); await db.officers.clear(); await db.reimbursements.clear(); await db.receipts.clear(); await db.sponsors.clear(); await db.offlineChanges.clear(); // Note: We don't clear themeSettings as they should persist across logins // Reset sync timestamps const collections = [ "users", "events", "event_attendees", "event_request", "logs", "officers", "reimbursement", "receipts", "sponsors", ]; for (const collection of collections) { await db.syncInfo.update(collection, { lastSync: 0 }); } } }