// 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<Object>} - 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;
|