feat: ajoute une page pour afficher les spectacles passés et intègre leur grille sur la page d'accueil

This commit is contained in:
Jalil Arfaoui 2025-08-11 17:10:55 +02:00
parent 140666183d
commit 74cfe041ae
5 changed files with 343 additions and 39 deletions

View file

@ -0,0 +1,147 @@
---
const { evenements, limite = 6, afficherLienVoirTout = true } = Astro.props;
const evenementsAffiches = evenements.slice(0, limite);
---
{evenementsAffiches.length > 0 && (
<div class="spectacles-passes">
<div class="grille-affiches">
{evenementsAffiches.map(evenement => (
evenement.affiche && (
<a href={`/evenements/${evenement.slug}`} class="affiche-link">
<img
src={evenement.affiche.fields.file.url}
alt={`Affiche de ${evenement.nom}`}
class="affiche-thumb"
/>
<div class="affiche-overlay">
<span class="affiche-nom">{evenement.nom}</span>
<span class="affiche-date">{new Date(evenement.date).toLocaleDateString("fr-FR")}</span>
</div>
</a>
)
))}
</div>
{afficherLienVoirTout && evenements.length > limite && (
<a href="/evenements" class="voir-tout">Voir tous nos spectacles passés →</a>
)}
</div>
)}
<style>
.spectacles-passes {
margin: 2rem 0;
}
.grille-affiches {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.affiche-link {
position: relative;
display: block;
overflow: hidden;
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
background: white;
}
.affiche-link:hover {
transform: translateY(-5px);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.2);
}
.affiche-thumb {
width: 100%;
height: auto;
display: block;
aspect-ratio: 3/4;
object-fit: cover;
}
.affiche-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
color: white;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.affiche-nom {
font-weight: bold;
font-size: 0.9rem;
line-height: 1.2;
}
.affiche-date {
font-size: 0.8rem;
opacity: 0.9;
}
.voir-tout {
display: inline-block;
margin-top: 1rem;
padding: 0.75rem 1.5rem;
background-color: orange;
color: white;
text-decoration: none;
border: black solid 2px;
border-radius: 0.4rem;
font-weight: bold;
box-shadow: 0.2em 0.2em 0.5em rgba(0,0,0,0.3);
transition: all 0.3s ease;
}
.voir-tout:hover {
background-color: darkorange;
transform: translateX(5px);
box-shadow: 0.3em 0.3em 0.7em rgba(0,0,0,0.4);
}
@media (max-width: 768px) {
.grille-affiches {
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.affiche-overlay {
padding: 0.5rem;
}
.affiche-nom {
font-size: 0.8rem;
}
.affiche-date {
font-size: 0.7rem;
}
}
@media (max-width: 480px) {
.grille-affiches {
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
}
.affiche-overlay {
padding: 0.4rem;
}
.affiche-nom {
font-size: 0.75rem;
}
.affiche-date {
font-size: 0.65rem;
}
}
</style>

View file

@ -18,6 +18,7 @@ export interface ContentFulEvenement {
lieuUrl: EntryFieldTypes.Text, lieuUrl: EntryFieldTypes.Text,
position: EntryFieldTypes.Location, position: EntryFieldTypes.Location,
affiche: EntryFieldTypes.AssetLink, affiche: EntryFieldTypes.AssetLink,
album?: EntryFieldTypes.Text,
} }
} }
@ -31,7 +32,8 @@ export const evenementFromContentfull = ({
affiche, affiche,
lieu, lieu,
lieuUrl, lieuUrl,
position position,
album
} }
}: Entry<ContentFulEvenement>): Évènement => ({ }: Entry<ContentFulEvenement>): Évènement => ({
nom: nom as string, nom: nom as string,
@ -41,7 +43,8 @@ export const evenementFromContentfull = ({
affiche, affiche,
lieu: lieu as string | undefined, lieu: lieu as string | undefined,
lieuUrl: lieuUrl as string | undefined, lieuUrl: lieuUrl as string | undefined,
position position,
album: album as string | undefined
}) })
export const sortByDate = (evenements: Entry<ContentFulEvenement>[]): Entry<ContentFulEvenement>[] => { export const sortByDate = (evenements: Entry<ContentFulEvenement>[]): Entry<ContentFulEvenement>[] => {
@ -55,7 +58,7 @@ export const sortByDate = (evenements: Entry<ContentFulEvenement>[]): Entry<Cont
type ContentfulDate = `${number}-${number}-${number}T${number}:${number}:${number}Z` type ContentfulDate = `${number}-${number}-${number}T${number}:${number}:${number}Z`
interface Évènement { interface Évènement {
nom: string, slug: string, description: Document, date: ContentfulDate, affiche: unknown, lieu?: string | undefined, lieuUrl?: string | undefined, position: unknown nom: string, slug: string, description: Document, date: ContentfulDate, affiche: unknown, lieu?: string | undefined, lieuUrl?: string | undefined, position: unknown, album?: string | undefined
} }
function estÀVenir(évènement: Évènement): boolean { function estÀVenir(évènement: Évènement): boolean {
@ -68,6 +71,10 @@ function estÀVenir(évènement: Évènement): boolean {
return (dateEvenement.getTime() - aujourdhui.getTime() >= 0); return (dateEvenement.getTime() - aujourdhui.getTime() >= 0);
} }
function estPassé(évènement: Évènement): boolean {
return !estÀVenir(évènement);
}
export const fetchEvenements = async () => { export const fetchEvenements = async () => {
const entries = await contentfulClient.getEntries<ContentFulEvenement>({ const entries = await contentfulClient.getEntries<ContentFulEvenement>({
content_type: "evenement", content_type: "evenement",
@ -78,6 +85,17 @@ export const fetchEvenements = async () => {
.filter(estÀVenir) .filter(estÀVenir)
} }
export const fetchEvenementsPassés = async () => {
const entries = await contentfulClient.getEntries<ContentFulEvenement>({
content_type: "evenement",
});
return sortByDate(entries.items)
.map(evenementFromContentfull)
.filter(estPassé)
.reverse() // Afficher les plus récents en premier
}
export const fetchEvenement = async (slug: string) => { export const fetchEvenement = async (slug: string) => {
const entries = await contentfulClient.getEntries<ContentFulEvenement>({ const entries = await contentfulClient.getEntries<ContentFulEvenement>({
content_type: "evenement", content_type: "evenement",

View file

@ -0,0 +1,67 @@
---
import Layout from '../layouts/Layout.astro';
import { fetchEvenementsPassés } from "../lib/contentful";
import GrilleSpectaclesPassés from "../components/GrilleSpectaclesPassés.astro";
export const prerender = false
const evenementsPassés = await fetchEvenementsPassés()
---
<Layout title="Nos spectacles passés - Les Particules">
<main>
<a href="../" style="text-decoration: none; color: black; display: inline">
<img alt="Logo" src="/les-particules-bleu-sur-noir-pastille.svg" style="width:80px; vertical-align: middle; display: inline" />
</a>
<h1 style="display: inline; margin-left: 0.2em">
Nos spectacles passés
</h1>
{evenementsPassés.length > 0 ? (
<div class="dates">
<p class="intro">
Découvrez les spectacles que nous avons eu le plaisir de jouer !
</p>
<GrilleSpectaclesPassés evenements={evenementsPassés} afficherLienVoirTout={false} limite={999} />
</div>
) : (
<div class="dates">
<p class="intro">
Aucun spectacle passé pour le moment. Revenez bientôt !
</p>
</div>
)}
</main>
</Layout>
<style>
main {
margin: auto;
padding: 1.5rem;
max-width: none;
}
h1 {
font-size: 2rem;
font-weight: 700;
margin: 0.5em 0;
}
.dates {
line-height: 1.6;
margin: 1rem 0;
border: 1px solid rgba(var(--accent), 25%);
background-color: white;
padding: 1rem;
border-radius: 0.4rem;
box-shadow: 0.2em 0.2em 1em black;
}
.intro {
font-size: 1.1em;
margin-bottom: 1.5em;
color: rgb(68,68,68);
font-weight: 500;
}
</style>

View file

@ -9,22 +9,39 @@ export const prerender = false
const {slug} = Astro.params const {slug} = Astro.params
const { nom, description, date, lieu, lieuUrl, affiche } = await fetchEvenement(slug) const { nom, description, date, lieu, lieuUrl, affiche, album } = await fetchEvenement(slug)
const Wrapper = lieuUrl ? 'a' : 'div' const Wrapper = lieuUrl ? 'a' : 'div'
--- ---
<Layout> <Layout>
<h1>{nom}</h1> <h1>{nom}</h1>
<div class="content"> <div class="content">
{affiche && <img alt=`Affiche de ${nom}` src={affiche?.fields.file.url} />} {affiche && <img alt=`Affiche de ${nom}` src={affiche?.fields.file.url} />}
<Card title={date ? new Date(date as string).toLocaleDateString("fr-FR") : ""}> <div class="infos">
<div> <Card title={date ? new Date(date as string).toLocaleDateString("fr-FR") : ""}>
à <Wrapper target="_blank" href={lieuUrl}>{lieu}</Wrapper> <div>
</div> à <Wrapper target="_blank" href={lieuUrl}>{lieu}</Wrapper>
{ </div>
description && <article set:html={documentToHtmlString(description)} /> {
} description && <article set:html={documentToHtmlString(description)} />
</Card> }
</Card>
{album && (
<div class="album">
<div class="flickr-embed">
<iframe
src={`${album}/player/`}
width="100%"
height="500"
frameborder="0"
allowfullscreen
></iframe>
</div>
</div>
)}
</div>
</div> </div>
</Layout> </Layout>
@ -42,29 +59,75 @@ const Wrapper = lieuUrl ? 'a' : 'div'
</style> </style>
<style> <style>
img { .content {
float:left; display: flex;
width: 30%; gap: 2rem;
margin: 1em; align-items: flex-start;
}
.content > img {
flex: 0 0 30%;
max-width: 30%;
height: auto;
margin: 0;
object-fit: contain;
}
.infos {
flex: 1;
} }
article { article {
font-size: 1.2em; font-size: 1.2em;
} }
.album {
margin: 2rem 0;
padding: 0;
width: 100%;
max-width: 80ch; /* Même largeur max que la Card */
box-sizing: border-box;
}
.album h2 {
font-size: 1.8rem;
margin-bottom: 1rem;
color: rgb(68,68,68);
}
.flickr-embed {
text-align: center;
}
/* Styles pour rendre l'embed Flickr responsive */
.flickr-embed {
width: 100%;
}
/* Force l'iframe Flickr à être responsive */
.flickr-embed iframe {
max-width: 100% !important;
width: 100% !important;
min-height: 400px;
height: 500px;
}
.flickr-embed img {
max-width: 100% !important;
height: auto !important;
}
@media (max-width: 48em) { @media (max-width: 48em) {
.content { .content {
display: flex; flex-direction: column;
flex-direction: column-reverse; }
img { .content > img {
float:none; width: 100%;
width: 100%; border: black solid 0.1em;
border: black solid 0.1em; border-radius: 1em;
border-radius: 1em; margin: 0 auto 1em;
margin: 1em auto; box-shadow: 0.2em 0.2em 1em black;
box-shadow: 0.2em 0.2em 1em black;
}
} }
} }

View file

@ -1,13 +1,14 @@
--- ---
import Layout from '../layouts/Layout.astro'; import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro'; import Card from '../components/Card.astro';
import { fetchEvenements } from "../lib/contentful"; import { fetchEvenements, fetchEvenementsPassés } from "../lib/contentful";
import Evenement from "../components/Evenement.astro"; import Evenement from "../components/Evenement.astro";
import FlickrGallery from "../components/FlickrGallery.astro"; import GrilleSpectaclesPassés from "../components/GrilleSpectaclesPassés.astro";
export const prerender = false export const prerender = false
const evenements = await fetchEvenements() const evenements = await fetchEvenements()
const evenementsPassés = await fetchEvenementsPassés()
--- ---
@ -17,24 +18,32 @@ const evenements = await fetchEvenements()
📌 Albi, Occitanie 📌 Albi, Occitanie
</p> </p>
<h2>Retrouvez-nous prochainement</h2> {evenements.length > 0 && (
<div class="dates"> <>
<div class="evenements"> <h2>Retrouvez-nous prochainement</h2>
{evenements.map(evenement => ( <div class="dates">
<Evenement evenement={evenement} /> <div class="evenements">
))} {evenements.map(evenement => (
</div> <Evenement evenement={evenement} />
</div> ))}
</div>
</div>
</>
)}
<h2>Nos derniers spectacles</h2> {evenementsPassés.length > 0 && (
<FlickrGallery /> <>
<h2>Nos spectacles passés</h2>
<GrilleSpectaclesPassés evenements={evenementsPassés} limite={6} />
</>
)}
<ul role="list" class="link-card-grid"> <ul role="list" class="link-card-grid">
<Card <Card
title="Les Particules Fines" title="Les Particules Fines"
href="/fines" href="/fines"
> >
Cours dimpro adultes débutant·e·s Cours d'impro adultes débutant·e·s
</Card> </Card>
<Card <Card
href="./inscription-newsletter" href="./inscription-newsletter"