Création de la gestion des catégorie

This commit is contained in:
Serreau Jovann
2026-02-13 16:20:51 +01:00
parent 41b5af9092
commit fcfcc1e219
15 changed files with 348 additions and 188 deletions

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260213134522 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE category (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id))');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE category');
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace App\Controller\Dashboard;
use App\Entity\Category;
use App\Entity\Customer;
use App\Entity\CustomerAddress;
use App\Form\CategoryType;
use App\Form\CustomerAddAddressType;
use App\Form\CustomerAddType;
use App\Form\CustomerType;
use App\Logger\AppLogger;
use App\Repository\CategoryRepository;
use App\Repository\ContratsRepository;
use App\Repository\CustomerAddressRepository;
use App\Repository\CustomerRepository;
use App\Repository\DevisRepository;
use App\Service\Stripe\Client as StripeClient;
use Doctrine\ORM\EntityManagerInterface;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/crm/category')]
class CategoryController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly AppLogger $appLogger,
private readonly EventDispatcherInterface $eventDispatcher
) {
}
#[Route('', name: 'app_crm_category', options: ['sitemap' => false], methods: ['GET'])]
public function index(
CategoryRepository $categoryRepository,
PaginatorInterface $paginator,
Request $request
): Response {
$this->appLogger->record('VIEW', 'Consultation de la page de catégorie');
foreach (['2-7 Ans','3-15 Ans','3-99 Ans','Barnums','Alimentaire','Options'] as $catName) {
$cat = $categoryRepository->findOneBy(['name'=>$catName]);
if(!$cat instanceof Category) {
$cat = new Category();
$cat->setName($catName);
$this->em->persist($cat);
}
$this->em->flush();
}
return $this->render('dashboard/category/list.twig', [
'categorys' => $categoryRepository->findAll(),
]);
}
#[Route('/add', name: 'app_crm_category_add', options: ['sitemap' => false], methods: ['GET', 'POST'])]
public function add(Request $request, CategoryRepository $categoryRepository): Response
{
$this->appLogger->record('VIEW', 'Consultation de la page de création catégorie');
$category = new Category();
$form = $this->createForm(CategoryType::class, $category);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$this->appLogger->record('VIEW', 'Création d\'une catégorie');
$this->em->persist($category);
$this->em->flush();
return $this->redirectToRoute('app_crm_category');
}
return $this->render('dashboard/category/add.twig', [
'form' => $form->createView(),
]);
}
#[Route('/delete/{id}', name: 'app_crm_category_delete', options: ['sitemap' => false], methods: ['GET'])]
public function delete(
?Category $category,
PaginatorInterface $paginator,
Request $request
): Response {
$this->appLogger->record('VIEW', 'Suppression de la catégorie '.$category->getName());
$this->em->remove($category);
$this->em->flush();
return $this->redirectToRoute('app_crm_category');
}
}

View File

@@ -9,6 +9,7 @@ use App\Entity\Product;
use App\Entity\ProductReserve;
use App\Entity\Promotion;
use App\Entity\SitePerformance;
use App\Repository\CategoryRepository;
use App\Repository\CustomerRepository;
use App\Repository\CustomerTrackingRepository;
use App\Repository\FormulesRepository;
@@ -848,10 +849,11 @@ class ReserverController extends AbstractController
}
#[Route('/catalogue', name: 'reservation_catalogue')]
public function revervationCatalogue(ProductRepository $productRepository): Response
public function revervationCatalogue(CategoryRepository $categoryRepository,ProductRepository $productRepository): Response
{
return $this->render('revervation/catalogue.twig', [
'products' => $productRepository->findBy(['isPublish' => true]),
'categories' => $categoryRepository->findBy([],['id' => 'ASC']),
'tvaEnabled' => $this->isTvaEnabled(),
]);
}
@@ -883,11 +885,6 @@ class ReserverController extends AbstractController
]);
}
#[Route('/comment-reserver', name: 'reservation_workflow')]
public function revervationWorkfkow(): Response
{
return $this->render('revervation/workflow.twig');
}
#[Route('/options/{id}', name: 'reservation_options_show')]
public function revervationShowOpitons(string $id, ProductRepository $productRepository): Response

