/** * Background Data Service * Handles preemptive loading of tree data for better performance */ export class BackgroundDataService { constructor() { this.treeDataCache = null; this.isLoading = false; this.loadPromise = null; this.authToken = null; this.batchSize = 1000; } setAuthToken(token) { this.authToken = token; } async authenticatedFetch(url, options = {}) { if (!this.authToken) { throw new Error('No auth token available'); } const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.authToken}`, ...options.headers }; const response = await fetch(url, { ...options, headers }); if (response.status === 401) { // Token expired or invalid localStorage.removeItem('auth_token'); localStorage.removeItem('user_info'); window.location.href = '/login'; return null; } return response; } /** * Start preemptive loading of all tree data */ async startPreemptiveLoading() { if (this.isLoading || this.treeDataCache) { return this.loadPromise; } console.log('🚀 Starting preemptive tree data loading...'); this.isLoading = true; this.loadPromise = this.loadAllTreesInBackground(); return this.loadPromise; } /** * Load all trees in background with batching */ async loadAllTreesInBackground() { try { let allTrees = []; let offset = 0; let hasMoreTrees = true; console.log('📦 Loading trees in background batches...'); while (hasMoreTrees && allTrees.length < 3000) { // Safety limit console.log(`📥 Background batch: offset=${offset}, limit=${this.batchSize}`); const response = await this.authenticatedFetch(`/api/trees?limit=${this.batchSize}&offset=${offset}`); if (!response) { console.error('❌ Failed to fetch background batch'); break; } const batchTrees = await response.json(); console.log(`✅ Background batch loaded: ${batchTrees.length} trees`); if (batchTrees.length === 0) { hasMoreTrees = false; break; } allTrees = allTrees.concat(batchTrees); offset += this.batchSize; // If we got less than the batch size, we've reached the end if (batchTrees.length < this.batchSize) { hasMoreTrees = false; } // Small delay to prevent overwhelming the server await this.delay(100); } // Filter out problematic trees const filteredTrees = allTrees.filter(tree => { // Exclude tree ID 18 if (tree.id === 18) { console.log('🔄 Background: Excluding tree ID 18 from cache'); return false; } return true; }); this.treeDataCache = filteredTrees; this.isLoading = false; console.log(`🎉 Background loading complete: ${filteredTrees.length} trees cached`); // Dispatch custom event to notify that data is ready window.dispatchEvent(new CustomEvent('treeDataReady', { detail: { count: filteredTrees.length } })); return filteredTrees; } catch (error) { console.error('❌ Background tree loading failed:', error); this.isLoading = false; throw error; } } /** * Get cached tree data (returns immediately if available) */ getCachedTreeData() { return this.treeDataCache; } /** * Check if data is ready */ isDataReady() { return this.treeDataCache !== null; } /** * Get tree data - uses cache if available, otherwise loads */ async getTreeData() { if (this.treeDataCache) { console.log('⚡ Using cached tree data'); return this.treeDataCache; } if (this.isLoading) { console.log('⏳ Waiting for background loading to complete...'); return await this.loadPromise; } console.log('🔄 Cache miss, loading trees now...'); return await this.startPreemptiveLoading(); } /** * Clear cached data (useful for refresh scenarios) */ clearCache() { this.treeDataCache = null; this.isLoading = false; this.loadPromise = null; console.log('🗑️ Tree data cache cleared'); } /** * Simple delay utility */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Get loading status for UI feedback */ getLoadingStatus() { return { isLoading: this.isLoading, isReady: this.isDataReady(), cacheSize: this.treeDataCache ? this.treeDataCache.length : 0 }; } } // Export singleton instance export const backgroundDataService = new BackgroundDataService();