Files
kashilo/js/components/pages/page-search.js
2026-01-28 07:02:55 +01:00

235 lines
6.8 KiB
JavaScript

import { t, i18n } from '../../i18n.js';
import { router } from '../../router.js';
import '../search-box.js';
import '../listing-card.js';
class PageSearch extends HTMLElement {
constructor() {
super();
this.results = [];
this.loading = false;
}
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 compact no-navigate></search-box>
</section>
<section class="search-results" id="results">
${this.renderResults()}
</section>
</div>
`;
}
async performSearch() {
this.loading = true;
this.updateResults();
await new Promise(resolve => setTimeout(resolve, 500));
this.results = this.getMockResults();
this.loading = false;
this.updateResults();
}
getMockResults() {
return [
{ id: 1, title: 'iPhone 13 Pro', price: 699, location: 'Berlin' },
{ id: 2, title: 'Vintage Sofa', price: 250, location: 'München' },
{ id: 3, title: 'Mountain Bike', price: 450, location: 'Hamburg' },
{ id: 4, title: 'Gaming PC', price: 1200, location: 'Köln' },
{ id: 5, title: 'Schreibtisch', price: 80, location: 'Zürich' },
{ id: 6, title: 'Winterjacke', price: 45, location: 'Wien' },
];
}
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.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(item => /* html */ `
<listing-card
listing-id="${item.id}"
title="${this.escapeHtml(item.title)}"
price="${item.price}"
location="${this.escapeHtml(item.location)}"
></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 .listings-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--space-md);
}
@media (min-width: 768px) {
page-search .listings-grid {
grid-template-columns: repeat(4, 1fr);
}
}
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);