35
src/Entity/Category.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
namespace App\Entity;
use App\Repository\CategoryRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CategoryRepository::class)]
class Category
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
}

33
src/Form/CategoryType.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
namespace App\Form;
use App\Entity\Category;
use App\Entity\Customer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, [
'label' => 'Nom de la categorie',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Category::class,
]);
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Form;
use App\Entity\Customer;
use App\Entity\Product;
use App\Repository\CategoryRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
@@ -17,8 +18,18 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class ProductType extends AbstractType
{
public function __construct(private readonly CategoryRepository $categoryRepository)
{
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$categoryList = [];
foreach ($this->categoryRepository->findAll() as $category) {
$categoryList[$category->getName()] = $category->getName();
}
$builder
->add('name',TextType::class,[
'label' => 'Nom du produit',
@@ -35,14 +46,7 @@ class ProductType extends AbstractType
->add('category',ChoiceType::class,[
'label' => 'Catégorie du produit',
'required' => true,
'choices' => [
'2-7 Ans' =>'2-7 ans',
'3-15 Ans' =>'3-15 ans',
'3-99 Ans' =>'3-99 ans',
'Barnums' =>'barnums',
'Alimentaire' =>'alimentaire',
'Options' =>'options',
]
'choices' => $categoryList,
])
->add('caution',NumberType::class,[
'label' => 'Caution du produit',

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\Category;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Category>
*/
class CategoryRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Category::class);
}
// /**
// * @return Category[] Returns an array of Category objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('c.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Category
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@@ -52,7 +52,7 @@ class RedirecListener
if ($pathInfo === "/") {
if ($host === "prestataire.ludikevent.fr") {
$redirect = new RedirectResponse("https://etl.ludikevent.fr/etl");
$redirect = new RedirectResponse("https://prestataire.ludikevent.fr/etl");
$event->setResponse($redirect);
$event->stopPropagation();
return;

View File

@@ -103,9 +103,7 @@ class SiteMapListener implements EventSubscriberInterface
$catUrl = new UrlConcrete($reservationUrl, $t, UrlConcrete::CHANGEFREQ_DAILY, 0.7);
$urlContainer->addUrl($catUrl, 'reservation');
$reservationUrl = $urlGenerator->generate('reservation_workflow', [], UrlGeneratorInterface::ABSOLUTE_URL);
$workFlow = new UrlConcrete($reservationUrl, $t, UrlConcrete::CHANGEFREQ_MONTHLY, 0.5);
$urlContainer->addUrl($workFlow, 'reservation');
foreach ($this->formulesRepository->findBy(['isPublish'=>true]) as $formule) {
$formulesUrls = $urlGenerator->generate(

View File

@@ -49,6 +49,7 @@
{{ menu.nav_link(path('app_crm_reservation'), 'Planing de réservation', '<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />', 'app_crm_reservation') }}
{{ menu.nav_link(path('app_template_point_controle_index'), 'Modèles de contrôle', '<path stroke-linecap="round" stroke-linejoin="round" d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z" />', 'app_template_point_controle_index') }}
{{ menu.nav_link(path('app_crm_product'), 'Produits', '<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />', 'app_crm_product') }}
{{ menu.nav_link(path('app_crm_category'), 'Categorie', '<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />', 'app_crm_product') }}
{{ menu.nav_link(path('app_crm_formules'), 'Formules', '<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />', 'app_crm_formules') }}
{{ menu.nav_link(path('app_crm_promotion'), 'Promotions', '<path stroke-linecap="round" stroke-linejoin="round" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />', 'app_crm_promotion') }}
{{ menu.nav_link(path('app_crm_facture'), 'Facture', '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />', 'app_crm_facture') }}

View File

@@ -0,0 +1,28 @@
{% extends 'dashboard/base.twig' %}
{% block title %}Nouvelle Catégorie{% endblock %}
{% block title_header %}Nouvelle <span class="text-blue-500">Catégorie</span>{% endblock %}
{% block body %}
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] shadow-2xl overflow-hidden">
<div class="p-10">
{{ form_start(form, {'attr': {'class': 'space-y-12'}}) }}
{{ form_row(form.name) }}
{# ACTIONS : Boutons espacés #}
<div class="pt-10 border-t border-white/5 flex items-center justify-end">
<div class="flex items-center space-x-16"> {# Espace large entre les deux boutons #}
<a data-turbo="false" href="{{ path('app_crm_category') }}" class="mr-2 text-[10px] font-black text-slate-300 hover:text-rose-500 uppercase tracking-widest transition-colors">
Annuler l'opération
</a>
<button type="submit" class="p-2 px-16 py-4 bg-blue-600 hover:bg-blue-500 text-white text-[10px] font-black uppercase tracking-[0.2em] rounded-2xl shadow-lg shadow-blue-600/20 transition-all hover:scale-105 active:scale-95">
Valider et enregistrer
</button>
</div>
</div>
{{ form_end(form) }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,64 @@
{% extends 'dashboard/base.twig' %}
{% block title %}Catégorie{% endblock %}
{% block title_header %}Catégorie{% endblock %}
{% block actions %}
<div class="flex items-center space-x-3">
<a data-turbo="false" href="{{ path('app_crm_category_add') }}" class="flex items-center space-x-2 px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white text-[10px] font-black uppercase tracking-[0.2em] rounded-xl transition-all shadow-lg shadow-blue-600/20 group">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M12 4v16m8-8H4" />
</svg>
<span>Nouvelle catégorie</span>
</a>
</div>
{% endblock %}
{% block body %}
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] overflow-hidden shadow-2xl animate-in fade-in duration-700">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-white/5 bg-black/20">
<th class="px-6 py-5 text-[10px] font-black text-slate-300 uppercase tracking-[0.2em]">Nom</th>
<th class="px-6 py-5 text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
{% for category in categorys %}
<tr class="group hover:bg-white/[0.02] transition-colors">
{# CLIENT #}
<td class="px-6 py-4">
<div class="flex flex-col">
<span class="text-[11px] font-mono font-bold text-blue-500 tracking-wider">
{{ category.name }}
</span>
</div>
</td>
{# ACTIONS #}
<td class="px-6 py-4 text-right">
<div class="flex items-center justify-end space-x-2">
<a data-turbo="false" href="{{ path('app_crm_category_delete', {id: category.id}) }}?_token={{ csrf_token('delete' ~ category.id) }}"
data-turbo-method="post"
data-turbo-confirm="Confirmer la suppression de la catégorie {{ category.name }} ?"
class="p-2 bg-rose-500/10 hover:bg-rose-600 text-rose-500 hover:text-white rounded-xl transition-all border border-rose-500/20">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
</a>
</div>
</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="py-24 text-center">
<p class="text-slate-500 italic uppercase tracking-[0.2em] text-[10px] font-black">Aucun devis trouvé</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@@ -133,7 +133,6 @@
{{ macros.nav_link('reservation_catalogue', 'nav.catalogue') }}
{{ macros.nav_link('reservation_formules', 'nav.packages') }}
{{ macros.nav_link('/images/Catalogue.pdf', 'nav.pdf', true) }}
{{ macros.nav_link('reservation_workflow', 'nav.how_to_book') }}
{{ macros.nav_link('reservation_estimate_delivery', 'Estimer la livraison') }}
{{ macros.nav_link('reservation_contact', 'nav.contact') }}
@@ -190,7 +189,6 @@
{{ macros.mobile_nav_link('reservation_catalogue', 'Nos structures') }}
{{ macros.mobile_nav_link('reservation_formules', 'Nos Formules') }}
{{ macros.mobile_nav_link('/provider/Catalogue.pdf', 'Catalogue', true) }}
{{ macros.mobile_nav_link('reservation_workflow', 'Comment reserver') }}
{{ macros.mobile_nav_link('reservation_estimate_delivery', 'Estimer la livraison') }}
{{ macros.mobile_nav_link('reservation_search', 'Rechercher') }}
<a is="flow-reserve" class="block px-3 py-2 text-base font-medium text-gray-700 hover:bg-gray-50 rounded-xl flex items-center justify-between">

View File

@@ -37,19 +37,10 @@
{{ 'catalog.filter.all'|trans }}
</button>
{% set categories_list = [
{'id': '2-7 ans', 'label': 'catalog.filter.cat_2_7'|trans, 'hover': 'hover:border-amber-500 hover:text-amber-600'},
{'id': '3-15 ans', 'label': 'catalog.filter.cat_3_15'|trans, 'hover': 'hover:border-blue-600 hover:text-blue-600'},
{'id': '3-99 ans', 'label': 'catalog.filter.cat_3_99'|trans, 'hover': 'hover:border-indigo-600 hover:text-indigo-600'},
{'id': 'barnums', 'label': 'catalog.filter.cat_barnums'|trans, 'hover': 'hover:border-slate-800 hover:text-slate-800'},
{'id': 'alimentaire', 'label': 'catalog.filter.cat_food'|trans, 'hover': 'hover:border-rose-500 hover:text-rose-600'},
{'id': 'options', 'label': 'catalog.filter.cat_options'|trans, 'hover': 'hover:border-emerald-500 hover:text-emerald-600'}
] %}
{% 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-600 border border-slate-200 {{ cat.hover }}">
{{ cat.label }}
{% for categorie in categories %}
<button data-filter="{{ categorie.name }}"
class="filter-btn px-5 py-2.5 rounded-xl font-black italic text-[9px] tracking-widest transition-all uppercase bg-white text-slate-600 border border-slate-200">
{{ categorie.name }}
</button>
{% endfor %}
</div>

View File

@@ -1,155 +0,0 @@
{% extends 'revervation/base.twig' %}
{% block title %}{{ 'workflow.seo.title'|trans }}{% endblock %}
{% block description %}{{ 'workflow.seo.description'|trans }}{% endblock %}
{% block breadcrumb_json %}
,{
"@type": "ListItem",
"position": 1,
"name": "{{ 'workflow.breadcrumb'|trans }}",
"item": "{{ url('reservation_workflow') }}"
}
{% endblock %}
{% block body %}
<div class="min-h-screen bg-white font-sans antialiased pb-20">
{# --- HEADER --- #}
<div class="max-w-4xl mx-auto pt-20 pb-16 px-4 text-center">
<span class="inline-block bg-blue-100 text-blue-600 px-4 py-1.5 rounded-full text-[10px] font-black uppercase tracking-widest mb-6" data-aos="fade-down">
{{ 'workflow.header.badge'|trans }}
</span>
<h1 class="text-6xl md:text-8xl font-black text-slate-900 uppercase italic tracking-tighter leading-[0.85] mb-6" data-aos="fade-up" data-aos-delay="100">
{{ 'workflow.header.title_part1'|trans }} <br><span class="text-blue-600">{{ 'workflow.header.title_part2'|trans }}</span>
</h1>
<p class="text-xl text-slate-500 font-medium italic leading-relaxed" data-aos="fade-up" data-aos-delay="200">{{ 'workflow.header.subtitle'|trans }}</p>
</div>
{# --- TIMELINE --- #}
<div class="max-w-5xl mx-auto px-4 relative">
<div class="hidden md:block absolute left-1/2 top-0 bottom-0 w-1 bg-slate-100 -translate-x-1/2 z-0"></div>
<div class="space-y-24 relative z-10">
{# 1. SELECTION #}
<div class="flex flex-col md:flex-row items-center gap-12 group" data-aos="fade-right">
<div class="flex-1 text-center md:text-right order-2 md:order-1">
<h2 class="text-3xl font-black text-slate-900 uppercase italic mb-4 group-hover:text-blue-600 transition-colors">{{ 'workflow.step1.title'|trans }}</h2>
<p class="text-slate-500 font-medium leading-relaxed">{{ 'workflow.step1.desc'|trans }}</p>
</div>
<div class="w-24 h-24 bg-blue-600 rounded-[2rem] flex items-center justify-center shadow-xl shadow-blue-200 order-1 md:order-2 shrink-0 transform group-hover:rotate-12 transition-transform">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
</div>
<div class="flex-1 hidden md:block order-3"></div>
</div>
{# 2. DATES #}
<div class="flex flex-col md:flex-row items-center gap-12 group" data-aos="fade-left">
<div class="flex-1 hidden md:block"></div>
<div class="w-24 h-24 bg-amber-500 rounded-[2rem] flex items-center justify-center shadow-xl shadow-amber-200 shrink-0 transform group-hover:-rotate-12 transition-transform">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 00-2 2z" /></svg>
</div>
<div class="flex-1 text-center md:text-left">
<h2 class="text-3xl font-black text-slate-900 uppercase italic mb-4 group-hover:text-amber-500 transition-colors">{{ 'workflow.step2.title'|trans }}</h2>
<p class="text-slate-500 font-medium leading-relaxed">{{ 'workflow.step2.desc'|trans }}</p>
</div>
</div>
{# 3. DETAILS & LIVRAISON #}
<div class="flex flex-col md:flex-row items-center gap-12 group" data-aos="fade-right">
<div class="flex-1 text-center md:text-right order-2 md:order-1">
<h2 class="text-3xl font-black text-slate-900 uppercase italic mb-4 group-hover:text-blue-600 transition-colors">{{ 'workflow.step3.title'|trans }}</h2>
<p class="text-slate-500 font-medium leading-relaxed">
{{ 'workflow.step3.desc'|trans }}
<span class="block mt-2 text-blue-600 font-bold italic">{{ 'workflow.step3.highlight'|trans }}</span>
</p>
</div>
<div class="w-24 h-24 bg-blue-600 rounded-[2rem] flex items-center justify-center shadow-xl shadow-blue-200 order-1 md:order-2 shrink-0 transform group-hover:rotate-12 transition-transform">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
</div>
<div class="flex-1 hidden md:block order-3"></div>
</div>
{# 4. SIGNATURE #}
<div class="flex flex-col md:flex-row items-center gap-12 group" data-aos="fade-left">
<div class="flex-1 hidden md:block"></div>
<div class="w-24 h-24 bg-indigo-600 rounded-[2rem] flex items-center justify-center shadow-xl shadow-indigo-200 shrink-0 transform group-hover:-rotate-12 transition-transform">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /></svg>
</div>
<div class="flex-1 text-center md:text-left">
<h2 class="text-3xl font-black text-slate-900 uppercase italic mb-4 group-hover:text-indigo-600 transition-colors">{{ 'workflow.step4.title'|trans }}</h2>
<p class="text-slate-500 font-medium leading-relaxed">{{ 'workflow.step4.desc'|trans }}</p>
</div>
</div>
{# 5. ARRHES #}
<div class="flex flex-col md:flex-row items-center gap-12 group" data-aos="fade-right">
<div class="flex-1 text-center md:text-right order-2 md:order-1">
<h2 class="text-3xl font-black text-slate-900 uppercase italic mb-4 group-hover:text-rose-500 transition-colors">{{ 'workflow.step5.title'|trans }}</h2>
<p class="text-slate-500 font-medium leading-relaxed">{{ 'workflow.step5.desc'|trans|raw }}</p>
</div>
<div class="w-24 h-24 bg-rose-500 rounded-[2rem] flex items-center justify-center shadow-xl shadow-rose-200 order-1 md:order-2 shrink-0 transform group-hover:scale-110 transition-transform">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M3 10h18M7 15h1m4 0h1m-7 4h12a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
</div>
<div class="flex-1 hidden md:block order-3"></div>
</div>
{# 6. RAPPEL #}
<div class="flex flex-col md:flex-row items-center gap-12 group" data-aos="fade-left">
<div class="flex-1 hidden md:block"></div>
<div class="w-24 h-24 bg-violet-600 rounded-[2rem] flex items-center justify-center shadow-xl shadow-violet-200 shrink-0 transform group-hover:-rotate-12 transition-transform">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" /></svg>
</div>
<div class="flex-1 text-center md:text-left">
<h2 class="text-3xl font-black text-slate-900 uppercase italic mb-4 group-hover:text-violet-600 transition-colors">{{ 'workflow.step6.title'|trans }}</h2>
<p class="text-slate-500 font-medium leading-relaxed">{{ 'workflow.step6.desc'|trans|raw }}</p>
</div>
</div>
{# 8 & 9. RECEPTION & SOLDE #}
<div class="flex flex-col md:flex-row items-center gap-12 group" data-aos="fade-right">
<div class="flex-1 text-center md:text-right order-2 md:order-1">
<h2 class="text-3xl font-black text-slate-900 uppercase italic mb-4 group-hover:text-emerald-500 transition-colors">{{ 'workflow.step8_9.title'|trans }}</h2>
<p class="text-slate-500 font-medium leading-relaxed">
{{ 'workflow.step8_9.desc'|trans }}
<span class="block mt-2 text-emerald-600 font-bold">{{ 'workflow.step8_9.highlight'|trans }}</span>
</p>
</div>
<div class="w-24 h-24 bg-emerald-500 rounded-[2rem] flex items-center justify-center shadow-xl shadow-emerald-200 order-1 md:order-2 shrink-0 transform group-hover:scale-110 transition-transform">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
</div>
<div class="flex-1 hidden md:block order-3"></div>
</div>
{# 10. POST-LOCATION #}
<div class="flex flex-col md:flex-row items-center gap-12 group" data-aos="fade-left">
<div class="flex-1 hidden md:block"></div>
<div class="w-24 h-24 bg-blue-900 rounded-[2rem] flex items-center justify-center shadow-xl shadow-slate-300 shrink-0 transform group-hover:rotate-12 transition-transform">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-7.714 2.143L11 21l-2.286-6.857L1 12l7.714-2.143L11 3z" /></svg>
</div>
<div class="flex-1 text-center md:text-left">
<h2 class="text-3xl font-black text-slate-900 uppercase italic mb-4">{{ 'workflow.step10.title'|trans }}</h2>
<p class="text-slate-500 font-medium leading-relaxed">{{ 'workflow.step10.desc'|trans }}</p>
</div>
</div>
</div>
</div>
{# --- CTA FINAL --- #}
<div class="max-w-4xl mx-auto mt-32 px-4">
<div class="bg-slate-900 rounded-[3rem] p-12 text-center relative overflow-hidden shadow-2xl" data-aos="zoom-in">
<div class="relative z-10">
<h3 class="text-3xl md:text-5xl font-black text-white uppercase italic mb-8">{{ 'workflow.cta.title'|trans|raw }}</h3>
<a href="{{ path('reservation_catalogue') }}" class="inline-flex items-center gap-4 bg-white text-slate-900 px-10 py-5 rounded-2xl font-black uppercase text-xs tracking-widest hover:bg-blue-600 hover:text-white transition-all transform hover:scale-105">
{{ 'workflow.cta.button'|trans }}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M14 5l7 7m0 0l-7 7m7-7H3" /></svg>
</a>
</div>
<div class="absolute top-0 right-0 -mr-16 -mt-16 w-64 h-64 bg-blue-600/20 rounded-full blur-3xl"></div>
</div>
</div>
</div>
{% endblock %}