feat(ReserverController): Ajoute les routes et vues pour le catalogue.

 feat(templates): Ajoute template catalogue et modifie base et contact.

 feat(assets): Ajoute loader turbo, filtre catalogue et améliore JS.
```
This commit is contained in:
Serreau Jovann
2026-01-20 13:51:23 +01:00
parent 211e61bd0e
commit d59dc240f9
6 changed files with 256 additions and 13 deletions

View File

@@ -1,27 +1,55 @@
import './reserve.scss';
import * as Sentry from "@sentry/browser";
import * as Turbo from "@hotwired/turbo"
// --- INITIALISATION SENTRY ---
Sentry.init({
dsn: "https://803814be6540031b1c37bf92ba9c0f79@sentry.esy-web.dev/24",
tunnel: "/sentry-tunnel",
sendDefaultPii: true,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration()
],
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 1.0,
tracePropagationTargets: ["localhost", "esy-web.dev", "revervation.ludikevent.fr"],
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0
});
// --- LOGIQUE DU LOADER TURBO ---
const initLoader = () => {
let loaderEl = document.getElementById('turbo-loader');
if (!loaderEl) {
loaderEl = document.createElement('div');
loaderEl.id = 'turbo-loader';
loaderEl.className = 'fixed inset-0 z-[9999] flex items-center justify-center bg-white transition-opacity duration-300 opacity-0 pointer-events-none';
loaderEl.innerHTML = `
<div class="relative flex items-center justify-center">
<div class="absolute w-24 h-24 border-4 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
<img src="/provider/images/favicon.png" class="w-12 h-12 relative z-10 animate-pulse" alt="Logo">
</div>
`;
document.body.appendChild(loaderEl);
}
const showLoader = () => {
loaderEl.classList.remove('opacity-0', 'pointer-events-none');
loaderEl.classList.add('opacity-100');
};
const hideLoader = () => {
setTimeout(() => {
loaderEl.classList.remove('opacity-100');
loaderEl.classList.add('opacity-0', 'pointer-events-none');
}, 300);
};
// --- ÉVÉNEMENTS TURBO (SANS BOUCLE INFINIE) ---
document.addEventListener("turbo:click", showLoader);
document.addEventListener("turbo:submit-start", showLoader);
document.addEventListener("turbo:load", hideLoader);
document.addEventListener("turbo:render", hideLoader);
};
// --- LOGIQUE DU MENU MOBILE ---
const initMobileMenu = () => {
const btn = document.getElementById('menu-button');
const menu = document.getElementById('mobile-menu');
if (btn && menu) {
btn.onclick = () => {
const isExpanded = btn.getAttribute('aria-expanded') === 'true';
@@ -31,11 +59,59 @@ const initMobileMenu = () => {
}
};
// --- LOGIQUE FILTRE CATALOGUE ---
const initCatalogueSearch = () => {
const filters = document.querySelectorAll('.filter-btn');
const products = document.querySelectorAll('.product-item');
const emptyMsg = document.getElementById('empty-msg');
if (!filters.length) return;
filters.forEach(btn => {
btn.onclick = () => {
const category = btn.getAttribute('data-filter').toLowerCase();
let count = 0;
// UI boutons
filters.forEach(f => {
f.classList.remove('bg-slate-900', 'text-white');
f.classList.add('bg-white', 'text-slate-500');
});
btn.classList.add('bg-slate-900', 'text-white');
btn.classList.remove('bg-white', 'text-slate-500');
// Filtrage
products.forEach(item => {
const itemCat = item.getAttribute('data-category').toLowerCase();
if (category === 'all' || itemCat.includes(category)) {
item.style.display = 'block';
count++;
} else {
item.style.display = 'none';
}
});
if (emptyMsg) {
count === 0 ? emptyMsg.classList.remove('hidden') : emptyMsg.classList.add('hidden');
}
};
});
};
// --- INITIALISATION ---
document.addEventListener('DOMContentLoaded', () => {
initLoader();
initMobileMenu();
initCatalogueSearch();
});
document.addEventListener('turbo:load', () => {
initMobileMenu();
initCatalogueSearch();
});
// Nettoyage avant cache pour éviter les bugs au retour arrière
document.addEventListener("turbo:before-cache", () => {
document.querySelectorAll('.product-item').forEach(i => i.style.display = 'block');
if (document.getElementById('empty-msg')) document.getElementById('empty-msg').classList.add('hidden');
});

View File

@@ -53,6 +53,20 @@ class ReserverController extends AbstractController
return $this->render('revervation/home.twig',[
'products' => $products
]);
}
#[Route('/reservation/catalogue', name: 'reservation_catalogue')]
public function revervationCatalogue(ProductRepository $productRepository): Response
{
return $this->render('revervation/catalogue.twig',[
'products' => $productRepository->findAll(),
]);
}
#[Route('/reservation/produit/{id}', name: 'reservation_product_show')]
public function revervationShowProduct(ProductRepository $productRepository): Response
{
}
#[Route('/reservation/contact', name: 'reservation_contact')]
public function revervationContact(Request $request, Mailer $mailer): Response

View File

@@ -108,7 +108,7 @@
{# Menu Desktop #}
<div class="hidden md:flex items-center space-x-8">
<a href="{{ path('reservation') }}" class="text-gray-700 hover:text-blue-600 font-medium transition-colors">Accueil</a>
<a href="{{ path('reservation_contact') }}" class="text-gray-700 hover:text-blue-600 font-medium transition-colors">Catalogue</a>
<a href="{{ path('reservation_catalogue') }}" class="text-gray-700 hover:text-blue-600 font-medium transition-colors">Catalogue</a>
<a href="{{ path('reservation_contact') }}" class="text-gray-700 hover:text-blue-600 font-medium transition-colors">Contact</a>
<a href="{{ path('reservation_search') }}" class="p-2 text-gray-500 hover:text-blue-600 transition-colors" aria-label="Rechercher">

View File

@@ -0,0 +1,140 @@
{% extends 'revervation/base.twig' %}
{% block title %}Catalogue Ludikevent - Location de structures gonflables{% endblock %}
{% block breadcrumb_json %}
,{
"@type": "ListItem",
"position": 1,
"name": "Catalogue",
"item": "{{ path('reservation_catalogue') }}"
}
{% endblock %}
{% block body %}
<div class="min-h-screen bg-gray-50/50 font-sans antialiased pb-20">
{# --- HEADER --- #}
<div class="max-w-[1600px] mx-auto pt-16 pb-8 px-4 text-center">
<nav class="flex justify-center space-x-4 text-[10px] mb-8 uppercase tracking-[0.3em] font-black italic">
<a href="{{ url('reservation') }}" class="text-slate-400 hover:text-blue-600 transition">ACCUEIL</a>
<span class="text-slate-300">/</span>
<span class="text-amber-500 underline decoration-2 underline-offset-4">Catalogue</span>
</nav>
<h1 class="text-5xl md:text-7xl font-black text-slate-900 uppercase tracking-tighter italic leading-none mb-6">
Tout notre <span class="text-blue-600">Univers</span>
</h1>
</div>
{# --- BARRE DE RECHERCHE / FILTRES (STICKY) --- #}
<div class="sticky top-0 z-40 bg-gray-50/90 backdrop-blur-md border-b border-slate-200 mb-12">
<div class="max-w-[1600px] mx-auto px-4 py-4">
<div class="flex flex-wrap justify-center gap-2 md:gap-3">
{# Bouton Tout voir #}
<button data-filter="all" class="filter-btn px-5 py-2.5 rounded-xl font-black italic text-[9px] tracking-widest transition-all uppercase shadow-sm bg-slate-900 text-white border border-slate-900">
Tout voir
</button>
{# Liste des catégories en dur #}
{% set categories_list = [
{'id': '3-15 ans', 'label': '3-15 ANS', 'hover': 'hover:border-blue-600 hover:text-blue-600'},
{'id': '2-7 ans', 'label': '2-7 ANS', 'hover': 'hover:border-amber-500 hover:text-amber-500'},
{'id': '3-99 ans', 'label': '3-99 ANS', 'hover': 'hover:border-indigo-600 hover:text-indigo-600'},
{'id': 'barnums', 'label': 'BARNUMS', 'hover': 'hover:border-slate-800 hover:text-slate-800'},
{'id': 'alimentaire', 'label': 'ALIMENTAIRE', 'hover': 'hover:border-rose-500 hover:text-rose-500'},
{'id': 'options', 'label': 'OPTIONS', 'hover': 'hover:border-emerald-500 hover:text-emerald-500'}
] %}
{% for cat in categories_list %}
<button data-filter="{{ cat.id }}"
class="filter-btn px-5 py-2.5 rounded-xl font-black italic text-[9px] tracking-widest transition-all uppercase bg-white text-slate-400 border border-slate-200 {{ cat.hover }}">
{{ cat.label }}
</button>
{% endfor %}
</div>
</div>
</div>
{# --- GRILLE DE PRODUITS (5 COLONNES) --- #}
<div class="max-w-[1600px] mx-auto px-4">
<div id="product-grid" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-x-6 gap-y-12">
{% for product in products %}
<div class="product-item group transition-all duration-500" data-category="{{ product.category|lower }}">
<a href="{{ path('reservation_product_show', {id: product.id}) }}" class="block">
{# Image avec arrondi et badge prix #}
<div class="relative overflow-hidden rounded-[2.5rem] bg-slate-100 aspect-square mb-4 shadow-sm group-hover:shadow-2xl transition-all duration-700">
{% if product.imageName %}
<img src="{{ vich_uploader_asset(product,'imageFile') | imagine_filter('webp') }}"
alt="{{ product.name }}"
class="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-1000">
{% else %}
<div class="w-full h-full flex items-center justify-center">
<img src="{{ asset('provider/images/favicon.png') }}" class="w-10 h-10 opacity-10">
</div>
{% endif %}
{# Badge Prix #}
<div class="absolute top-3 right-3 bg-white/95 backdrop-blur-md px-3 py-1.5 rounded-xl shadow-sm border border-slate-100">
<p class="text-slate-900 font-black text-[12px] italic leading-none">
{{ product.priceDay }}
</p>
</div>
</div>
{# Informations produit #}
<div class="px-2">
<div class="flex items-center gap-2 mb-1">
<span class="text-[7px] font-black text-blue-600 uppercase tracking-widest italic">
{{ product.category }}
</span>
<span class="text-[7px] font-bold text-slate-300 uppercase tracking-tighter">
REF. {{ product.ref }}
</span>
</div>
<h3 class="text-sm font-black text-slate-900 uppercase italic tracking-tighter leading-tight group-hover:text-blue-600 transition-colors line-clamp-2">
{{ product.name }}
</h3>
<div class="mt-3 flex items-center gap-1 text-[8px] font-black uppercase text-slate-400 group-hover:text-blue-600 transition-colors">
<span>Voir le produit</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 transform group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</div>
</div>
</a>
</div>
{% endfor %}
{# Message si catégorie vide #}
<div id="empty-msg" class="hidden col-span-full py-24 text-center bg-white rounded-[3.5rem] border-2 border-dashed border-slate-100">
<div class="text-4xl mb-4">🎈</div>
<p class="text-slate-400 font-black italic uppercase tracking-widest text-xs">
Arrive bientôt dans cette catégorie...
</p>
</div>
</div>
</div>
{# --- CTA FINAL --- #}
<div class="max-w-[1600px] mx-auto px-4 mt-32">
<div class="bg-slate-900 rounded-[3.5rem] p-12 text-center relative overflow-hidden shadow-2xl">
<div class="relative z-10">
<h2 class="text-3xl md:text-5xl font-black text-white uppercase italic tracking-tighter">Vous ne trouvez pas <span class="text-blue-500">votre bonheur ?</span></h2>
<p class="text-slate-400 mt-4 max-w-xl mx-auto font-medium italic text-sm">Contactez Lilian directement pour une demande sur mesure ou des conseils personnalisés.</p>
<div class="mt-8">
<a href="{{ path('reservation_contact') }}" class="inline-block px-10 py-5 bg-blue-600 text-white rounded-2xl font-black uppercase tracking-widest hover:bg-white hover:text-slate-900 transition-all shadow-xl">
Nous appeler ⚡
</a>
</div>
</div>
<div class="absolute -right-20 -bottom-20 w-80 h-80 bg-blue-600/10 rounded-full blur-3xl"></div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,12 +1,25 @@
{% extends 'revervation/base.twig' %}
{% block title %}Contactez l'expert - Ludik Event{% endblock %}
{% block breadcrumb_json %}
,{
"@type": "ListItem",
"position": 1,
"name": "Nous contact",
"item": "{{ path('reservation_contact') }}"
}
{% endblock %}
{% block body %}
<div class="min-h-screen bg-gray-50/50 font-sans antialiased pb-20">
{# --- HEADER --- #}
<div class="max-w-6xl mx-auto pt-16 pb-12 px-4">
<nav class="flex justify-center space-x-2 text-xs mb-8 uppercase tracking-[0.2em] font-black">
<a href="{{ url('reservation') }}" class="text-slate-900 hover:text-indigo-600 transition">ACCUEIL</a>
<span class="text-slate-300">/</span>
<span class="text-amber-500">Contact</span>
</nav>
<div class="flex flex-col md:flex-row items-end justify-between gap-6 border-b border-slate-200 pb-8">
<div>
<h1 class="text-5xl md:text-7xl font-black text-slate-900 uppercase tracking-tighter italic leading-none">

View File

@@ -18,7 +18,7 @@
Ludik Event simplifie vos événements. Des structures certifiées, une livraison rapide et des souvenirs inoubliables pour les petits et les grands.
</p>
<div class="mt-10 flex flex-col sm:flex-row gap-4 sm:justify-center lg:justify-start">
<a href="#catalogue" class="px-8 py-4 bg-blue-600 text-white rounded-2xl font-bold text-lg shadow-xl shadow-blue-200 hover:bg-blue-700 transition-all hover:-translate-y-1 text-center uppercase tracking-tighter">
<a href="{{ path('reservation_catalogue') }}" class="px-8 py-4 bg-blue-600 text-white rounded-2xl font-bold text-lg shadow-xl shadow-blue-200 hover:bg-blue-700 transition-all hover:-translate-y-1 text-center uppercase tracking-tighter">
Voir le catalogue
</a>
<a href="tel:0614172447" class="px-8 py-4 bg-white text-gray-700 border border-gray-200 rounded-2xl font-bold text-lg hover:bg-gray-50 transition-all text-center uppercase tracking-tighter">