import { CommonModule } from '@angular/common'; import { AfterViewInit, Component, Input, OnDestroy, OnInit, } from '@angular/core'; import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, } from '@angular/forms'; import { NavigationEnd, Router } from '@angular/router'; import { forkJoin, of, Subscription } from 'rxjs'; import { catchError, debounceTime, distinctUntilChanged, filter, switchMap, take, } from 'rxjs/operators'; import { Pin } from '../../model/Pin'; import { AutocompleteService } from '../../services/auto-complete/auto-complete.service'; import { ExifService } from '../../services/exif/exif.service'; import { ImageService } from '../../services/image/image.service'; 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'; @Component({ selector: 'app-edit-pin-popup', standalone: true, imports: [ReactiveFormsModule, CommonModule, DragDropComponent], templateUrl: './edit-pin-popup.component.html', }) export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy { @Input() isHomePage: boolean = false; @Input() pin!: Pin; form!: FormGroup; suggestions: any[] = []; inputFocused: boolean = false; files: any[] = []; isPinModalOpen: boolean = false; @Input() modalId!: string; private modalOpenSubscription!: Subscription; private routerSubscription!: Subscription; private locationSubscription!: Subscription; constructor( private fb: FormBuilder, private autocompleteService: AutocompleteService, private pinService: PinService, private exifService: ExifService, private modalService: ModalService, private router: Router, private mapReloadService: MapReloadService, private imageService: ImageService ) { // Initialiser le formulaire avec des valeurs par défaut this.form = this.fb.group({ title: new FormControl(''), description: new FormControl(''), location: new FormControl(''), complete_address: new FormControl(''), coordinates: new FormControl([]), files: new FormControl(null), date: new FormControl(''), }); } onFocus(): void { this.inputFocused = true; } onBlur(): void { setTimeout(() => { this.inputFocused = false; // Désactiver le focus après un petit délai pour permettre un clic sur la liste }, 200); } ngOnInit(): void { // Initialiser le formulaire avec les valeurs de base this.form.patchValue({ title: this.pin?.title || '', description: this.pin?.description || '', location: this.pin?.complete_address || '', complete_address: this.pin?.complete_address || '', coordinates: this.pin?.location || [], files: this.pin?.files || [], date: this.pin?.date ? new Date(this.pin.date).toISOString().split('T')[0] : '', }); // Initialiser les fichiers existants this.files = this.pin?.files || []; // S'abonner aux changements d'état du modal this.modalOpenSubscription = this.modalService .getModalState(this.modalId) .subscribe((state) => { this.isPinModalOpen = state; if (state) { setTimeout(() => this.moveModalToBody(), 0); } }); // S'abonner aux événements de navigation du router this.routerSubscription = this.router.events .pipe(filter((event) => event instanceof NavigationEnd)) .subscribe(() => { // Attendre que le DOM soit mis à jour après la navigation setTimeout(() => this.moveModalToBody(), 0); }); // Configuration de l'autocomplétion pour le champ d'adresse this.form .get('location') ?.valueChanges.pipe( debounceTime(300), // Attendre 300ms après la dernière frappe distinctUntilChanged(), // Ignorer si la nouvelle valeur est la même que la précédente switchMap((query) => { // Vérifier que query est une chaîne de caractères if (typeof query !== 'string') { return of([]); } const trimmedQuery = query.trim(); if (trimmedQuery.length > 2) { return this.autocompleteService.getAddressSuggestions(trimmedQuery); } return of([]); }), catchError((error) => { console.error('Error fetching suggestions:', error); return of([]); }) ) .subscribe((data) => { this.suggestions = data; }); } ngAfterViewInit() { // Appel initial pour déplacer le modal this.moveModalToBody(); } ngOnDestroy() { // Nettoyage des abonnements pour éviter les fuites de mémoire if (this.modalOpenSubscription) { this.modalOpenSubscription.unsubscribe(); } if (this.routerSubscription) { this.routerSubscription.unsubscribe(); } if (this.locationSubscription) { this.locationSubscription.unsubscribe(); } } // Méthode dédiée pour déplacer le modal vers le body private moveModalToBody(): void { const modal = document.getElementById('pin-modal'); 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 { const locationControl = this.form.get('location'); if (locationControl instanceof FormControl) { locationControl.setValue(suggestion.display_name); this.form.get('complete_address')?.setValue(suggestion.display_name); this.form.get('coordinates')?.setValue([suggestion.lat, suggestion.lon]); } this.suggestions = []; } async onFilesReceived(files: FileList): Promise { // Ajouter les nouveaux fichiers à la liste existante this.files = [...this.files, ...Array.from(files)]; for (let i = 0; i < files.length; i++) { try { const data = await this.exifService.getLocation(files[i]); if (data.latitude !== undefined && data.longitude !== undefined) { try { const address = await this.autocompleteService .getAddressFromCoordinates(data.latitude, data.longitude) .pipe(take(1)) .toPromise(); if (address) { this.form.get('location')?.setValue(address.display_name); this.form.get('complete_address')?.setValue(address.display_name); this.form.get('coordinates')?.setValue([data.latitude, data.longitude]); break; } } catch (addressError) { console.error("Erreur lors de la récupération de l'adresse:", addressError); // Utiliser les coordonnées brutes en cas d'échec this.form.get('location')?.setValue(`${data.latitude}, ${data.longitude}`); this.form.get('complete_address')?.setValue(`${data.latitude}, ${data.longitude}`); this.form.get('coordinates')?.setValue([data.latitude, data.longitude]); } } } catch (error) { console.error('Erreur :', error); } } } submitForm(): void { if (this.form.valid) { const coordinates = this.form.get('coordinates')?.value; const pinData = { ...this.form.value, files: this.files, date: this.form.get('date')?.value || null, location: coordinates || this.pin.location, // Utiliser les coordonnées pour location complete_address: this.form.get('complete_address')?.value || this.form.get('location')?.value, }; // Supprimer le champ coordinates qui n'est pas dans le modèle Pin delete pinData.coordinates; // Filtrer les fichiers qui sont déjà des IDs (images existantes) const existingFiles = this.files.filter( (file) => typeof file === 'string' ); const newFiles = this.files.filter((file) => file instanceof File); // Récupérer la date et la formater correctement const dateValue = this.form.get('date')?.value; const formattedDate = dateValue ? new Date(dateValue).toISOString() : null; if (newFiles.length > 0) { // Upload des nouveaux fichiers const uploadObservables = newFiles.map((file) => this.imageService.postImage(file) ); forkJoin(uploadObservables).subscribe((responses) => { // Combiner les IDs des nouvelles images avec les IDs existants const allFileIds = [ ...existingFiles, ...responses.map((res: any) => res.id), ]; const pinData = { ...this.form.getRawValue(), files: allFileIds, user_id: this.pin.user_id, date: formattedDate, location: coordinates || this.pin.location, // Utiliser les coordonnées pour location complete_address: this.form.get('complete_address')?.value || this.form.get('location')?.value, }; this.pinService.updatePin(this.pin.id, pinData).subscribe(() => { this.mapReloadService.requestReload(); this.closePinModal(); }); }); } else { // Si pas de nouveaux fichiers, mettre à jour directement avec les fichiers existants const pinData = { ...this.form.getRawValue(), files: existingFiles, user_id: this.pin.user_id, date: formattedDate, location: coordinates || this.pin.location, // Utiliser les coordonnées pour location complete_address: this.form.get('complete_address')?.value || this.form.get('location')?.value, }; this.pinService.updatePin(this.pin.id, pinData).subscribe(() => { this.mapReloadService.requestReload(); this.closePinModal(); }); } } openPinModal() { this.modalService.openModal(this.modalId); } closePinModal() { this.modalService.closeModal(this.modalId); } removeFile(fileName: string): void { const index = this.files.findIndex((file) => { if (typeof file === 'string') { return file === fileName; } return file.name === fileName; }); if (index > -1) { this.files.splice(index, 1); this.form.patchValue({ files: this.files }); } } }