Add pin filters

master
Alexis Feron 3 days ago
parent 58d7526834
commit 3146b28f3e

@ -1,3 +1,41 @@
<div class="map-container h-[calc(100vh_-_72px)]">
<div id="map" class="h-full w-full z-0"></div>
</div>
<div
class="floating-filters text-center absolute top-20 right-4 flex flex-col space-y-2 z-10 mt-2 bg-white p-3 rounded-xl shadow-lg dark:bg-gray-900 dark:text-white"
>
<div
class="filters flex flex-col md:flex-row items-start justify-start gap-4"
>
<label>
Pays :
<select
[(ngModel)]="selectedCountry"
(change)="onCountryChange(selectedCountry)"
class="bg-white dark:bg-gray-900 dark:text-white"
>
<option value="">Tous</option>
<option *ngFor="let country of availableCountries" [value]="country">
{{ country }}
</option>
</select>
</label>
<span class="hidden md:inline">|</span>
<!-- Amis taguées -->
<label>
Amis :
<select
[(ngModel)]="selectedPerson"
(change)="onPersonChange(selectedPerson)"
class="bg-white dark:bg-gray-900 dark:text-white"
>
<option value="">Tous</option>
<option *ngFor="let person of availablePersons" [value]="person">
{{ person }}
</option>
</select>
</label>
</div>
</div>

