/** * Categories Service - Handles category tree and translations */ import { directus } from './directus.js' import { getCurrentLanguage, LOCALE_TO_DIRECTUS } from '../i18n.js' class CategoriesService { constructor() { this.cache = null this.cacheTimestamp = 0 this.cacheTimeout = 24 * 60 * 60 * 1000 // 24 hours this.storageKey = 'kashilo_categories' this.storageTimestampKey = 'kashilo_categories_ts' this._pending = null this._loadFromStorage() } _loadFromStorage() { try { const data = localStorage.getItem(this.storageKey) const ts = parseInt(localStorage.getItem(this.storageTimestampKey) || '0', 10) if (data && ts && Date.now() - ts < this.cacheTimeout) { this.cache = JSON.parse(data) this.cacheTimestamp = ts } } catch (e) { // ignore corrupt storage } } _saveToStorage() { try { localStorage.setItem(this.storageKey, JSON.stringify(this.cache)) localStorage.setItem(this.storageTimestampKey, String(this.cacheTimestamp)) } catch (e) { // storage full or unavailable } } async getAll() { if (this.cache && Date.now() - this.cacheTimestamp < this.cacheTimeout) { return this.cache } if (this._pending) return this._pending this._pending = directus.getCategories().then(categories => { this.cache = categories this.cacheTimestamp = Date.now() this._saveToStorage() this._pending = null return categories }).catch(err => { this._pending = null throw err }) return this._pending } async getById(id) { return directus.getCategory(id) } async getBySlug(slug) { return directus.getCategory(slug) } async getTree() { const all = await this.getAll() return this.buildTree(all) } buildTree(categories, parentId = null) { return categories .filter(cat => (cat.parent?.id || cat.parent) === parentId) .map(cat => ({ ...cat, children: this.buildTree(categories, cat.id) })) } async getSubcategories(parentId) { return directus.getSubcategories(parentId) } async getRootCategories() { const all = await this.getAll() return all.filter(cat => !cat.parent) } async getCategoryPath(categoryId) { const all = await this.getAll() const path = [] let current = all.find(c => c.id === categoryId) while (current) { path.unshift(current) const parentId = current.parent?.id || current.parent current = parentId ? all.find(c => c.id === parentId) : null } return path } async getCategoryWithChildren(categoryId) { const all = await this.getAll() const category = all.find(c => c.id === categoryId) if (!category) return null const collectChildren = (parentId) => { return all .filter(c => (c.parent?.id || c.parent) === parentId) .map(c => ({ ...c, children: collectChildren(c.id) })) } return { ...category, children: collectChildren(categoryId) } } getTranslatedName(category, lang = null) { const currentLang = lang || getCurrentLanguage() const directusCode = LOCALE_TO_DIRECTUS[currentLang] || currentLang if (category.translations && Array.isArray(category.translations)) { const translation = category.translations.find( t => t.languages_code === directusCode || t.languages_code === currentLang || t.languages_code?.startsWith(currentLang) ) if (translation?.name) { return translation.name } } return category.name } formatCategoryPath(categories, lang = null) { return categories .map(cat => this.getTranslatedName(cat, lang)) .join(' › ') } async searchCategories(query) { if (!query || query.length < 2) return [] const all = await this.getAll() const lowerQuery = query.toLowerCase() return all.filter(cat => { if (cat.name?.toLowerCase().includes(lowerQuery)) return true if (cat.slug?.toLowerCase().includes(lowerQuery)) return true if (cat.translations) { return cat.translations.some(t => t.name?.toLowerCase().includes(lowerQuery) ) } return false }) } async getCategoriesForSelect(includeChildren = true) { const tree = await this.getTree() const options = [] const flatten = (categories, depth = 0) => { for (const cat of categories) { options.push({ id: cat.id, name: this.getTranslatedName(cat), slug: cat.slug, icon: cat.icon, depth, label: ' '.repeat(depth) + this.getTranslatedName(cat) }) if (includeChildren && cat.children?.length > 0) { flatten(cat.children, depth + 1) } } } flatten(tree) return options } clearCache() { this.cache = null this.cacheTimestamp = 0 localStorage.removeItem(this.storageKey) localStorage.removeItem(this.storageTimestampKey) } } export const categoriesService = new CategoriesService()