From 25fd24ffdf2a1ce7cd553d4725d9f5f3b119fa2e Mon Sep 17 00:00:00 2001 From: Alexis Feron Date: Tue, 13 May 2025 15:09:44 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=9A=B8=20Improving=20the=20UX=20of=20?= =?UTF-8?q?the=20pin=20CRUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add-pin-popup/add-pin-popup.component.ts | 7 +- .../confirm-modal.component.html | 70 ++++++++++++++++++ .../confirm-modal.component.spec.ts | 23 ++++++ .../confirm-modal/confirm-modal.component.ts | 71 +++++++++++++++++++ .../edit-pin-popup.component.ts | 5 +- .../leaflet-map/leaflet-map.component.ts | 44 +++++++++++- .../login-page/login-page.component.html | 2 +- .../pin-marker/pin-marker.component.html | 6 ++ .../pin-marker/pin-marker.component.ts | 18 ++++- .../register-page.component.html | 2 +- .../map-reload/map-reload.service.spec.ts | 16 +++++ .../services/map-reload/map-reload.service.ts | 14 ++++ 12 files changed, 270 insertions(+), 8 deletions(-) create mode 100644 src/app/components/confirm-modal/confirm-modal.component.html create mode 100644 src/app/components/confirm-modal/confirm-modal.component.spec.ts create mode 100644 src/app/components/confirm-modal/confirm-modal.component.ts create mode 100644 src/app/services/map-reload/map-reload.service.spec.ts create mode 100644 src/app/services/map-reload/map-reload.service.ts diff --git a/src/app/components/add-pin-popup/add-pin-popup.component.ts b/src/app/components/add-pin-popup/add-pin-popup.component.ts index a96a4ee..ae87b65 100644 --- a/src/app/components/add-pin-popup/add-pin-popup.component.ts +++ b/src/app/components/add-pin-popup/add-pin-popup.component.ts @@ -15,9 +15,9 @@ import { } from 'rxjs/operators'; import { AutocompleteService } from '../../services/auto-complete/auto-complete.service'; import { ExifService } from '../../services/exif/exif.service'; +import { MapReloadService } from '../../services/map-reload/map-reload.service'; // Assurez-vous d'importer le service de rechargement de la carte import { PinService } from '../../services/pin/pin.service'; import { DragDropComponent } from '../drag-drop/drag-drop.component'; - @Component({ selector: 'app-add-pin-popup', standalone: true, @@ -36,7 +36,8 @@ export class AddPinPopupComponent implements OnInit { private fb: FormBuilder, private autocompleteService: AutocompleteService, private pinService: PinService, - private exifService: ExifService + private exifService: ExifService, + private mapReloadService: MapReloadService ) { this.form = this.fb.group({ title: new FormControl(''), @@ -121,7 +122,9 @@ export class AddPinPopupComponent implements OnInit { }; this.pinService.addPin(pinData).subscribe(() => { + this.mapReloadService.requestReload(); // Demander le rechargement de la carte this.closePinModal(); + this.form.reset(); // Réinitialiser le formulaire après soumission }); } else { console.error('Le formulaire est invalide'); diff --git a/src/app/components/confirm-modal/confirm-modal.component.html b/src/app/components/confirm-modal/confirm-modal.component.html new file mode 100644 index 0000000..88a30e7 --- /dev/null +++ b/src/app/components/confirm-modal/confirm-modal.component.html @@ -0,0 +1,70 @@ + +
+ + +
+
+ +
+

+ Confirmation +

+ +
+

{{ message }}

