🚸 Improved share modal & updated user filter
continuous-integration/drone/push Build is passing Details

tutorial
Alexis Feron 6 days ago
parent 4f983285f0
commit ca7c0007fe

@ -68,7 +68,11 @@
<div class="p-4 md:p-5">
<form class="grid gap-6 mb-1 md:grid-cols-2" [formGroup]="form">
<div class="mb-4">
<label for="title" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Titre</label>
<label
for="title"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Titre</label
>
<input
type="text"
id="title"
@ -76,9 +80,16 @@
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Titre"
/>
<div *ngIf="form.get('title')?.invalid && form.get('title')?.touched" class="mt-1 text-sm text-red-600 dark:text-red-500">
<span *ngIf="form.get('title')?.errors?.['required']">Le titre est requis</span>
<span *ngIf="form.get('title')?.errors?.['minlength']">Le titre doit contenir au moins 3 caractères</span>
<div
*ngIf="form.get('title')?.invalid && form.get('title')?.touched"
class="mt-1 text-sm text-red-600 dark:text-red-500"
>
<span *ngIf="form.get('title')?.errors?.['required']"
>Le titre est requis</span
>
<span *ngIf="form.get('title')?.errors?.['minlength']"
>Le titre doit contenir au moins 3 caractères</span
>
</div>
</div>
@ -97,8 +108,15 @@
(focus)="onFocus()"
(blur)="onBlur()"
/>
<div *ngIf="form.get('location')?.invalid && form.get('location')?.touched" class="mt-1 text-sm text-red-600 dark:text-red-500">
<span *ngIf="form.get('location')?.errors?.['required']">La localisation est requise</span>
<div
*ngIf="
form.get('location')?.invalid && form.get('location')?.touched
"
class="mt-1 text-sm text-red-600 dark:text-red-500"
>
<span *ngIf="form.get('location')?.errors?.['required']"
>La localisation est requise</span
>
</div>
<ul
*ngIf="suggestions.length > 0 && inputFocused"
@ -130,7 +148,11 @@
</div>
<div class="mb-4">
<label for="description" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Description</label>
<label
for="description"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Description</label
>
<textarea
id="description"
rows="4"
@ -138,9 +160,19 @@
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Description"
></textarea>
<div *ngIf="form.get('description')?.invalid && form.get('description')?.touched" class="mt-1 text-sm text-red-600 dark:text-red-500">
<span *ngIf="form.get('description')?.errors?.['required']">La description est requise</span>
<span *ngIf="form.get('description')?.errors?.['minlength']">La description doit contenir au moins 3 caractères</span>
<div
*ngIf="
form.get('description')?.invalid &&
form.get('description')?.touched
"
class="mt-1 text-sm text-red-600 dark:text-red-500"
>
<span *ngIf="form.get('description')?.errors?.['required']"
>La description est requise</span
>
<span *ngIf="form.get('description')?.errors?.['minlength']"
>La description doit contenir au moins 3 caractères</span
>
</div>
</div>

