feat(search): add category search support and sync with chips

This commit is contained in:
2026-03-20 18:14:02 +04:00
parent 4008a8c883
commit b083c1d001
8 changed files with 67 additions and 17 deletions

View File

@@ -11,7 +11,11 @@ import CategoryChips from '#components/category-chips';
export default class AppHeaderComponent extends Component {
@service storage;
@tracked isUserMenuOpen = false;
@tracked hasQuery = false;
@tracked searchQuery = '';
get hasQuery() {
return !!this.searchQuery;
}
@action
toggleUserMenu() {
@@ -25,22 +29,21 @@ export default class AppHeaderComponent extends Component {
@action
handleQueryChange(query) {
this.hasQuery = !!query;
this.searchQuery = query;
}
@action
handleChipSelect() {
// When a chip is selected, we might want to ensure the search box is cleared visually,
// although the route transition will happen.
// The SearchBox component manages its own state, so we rely on the route transition.
// However, if we want to clear the search box input from here, we'd need to control it.
// For now, let's just let the route change happen.
handleChipSelect(category) {
this.searchQuery = category.label;
// The existing logic in CategoryChips triggers the route transition.
// This update simply fills the search box.
}
<template>
<header class="app-header">
<div class="header-left">
<SearchBox
@query={{this.searchQuery}}
@onToggleMenu={{@onToggleMenu}}
@onQueryChange={{this.handleQueryChange}}
/>

View File

@@ -7,6 +7,7 @@ import { fn } from '@ember/helper';
import { task, timeout } from 'ember-concurrency';
import Icon from '#components/icon';
import humanizeOsmTag from '../helpers/humanize-osm-tag';
import { POI_CATEGORIES } from '../utils/poi-categories';
import eq from 'ember-truth-helpers/helpers/eq';
export default class SearchBoxComponent extends Component {
@@ -15,11 +16,19 @@ export default class SearchBoxComponent extends Component {
@service mapUi;
@service map; // Assuming we might need map context, but mostly we use router
@tracked query = '';
@tracked _internalQuery = '';
@tracked results = [];
@tracked isFocused = false;
@tracked isLoading = false;
get query() {
return this.args.query ?? this._internalQuery;
}
set query(value) {
this._internalQuery = value;
}
get showPopover() {
return this.isFocused && this.results.length > 0;
}
@@ -51,8 +60,20 @@ export default class SearchBoxComponent extends Component {
if (this.mapUi.currentCenter) {
({ lat, lon } = this.mapUi.currentCenter);
}
// Filter categories
const q = this.query.toLowerCase();
const categoryMatches = POI_CATEGORIES.filter((c) =>
c.label.toLowerCase().includes(q)
).map((c) => ({
source: 'category',
title: c.label,
id: c.id,
icon: 'search',
}));
const results = await this.photon.search(this.query, lat, lon);
this.results = results;
this.results = [...categoryMatches, ...results];
} catch (e) {
console.error('Search failed', e);
this.results = [];
@@ -98,7 +119,29 @@ export default class SearchBoxComponent extends Component {
@action
selectResult(place) {
if (place.source === 'category') {
this.query = place.title;
if (this.args.onQueryChange) {
this.args.onQueryChange(place.title);
}
this.results = [];
this.router.transitionTo('search', {
queryParams: {
q: place.title,
category: place.id,
selected: null,
lat: null,
lon: null,
},
});
return;
}
this.query = place.title;
if (this.args.onQueryChange) {
this.args.onQueryChange(place.title);
}
this.results = []; // Hide popover
// If it has an OSM ID, go to place details
@@ -183,7 +226,11 @@ export default class SearchBoxComponent extends Component {
{{on "click" (fn this.selectResult result)}}
>
<div class="result-icon">
<Icon @name="map-pin" @size={{16}} @color="#666" />
<Icon
@name={{if result.icon result.icon "map-pin"}}
@size={{16}}
@color="#666"
/>
</div>
<div class="result-info">
<span class="result-title">{{result.title}}</span>