🎨 timeline page
continuous-integration/drone/push Build is passing Details

timeline
Mathis FRAMIT 1 week ago
parent 4287038e2c
commit 7189e80d56

@ -1,73 +1,120 @@
<!-- Spinner pendant le chargement --> <!-- Spinner pendant le chargement -->
<div *ngIf="loading" class="flex justify-center items-center h-64"> <div *ngIf="loading" class="flex justify-center items-center h-64">
<div <div class="animate-spin rounded-full h-16 w-16 border-4 border-blue-500 border-t-transparent"></div>
class="animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"
></div>
</div> </div>
<!-- Timeline --> <!-- Timeline principale -->
<div <div *ngIf="!loading && pins.length > 0" class="relative mx-auto max-w-7xl py-20 px-6">
*ngIf="!loading && pins.length > 0" <!-- Barre centrale -->
class="relative mx-auto max-w-3xl py-10 px-4" <div class="absolute left-1/2 transform -translate-x-1/2 h-full bg-blue-500 w-6 rounded-full z-0"></div>
>
<div
class="absolute h-full left-1/2 transform -translate-x-1/2 border-l-2 border-blue-500"
></div>
<div <!-- Groupement par années -->
*ngFor="let pin of pins; let i = index" <ng-container *ngFor="let year of sortedYears">
class="mb-12 flex justify-between items-center w-full" <!-- Marqueur d'année -->
> <div class="relative mb-24 flex justify-center items-center">
<!-- Si pair, carte à droite, point à gauche --> <div class="absolute left-1/2 transform -translate-x-1/2 h-16 w-8 bg-blue-500 z-0"></div>
<div <div class="bg-blue-600 text-white text-4xl font-bold px-10 py-5 rounded-full shadow-2xl z-10 border-4 border-white">
class="w-5/12" {{ year }}
[ngClass]="{ 'order-1': i % 2 === 0, 'order-2': i % 2 !== 0 }" </div>
></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> </div>
<!-- Content card --> <!-- Pins de l'année -->
<div <ng-container *ngFor="let pin of groupedPins[year]; let i = index">
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]" <div class="mb-32 flex flex-col sm:flex-row justify-between items-center w-full relative z-10">
[ngClass]="{ <!-- Espace vide -->
'order-3': i % 2 === 0, <div class="w-full sm:w-5/12" [ngClass]="{ 'order-1': i % 2 === 0, 'order-2': i % 2 !== 0 }"></div>
'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"> <!-- Bulle centrale avec la date -->
<img <div
[src]="imageUrls[i][0]" class="z-20 flex items-center justify-center bg-white border-[6px] border-blue-600 text-blue-700 font-bold text-lg shadow-2xl w-32 h-32 rounded-full text-center px-4 leading-tight"
alt="image" [ngClass]="{ 'order-2': i % 2 === 0, 'order-1': i % 2 !== 0 }"
class="rounded-lg mx-auto max-h-40 object-cover shadow" >
/> <span>{{ pin.date | date: 'dd/MM/yyyy' }}</span>
</ng-container> </div>
<ng-container *ngIf="!imageUrls[i] || imageUrls[i].length === 0">
<div class="text-gray-400 italic text-center">Aucune image</div> <!-- Carte de contenu -->
</ng-container> <div
</div> class="bg-white dark:bg-gray-800 rounded-3xl shadow-2xl px-10 py-8 w-full sm:w-5/12 transition-all duration-300 hover:scale-[1.02]"
</div> [ngClass]="{
'order-3 text-center sm:text-left': i % 2 === 0,
'order-0 text-center sm:text-right': i % 2 !== 0
}"
>
<!-- Titre centré -->
<h3 class="text-3xl font-extrabold text-gray-900 dark:text-white mb-4 text-center">
{{ pin.title || 'Titre inconnu' }}
</h3>
<!-- Description justifiée tronquée -->
<div
class="text-lg text-gray-700 dark:text-gray-300 mb-4 text-justify transition-all duration-300"
[ngClass]="{
'line-clamp-5 overflow-hidden': !expandedDescriptions[pins.indexOf(pin)]
}"
>
{{ pin.description || 'Aucune description' }}
</div>
<!-- Bouton "voir plus / moins" -->
<div *ngIf="pin.description.length > 200" class="text-right mb-6">
<button
class="text-blue-600 font-semibold hover:underline"
(click)="toggleDescription(pins.indexOf(pin))"
>
{{ expandedDescriptions[pins.indexOf(pin)] ? 'Voir moins' : 'Voir plus' }}
</button>
</div>
<!-- Carrousel d'images -->
<ng-container *ngIf="imageUrls[pins.indexOf(pin)].length > 0">
<div class="relative flex items-center justify-center">
<button
*ngIf="imageUrls[pins.indexOf(pin)].length > 1"
(click)="prevImage(pins.indexOf(pin))"
class="absolute left-0 bg-white border border-blue-500 text-blue-500 rounded-full w-10 h-10 shadow-md hover:bg-blue-100 z-10"
>
</button>
<img
[src]="imageUrls[pins.indexOf(pin)][carouselIndexes[pins.indexOf(pin)]]"
alt="image"
class="rounded-xl mx-auto w-full h-64 object-cover shadow-lg"
/>
<button
*ngIf="imageUrls[pins.indexOf(pin)].length > 1"
(click)="nextImage(pins.indexOf(pin))"
class="absolute right-0 bg-white border border-blue-500 text-blue-500 rounded-full w-10 h-10 shadow-md hover:bg-blue-100 z-10"
>
</button>
</div>
<!-- Indicateur de position -->
<div *ngIf="imageUrls[pins.indexOf(pin)].length > 1" class="flex justify-center mt-2 space-x-2">
<div
*ngFor="let img of imageUrls[pins.indexOf(pin)]; let j = index"
class="w-3 h-3 rounded-full"
[ngClass]="{
'bg-blue-600': j === carouselIndexes[pins.indexOf(pin)],
'bg-blue-200': j !== carouselIndexes[pins.indexOf(pin)]
}"
></div>
</div>
</ng-container>
<!-- Fallback sil ny a pas dimage -->
<ng-container *ngIf="!imageUrls[pins.indexOf(pin)] || imageUrls[pins.indexOf(pin)].length === 0">
<div class="text-gray-400 italic text-center">Aucune image</div>
</ng-container>
</div>
</div>
</ng-container>
</ng-container>
</div> </div>
<!-- Message si vide --> <!-- Message si vide -->
<div <div *ngIf="!loading && pins.length === 0" class="text-center text-gray-500 py-12 text-xl">
*ngIf="!loading && pins.length === 0"
class="text-center text-gray-500 py-12 text-lg"
>
Aucun souvenir à afficher pour le moment. Aucun souvenir à afficher pour le moment.
</div> </div>

