Add date to pin and timeline page

tutorial
Alexis Feron 2 weeks ago
parent d2e6f8d567
commit a83133a6f0

@ -3,9 +3,11 @@ import { AuthGuard } from './auth.guard';
import { HomePageComponent } from './components/home-page/home-page.component';
import { LeafletMapComponent } from './components/leaflet-map/leaflet-map.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { TimelineComponent } from './components/timeline/timeline.component';
export const routes: Routes = [
{ path: '', component: HomePageComponent },
{ path: 'map', component: LeafletMapComponent, canActivate: [AuthGuard] },
{ path: 'timeline', component: TimelineComponent, canActivate: [AuthGuard] },
{ path: '**', component: NotFoundComponent },
];

@ -26,12 +26,12 @@
'opacity-0 scale-50 pointer-events-none': !isPinModalOpen,
'opacity-100 scale-100': isPinModalOpen
}"
class="fixed top-0 right-0 left-0 z-50 flex justify-center items-center w-full h-full transition-transform duration-300 ease-in-out"
class="fixed top-0 right-0 left-0 z-50 flex justify-center items-center w-full h-full transition-transform duration-300 ease-in-out overflow-y-auto"
>
<div class="relative p-4 w-full max-w-xl max-h-full">
<!-- Modal content -->
<div
class="relative bg-white rounded-lg shadow dark:bg-gray-700 transition-transform duration-300 ease-in-out"
class="relative bg-white rounded-lg shadow dark:bg-gray-700 transition-transform duration-300 ease-in-out my-8"
>
<!-- Modal header -->
<div
@ -113,6 +113,11 @@
</div>
<div>
<label
for="files"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Images</label
>
<app-drag-drop
(filesSelected)="onFilesReceived($event)"
></app-drag-drop>
@ -143,25 +148,47 @@
</div>
</div> -->
<div class="flex justify-between">
<button
type="reset"
(click)="closePinModal()"
class="w-full text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"
>
Annuler
</button>
</div>
<div class="flex justify-between">
<button
type="submit"
(click)="submitForm()"
class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
<!-- <div *ngIf="files.length > 0">
<div *ngFor="let file of files">
<img
[src]="getImagePreview(file)"
alt="Image preview"
width="100"
/>
</div>
</div> -->
<div>
<label
for="date"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Date (optionnel)</label
>
Valider
</button>
<input
type="date"
id="date"
formControlName="date"
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-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
/>
</div>
</form>
<!-- Boutons alignés sous la grille -->
<div class="flex gap-4 mt-6">
<button
type="reset"
(click)="closePinModal()"
class="w-1/2 text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"
>
Annuler
</button>
<button
type="submit"
(click)="submitForm()"
class="w-1/2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Valider
</button>
</div>
</div>
</div>
</div>

@ -15,11 +15,11 @@ import {
} from 'rxjs/operators';
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';
import { ModalService } from '../../services/modal/modal.service';
import { ImageService } from '../../services/image/image.service';
@Component({
selector: 'app-add-pin-popup',
@ -51,6 +51,7 @@ export class AddPinPopupComponent implements OnInit {
description: new FormControl(''),
location: new FormControl(''),
files: new FormControl(null),
date: new FormControl(''),
});
}
@ -159,6 +160,7 @@ export class AddPinPopupComponent implements OnInit {
const pinData = {
...this.form.value,
files: this.files,
date: this.form.get('date')?.value || null,
};
console.log('Files : ' + JSON.stringify(this.files));

@ -43,12 +43,12 @@
'opacity-0 scale-50 pointer-events-none': !isPinModalOpen,
'opacity-100 scale-100': isPinModalOpen
}"
class="fixed top-0 right-0 left-0 z-50 flex justify-center items-center w-full h-full transition-transform duration-300 ease-in-out"
class="fixed top-0 right-0 left-0 z-50 flex justify-center items-center w-full h-full transition-transform duration-300 ease-in-out overflow-y-auto"
>
<div class="relative p-4 w-full max-w-xl max-h-full">
<!-- Modal content -->
<div
class="relative bg-white rounded-lg shadow dark:bg-gray-700 transition-transform duration-300 ease-in-out"
class="relative bg-white rounded-lg shadow dark:bg-gray-700 transition-transform duration-300 ease-in-out my-8"
>
<!-- Modal header -->
<div
@ -130,6 +130,11 @@
</div>
<div>
<label
for="files"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Images</label
>
<app-drag-drop
(filesSelected)="onFilesReceived($event)"
></app-drag-drop>
@ -150,25 +155,37 @@
></textarea>
</div>
<div class="flex justify-between">
<button
type="reset"
(click)="closePinModal()"
class="w-full text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"
>
Annuler
</button>
</div>
<div class="flex justify-between">
<button
type="submit"
(click)="submitForm()"
class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
<div>
<label
for="date"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Date (optionnel)</label
>
Valider
</button>
<input
type="date"
id="date"
formControlName="date"
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-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
/>
</div>
</form>
<!-- Boutons alignés sous la grille -->
<div class="flex gap-4 mt-6">
<button
type="reset"
(click)="closePinModal()"
class="w-1/2 text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"
>
Annuler
</button>
<button
type="submit"
(click)="submitForm()"
class="w-1/2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Valider
</button>
</div>
</div>
</div>
</div>

