refactor: replace hardcoded categories with Directus-powered category tree and translations

This commit is contained in:
2026-02-07 11:23:39 +01:00
parent 4f00b303e8
commit bb50615e0a
12 changed files with 391 additions and 196 deletions

View File

@@ -1,16 +1,6 @@
import { t, i18n } from '../i18n.js'
import { escapeHTML } from '../utils/helpers.js'
const CATEGORIES = {
electronics: ['phones', 'computers', 'tv_audio', 'gaming', 'appliances'],
vehicles: ['cars', 'motorcycles', 'bikes', 'parts'],
furniture: ['living', 'bedroom', 'office', 'outdoor_furniture'],
clothing: ['women', 'men', 'kids', 'shoes', 'accessories'],
sports: ['fitness', 'outdoor', 'winter', 'water', 'team_sports'],
books: ['fiction', 'nonfiction', 'textbooks', 'music_movies'],
garden: ['plants', 'tools', 'outdoor_living', 'decoration'],
other: ['collectibles', 'art', 'handmade', 'services']
}
import { categoriesService } from '../services/categories.js'
const COUNTRIES = ['ch', 'de', 'at', 'fr', 'it', 'li']
const RADIUS_OPTIONS = [5, 10, 20, 50, 100, 200]
@@ -51,6 +41,7 @@ class SearchBox extends HTMLElement {
this.selectedRadius = 50
this.useCurrentLocation = false
this.searchQuery = ''
this.categoryTree = []
this.geoLoading = false
this.currentLat = null
this.currentLng = null
@@ -83,12 +74,18 @@ class SearchBox extends HTMLElement {
this.searchQuery = this.getAttribute('query')
}
this.loadAndRender()
this.unsubscribe = i18n.subscribe(() => this.loadAndRender())
}
async loadAndRender() {
try {
this.categoryTree = await categoriesService.getTree()
} catch (e) {
this.categoryTree = []
}
this.render()
this.setupEventListeners()
this.unsubscribe = i18n.subscribe(() => {
this.render()
this.setupEventListeners()
})
}
disconnectedCallback() {
@@ -151,6 +148,18 @@ class SearchBox extends HTMLElement {
`
}
getCategoryLabel() {
const cat = this.categoryTree.find(c => c.slug === this.selectedCategory)
if (!cat) return this.selectedCategory
const catName = categoriesService.getTranslatedName(cat)
if (this.selectedSubcategory) {
const sub = (cat.children || []).find(s => s.slug === this.selectedSubcategory)
if (sub) return `${catName} ${categoriesService.getTranslatedName(sub)}`
}
return catName
}
hasActiveFilters() {
return this.searchQuery || this.selectedCategory || this.useCurrentLocation
}
@@ -183,9 +192,7 @@ class SearchBox extends HTMLElement {
// Category badge
if (this.selectedCategory) {
const categoryLabel = this.selectedSubcategory
? `${t(`categories.${this.selectedCategory}`)} ${t(`subcategories.${this.selectedSubcategory}`)}`
: t(`categories.${this.selectedCategory}`)
const categoryLabel = this.getCategoryLabel()
badges.push(/* html */`
<button type="button" class="filter-badge" data-filter="category">
@@ -242,9 +249,7 @@ class SearchBox extends HTMLElement {
<button type="button" class="category-dropdown-trigger" id="category-trigger">
<span class="category-dropdown-label">
${this.selectedCategory
? (this.selectedSubcategory
? `${t(`categories.${this.selectedCategory}`)} ${t(`subcategories.${this.selectedSubcategory}`)}`
: t(`categories.${this.selectedCategory}`))
? this.getCategoryLabel()
: t('search.allCategories')}
</span>
<svg class="category-dropdown-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -256,21 +261,21 @@ class SearchBox extends HTMLElement {
<button type="button" class="category-item category-item--all ${!this.selectedCategory ? 'active' : ''}" data-category="" data-subcategory="">
${t('search.allCategories')}
</button>
${Object.keys(CATEGORIES).map(cat => `
<div class="category-accordion ${this._expandedCategory === cat ? 'expanded' : ''}">
<button type="button" class="category-item ${this.selectedCategory === cat ? 'active' : ''}" data-category="${cat}">
<span>${t(`categories.${cat}`)}</span>
${(this.categoryTree || []).map(cat => `
<div class="category-accordion ${this._expandedCategory === cat.slug ? 'expanded' : ''}">
<button type="button" class="category-item ${this.selectedCategory === cat.slug ? 'active' : ''}" data-category="${cat.slug}">
<span>${categoriesService.getTranslatedName(cat)}</span>
<svg class="category-item-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div class="subcategory-list">
<button type="button" class="subcategory-item ${this.selectedCategory === cat && !this.selectedSubcategory ? 'active' : ''}" data-category="${cat}" data-subcategory="">
${t('search.allIn')} ${t(`categories.${cat}`)}
<button type="button" class="subcategory-item ${this.selectedCategory === cat.slug && !this.selectedSubcategory ? 'active' : ''}" data-category="${cat.slug}" data-subcategory="">
${t('search.allIn')} ${categoriesService.getTranslatedName(cat)}
</button>
${CATEGORIES[cat].map(sub => `
<button type="button" class="subcategory-item ${this.selectedCategory === cat && this.selectedSubcategory === sub ? 'active' : ''}" data-category="${cat}" data-subcategory="${sub}">
${t(`subcategories.${sub}`)}
${(cat.children || []).map(sub => `
<button type="button" class="subcategory-item ${this.selectedCategory === cat.slug && this.selectedSubcategory === sub.slug ? 'active' : ''}" data-category="${cat.slug}" data-subcategory="${sub.slug}">
${categoriesService.getTranslatedName(sub)}
</button>
`).join('')}
</div>
@@ -696,15 +701,16 @@ class SearchBox extends HTMLElement {
if (filters.useCurrentLocation !== undefined) this.useCurrentLocation = filters.useCurrentLocation
this.saveFiltersToStorage()
this.render()
this.setupEventListeners()
if (this.categoryTree && this.categoryTree.length > 0) {
this.render()
this.setupEventListeners()
}
}
clearFilters() {
this.resetFilters()
localStorage.removeItem('searchFilters')
this.render()
this.setupEventListeners()
this.loadAndRender()
}
}
@@ -1174,4 +1180,4 @@ style.textContent = /* css */`
`
document.head.appendChild(style)
export { SearchBox, CATEGORIES, COUNTRIES, RADIUS_OPTIONS }
export { SearchBox, COUNTRIES, RADIUS_OPTIONS }