205 lines
5.9 KiB
JavaScript
205 lines
5.9 KiB
JavaScript
/**
|
||
* 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()
|