Add pin filters

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

@ -1,3 +1,41 @@
<div class="map-container h-[calc(100vh_-_72px)]"> <div class="map-container h-[calc(100vh_-_72px)]">
<div id="map" class="h-full w-full z-0"></div> <div id="map" class="h-full w-full z-0"></div>
</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 { Component, OnInit, ViewContainerRef } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import * as L from 'leaflet'; import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import { Pin } from '../../model/Pin'; import { Pin } from '../../model/Pin';
import { AutocompleteService } from '../../services/auto-complete/auto-complete.service';
import { PinService } from '../../services/pin/pin.service'; import { PinService } from '../../services/pin/pin.service';
import { PinMarkerComponent } from '../pin-marker/pin-marker.component'; import { PinMarkerComponent } from '../pin-marker/pin-marker.component';
@Component({ @Component({
selector: 'app-leaflet-map', selector: 'app-leaflet-map',
templateUrl: './leaflet-map.component.html', templateUrl: './leaflet-map.component.html',
standalone: true,
imports: [NgFor, FormsModule],
}) })
export class LeafletMapComponent implements OnInit { export class LeafletMapComponent implements OnInit {
private map!: L.Map; private map!: L.Map;
private markersMap: { [key: string]: L.Marker } = {}; private markersMap: { [key: string]: L.Marker } = {};
private allPins: Pin[] = [];
private pinCountries: { [pinId: string]: string } = {};
availableCountries: string[] = [];
availablePersons: string[] = [];
selectedCountry: string = '';
selectedPerson: string = '';
constructor( constructor(
private viewContainerRef: ViewContainerRef, private viewContainerRef: ViewContainerRef,
private pinsService: PinService, private pinsService: PinService,
private autocompleteService: AutocompleteService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router private router: Router
) {} ) {}
@ -26,67 +39,128 @@ export class LeafletMapComponent implements OnInit {
} }
private initializeMap(): void { private initializeMap(): void {
// Initialize the map
this.map = L.map('map', { this.map = L.map('map', {
maxBounds: L.latLngBounds( maxBounds: L.latLngBounds(L.latLng(-90, -180), L.latLng(90, 180)),
L.latLng(-90, -180), // South-West maxBoundsViscosity: 1.0,
L.latLng(90, 180) // North-East minZoom: 2,
),
maxBoundsViscosity: 1.0, // Prevent dragging the map out of bounds
minZoom: 2, // Prevent zooming out too much
}).setView([46.603354, 1.888334], 6); }).setView([46.603354, 1.888334], 6);
// Add OpenStreetMap tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '', attribution: '',
}).addTo(this.map); }).addTo(this.map);
this.map.attributionControl.setPrefix(''); 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(` 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"> <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"/> <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> </svg>
`); `);
const notVisitedIcon = this.createDivIcon(` const filteredPins = this.allPins.filter((pin) => {
<svg class="w-6 h-6 text-gray-800" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> const pinCountry = this.pinCountries[pin.id];
<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"/> const matchesCountry = this.selectedCountry
</svg> ? pinCountry === this.selectedCountry
`); : true;
const matchesPerson = this.selectedPerson
this.pinsService.getPins().subscribe((pins: Pin[]) => { ? pin.description?.includes(`@${this.selectedPerson}`)
// Add markers : true;
pins.forEach((pin: Pin) => { return matchesCountry && matchesPerson;
//const icon = pin.visited ? visitedIcon : notVisitedIcon; });
const icon = visitedIcon;
const marker = L.marker(pin.location as [number, number], {
icon,
}).addTo(this.map);
marker.on('popupclose', () => { filteredPins.forEach((pin) => {
this.router.navigate(['/map']); const marker = L.marker(pin.location as [number, number], {
}); icon: visitedIcon,
}).addTo(this.map);
marker.on('popupopen', () => { marker.on('popupclose', () => {
this.router.navigate(['/map'], { queryParams: { pin: pin.id } }); this.router.navigate(['/map']);
}); });
// Dynamically create Angular component and attach it to popup marker.on('popupopen', () => {
const popupDiv = document.createElement('div'); this.router.navigate(['/map'], { queryParams: { pin: pin.id } });
const componentRef = });
this.viewContainerRef.createComponent(PinMarkerComponent);
componentRef.instance.pin = pin; const popupDiv = document.createElement('div');
componentRef.instance.marker = marker; const componentRef =
popupDiv.appendChild(componentRef.location.nativeElement); 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 // Ouvrir automatiquement la pop-up si un ID est passé dans l'URL
this.route.queryParams.subscribe((params) => { this.route.queryParams.subscribe((params) => {

@ -5,7 +5,7 @@
<a class="flex items-center space-x-3 rtl:space-x-reverse"> <a class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="./logo.png" class="h-10" alt="Memory Map Logo" /> <img src="./logo.png" class="h-10" alt="Memory Map Logo" />
<span <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 >Memory Map</span
> >
</a> </a>
@ -136,9 +136,26 @@
<!-- Bouton de déconnexion --> <!-- Bouton de déconnexion -->
<button <button
(click)="logout()" (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> </button>
</div> </div>

Loading…
Cancel
Save