| | |
| | | endpoint: store.config.endpoint, |
| | | filters: { ...store.filters }, |
| | | etag: response.headers.get('ETag'), |
| | | lastModified: response.headers.get('Last-Modified') |
| | | lastModified: response.headers.get('Last-Modified'), |
| | | has_more: data.has_more || false |
| | | }; |
| | | |
| | | store.cache.set(cacheKey, cacheEntry); |
| | |
| | | const cacheKey = this.generateCacheKey(store.filters); |
| | | const cacheEntry = store.cache.get(cacheKey); |
| | | |
| | | // First check if we have cached results for exact filters |
| | | if (cacheEntry && cacheEntry.items) { |
| | | return cacheEntry.items.reduce((acc, id) => { |
| | | const item = store.data.get(id); |
| | |
| | | }, []); |
| | | } |
| | | |
| | | // If we have a search filter and complete base data, filter locally |
| | | if (store.filters.search && store.filters.search.trim()) { |
| | | const searchQuery = store.filters.search.toLowerCase().trim(); |
| | | |
| | | // Get all items and filter them locally |
| | | const allItems = Array.from(store.data.values()); |
| | | |
| | | // Filter by current filters (excluding search and page) |
| | | let filtered = allItems.filter(item => { |
| | | // Apply all filters except search and page |
| | | for (const [key, value] of Object.entries(store.filters)) { |
| | | if (key === 'search' || key === 'page') continue; |
| | | |
| | | if (value !== null && value !== undefined && value !== '') { |
| | | if (item[key] !== value) return false; |
| | | } |
| | | } |
| | | return true; |
| | | }); |
| | | |
| | | // Apply search filter to common searchable fields |
| | | filtered = filtered.filter(item => { |
| | | // Search in common fields: name, title, path, description |
| | | const searchableFields = ['name', 'title', 'path', 'description', 'slug']; |
| | | |
| | | return searchableFields.some(field => { |
| | | const value = item[field]; |
| | | if (!value) return false; |
| | | return value.toLowerCase().includes(searchQuery); |
| | | }); |
| | | }); |
| | | |
| | | return filtered; |
| | | } |
| | | |
| | | // Fallback to all data |
| | | return this.getAll(name); |
| | | } |
| | | |
| | |
| | | } |
| | | }); |
| | | } |
| | | const shouldFetch = await this.shouldFetchWithFilters(name, updates, oldFilters); |
| | | |
| | | this.notify(name, 'filters-changed', { |
| | | oldFilters, |
| | |
| | | updates |
| | | }); |
| | | |
| | | if (store.config.endpoint) { |
| | | if (store.config.endpoint && shouldFetch) { |
| | | await this.fetch(name); |
| | | } else if (store.config.endpoint) { |
| | | this.notify(name, 'data-loaded'); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Determine if we need to fetch or can use local data |
| | | * @param {string} name - Store name |
| | | * @param {object} updates - Filter updates being applied |
| | | * @param {object} oldFilters - Previous filter state |
| | | * @returns {Promise<boolean>} - True if fetch is needed, false if local filtering suffices |
| | | */ |
| | | async shouldFetchWithFilters(name, updates, oldFilters) { |
| | | const store = this.stores.get(name); |
| | | |
| | | // If no endpoint or no lastResponse, always fetch |
| | | if (!store.config.endpoint || !store.lastResponse) { |
| | | return true; |
| | | } |
| | | |
| | | // PAGE OPTIMIZATION: Don't fetch if trying to go beyond available pages |
| | | if ('page' in updates) { |
| | | const newPage = updates.page; |
| | | const oldPage = oldFilters.page || 1; |
| | | |
| | | // If trying to go to a higher page but no more data available |
| | | if (newPage > oldPage && !store.lastResponse.has_more) { |
| | | // Reset page to last valid page |
| | | store.filters.page = oldPage; |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // SEARCH OPTIMIZATION: Check if we need to fetch for search |
| | | if ('search' in updates) { |
| | | const searchQuery = updates.search?.trim() || ''; |
| | | const oldSearch = oldFilters.search?.trim() || ''; |
| | | |
| | | // If search is being cleared, we might already have the data |
| | | if (!searchQuery && oldSearch) { |
| | | // Check if we have all base data (without search) |
| | | const baseFilters = { ...store.filters }; |
| | | delete baseFilters.search; |
| | | baseFilters.page = 1; |
| | | |
| | | // If we have complete base data, no need to fetch |
| | | if (this.hasCompleteData(store, baseFilters)) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // If search is new or changed, check if we have all data to filter locally |
| | | if (searchQuery && searchQuery !== oldSearch) { |
| | | // Check: do we have all data for base filters (no search, page 1)? |
| | | const baseFilters = { ...store.filters }; |
| | | delete baseFilters.search; |
| | | baseFilters.page = 1; |
| | | |
| | | // If we have complete base data, we can filter locally |
| | | if (this.hasCompleteData(store, baseFilters)) { |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Default: fetch is needed |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Check if we have complete data for given filters |
| | | * @param {object} store - Store instance |
| | | * @param {object} filters - Filters to check |
| | | * @returns {boolean} - True if we have all data |
| | | */ |
| | | hasCompleteData(store, filters) { |
| | | const cacheKey = this.generateCacheKey(filters); |
| | | const cached = store.cache.get(cacheKey); |
| | | |
| | | if (!cached) return false; |
| | | |
| | | // Check if cache indicates no more data |
| | | return cached.has_more === false || store.lastResponse?.has_more === false; |
| | | } |
| | | |
| | | setFilter(name, key, value) { |
| | | return this.updateFilters(name, { [key]: value }); |
| | | } |