+ +
+ + +
+
+
diff --git a/src/app/components/confirm-modal/confirm-modal.component.spec.ts b/src/app/components/confirm-modal/confirm-modal.component.spec.ts new file mode 100644 index 0000000..336c2b4 --- /dev/null +++ b/src/app/components/confirm-modal/confirm-modal.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConfirmModalComponent } from './confirm-modal.component'; + +describe('ConfirmModalComponent', () => { + let component: ConfirmModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ConfirmModalComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ConfirmModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/confirm-modal/confirm-modal.component.ts b/src/app/components/confirm-modal/confirm-modal.component.ts new file mode 100644 index 0000000..bf44ede --- /dev/null +++ b/src/app/components/confirm-modal/confirm-modal.component.ts @@ -0,0 +1,71 @@ +import { CommonModule } from '@angular/common'; +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; +import { Subscription } from 'rxjs'; +import { ModalService } from '../../services/modal/modal.service'; + +@Component({ + selector: 'app-confirm-modal', + standalone: true, + imports: [CommonModule], + templateUrl: './confirm-modal.component.html', +}) +export class ConfirmModalComponent implements OnInit, OnDestroy { + @Input() modalId: string = 'confirm-modal'; + @Input() message: string = 'Es-tu sûr de vouloir supprimer ?'; + @Output() confirmed = new EventEmitter(); + @Output() cancelled = new EventEmitter(); + + isOpen = false; + private subscription!: Subscription; + + constructor(private modalService: ModalService) {} + + ngOnInit() { + this.subscription = this.modalService + .getModalState(this.modalId) + .subscribe((state) => { + this.isOpen = state; + if (state) { + setTimeout(() => this.moveModalToBody(), 0); + } + }); + } + + ngAfterViewInit() { + // Appel initial pour déplacer le modal + this.moveModalToBody(); + } + + ngOnDestroy() { + this.subscription?.unsubscribe(); + } + + confirm() { + this.confirmed.emit(); + this.modalService.closeModal(this.modalId); + } + + cancel() { + this.cancelled.emit(); + this.modalService.closeModal(this.modalId); + } + + closeModal() { + this.isOpen = false; + this.modalService.closeModal(this.modalId); + } + + private moveModalToBody(): void { + const modal = document.getElementById('confirm-modal'); + if (modal && modal.parentElement !== document.body) { + document.body.appendChild(modal); + } + } +} diff --git a/src/app/components/edit-pin-popup/edit-pin-popup.component.ts b/src/app/components/edit-pin-popup/edit-pin-popup.component.ts index 8c978d0..540f1cb 100644 --- a/src/app/components/edit-pin-popup/edit-pin-popup.component.ts +++ b/src/app/components/edit-pin-popup/edit-pin-popup.component.ts @@ -25,6 +25,7 @@ import { import { Pin } from '../../model/Pin'; import { AutocompleteService } from '../../services/auto-complete/auto-complete.service'; import { ExifService } from '../../services/exif/exif.service'; +import { MapReloadService } from '../../services/map-reload/map-reload.service'; // Assurez-vous d'importer le service de rechargement de la carte import { ModalService } from '../../services/modal/modal.service'; import { PinService } from '../../services/pin/pin.service'; import { DragDropComponent } from '../drag-drop/drag-drop.component'; @@ -56,7 +57,8 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy { private pinService: PinService, private exifService: ExifService, private modalService: ModalService, - private router: Router + private router: Router, + private mapReloadService: MapReloadService // Assurez-vous d'importer le service de rechargement de la carte ) { // Initialiser le formulaire avec des valeurs par défaut this.form = this.fb.group({ @@ -248,6 +250,7 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy { }; this.pinService.updatePin(this.pin.id, pinData).subscribe(() => { + this.mapReloadService.requestReload(); // Demander le rechargement de la carte this.closePinModal(); }); } else { diff --git a/src/app/components/leaflet-map/leaflet-map.component.ts b/src/app/components/leaflet-map/leaflet-map.component.ts index 110c9a7..9bae735 100644 --- a/src/app/components/leaflet-map/leaflet-map.component.ts +++ b/src/app/components/leaflet-map/leaflet-map.component.ts @@ -6,6 +6,7 @@ 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 { MapReloadService } from '../../services/map-reload/map-reload.service'; // Assurez-vous d'importer le service de rechargement de la carte import { PinService } from '../../services/pin/pin.service'; import { PinMarkerComponent } from '../pin-marker/pin-marker.component'; @@ -31,12 +32,17 @@ export class LeafletMapComponent implements OnInit { private pinsService: PinService, private autocompleteService: AutocompleteService, private route: ActivatedRoute, - private router: Router + private router: Router, + private mapReloadService: MapReloadService // Assurez-vous d'importer le service de rechargement de la carte ) {} ngOnInit(): void { this.initializeMap(); + this.mapReloadService.reload$.subscribe(() => { + this.loadPins(); // recharge les pins quand demandé + }); + this.route.queryParams.subscribe((params) => { const pinId = params['pin']; if (pinId) { @@ -210,4 +216,40 @@ export class LeafletMapComponent implements OnInit { popupAnchor: [0, -24], }); } + + public loadPins(): void { + this.pinsService.getPins().subscribe((pins: Pin[]) => { + this.allPins = pins; + this.extractPersons(pins); + + const countrySet = new Set(); + 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(); + }); + }); + } } diff --git a/src/app/components/login-page/login-page.component.html b/src/app/components/login-page/login-page.component.html index ad6dd20..002facc 100644 --- a/src/app/components/login-page/login-page.component.html +++ b/src/app/components/login-page/login-page.component.html @@ -124,7 +124,7 @@ Vous n'êtes pas encore inscrit ? Créer un compte diff --git a/src/app/components/pin-marker/pin-marker.component.html b/src/app/components/pin-marker/pin-marker.component.html index 3863092..def3ec3 100644 --- a/src/app/components/pin-marker/pin-marker.component.html +++ b/src/app/components/pin-marker/pin-marker.component.html @@ -1,3 +1,8 @@ + +
diff --git a/src/app/services/map-reload/map-reload.service.spec.ts b/src/app/services/map-reload/map-reload.service.spec.ts new file mode 100644 index 0000000..ec33701 --- /dev/null +++ b/src/app/services/map-reload/map-reload.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { MapReloadService } from './map-reload.service'; + +describe('MapReloadService', () => { + let service: MapReloadService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(MapReloadService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/map-reload/map-reload.service.ts b/src/app/services/map-reload/map-reload.service.ts new file mode 100644 index 0000000..a328c01 --- /dev/null +++ b/src/app/services/map-reload/map-reload.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class MapReloadService { + private reloadSubject = new Subject(); + public reload$ = this.reloadSubject.asObservable(); + + requestReload(): void { + this.reloadSubject.next(); + } +} From 69b8adec7ec08189a718f33c03fbe9bfe0389505 Mon Sep 17 00:00:00 2001 From: Alexis Feron Date: Thu, 15 May 2025 08:23:46 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Improve=20pins=20perf?= =?UTF-8?q?=20and=20modal=20background?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../add-pin-popup/add-pin-popup.component.ts | 2 +- .../confirm-modal.component.html | 1 + .../confirm-modal/confirm-modal.component.ts | 4 + .../edit-pin-popup.component.html | 1 + .../edit-pin-popup.component.ts | 8 +- .../leaflet-map/leaflet-map.component.ts | 94 +++++++------------ 6 files changed, 49 insertions(+), 61 deletions(-) diff --git a/src/app/components/add-pin-popup/add-pin-popup.component.ts b/src/app/components/add-pin-popup/add-pin-popup.component.ts index ae87b65..4201de9 100644 --- a/src/app/components/add-pin-popup/add-pin-popup.component.ts +++ b/src/app/components/add-pin-popup/add-pin-popup.component.ts @@ -15,7 +15,7 @@ import { } from 'rxjs/operators'; import { AutocompleteService } from '../../services/auto-complete/auto-complete.service'; import { ExifService } from '../../services/exif/exif.service'; -import { MapReloadService } from '../../services/map-reload/map-reload.service'; // Assurez-vous d'importer le service de rechargement de la carte +import { MapReloadService } from '../../services/map-reload/map-reload.service'; import { PinService } from '../../services/pin/pin.service'; import { DragDropComponent } from '../drag-drop/drag-drop.component'; @Component({ diff --git a/src/app/components/confirm-modal/confirm-modal.component.html b/src/app/components/confirm-modal/confirm-modal.component.html index 88a30e7..bf51305 100644 --- a/src/app/components/confirm-modal/confirm-modal.component.html +++ b/src/app/components/confirm-modal/confirm-modal.component.html @@ -6,6 +6,7 @@ 'opacity-100': isOpen }" (click)="cancel()" + id="confirm-modal-background" > diff --git a/src/app/components/confirm-modal/confirm-modal.component.ts b/src/app/components/confirm-modal/confirm-modal.component.ts index bf44ede..ae7bfee 100644 --- a/src/app/components/confirm-modal/confirm-modal.component.ts +++ b/src/app/components/confirm-modal/confirm-modal.component.ts @@ -67,5 +67,9 @@ export class ConfirmModalComponent implements OnInit, OnDestroy { if (modal && modal.parentElement !== document.body) { document.body.appendChild(modal); } + const bg = document.getElementById('confirm-modal-background'); + if (bg && bg.parentElement !== document.body) { + document.body.appendChild(bg); + } } } diff --git a/src/app/components/edit-pin-popup/edit-pin-popup.component.html b/src/app/components/edit-pin-popup/edit-pin-popup.component.html index 9d50c32..c21d4b5 100644 --- a/src/app/components/edit-pin-popup/edit-pin-popup.component.html +++ b/src/app/components/edit-pin-popup/edit-pin-popup.component.html @@ -31,6 +31,7 @@ 'opacity-100': isPinModalOpen }" (click)="closePinModal()" + id="pin-modal-background" > diff --git a/src/app/components/edit-pin-popup/edit-pin-popup.component.ts b/src/app/components/edit-pin-popup/edit-pin-popup.component.ts index 540f1cb..b70a1c4 100644 --- a/src/app/components/edit-pin-popup/edit-pin-popup.component.ts +++ b/src/app/components/edit-pin-popup/edit-pin-popup.component.ts @@ -25,7 +25,7 @@ import { import { Pin } from '../../model/Pin'; import { AutocompleteService } from '../../services/auto-complete/auto-complete.service'; import { ExifService } from '../../services/exif/exif.service'; -import { MapReloadService } from '../../services/map-reload/map-reload.service'; // Assurez-vous d'importer le service de rechargement de la carte +import { MapReloadService } from '../../services/map-reload/map-reload.service'; import { ModalService } from '../../services/modal/modal.service'; import { PinService } from '../../services/pin/pin.service'; import { DragDropComponent } from '../drag-drop/drag-drop.component'; @@ -58,7 +58,7 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy { private exifService: ExifService, private modalService: ModalService, private router: Router, - private mapReloadService: MapReloadService // Assurez-vous d'importer le service de rechargement de la carte + private mapReloadService: MapReloadService ) { // Initialiser le formulaire avec des valeurs par défaut this.form = this.fb.group({ @@ -192,6 +192,10 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy { if (modal && modal.parentElement !== document.body) { document.body.appendChild(modal); } + const bg = document.getElementById('pin-modal-background'); + if (bg && bg.parentElement !== document.body) { + document.body.appendChild(bg); + } } selectSuggestion(suggestion: any): void { diff --git a/src/app/components/leaflet-map/leaflet-map.component.ts b/src/app/components/leaflet-map/leaflet-map.component.ts index 9bae735..1b4e4c3 100644 --- a/src/app/components/leaflet-map/leaflet-map.component.ts +++ b/src/app/components/leaflet-map/leaflet-map.component.ts @@ -6,7 +6,7 @@ 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 { MapReloadService } from '../../services/map-reload/map-reload.service'; // Assurez-vous d'importer le service de rechargement de la carte +import { MapReloadService } from '../../services/map-reload/map-reload.service'; import { PinService } from '../../services/pin/pin.service'; import { PinMarkerComponent } from '../pin-marker/pin-marker.component'; @@ -33,7 +33,7 @@ export class LeafletMapComponent implements OnInit { private autocompleteService: AutocompleteService, private route: ActivatedRoute, private router: Router, - private mapReloadService: MapReloadService // Assurez-vous d'importer le service de rechargement de la carte + private mapReloadService: MapReloadService ) {} ngOnInit(): void { @@ -74,34 +74,38 @@ export class LeafletMapComponent implements OnInit { this.allPins = pins; this.extractPersons(pins); - const countrySet = new Set(); - 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(); - }); + this.renderPins(); // Afficher d'abord les pins sans les filtres + this.loadCountriesForFiltrers(pins); // Ensuite, charger les pays en arrière-plan + }); + } + + private loadCountriesForFiltrers(pins: Pin[]): void { + const countrySet = new Set(); + 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(); }); } @@ -221,35 +225,9 @@ export class LeafletMapComponent implements OnInit { this.pinsService.getPins().subscribe((pins: Pin[]) => { this.allPins = pins; this.extractPersons(pins); + this.renderPins(); // Afficher d'abord les pins sans les filtres - const countrySet = new Set(); - 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(); - }); + this.loadCountriesForFiltrers(pins); // Ensuite, charger les pays en arrière-plan }); } }