@ -66,6 +66,7 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy {
description: new FormControl(''),
location: new FormControl(''),
files: new FormControl(null),
date: new FormControl(''),
});
}
@ -85,6 +86,7 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy {
title: this.pin?.title || '',
description: this.pin?.description || '',
location: "Chargement de l'adresse...",
date: this.pin?.date || '',
});
// Vérifier si nous avons des coordonnées valides dans pin.location
@ -251,6 +253,7 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy {
...this.form.value,
files: this.files,
user_id: this.pin.user_id,
date: this.form.get('date')?.value || null,
};
this.pinService.updatePin(this.pin.id, pinData).subscribe(() => {

@ -1,48 +1,46 @@
<div class="map-container h-[calc(100vh_-_72px)]">
<div class="map-container h-[calc(100vh_-_72px)] relative">
<div
id="map"
class="h-full w-full z-0"
(drop)="onDrop($event)"
(dragover)="onDragOver($event)"
></div>
</div>
<div
class="floating-filters text-center absolute top-[86px] right-[14px] flex flex-col space-y-2 z-10 bg-white p-3 rounded-xl shadow-lg dark:bg-gray-900 dark:text-white"
>
<div
class="filters flex md:items-center flex-col md:flex-row align-top justify-start gap-4"
class="z-50 floating-filters text-center absolute top-4 right-4 flex flex-col space-y-2 bg-white p-3 rounded-xl shadow-lg dark:bg-gray-900 dark:text-white"
>
<label class="flex items-center space-x-2">
Pays :
<select
[(ngModel)]="selectedCountry"
(change)="onCountryChange(selectedCountry)"
class="bg-white dark:bg-gray-900 dark:text-white ml-2"
>
<option value="">Tous</option>
<option *ngFor="let country of availableCountries" [value]="country">
{{ country }}
</option>
</select>
</label>
<div class="hidden md:block h-5 border-l-2"></div>
<div
class="filters flex md:items-center flex-col md:flex-row align-top justify-start gap-4"
>
<label class="flex items-center space-x-2">
Pays :
<select
[(ngModel)]="selectedCountry"
(change)="onCountryChange(selectedCountry)"
class="bg-white dark:bg-gray-900 dark:text-white ml-2"
>
<option value="">Tous</option>
<option *ngFor="let country of availableCountries" [value]="country">
{{ country }}
</option>
</select>
</label>
<div class="hidden md:block h-5 border-l-2"></div>
<!-- Amis taguées -->
<label class="flex items-center space-x-2">
Amis :
<select
[(ngModel)]="selectedPerson"
(change)="onPersonChange(selectedPerson)"
class="bg-white dark:bg-gray-900 dark:text-white ml-2"
>
<option value="__all__">Tous</option>
<option value="__none__">Aucun</option>
<option *ngFor="let person of availablePersons" [value]="person">
{{ person }}
</option>
</select>
</label>
<!-- Amis taguées -->
<label class="flex items-center space-x-2">
Amis :
<select
[(ngModel)]="selectedPerson"
(change)="onPersonChange(selectedPerson)"
class="bg-white dark:bg-gray-900 dark:text-white ml-2"
>
<option value="__all__">Tous</option>
<option value="__none__">Aucun</option>
<option *ngFor="let person of availablePersons" [value]="person">
{{ person }}
</option>
</select>
</label>
</div>
</div>
</div>

@ -169,16 +169,16 @@
>
<li>
<a
routerLink="/"
*ngIf="!isHome"
routerLink="/map"
*ngIf="isTimeline"
class="block py-2 text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
>Accueil</a
>Carte</a
>
<a
routerLink="/map"
*ngIf="isHome"
routerLink="/timeline"
*ngIf="!isTimeline"
class="block py-2 text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-300"
>Carte</a
>Timeline</a
>
</li>
<li>

@ -38,7 +38,7 @@ import { FriendPageComponent } from '../friend-page/friend-page.component';
templateUrl: './navbar.component.html',
})
export class NavbarComponent implements OnInit {
isHome: boolean = false;
isTimeline: boolean = false;
isSearchOpen: boolean = false;
isNavbarOpen: boolean = false;
@ -75,10 +75,10 @@ export class NavbarComponent implements OnInit {
this.pins = pins;
});
this.isHome = this.router.url === '/';
this.isTimeline = this.router.url === '/timeline';
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
this.isHome = event.url === '/';
this.isTimeline = event.url === '/timeline';
}
});

