Files
kashilo/js/components/pages/page-search.js

245 lines
7.2 KiB
JavaScript

import { t, i18n } from '../../i18n.js'
import { router } from '../../router.js'
import { listingsService } from '../../services/listings.js'
import { directus } from '../../services/directus.js'
import '../search-box.js'
import '../listing-card.js'
class PageSearch extends HTMLElement {
constructor() {
super()
this.results = []
this.loading = false
this.error = null
}
connectedCallback() {
this.parseUrlParams()
this.render()
this.afterRender()
this.unsubscribe = i18n.subscribe(() => {
this.render()
this.afterRender()
})
if (this.hasFilters()) {
this.performSearch()
}
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
}
parseUrlParams() {
const params = new URLSearchParams(window.location.hash.split('?')[1] || '')
this.query = params.get('q') || ''
this.category = params.get('category') || ''
this.subcategory = params.get('sub') || ''
this.country = params.get('country') || 'ch'
this.useCurrentLocation = params.get('location') === 'current'
this.radius = parseInt(params.get('radius')) || 50
this.lat = params.get('lat') ? parseFloat(params.get('lat')) : null
this.lng = params.get('lng') ? parseFloat(params.get('lng')) : null
}
hasFilters() {
return this.query || this.category || this.subcategory
}
afterRender() {
const searchBox = this.querySelector('search-box')
if (searchBox) {
searchBox.setFilters({
query: this.query,
category: this.category,
subcategory: this.subcategory,
country: this.country,
useCurrentLocation: this.useCurrentLocation,
radius: this.radius
})
searchBox.addEventListener('search', (e) => {
this.handleSearch(e.detail)
})
}
}
handleSearch(filters) {
this.query = filters.query
this.category = filters.category
this.subcategory = filters.subcategory
this.country = filters.country
this.useCurrentLocation = filters.useCurrentLocation
this.radius = filters.radius
this.lat = filters.lat
this.lng = filters.lng
this.performSearch()
}
render() {
this.innerHTML = /* html */`
<div class="search-page">
<section class="search-header">
<search-box no-navigate></search-box>
</section>
<section class="search-results" id="results">
${this.renderResults()}
</section>
</div>
`
}
async performSearch() {
this.loading = true
this.error = null
this.updateResults()
try {
const filters = {
search: this.query || undefined,
category: this.category || undefined,
limit: 50
}
const response = await listingsService.getListingsWithFilters(filters)
this.results = response.items || []
this.loading = false
this.updateResults()
} catch (err) {
console.error('Search failed:', err)
this.error = err.message
this.loading = false
this.updateResults()
}
}
updateResults() {
const resultsContainer = this.querySelector('#results')
if (resultsContainer) {
resultsContainer.innerHTML = this.renderResults()
}
}
renderResults() {
if (this.loading) {
return /* html */`
<div class="loading">
<div class="spinner"></div>
<p>${t('search.loading')}</p>
</div>
`
}
if (this.error) {
return /* html */`
<div class="empty-state">
<div class="empty-state-icon">⚠️</div>
<p>${t('common.error')}</p>
</div>
`
}
if (!this.hasFilters() && this.results.length === 0) {
return /* html */`
<div class="empty-state">
<div class="empty-state-icon">🔍</div>
<p>${t('search.enterQuery')}</p>
</div>
`
}
if (this.results.length === 0) {
return /* html */`
<div class="empty-state">
<div class="empty-state-icon">😕</div>
<p>${t('search.noResults')}</p>
</div>
`
}
return /* html */`
<p class="results-count">${t('search.resultsCount', { count: this.results.length })}</p>
<div class="listings-grid">
${this.results.map(listing => {
const imageId = listing.images?.[0]?.directus_files_id?.id || listing.images?.[0]?.directus_files_id
const imageUrl = imageId ? directus.getThumbnailUrl(imageId, 300) : ''
const locationName = listing.location?.name || listing.location || ''
return /* html */`
<listing-card
listing-id="${listing.id}"
title="${this.escapeHtml(listing.title || '')}"
price="${listing.price || ''}"
currency="${listing.currency || 'EUR'}"
location="${this.escapeHtml(locationName)}"
image="${imageUrl}"
></listing-card>
`
}).join('')}
</div>
`
}
escapeHtml(text) {
const div = document.createElement('div')
div.textContent = text
return div.innerHTML
}
}
customElements.define('page-search', PageSearch)
const style = document.createElement('style')
style.textContent = /* css */`
page-search .search-page {
padding: var(--space-lg) 0;
}
page-search .search-header {
margin-bottom: var(--space-xl);
}
page-search .results-count {
color: var(--color-text-muted);
margin-bottom: var(--space-md);
}
page-search .loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--space-2xl);
color: var(--color-text-muted);
}
page-search .spinner {
width: 40px;
height: 40px;
border: 3px solid var(--color-border);
border-top-color: var(--color-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: var(--space-md);
}
@keyframes spin {
to { transform: rotate(360deg); }
}
page-search .empty-state {
text-align: center;
padding: var(--space-2xl);
color: var(--color-text-muted);
}
page-search .empty-state-icon {
font-size: 3rem;
margin-bottom: var(--space-md);
}
`
document.head.appendChild(style)