🎨 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 -->
<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 class="animate-spin rounded-full h-16 w-16 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>
<!-- Timeline principale -->
<div *ngIf="!loading && pins.length > 0" class="relative mx-auto max-w-7xl py-20 px-6">
<!-- Barre centrale -->
<div class="absolute left-1/2 transform -translate-x-1/2 h-full bg-blue-500 w-6 rounded-full z-0"></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>
<!-- Groupement par années -->
<ng-container *ngFor="let year of sortedYears">
<!-- Marqueur d'année -->
<div class="relative mb-24 flex justify-center items-center">
<div class="absolute left-1/2 transform -translate-x-1/2 h-16 w-8 bg-blue-500 z-0"></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">
{{ year }}
</div>
</div>
<!-- Pins de l'année -->
<ng-container *ngFor="let pin of groupedPins[year]; let i = index">
<div class="mb-32 flex flex-col sm:flex-row justify-between items-center w-full relative z-10">
<!-- Espace vide -->
<div class="w-full sm:w-5/12" [ngClass]="{ 'order-1': i % 2 === 0, 'order-2': i % 2 !== 0 }"></div>
<!-- Bulle centrale avec la date -->
<div
class="z-20 flex items-center bg-blue-600 shadow-lg w-10 h-10 rounded-full"
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"
[ngClass]="{ 'order-2': i % 2 === 0, 'order-1': i % 2 !== 0 }"
>
<span class="text-white font-semibold mx-auto">{{ i + 1 }}</span>
<span>{{ pin.date | date: 'dd/MM/yyyy' }}</span>
</div>
<!-- Content card -->
<!-- Carte de contenu -->
<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]"
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]"
[ngClass]="{
'order-3': i % 2 === 0,
'order-0': i % 2 !== 0,
'text-right': i % 2 !== 0
'order-3 text-center sm:text-left': i % 2 === 0,
'order-0 text-center sm: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" }}
<!-- 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>
<div class="text-gray-700 dark:text-gray-300 mb-3">
{{ pin.description || "Aucune description" }}
<!-- 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>
<ng-container *ngIf="imageUrls[i] && imageUrls[i].length > 0">
<!-- 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[i][0]"
[src]="imageUrls[pins.indexOf(pin)][carouselIndexes[pins.indexOf(pin)]]"
alt="image"
class="rounded-lg mx-auto max-h-40 object-cover shadow"
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>
<ng-container *ngIf="!imageUrls[i] || imageUrls[i].length === 0">
<!-- 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>
<!-- Message si vide -->
<div
*ngIf="!loading && pins.length === 0"
class="text-center text-gray-500 py-12 text-lg"
>
<div *ngIf="!loading && pins.length === 0" class="text-center text-gray-500 py-12 text-xl">
Aucun souvenir à afficher pour le moment.
</div>

@ -15,6 +15,10 @@ export class TimelineComponent implements OnInit {
pins: Pin[] = [];
imageUrls: SafeUrl[][] = [];
loading = true;
groupedPins: { [year: string]: Pin[] } = {};
sortedYears: string[] = [];
carouselIndexes: number[] = [];
expandedDescriptions: { [index: number]: boolean } = {};
constructor(
private pinService: PinService,
@ -41,7 +45,47 @@ export class TimelineComponent implements OnInit {
}
});
this.carouselIndexes = this.pins.map(() => 0);
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