// FilterService.js - Service for processing and managing filters import cache from '../utils/cache'; class FilterService { constructor(options = {}) { this.options = { taxonomyMap: {}, contentTypeMap: {}, initialFilters: {}, ...options }; this.termCache = { terms: new Map(), timestamp: Date.now() }; } /** * Build query parameters from filters * @param {Object} filters - Filter state * @param {Object} context - Context data * @param {number} page - Page number * @param {Object} highlight - Highlighted item * @returns {URLSearchParams} - URL search params */ buildQueryParams(filters, context, page = 1, highlight = null) { const params = new URLSearchParams(); // Add page parameter params.append('page', page); // Add context if present if (context) { params.append('context', JSON.stringify(context)); } // Add highlight if present if (highlight) { params.append('highlight', JSON.stringify(highlight)); } // Add filters if (filters) { Object.entries(filters).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach(v => { params.append(`filters[${key}][]`, v); }); } else if (value !== null && value !== undefined) { params.append(`filters[${key}]`, value); } }); } return params; } /** * Update URL based on current filters * @param {Object} filters - Current filters */ updateURL(filters) { const params = new URLSearchParams(); // Add content type if (filters.content && filters.content !== 'all') { params.set('type', filters.content); } // Add order and direction if (filters.order) { params.set('order', filters.order); if (filters.direction) { params.set('direction', filters.direction); } } // Add taxonomy filters Object.entries(filters).forEach(([key, value]) => { if (Array.isArray(value) && value.length > 0) { value.forEach(v => { params.append(key, v); }); } }); // Add favourites filter if (filters.favouritesOnly) { params.set('favourites', '1'); } // Update URL without reloading page const newUrl = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`; history.pushState({ filters }, '', newUrl); } /** * Parse filters from URL parameters * @returns {Object} - Parsed filters */ parseFiltersFromURL() { const params = new URLSearchParams(window.location.search); const filters = {}; // Get content type const type = params.get('f_content'); if (type) { filters.content = type; } // Get order and direction const order = params.get('f_order'); if (order) { filters.order = order; } const direction = params.get('f_direction'); if (direction) { filters.direction = direction; } // Get favourites flag if (params.get('f_favourites') === '1') { filters.favouritesOnly = true; } // Process all potential taxonomy parameters // Look for parameters with f_ prefix for (const [key, value] of params.entries()) { if (key.startsWith('f_') && key !== 'f_content' && key !== 'f_order' && key !== 'f_direction' && key !== 'f_favourites') { const taxName = key.replace('f_', ''); // Add to filters as array if not already if (!filters[taxName]) { filters[taxName] = []; } // Multiple values could exist for same taxonomy if (!filters[taxName].includes(value)) { filters[taxName].push(value); } } } return filters; } /** * Get applicable taxonomies for a content type * @param {string} contentType - Content type * @returns {Array} - Applicable taxonomies */ getApplicableTaxonomies(contentType) { const taxonomies = []; Object.entries(this.options.taxonomyMap).forEach(([taxonomy, info]) => { if (info.types && info.types.includes(contentType)) { taxonomies.push(taxonomy); } }); return taxonomies; } /** * Fetch taxonomy terms * @param {string} taxonomy - Taxonomy name * @param {string} search - Search query * @param {number} page - Page number * @returns {Promise} - Terms data */ async fetchTerms(taxonomy, search = '', page = 1) { // Create cache key based on parameters const cacheKey = `terms_${taxonomy}_${search}_${page}`; // Try to get from cache first const cachedTerms = cache.get(cacheKey); if (cachedTerms) { return cachedTerms; } // If not in cache, fetch from API try { const params = new URLSearchParams({ search, page }); const response = await fetch(`${window.feedSettings?.apiUrl}terms/${taxonomy}?${params.toString()}`, { headers: { 'X-WP-Nonce': window.feedSettings?.nonce || '' } }); if (!response.ok) { throw new Error(`Failed to fetch terms: ${response.status}`); } const data = await response.json(); // Cache the result cache.set(cacheKey, data, 3600000); // Cache for 1 hour // Update term cache if (!this.termCache.terms.has(taxonomy)) { this.termCache.terms.set(taxonomy, new Map()); } Object.entries(data.terms).forEach(([id, term]) => { this.termCache.terms.get(taxonomy).set(parseInt(id), term); }); return data; } catch (error) { console.error(`Error fetching terms for ${taxonomy}:`, error); throw error; } } /** * Get term details from cache * @param {string} taxonomy - Taxonomy name * @param {number} id - Term ID * @returns {Object|null} - Term details or null if not found */ getTermDetails(taxonomy, id) { if (!this.termCache.terms.has(taxonomy)) { return null; } return this.termCache.terms.get(taxonomy).get(parseInt(id)) || null; } /** * Get a formatted term path * @param {number} termId - Term ID * @param {string} taxonomy - Taxonomy name * @returns {string} - Formatted path */ async getTermPath(termId, taxonomy) { // Try to get from term cache first const termDetails = this.getTermDetails(taxonomy, termId); if (termDetails && termDetails.path) { return termDetails.path; } // If not in cache, fetch from server try { const response = await fetch(`${window.feedSettings?.apiUrl}terms/${taxonomy}/${termId}`, { headers: { 'X-WP-Nonce': window.feedSettings?.nonce || '' } }); if (!response.ok) { throw new Error(`Failed to fetch term: ${response.status}`); } const data = await response.json(); // Update term cache if (!this.termCache.terms.has(taxonomy)) { this.termCache.terms.set(taxonomy, new Map()); } this.termCache.terms.get(taxonomy).set(termId, data); return data.path || data.name; } catch (error) { console.error(`Error fetching term path for ${termId} in ${taxonomy}:`, error); return ''; } } /** * Pre-load common terms for a taxonomy * @param {string} taxonomy - Taxonomy name * @param {Object} terms - Terms data */ preloadTerms(taxonomy, terms) { if (!this.termCache.terms.has(taxonomy)) { this.termCache.terms.set(taxonomy, new Map()); } Object.entries(terms).forEach(([id, term]) => { this.termCache.terms.get(taxonomy).set( parseInt(id), typeof term === 'string' ? { id, name: term } : term ); }); } /** * Get highlighted item from URL * @returns {Object|null} - Highlight object or null if none */ getHighlightFromURL() { const params = new URLSearchParams(window.location.search); const contentTypes = ['tattoo', 'piercing', 'artwork']; for (const type of contentTypes) { if (params.has(type)) { return { [type]: params.get(type) }; } } return null; } } export default FilterService;