@ -10,11 +10,11 @@ import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import * as L from 'leaflet';
import { Pin } from '../../model/Pin';
import { AutocompleteService } from '../../services/auto-complete/auto-complete.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 { PinMarkerComponent } from '../pin-marker/pin-marker.component';
import { AutocompleteService } from '../../services/auto-complete/auto-complete.service';
@Component({
selector: 'app-leaflet-map',
@ -112,10 +112,12 @@ export class LeafletMapComponent implements OnInit {
// Créer le contenu du menu contextuel
const menuContent = document.createElement('div');
menuContent.className = 'bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden';
menuContent.className =
'bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden';
const addPinButton = document.createElement('button');
addPinButton.className = 'w-full px-4 py-2.5 text-sm font-medium text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-600 flex items-center gap-2';
addPinButton.className =
'w-full px-4 py-2.5 text-sm font-medium text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-600 flex items-center gap-2';
// Ajouter l'icône de pin
const pinIcon = document.createElement('span');
@ -147,7 +149,7 @@ export class LeafletMapComponent implements OnInit {
closeButton: false,
className: 'context-menu-popup',
maxWidth: 200,
offset: [0, -10]
offset: [0, -10],
})
.setLatLng(latlng)
.setContent(menuContent)
@ -165,15 +167,15 @@ export class LeafletMapComponent implements OnInit {
this.modalService.openModal('add-pin-modal', [], {
location: address?.display_name || '',
complete_address: address?.display_name || '',
coordinates: [latlng.lat, latlng.lng]
coordinates: [latlng.lat, latlng.lng],
});
} catch (error) {
console.error('Erreur lors de la récupération de l\'adresse:', error);
console.error("Erreur lors de la récupération de l'adresse:", error);
// En cas d'erreur, ouvrir la modal avec juste les coordonnées
this.modalService.openModal('add-pin-modal', [], {
location: `${latlng.lat}, ${latlng.lng}`,
complete_address: `${latlng.lat}, ${latlng.lng}`,
coordinates: [latlng.lat, latlng.lng]
coordinates: [latlng.lat, latlng.lng],
});
}
}
@ -201,16 +203,19 @@ export class LeafletMapComponent implements OnInit {
private extractPersons(pins: Pin[]): void {
const personsSet = new Set<string>();
const regex = /@(\w+)/g;
// Pour chaque pin, récupérer ses partages
pins.forEach((pin) => {
const desc = pin.description || '';
let match;
while ((match = regex.exec(desc)) !== null) {
personsSet.add(match[1]);
}
this.pinsService.getPinShares(pin.id).subscribe((response: any) => {
if (response && response.shares) {
response.shares.forEach((share: any) => {
personsSet.add(share.username);
});
this.availablePersons = Array.from(personsSet).sort();
}
});
});
}
onCountryChange(country: string) {
this.selectedCountry = country;
@ -311,7 +316,9 @@ export class LeafletMapComponent implements OnInit {
public loadPins(): void {
this.pinsService.getPins().subscribe((pins: Pin[]) => {
// Supprimer du body toutes les divs confirm-modal-* / share-modal-* / edit-pin-popup-*
const modals = document.querySelectorAll('div[id^="confirm-modal-"], div[id^="share-modal-"], div[id^="edit-pin-popup-"]');
const modals = document.querySelectorAll(
'div[id^="confirm-modal-"], div[id^="share-modal-"], div[id^="edit-pin-popup-"]'
);
modals.forEach((modal) => {
modal.remove();
});

@ -1,4 +1,4 @@
<div id="share-modal-{{pinId}}">
<div id="share-modal-{{ pinId }}">
<!-- Fond assombri -->
<div
class="fixed inset-0 bg-gray-900 bg-opacity-50 w-full h-full transition-opacity duration-300 ease-in-out z-40"
@ -7,12 +7,12 @@
'opacity-100': isShareModalOpen
}"
(click)="closeShareModal()"
id="share-modal-background-{{pinId}}"
id="share-modal-background-{{ pinId }}"
></div>
<!-- Main modal -->
<div
id="share-modal-{{pinId}}"
id="share-modal-{{ pinId }}"
tabindex="-1"
aria-hidden="true"
[ngClass]="{
@ -87,11 +87,12 @@
}}</span>
</div>
<button
*ngIf="!user.isShared"
(click)="sharePin(user.friend_user_id)"
class="p-2 bg-green-500 text-white rounded-full"
class="p-2 bg-green-500 text-white rounded-full hover:bg-green-600 transition-colors"
>
<svg
class="w-6 h-6 text-gray-800 dark:text-white"
class="w-6 h-6 text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
@ -107,6 +108,26 @@
/>
</svg>
</button>
<div
*ngIf="user.isShared"
class="p-2 bg-gray-200 text-gray-600 rounded-full flex items-center"
>
<svg
class="w-6 h-6"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
/>
</svg>
</div>
</div>
</div>
</div>

@ -1,7 +1,12 @@
import { CommonModule } from '@angular/common';
import { Component, Input, EventEmitter, OnDestroy, OnInit } from '@angular/core';
import {
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { Subject, Subscription } from 'rxjs';
import { FriendsService } from '../../services/friends/friends.service';
import { ModalService } from '../../services/modal/modal.service';
@ -25,6 +30,7 @@ export class ShareModalComponent implements OnInit, OnDestroy {
searchTermChanged = new Subject<string>();
listUser: any[] = [];
listFriend: any[] = [];
pinShares: any[] = [];
@Input() pinId!: string;
@Input() pinOpened!: EventEmitter<void>;
@ -32,7 +38,7 @@ export class ShareModalComponent implements OnInit, OnDestroy {
constructor(
private modalService: ModalService,
private friendService: FriendsService,
private pinService: PinService,
private pinService: PinService
) {}
ngOnInit() {
@ -84,22 +90,33 @@ export class ShareModalComponent implements OnInit, OnDestroy {
}
protected getFriend() {
// Récupérer d'abord les partages du pin
this.pinService.getPinShares(this.pinId).subscribe((response: any) => {
this.pinShares = response.shares || [];
// Ensuite récupérer les amis
this.friendService.getFriend().subscribe((friends: any[]) => {
this.listFriend = [];
this.listUser = [];
// Récupérer les détails de chaque ami
friends.forEach((friend) => {
if (friend.status === 'accepted') {
this.friendService
.getFriendById(friend.friend_user_id)
.subscribe((userDetails: any) => {
const friendWithDetails = {
...friend,
username: userDetails.username,
isShared: this.pinShares.some(
(share) => share.user_id === friend.friend_user_id
),
};
this.listFriend.push(friendWithDetails);
this.listUser.push(friendWithDetails);
});
}
});
});
});
}

@ -1,11 +1,8 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { switchMap } from 'rxjs';
import { environment } from '../../../environment';
import { Pin } from '../../model/Pin';
import { AutocompleteService } from '../auto-complete/auto-complete.service';
import { AuthService } from '../auth/auth.service';
// import { ImageService } from '../image/image.service';
@Injectable({
providedIn: 'root',
@ -16,11 +13,7 @@ export class PinService {
private apiURL = environment.apiURL;
constructor(
private http: HttpClient,
private autoCompleteService: AutocompleteService,
private authService: AuthService
) {}
constructor(private http: HttpClient, private authService: AuthService) {}
getPins(): any {
const url = `${this.apiURL}/pins`;
@ -35,24 +28,16 @@ export class PinService {
const headers = this.authService.getAuthHeaders();
headers.set('Content-Type', 'application/json');
return this.http.post<any>(
url, pin, { headers }
);
return this.http.post<any>(url, pin, { headers });
}
updatePin(
id: string,
pin: Pin
) {
updatePin(id: string, pin: Pin) {
const url = `${this.apiURL}/pin/${id}`;
const headers = this.authService.getAuthHeaders();
headers.set('Content-Type', 'application/json');
// Obtenir les coordonnées GPS à partir de l'adresse
return this.http.patch<any>(
url, pin, { headers }
);
return this.http.patch<any>(url, pin, { headers });
}
deletePin(id: string) {
@ -70,4 +55,13 @@ export class PinService {
return this.http.post<any>(url, { friend_id: friendId }, { headers });
}
getPinShares(pinId: string) {
const url = `${this.apiURL}/pin/${pinId}/shares`;
const headers = new HttpHeaders({
'Content-Type': 'application/json',
Authorization: 'Bearer ' + localStorage.getItem('auth_token'),
});
return this.http.get<any>(url, { headers });
}
}

Loading…
Cancel
Save