@ -15,6 +15,10 @@ export class TimelineComponent implements OnInit {
pins: Pin[] = []; pins: Pin[] = [];
imageUrls: SafeUrl[][] = []; imageUrls: SafeUrl[][] = [];
loading = true; loading = true;
groupedPins: { [year: string]: Pin[] } = {};
sortedYears: string[] = [];
carouselIndexes: number[] = [];
expandedDescriptions: { [index: number]: boolean } = {};
constructor( constructor(
private pinService: PinService, private pinService: PinService,
@ -41,7 +45,47 @@ export class TimelineComponent implements OnInit {
} }
}); });
this.carouselIndexes = this.pins.map(() => 0);
this.loading = false; this.loading = false;
this.groupPinsByYear();
}); });
} }
private groupPinsByYear(): void {
this.groupedPins = {};
for (const pin of this.pins) {
const year = new Date(pin.date!).getFullYear().toString();
if (!this.groupedPins[year]) {
this.groupedPins[year] = [];
}
this.groupedPins[year].push(pin);
}
// Trie les pins dans chaque groupe (au cas où)
for (const year in this.groupedPins) {
this.groupedPins[year].sort((a, b) => a.date!.localeCompare(b.date!));
}
// Trie les années dans lordre croissant (utilisé dans le template)
this.sortedYears = Object.keys(this.groupedPins).sort((a, b) => +a - +b);
}
nextImage(index: number) {
const images = this.imageUrls[index];
this.carouselIndexes[index] =
(this.carouselIndexes[index] + 1) % images.length;
}
prevImage(index: number) {
const images = this.imageUrls[index];
this.carouselIndexes[index] =
(this.carouselIndexes[index] - 1 + images.length) % images.length;
}
toggleDescription(index: number): void {
this.expandedDescriptions[index] = !this.expandedDescriptions[index];
}
} }

Loading…
Cancel
Save