@ -0,0 +1,73 @@
<!-- Spinner pendant le chargement -->
<div *ngIf="loading" class="flex justify-center items-center h-64">
<div
class="animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"
></div>
</div>
<!-- Timeline -->
<div
*ngIf="!loading && pins.length > 0"
class="relative mx-auto max-w-3xl py-10 px-4"
>
<div
class="absolute h-full left-1/2 transform -translate-x-1/2 border-l-2 border-blue-500"
></div>
<div
*ngFor="let pin of pins; let i = index"
class="mb-12 flex justify-between items-center w-full"
>
<!-- Si pair, carte à droite, point à gauche -->
<div
class="w-5/12"
[ngClass]="{ 'order-1': i % 2 === 0, 'order-2': i % 2 !== 0 }"
></div>
<div
class="z-20 flex items-center bg-blue-600 shadow-lg w-10 h-10 rounded-full"
[ngClass]="{ 'order-2': i % 2 === 0, 'order-1': i % 2 !== 0 }"
>
<span class="text-white font-semibold mx-auto">{{ i + 1 }}</span>
</div>
<!-- Content card -->
<div
class="bg-white dark:bg-gray-800 rounded-xl shadow-xl px-6 py-5 w-5/12 transition-all duration-300 hover:scale-[1.02]"
[ngClass]="{
'order-3': i % 2 === 0,
'order-0': i % 2 !== 0,
'text-right': i % 2 !== 0
}"
>
<div class="text-sm text-gray-400 mb-1">
{{ pin.date ? (pin.date | date : "dd/MM/yyyy") : "Date inconnue" }}
</div>
<div class="text-lg font-bold text-gray-900 dark:text-white mb-1">
{{ pin.title || "Titre inconnu" }}
</div>
<div class="text-gray-700 dark:text-gray-300 mb-3">
{{ pin.description || "Aucune description" }}
</div>
<ng-container *ngIf="imageUrls[i] && imageUrls[i].length > 0">
<img
[src]="imageUrls[i][0]"
alt="image"
class="rounded-lg mx-auto max-h-40 object-cover shadow"
/>
</ng-container>
<ng-container *ngIf="!imageUrls[i] || imageUrls[i].length === 0">
<div class="text-gray-400 italic text-center">Aucune image</div>
</ng-container>
</div>
</div>
</div>
<!-- Message si vide -->
<div
*ngIf="!loading && pins.length === 0"
class="text-center text-gray-500 py-12 text-lg"
>
Aucun souvenir à afficher pour le moment.
</div>

@ -0,0 +1,47 @@
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { Pin } from '../../model/Pin';
import { ImageService } from '../../services/image/image.service';
import { PinService } from '../../services/pin/pin.service';
@Component({
selector: 'app-timeline',
standalone: true,
imports: [CommonModule],
templateUrl: './timeline.component.html',
})
export class TimelineComponent implements OnInit {
pins: Pin[] = [];
imageUrls: SafeUrl[][] = [];
loading = true;
constructor(
private pinService: PinService,
private imageService: ImageService,
private sanitizer: DomSanitizer
) {}
ngOnInit(): void {
this.pinService.getPins().subscribe((pins: Pin[]) => {
this.pins = pins
.filter((pin) => !!pin.date)
.sort((a, b) => (a.date! > b.date! ? 1 : -1));
this.imageUrls = this.pins.map(() => []); // initialise le tableau d'images
this.pins.forEach((pin, index) => {
if (pin.files && pin.files.length > 0) {
pin.files.forEach((imageId) => {
this.imageService.getImage(imageId).subscribe((blob) => {
const objectUrl = URL.createObjectURL(blob);
const safeUrl = this.sanitizer.bypassSecurityTrustUrl(objectUrl);
this.imageUrls[index].push(safeUrl);
});
});
}
});
this.loading = false;
});
}
}

@ -5,4 +5,5 @@ export interface Pin {
files: string[];
description: string;
user_id: string;
date?: string;
}

@ -34,6 +34,7 @@ export class PinService {
description: string;
location: string;
files: string[];
date: string;
}) {
const url = `${this.apiURL}/pin/add`;
const headers = new HttpHeaders({
@ -51,6 +52,7 @@ export class PinService {
location: coords,
files: pin.files,
user_id: '',
date: pin.date,
},
{ headers }
);

Loading…
Cancel
Save