@ -1,22 +1,35 @@
import { NgFor } from '@angular/common';
import { Component, OnInit, ViewContainerRef } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { Pin } from '../../model/Pin';
import { AutocompleteService } from '../../services/auto-complete/auto-complete.service';
import { PinService } from '../../services/pin/pin.service';
import { PinMarkerComponent } from '../pin-marker/pin-marker.component';
@Component({
selector: 'app-leaflet-map',
templateUrl: './leaflet-map.component.html',
standalone: true,
imports: [NgFor, FormsModule],
})
export class LeafletMapComponent implements OnInit {
private map!: L.Map;
private markersMap: { [key: string]: L.Marker } = {};
private allPins: Pin[] = [];
private pinCountries: { [pinId: string]: string } = {};
availableCountries: string[] = [];
availablePersons: string[] = [];
selectedCountry: string = '';
selectedPerson: string = '';
constructor(
private viewContainerRef: ViewContainerRef,
private pinsService: PinService,
private autocompleteService: AutocompleteService,
private route: ActivatedRoute,
private router: Router
) {}
@ -26,67 +39,128 @@ export class LeafletMapComponent implements OnInit {
}
private initializeMap(): void {
// Initialize the map
this.map = L.map('map', {
maxBounds: L.latLngBounds(
L.latLng(-90, -180), // South-West
L.latLng(90, 180) // North-East
),
maxBoundsViscosity: 1.0, // Prevent dragging the map out of bounds
minZoom: 2, // Prevent zooming out too much
maxBounds: L.latLngBounds(L.latLng(-90, -180), L.latLng(90, 180)),
maxBoundsViscosity: 1.0,
minZoom: 2,
}).setView([46.603354, 1.888334], 6);
// Add OpenStreetMap tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '',
}).addTo(this.map);
this.map.attributionControl.setPrefix('');
// Define custom icons
this.pinsService.getPins().subscribe((pins: Pin[]) => {
this.allPins = pins;
this.extractPersons(pins);
const countrySet = new Set<string>();
const requests = pins.map((pin) =>
this.autocompleteService
.getAddressFromCoordinates(pin.location[0], pin.location[1])
.toPromise()
.then((res: any) => {
const address = res?.address;
const country =
address?.country ||
this.extractLastFromDisplayName(res?.display_name);
if (country) {
this.pinCountries[pin.id] = country;
countrySet.add(country);
}
})
.catch((err: any) => {
console.error(
'Erreur lors de la récupération du pays pour le pin',
pin.id,
err
);
})
);
Promise.all(requests).then(() => {
this.availableCountries = Array.from(countrySet).sort();
this.renderPins();
});
});
}
private extractLastFromDisplayName(displayName: string): string {
if (!displayName) return '';
const parts = displayName.split(',');
return parts[parts.length - 1].trim();
}
private extractPersons(pins: Pin[]): void {
const personsSet = new Set<string>();
const regex = /@(\w+)/g;
pins.forEach((pin) => {
const desc = pin.description || '';
let match;
while ((match = regex.exec(desc)) !== null) {
personsSet.add(match[1]);
}
});
this.availablePersons = Array.from(personsSet).sort();
}
onCountryChange(country: string) {
this.selectedCountry = country;
this.renderPins();
}
onPersonChange(person: string) {
this.selectedPerson = person;
this.renderPins();
}
private renderPins(): void {
// Remove existing markers
Object.values(this.markersMap).forEach((marker) =>
this.map.removeLayer(marker)
);
this.markersMap = {};
const visitedIcon = this.createDivIcon(`
<svg class="w-6 h-6 text-gray-800" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M11.906 1.994a8.002 8.002 0 0 1 8.09 8.421 7.996 7.996 0 0 1-1.297 3.957.996.996 0 0 1-.133.204l-.108.129c-.178.243-.37.477-.573.699l-5.112 6.224a1 1 0 0 1-1.545 0L5.982 15.26l-.002-.002a18.146 18.146 0 0 1-.309-.38l-.133-.163a.999.999 0 0 1-.13-.202 7.995 7.995 0 0 1 6.498-12.518ZM15 9.997a3 3 0 1 1-5.999 0 3 3 0 0 1 5.999 0Z" clip-rule="evenodd"/>
</svg>
`);
const notVisitedIcon = this.createDivIcon(`
<svg class="w-6 h-6 text-gray-800" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-width="3" d="M11.083 5.104c.35-.8 1.485-.8 1.834 0l1.752 4.022a1 1 0 0 0 .84.597l4.463.342c.9.069 1.255 1.2.556 1.771l-3.33 2.723a1 1 0 0 0-.337 1.016l1.03 4.119c.214.858-.71 1.552-1.474 1.106l-3.913-2.281a1 1 0 0 0-1.008 0L7.583 20.8c-.764.446-1.688-.248-1.474-1.106l1.03-4.119A1 1 0 0 0 6.8 14.56l-3.33-2.723c-.698-.571-.342-1.702.557-1.771l4.462-.342a1 1 0 0 0 .84-.597l1.753-4.022Z"/>
</svg>
`);
this.pinsService.getPins().subscribe((pins: Pin[]) => {
// Add markers
pins.forEach((pin: Pin) => {
//const icon = pin.visited ? visitedIcon : notVisitedIcon;
const icon = visitedIcon;
const marker = L.marker(pin.location as [number, number], {
icon,
}).addTo(this.map);
const filteredPins = this.allPins.filter((pin) => {
const pinCountry = this.pinCountries[pin.id];
const matchesCountry = this.selectedCountry
? pinCountry === this.selectedCountry
: true;
const matchesPerson = this.selectedPerson
? pin.description?.includes(`@${this.selectedPerson}`)
: true;
return matchesCountry && matchesPerson;
});
marker.on('popupclose', () => {
this.router.navigate(['/map']);
});
filteredPins.forEach((pin) => {
const marker = L.marker(pin.location as [number, number], {
icon: visitedIcon,
}).addTo(this.map);
marker.on('popupopen', () => {
this.router.navigate(['/map'], { queryParams: { pin: pin.id } });
});
marker.on('popupclose', () => {
this.router.navigate(['/map']);
});
// Dynamically create Angular component and attach it to popup
const popupDiv = document.createElement('div');
const componentRef =
this.viewContainerRef.createComponent(PinMarkerComponent);
marker.on('popupopen', () => {
this.router.navigate(['/map'], { queryParams: { pin: pin.id } });
});
componentRef.instance.pin = pin;
componentRef.instance.marker = marker;
popupDiv.appendChild(componentRef.location.nativeElement);
const popupDiv = document.createElement('div');
const componentRef =
this.viewContainerRef.createComponent(PinMarkerComponent);
componentRef.instance.pin = pin;
componentRef.instance.marker = marker;
popupDiv.appendChild(componentRef.location.nativeElement);
marker.bindPopup(popupDiv, { closeButton: false, minWidth: 150 });
marker.bindPopup(popupDiv, { closeButton: false, minWidth: 150 });
// Stocker les marqueurs par ID
this.markersMap[pin.id] = marker;
});
this.markersMap[pin.id] = marker;
// Ouvrir automatiquement la pop-up si un ID est passé dans l'URL
this.route.queryParams.subscribe((params) => {

@ -5,7 +5,7 @@
<a class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="./logo.png" class="h-10" alt="Memory Map Logo" />
<span
class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white"
class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white hidden lg:inline"
>Memory Map</span
>
</a>
@ -136,9 +136,26 @@
<!-- Bouton de déconnexion -->
<button
(click)="logout()"
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900"
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm lg:px-5 lg:py-2.5 px-4 py-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900"
>
Déconnexion
<span class="hidden lg:inline">Déconnexion</span>
<svg
class="w-6 h-6 text-gray-800 lg:hidden dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M20 12H8m12 0-4 4m4-4-4-4M9 4H7a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h2"
/>
</svg>
</button>
</div>

Loading…
Cancel
Save