Files
ludikevent_crm/templates/revervation/flow_confirmed.twig
Serreau Jovann 1fb0cc6f3f revert: remettre le calculateur de frais de livraison
Restauration complète du système d'estimation de livraison :
- Page publique /estimer-la-livraison + liens navigation
- Calcul automatique livraison dans FlowController (admin)
- Champs distance/prix + carte Leaflet dans la vue admin flow
- Estimation livraison dans la confirmation de réservation
- Ligne "Frais de livraison" sur les devis générés

Seules les modifications CGV (suppression section 7.2 rayon 30km) sont conservées.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:20:35 +01:00

398 lines
26 KiB
Twig

{% extends 'revervation/base.twig' %}
{% block title %}Confirmation de votre demande{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
{% endblock %}
{% block body %}
<div class="max-w-7xl mx-auto px-4 py-12">
<div class="bg-white rounded-3xl shadow-xl p-8 border border-gray-100">
<h1 class="text-3xl font-black text-slate-900 uppercase italic mb-8 text-center">Confirmation de votre demande</h1>
{# --- CART DETAILS --- #}
{% if cart is defined %}
<div class="bg-slate-50 rounded-2xl p-6 border border-slate-100 mb-6">
<div class="flex items-center gap-4 mb-6">
<div class="bg-indigo-100 p-3 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
</div>
<div>
<h3 class="text-lg font-bold text-slate-900">Détails de la réservation</h3>
{% if cart.startDate and cart.endDate %}
<p class="text-sm text-slate-600">Du <span class="font-medium text-slate-900">{{ cart.startDate|date('d/m/Y') }}</span> au <span class="font-medium text-slate-900">{{ cart.endDate|date('d/m/Y') }}</span> ({{ cart.duration }} jours)</p>
{% endif %}
</div>
</div>
{% if cart.formule %}
<div class="mb-6 bg-blue-50 border border-blue-100 rounded-xl p-4 flex items-center gap-3">
<div class="bg-white p-2 rounded-lg text-blue-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>
</div>
<div>
<p class="text-[10px] font-black text-blue-400 uppercase tracking-widest">Formule appliquée</p>
<p class="text-sm font-bold text-slate-900">{{ cart.formule }}</p>
</div>
</div>
{% endif %}
<div class="space-y-4">
{% for item in cart.items %}
<div class="flex items-center bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
{% if item.image %}
<img src="{{ item.image }}" alt="{{ item.product.name }}" class="h-16 w-16 object-cover rounded-lg mr-4 bg-slate-100">
{% else %}
<div class="h-16 w-16 bg-slate-100 rounded-lg mr-4 flex items-center justify-center text-slate-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
{% endif %}
<div class="flex-1">
{% if item.in_formule is defined and item.in_formule %}
<span class="inline-block px-2 py-0.5 rounded text-[8px] font-black uppercase tracking-widest bg-blue-100 text-blue-600 mb-1">Inclus formule</span>
{% endif %}
<h4 class="font-bold text-slate-800">{{ item.product.name }}</h4>
<div class="text-xs text-slate-500 mb-2 prose prose-sm max-w-none">
{{ item.product.description|raw }}
</div>
<div class="text-xs text-slate-600 bg-slate-50 p-2 rounded-lg border border-slate-100 inline-block">
<div class="flex flex-wrap gap-x-3 gap-y-1">
<span>1er jour : <strong class="text-slate-800">{{ item.price1Day|number_format(2, ',', ' ') }} €</strong></span>
{% if cart.duration > 1 %}
<span class="text-slate-300">|</span>
<span>Jours supp. : <strong class="text-slate-800">{{ item.priceSup|number_format(2, ',', ' ') }} €</strong> <span class="text-slate-400">x {{ cart.duration - 1 }}</span></span>
{% endif %}
</div>
</div>
{# Linked Options #}
{% if item.options is defined and item.options|length > 0 %}
<div class="mt-2 space-y-1">
{% for opt in item.options %}
<div class="flex items-center gap-2 text-xs text-slate-500">
<span class="bg-indigo-50 text-indigo-600 px-1.5 py-0.5 rounded text-[10px] font-bold uppercase">Option</span>
<span>{{ opt.name }}</span>
<span class="font-bold text-slate-700">+ {{ opt.price|number_format(2, ',', ' ') }} €</span>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<div class="text-right">
<p class="font-bold text-slate-900">{{ item.totalPriceHT|number_format(2, ',', ' ') }} € HT</p>
{% if cart.tvaEnabled %}
<p class="text-xs text-slate-500">{{ item.totalPriceTTC|number_format(2, ',', ' ') }} € TTC</p>
{% endif %}
</div>
</div>
{% else %}
<p class="text-center text-slate-500 py-4">Aucun produit sélectionné.</p>
{% endfor %}
{# Orphan Options #}
{% if cart.options is defined and cart.options|length > 0 %}
<div class="border-t border-slate-100 pt-4 mt-4">
<h4 class="text-sm font-bold text-slate-800 mb-2 uppercase tracking-wide">Autres options</h4>
{% for opt in cart.options %}
<div class="flex items-center justify-between bg-white p-3 rounded-xl border border-slate-200 shadow-sm mb-2">
<div class="flex items-center gap-3">
<div class="h-8 w-8 bg-indigo-50 rounded-lg flex items-center justify-center text-indigo-500">
<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="M12 6v6m0 0v6m0-6h6m-6 0H6"/></svg>
</div>
<span class="text-sm font-medium text-slate-700">{{ opt.name }}</span>
</div>
<span class="font-bold text-slate-900">{{ opt.price|number_format(2, ',', ' ') }} € HT</span>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<div class="mt-6 border-t border-slate-200 pt-4 space-y-2">
{% if cart.discount > 0 %}
<div class="flex justify-between text-sm text-slate-400">
<span>Total HT</span>
<span class="font-medium line-through">{{ (cart.totalHT + cart.discount)|number_format(2, ',', ' ') }} €</span>
</div>
<div class="flex justify-between text-sm text-[#f39e36] font-bold">
<span>Promotion : {{ cart.promotion }}</span>
<span>-{{ cart.discount|number_format(2, ',', ' ') }} €</span>
</div>
{% endif %}
<div class="flex justify-between text-sm text-slate-600">
<span>Total HT {% if cart.discount > 0 %}Remisé{% endif %}</span>
<span class="font-medium">{{ cart.totalHT|number_format(2, ',', ' ') }} €</span>
</div>
{% if cart.tvaEnabled %}
<div class="flex justify-between text-sm text-slate-600">
<span>TVA (20%)</span>
<span class="font-medium">{{ cart.totalTva|number_format(2, ',', ' ') }} €</span>
</div>
<div class="flex justify-between text-lg font-black text-slate-900 pt-2 border-t border-slate-200 mt-2">
<span>Total TTC</span>
<span>{{ cart.totalTTC|number_format(2, ',', ' ') }} €</span>
</div>
{% endif %}
</div>
</div>
{% endif %}
<div class="bg-slate-50 rounded-2xl p-6 border border-slate-100 flex flex-col md:flex-row items-center md:items-start gap-6 mb-6">
<div class="bg-blue-100 p-4 rounded-full flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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 w-full text-center md:text-left">
<h3 class="text-lg font-bold text-slate-900 mb-4">Informations Client</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-white p-3 rounded-xl border border-slate-200 shadow-sm">
<span class="block text-xs text-slate-500 uppercase font-semibold tracking-wider">Nom complet</span>
<span class="block text-slate-800 font-medium mt-1">{{ session.customer.name }} {{ session.customer.surname }}</span>
</div>
<div class="bg-white p-3 rounded-xl border border-slate-200 shadow-sm">
<span class="block text-xs text-slate-500 uppercase font-semibold tracking-wider">Téléphone</span>
<span class="block text-slate-800 font-medium mt-1">{{ session.customer.phone }}</span>
</div>
<div class="bg-white p-3 rounded-xl border border-slate-200 shadow-sm md:col-span-2">
<span class="block text-xs text-slate-500 uppercase font-semibold tracking-wider">Email</span>
<span class="block text-slate-800 font-medium mt-1">{{ session.customer.email }}</span>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-2xl p-6 border border-slate-200 shadow-sm mb-6">
<h3 class="text-xl font-bold text-slate-900 mb-6 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
Informations de l'événement
</h3>
{# Billing Address #}
<div>
<div class="flex items-center gap-3 mb-6 pb-2 border-b border-slate-100">
<span class="bg-blue-100 text-blue-600 p-2 rounded-lg">
<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="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" /></svg>
</span>
<h4 class="font-bold text-lg text-slate-900">Adresse de facturation</h4>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="md:col-span-2">
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Adresse complète</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.billingAddress }}</span>
</div>
<div>
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Code Postal</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.billingZipCode }}</span>
</div>
<div>
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Ville</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.billingTown }}</span>
</div>
</div>
</div>
{# Event Address #}
<div class="mt-8">
<div class="flex items-center gap-3 mb-6 pb-2 border-b border-slate-100">
<span class="bg-blue-100 text-blue-600 p-2 rounded-lg">
<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="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" /><path stroke-linecap="round" stroke-linejoin="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
</span>
<h4 class="font-bold text-lg text-slate-900">Lieu de l'événement</h4>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="md:col-span-2">
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Adresse de l'événement</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.adressEvent }}</span>
</div>
<div class="md:col-span-2">
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Complément d'adresse</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.adress2Event }}</span>
</div>
<div>
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Code Postal</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.zipCodeEvent }}</span>
</div>
<div>
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Ville</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.townEvent }}</span>
</div>
</div>
</div>
{# Event Details #}
<div class="mt-8">
<div class="flex items-center gap-3 mb-6 pb-2 border-b border-slate-100">
<span class="bg-blue-100 text-blue-600 p-2 rounded-lg">
<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="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>
</span>
<h4 class="font-bold text-lg text-slate-900">Détails techniques</h4>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="md:col-span-2">
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Type d'événement</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.type }}</span>
</div>
<div class="md:col-span-2">
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Détails supplémentaires</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.details }}</span>
</div>
<div>
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Type de sol</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.typeSol }}</span>
</div>
<div>
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Pente</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.pente }}</span>
</div>
<div class="md:col-span-2">
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Accès (largeur portail, escaliers...)</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.access }}</span>
</div>
<div>
<span class="block text-xs text-slate-500 uppercase tracking-wide mb-2">Distance prise électrique (m)</span>
<span class="block w-full rounded-2xl border-slate-200 shadow-sm py-3 px-4 bg-slate-50">{{ session.distancePower }}</span>
</div>
</div>
</div>
</div>
{# Delivery Estimation #}
{% if delivery is defined and delivery.estimation is not null %}
<div class="bg-white rounded-2xl border border-slate-200 shadow-sm mb-8 overflow-hidden">
<div class="p-6 border-b border-slate-100 flex items-center gap-3 bg-slate-50/50">
<div class="bg-emerald-100 p-2 rounded-lg text-emerald-600">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<h3 class="text-lg font-bold text-slate-900">Estimation des frais de livraison</h3>
</div>
<div class="flex flex-col md:flex-row">
{# Details (60%) #}
<div class="w-full md:w-3/5 p-6 border-b md:border-b-0 md:border-r border-slate-100">
{% if delivery.details.isFree %}
<div class="bg-emerald-50 border border-emerald-100 rounded-xl p-4 mb-6">
<div class="flex items-center gap-3">
<span class="text-2xl font-black text-emerald-600 italic">Offert !</span>
<span class="px-3 py-1 bg-white text-emerald-700 rounded-full text-[10px] font-bold uppercase tracking-wide shadow-sm">Zone gratuite</span>
</div>
<p class="text-sm text-emerald-800 mt-2 font-medium">Votre événement se trouve à moins de 10km de nos locaux.</p>
</div>
{% else %}
<div class="mb-6">
<span class="block text-xs font-bold text-slate-400 uppercase tracking-widest mb-1">Coût estimé</span>
<p class="text-4xl font-black text-slate-900 italic">
{{ delivery.estimation|format_currency('EUR') }}
</p>
</div>
{% endif %}
<div class="space-y-3">
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-4">Détails du calcul</p>
<div class="flex justify-between text-sm">
<span class="text-slate-500">Distance réelle (Aller)</span>
<span class="font-bold text-slate-700">{{ delivery.details.distance|number_format(1, ',', ' ') }} km</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-500">Franchise kilométrique</span>
<span class="font-bold text-emerald-500">- 10.0 km (Offerts)</span>
</div>
<div class="border-t border-slate-100 my-2"></div>
<div class="flex justify-between text-sm">
<span class="text-slate-500">Distance facturée</span>
<span class="font-bold text-slate-700">{{ delivery.details.chargedDistance|number_format(1, ',', ' ') }} km</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-500">Nombre de trajets</span>
<span class="font-bold text-slate-700">{{ delivery.details.trips }} (2 A/R)</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-500">Tarif kilométrique</span>
<span class="font-bold text-slate-700">{{ delivery.details.rate }} € / km</span>
</div>
</div>
{% if not delivery.details.isFree %}
<div class="mt-4 p-3 bg-indigo-50 border border-indigo-100 rounded-xl text-center">
<p class="text-[10px] font-bold text-indigo-400 uppercase mb-1">Formule appliquée</p>
<code class="text-xs text-indigo-700 font-mono font-bold">
({{ delivery.details.distance|number_format(1) }} - 10) x {{ delivery.details.trips }} x {{ delivery.details.rate }}€ = {{ delivery.estimation|number_format(2) }}
</code>
</div>
{% endif %}
<div class="mt-4 p-3 bg-slate-50 rounded-xl text-center">
<p class="text-[10px] text-slate-400 italic">
Cette estimation est indicative. Le montant définitif figurera sur votre devis.
</p>
</div>
</div>
{# Map (40%) #}
<div class="w-full md:w-2/5 h-64 md:h-auto min-h-[16rem] bg-slate-100 relative">
{% if delivery.geometry %}
<leaflet-map class="absolute inset-0 z-0"
data-geometry="{{ delivery.geometry|json_encode|e('html_attr') }}">
</leaflet-map>
{% else %}
<div class="absolute inset-0 flex items-center justify-center text-slate-400">
<span class="text-xs font-medium">Carte non disponible</span>
</div>
{% endif %}
</div>
</div>
</div>
{% endif %}
<div class="pt-6 border-t border-slate-100 mt-8 flex flex-col md:flex-row justify-center gap-4">
<a data-turbo="false" href="{{ path('reservation_generate_devis', {sessionId: session.uuid}) }}" class="w-full md:w-auto px-8 py-4 bg-gray-200 text-gray-800 font-bold rounded-2xl shadow-sm hover:shadow-md hover:scale-[1.01] transition-all flex items-center justify-center gap-2 text-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
Télécharger le devis
</a>
<form is="submit-clear-storage" data-turbo="false" method="post" action="{{ path('reservation_flow_confirmed', {sessionId: session.uuid}) }}" class="w-full md:w-auto">
<button type="submit" class="w-full px-8 py-4 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-bold rounded-2xl shadow-lg shadow-blue-200 hover:shadow-xl hover:scale-[1.02] transition-all flex items-center justify-center gap-2 text-lg">
Je confirme la commande
</button>
</form>
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
{% if delivery is defined and delivery.geometry %}
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""
nonce="{{ csp_nonce('script') }}"></script>
{% endif %}
{% endblock %}