Trip places: filter by location
This commit is contained in:
parent
df09c4c799
commit
69cde40fc1
@ -1,13 +1,8 @@
|
||||
<section>
|
||||
<div class="max-w-full overflow-y-auto">
|
||||
<div class="mb-4">
|
||||
<p-floatlabel variant="in">
|
||||
<input id="search" pSize="small" [formControl]="searchInput" pInputText fluid />
|
||||
<label for="search">Search...</label>
|
||||
</p-floatlabel>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex justify-between items-center gap-4">
|
||||
<div class="mt-8 flex justify-between items-center gap-4 cursor-pointer"
|
||||
(click)="showSelectedPlaces = !showSelectedPlaces">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<h1 class="font-semibold tracking-tight text-xl">Selected</h1>
|
||||
@ -18,9 +13,9 @@
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<p-button [icon]="showSelectedPlaces ? 'pi pi-chevron-up' : 'pi pi-chevron-down'" text
|
||||
(click)="showSelectedPlaces = !showSelectedPlaces" />
|
||||
<p-button [icon]="showSelectedPlaces ? 'pi pi-chevron-up' : 'pi pi-chevron-down'" text />
|
||||
</div>
|
||||
|
||||
@if (showSelectedPlaces) {
|
||||
@for (p of selectedPlaces; track p.id) {
|
||||
<div
|
||||
@ -58,9 +53,39 @@
|
||||
|
||||
<div class="mt-8 mb-16">
|
||||
<h1 class="font-semibold tracking-tight text-xl">List</h1>
|
||||
<span class="text-xs text-gray-500">Available points</span>
|
||||
<span class="text-xs text-gray-500">Available Places</span>
|
||||
@defer {
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-2 mt-2 p-2">
|
||||
<p-floatlabel variant="in" class="md:col-span-2">
|
||||
<input id="search" pSize="small" [formControl]="searchInput" pInputText fluid />
|
||||
<label for="search">Search...</label>
|
||||
</p-floatlabel>
|
||||
|
||||
<div class="relative">
|
||||
<p-floatlabel variant="in">
|
||||
<input id="geocodeInput" pSize="small" (keydown.enter)="gmapsGeocodeFilter()"
|
||||
[formControl]="googleGeocodeInput" pInputText autofocus fluid />
|
||||
<label for="geocodeInput">Filter by Country / City </label>
|
||||
</p-floatlabel>
|
||||
|
||||
@if (boundariesFiltering) {<p-button icon="pi pi-times" variant="text" severity="danger"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2" pTooltip="Clear and Cancel"
|
||||
(click)="resetGeocodeFilters()" />}
|
||||
@else {<p-button icon="pi pi-sparkles" variant="text" [disabled]="!googleGeocodeInput.value"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2" pTooltip="Filter using location (GMaps API)"
|
||||
(click)="gmapsGeocodeFilter()" />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (boundariesFiltering) {
|
||||
<div class="mt-4 flex justify-center items-center gap-1">
|
||||
<p class="text-gray-500 dark:text-gray-400">Filtering for location: {{ googleGeocodeInput.value }}</p>
|
||||
<p-button icon="pi pi-times" variant="text" severity="danger" pTooltip="Cancel"
|
||||
(click)="resetGeocodeFilters()" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@for (p of displayedPlaces; track p.id) {
|
||||
<div
|
||||
class="mt-4 flex items-center gap-4 hover:bg-gray-50 rounded-xl cursor-pointer py-2 px-4 dark:hover:bg-gray-800"
|
||||
@ -91,8 +116,8 @@
|
||||
</div>
|
||||
</div>
|
||||
} @empty {
|
||||
<div class="text-center">
|
||||
<h1 class="tracking-tight">Nothing to see</h1>
|
||||
<div class="mt-8 text-center">
|
||||
<p class="text-base text-gray-500 dark:text-gray-400">Nothing to see</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@ -4,20 +4,25 @@ import { ButtonModule } from 'primeng/button';
|
||||
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||
import { FloatLabelModule } from 'primeng/floatlabel';
|
||||
import { InputTextModule } from 'primeng/inputtext';
|
||||
import { Place } from '../../types/poi';
|
||||
import { GoogleBoundaries, Place } from '../../types/poi';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { SkeletonModule } from 'primeng/skeleton';
|
||||
import { UtilsService } from '../../services/utils.service';
|
||||
import { take } from 'rxjs';
|
||||
import { isPointInBounds } from '../../shared/map';
|
||||
import { TooltipModule } from 'primeng/tooltip';
|
||||
|
||||
@Component({
|
||||
selector: 'app-trip-place-select-modal',
|
||||
imports: [FloatLabelModule, InputTextModule, ButtonModule, ReactiveFormsModule, SkeletonModule],
|
||||
imports: [FloatLabelModule, InputTextModule, ButtonModule, ReactiveFormsModule, SkeletonModule, TooltipModule],
|
||||
standalone: true,
|
||||
templateUrl: './trip-place-select-modal.component.html',
|
||||
styleUrl: './trip-place-select-modal.component.scss',
|
||||
})
|
||||
export class TripPlaceSelectModalComponent {
|
||||
searchInput = new FormControl('');
|
||||
googleGeocodeInput = new FormControl('');
|
||||
boundariesFiltering?: GoogleBoundaries;
|
||||
|
||||
selectedPlaces: Place[] = [];
|
||||
showSelectedPlaces: boolean = false;
|
||||
@ -48,17 +53,7 @@ export class TripPlaceSelectModalComponent {
|
||||
}
|
||||
|
||||
this.searchInput.valueChanges.subscribe({
|
||||
next: (value) => {
|
||||
if (!value) {
|
||||
this.displayedPlaces = this.places;
|
||||
return;
|
||||
}
|
||||
|
||||
const v = value.toLowerCase();
|
||||
this.displayedPlaces = this.places.filter(
|
||||
(p) => p.name.toLowerCase().includes(v) || p.description?.toLowerCase().includes(v),
|
||||
);
|
||||
},
|
||||
next: () => this.filterPlaces(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -86,7 +81,43 @@ export class TripPlaceSelectModalComponent {
|
||||
this.selectedPlaces.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
||||
}
|
||||
|
||||
filterPlaces() {
|
||||
const searchInputValue = (this.searchInput.value || '').toLowerCase();
|
||||
this.displayedPlaces = this.places.filter((place) => {
|
||||
if (this.boundariesFiltering) {
|
||||
if (!isPointInBounds(place.lat, place.lng, this.boundariesFiltering)) return false;
|
||||
}
|
||||
|
||||
if (!searchInputValue) return true;
|
||||
return (
|
||||
place.name.toLowerCase().includes(searchInputValue) ||
|
||||
place.description?.toLowerCase().includes(searchInputValue)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
resetGeocodeFilters() {
|
||||
this.boundariesFiltering = undefined;
|
||||
this.googleGeocodeInput.setValue('');
|
||||
this.filterPlaces();
|
||||
}
|
||||
|
||||
closeDialog() {
|
||||
this.ref.close(this.selectedPlaces);
|
||||
}
|
||||
|
||||
gmapsGeocodeFilter() {
|
||||
const value = this.googleGeocodeInput.value;
|
||||
if (!value) return;
|
||||
|
||||
this.apiService
|
||||
.gmapsGeocodeBoundaries(value)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (boundaries) => {
|
||||
this.boundariesFiltering = boundaries;
|
||||
this.filterPlaces();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import * as L from 'leaflet';
|
||||
import 'leaflet.markercluster';
|
||||
import 'leaflet-contextmenu';
|
||||
import { Place } from '../types/poi';
|
||||
import { GoogleBoundaries, Place } from '../types/poi';
|
||||
|
||||
export const DEFAULT_TILE_URL = 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png';
|
||||
export interface ContextMenuItem {
|
||||
@ -143,3 +143,14 @@ export function gpxToPolyline(gpx: string): L.Polyline {
|
||||
|
||||
return L.polyline(latlngs, { color: 'blue' });
|
||||
}
|
||||
|
||||
export function isPointInBounds(lat: number, lng: number, bounds: GoogleBoundaries): boolean {
|
||||
if (!bounds || !bounds.northeast || !bounds.southwest) return false;
|
||||
|
||||
const ne = bounds.northeast;
|
||||
const sw = bounds.southwest;
|
||||
|
||||
if (lat < sw.lat || lat > ne.lat) return false;
|
||||
|
||||
return sw.lng <= ne.lng ? lng >= sw.lng && lng <= ne.lng : lng >= sw.lng || lng <= ne.lng;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user