```
✨ feat(revervation/formule): Affiche détails et composition des formules
Partie gestion formules terminer
Affiche la composition des formules (pack ou personnalisable), avec
détails produits et options. Ajoute des sections dynamiques.
```
This commit is contained in:
@@ -273,8 +273,10 @@ class FormulesController extends AbstractController
|
||||
'product' => ''
|
||||
]
|
||||
];
|
||||
if(!empty($formules->getFormulesRestriction()->getRestrictionConfig())){
|
||||
$restriction = $formules->getFormulesRestriction()->getRestrictionConfig();
|
||||
if($formules->getFormulesRestriction() instanceof FormulesRestriction) {
|
||||
if (!empty($formules->getFormulesRestriction()->getRestrictionConfig())) {
|
||||
$restriction = $formules->getFormulesRestriction()->getRestrictionConfig();
|
||||
}
|
||||
}
|
||||
return $this->render('dashboard/formules/view.twig', [
|
||||
'formule' => $formules,
|
||||
|
||||
@@ -5,16 +5,18 @@ namespace App\Twig;
|
||||
use App\Entity\Contrats;
|
||||
use App\Entity\ContratsPayments;
|
||||
use App\Entity\Devis;
|
||||
use App\Entity\Product;
|
||||
use App\Service\Stripe\Client;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Jaybizzle\CrawlerDetect\CrawlerDetect;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
|
||||
|
||||
class StripeExtension extends AbstractExtension
|
||||
{
|
||||
public function __construct(private readonly \App\Service\Signature\Client $clientSignature,private readonly Client $client,private readonly EntityManagerInterface $em)
|
||||
public function __construct(private readonly UploaderHelper $uploaderHelper,private readonly \App\Service\Signature\Client $clientSignature,private readonly Client $client,private readonly EntityManagerInterface $em)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -25,10 +27,21 @@ class StripeExtension extends AbstractExtension
|
||||
new TwigFilter('totalQuotoAccompte', [$this, 'totalQuotoAccompte']),
|
||||
new TwigFilter('totalContrat',[$this,'totalContrat']),
|
||||
new TwigFilter('totalContratAccompte',[$this,'totalContratAccompte']),
|
||||
new TwigFilter('devisSignUrl',[$this,'devisSignUrl'])
|
||||
new TwigFilter('devisSignUrl',[$this,'devisSignUrl']),
|
||||
];
|
||||
}
|
||||
|
||||
public function loadProductByName(string $name)
|
||||
{
|
||||
$p = $this->em->getRepository(Product::class)->findOneBy(['name' => $name]);
|
||||
|
||||
return [
|
||||
'name' => $p->getName(),
|
||||
'image' => $this->uploaderHelper->asset($p,'imageFile'),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le total HT du devis en tenant compte des tarifs dégressifs (J1 + Jours Sup)
|
||||
*/
|
||||
@@ -162,6 +175,7 @@ class StripeExtension extends AbstractExtension
|
||||
new TwigFunction('isBot', [$this, 'isBot']),
|
||||
new TwigFunction('syncStripe', [$this, 'syncStripe']),
|
||||
new TwigFunction('contratPaymentPay', [$this, 'contratPaymentPay']),
|
||||
new TwigFunction('loadProductByName',[$this,'loadProductByName'])
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="flex items-center space-x-3">
|
||||
{# Bouton Nouvelle Formule avec animation au survol #}
|
||||
<a data-turbo="false" href="{{ path('app_crm_formules_add', {type: 'pack'}) }}"
|
||||
class="hidden 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 hover:-translate-y-0.5 active:translate-y-0">
|
||||
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 hover:-translate-y-0.5 active:translate-y-0">
|
||||
<svg class="w-4 h-4 transform group-hover:rotate-90 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{% block body %}
|
||||
<div class="min-h-screen bg-white font-sans antialiased pb-20">
|
||||
|
||||
{# --- HEADER / FIL D'ARIANE (STYLE SIMPLE) --- #}
|
||||
{# --- HEADER / FIL D'ARIANE --- #}
|
||||
<div class="max-w-7xl 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-[#fc0e50] transition">ACCUEIL</a>
|
||||
@@ -36,7 +36,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Badge Type (Rose) #}
|
||||
{# Badge Type #}
|
||||
<div class="absolute -top-4 -right-4 bg-[#fc0e50] text-white px-8 py-3 rounded-2xl border-4 border-slate-900 font-black text-sm uppercase italic rotate-3 shadow-lg">
|
||||
{{ formule.type|upper }}
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@
|
||||
</h1>
|
||||
|
||||
<div class="prose prose-slate mb-10">
|
||||
<div class="text-lg font-medium text-slate-600 leading-relaxed border-[#f39e36]">
|
||||
<div class="text-lg font-medium text-slate-600 leading-relaxed">
|
||||
{% set desc = formule.description %}
|
||||
{% if '<p' in desc or '<div' in desc or '<span' in desc or '<br' in desc %}
|
||||
{{ desc|raw }}
|
||||
@@ -81,18 +81,78 @@
|
||||
<div class="mt-6 pt-6 border-t border-slate-200 flex justify-between items-center">
|
||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Caution : {{ formule.caution|default('0') }}€</span>
|
||||
<span class="text-[10px] font-black text-[#fc0e50] uppercase tracking-widest italic animate-pulse">
|
||||
Économisez {{ (((formule.price1j * 5) - formule.price5j) / (formule.price1j * 5) * 100)|round }}% sur 5j
|
||||
</span>
|
||||
Économisez {{ (((formule.price1j * 5) - formule.price5j) / (formule.price1j * 5) * 100)|round }}% sur 5j
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{{ path('reservation_contact') }}" class="w-full bg-slate-900 text-white text-center py-6 rounded-3xl font-black uppercase italic tracking-widest hover:bg-[#fc0e50] transition-colors duration-300 shadow-xl">
|
||||
Réserver cette formule
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if formule.type == "pack" %}
|
||||
{# --- COMPOSITION DU PACK --- #}
|
||||
<div class="max-w-7xl mx-auto px-4 py-20">
|
||||
{# --- SECTION DYNAMIQUE (FREE OU PACK) --- #}
|
||||
<div class="max-w-7xl mx-auto px-4 py-20">
|
||||
|
||||
{% if formule.type == "free" %}
|
||||
{# --- DESIGN BENTO POUR FORMULE FREE --- #}
|
||||
<div class="flex items-center space-x-4 mb-12">
|
||||
<h2 class="text-4xl font-black text-slate-900 uppercase italic tracking-tighter">Composez <span class="text-[#f39e36]">votre pack</span></h2>
|
||||
<div class="h-1 flex-grow bg-slate-100 rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{# Grille Bento des Quotas #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-16">
|
||||
<div class="bg-blue-50 border-4 border-slate-900 rounded-[2.5rem] p-8 flex flex-col justify-between h-30 group hover:-translate-y-1 transition-transform">
|
||||
<div>
|
||||
<p class="text-4xl font-black text-slate-900 leading-none">{{ formule.formulesRestriction.nbStructureMax|default(0) }}</p>
|
||||
<p class="text-xs font-black text-slate-400 uppercase italic mt-2 tracking-widest">Structures au choix</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-orange-50 border-4 border-slate-900 rounded-[2.5rem] p-8 flex flex-col justify-between h-30 group hover:-translate-y-1 transition-transform">
|
||||
<div>
|
||||
<p class="text-4xl font-black text-slate-900 leading-none">{{ formule.formulesRestriction.nbAlimentaireMax|default(0) }}</p>
|
||||
<p class="text-xs font-black text-slate-400 uppercase italic mt-2 tracking-widest">Animations Alimentaires</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-purple-50 border-4 border-slate-900 rounded-[2.5rem] p-8 flex flex-col justify-between h-30 group hover:-translate-y-1 transition-transform">
|
||||
<div>
|
||||
<p class="text-4xl font-black text-slate-900 leading-none">{{ formule.formulesRestriction.nbBarhumsMax|default(0) }}</p>
|
||||
<p class="text-xs font-black text-slate-400 uppercase italic mt-2 tracking-widest">Barnums & Mobilier</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Catalogue des produits éligibles #}
|
||||
<h3 class="text-[10px] font-black text-slate-400 uppercase tracking-[0.4em] mb-8 text-center italic">— Catalogue éligible à cette formule —</h3>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-6">
|
||||
{% for item in formule.formulesRestriction.restrictionConfig %}
|
||||
{% set product = loadProductByName(item.product) %}
|
||||
<div class="group border-2 border-slate-900 rounded-[2rem] p-2 bg-white hover:bg-slate-50 transition-all shadow-[8px_8px_0px_0px_rgba(15,23,42,1)] hover:shadow-none hover:translate-x-1 hover:translate-y-1">
|
||||
<div class="aspect-square rounded-[1.5rem] overflow-hidden bg-slate-100 mb-3 border border-slate-900/10">
|
||||
{% if product.image %}
|
||||
<img src="{{ product.image|imagine_filter('webp') }}" alt="{{ product.name }}" class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500">
|
||||
{% else %}
|
||||
<div class="w-full h-full flex items-center justify-center italic text-[8px] font-black text-slate-300 uppercase">Image non disp.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="px-2 pb-2">
|
||||
<span class="text-[8px] font-black uppercase px-2 py-0.5 rounded-full border border-slate-900 {% if item.type == 'structure' %}bg-blue-100{% elseif item.type == 'alimentaire' %}bg-orange-100{% else %}bg-purple-100{% endif %}">
|
||||
{{ item.type }}
|
||||
</span>
|
||||
<h4 class="text-[11px] font-black text-slate-900 uppercase italic leading-tight mt-2 line-clamp-2">{{ product.name|default(item.product) }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if formule.type == "pack" %}
|
||||
{# --- COMPOSITION DU PACK (EXISTANT) --- #}
|
||||
<div class="flex items-center space-x-4 mb-12">
|
||||
<h2 class="text-4xl font-black text-slate-900 uppercase italic tracking-tighter">Ce pack <span class="text-[#f39e36]">comprend</span></h2>
|
||||
<div class="h-1 flex-grow bg-slate-100 rounded-full"></div>
|
||||
@@ -105,17 +165,17 @@
|
||||
<span class="w-2 h-2 bg-[#f39e36] rounded-full mr-2"></span> Structures & Matériel
|
||||
</h3>
|
||||
{% for item in formule.formulesProductIncluses %}
|
||||
<div class="flex items-center p-4 bg-white border-2 border-slate-100 rounded-3xl group hover:border-[#f39e36] transition-colors shadow-sm">
|
||||
<div class="w-16 h-16 rounded-2xl overflow-hidden border border-slate-100 flex-shrink-0 bg-slate-50">
|
||||
<div class="flex items-center p-4 bg-white border-4 border-slate-900 rounded-3xl group hover:border-[#f39e36] transition-colors shadow-[8px_8px_0px_0px_rgba(15,23,42,1)] mb-4">
|
||||
<div class="w-16 h-16 rounded-2xl overflow-hidden border-2 border-slate-900 flex-shrink-0 bg-slate-50">
|
||||
{% if item.product.imageName %}
|
||||
<img src="{{ vich_uploader_asset(item.product, 'imageFile') }}" class="w-full h-full object-cover">
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="ml-6 flex-grow">
|
||||
<p class="text-xs font-black text-[#f39e36] uppercase tracking-tighter">Réf: {{ item.product.ref }}</p>
|
||||
<p class="text-[9px] font-black text-[#f39e36] uppercase tracking-tighter">Réf: {{ item.product.ref }}</p>
|
||||
<h4 class="text-lg font-black text-slate-900 uppercase leading-none italic">{{ item.product.name }}</h4>
|
||||
</div>
|
||||
<div class="text-slate-200 group-hover:text-[#f39e36] transition-colors">
|
||||
<div class="text-[#f39e36]">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,27 +190,27 @@
|
||||
<span class="w-2 h-2 bg-[#fc0e50] rounded-full mr-2"></span> Services & Bonus Inclus
|
||||
</h3>
|
||||
{% for option in formule.formulesOptionsIncluses %}
|
||||
<div class="flex items-center p-6 bg-[#fc0e50]/5 border-2 border-[#fc0e50]/20 rounded-3xl relative overflow-hidden group">
|
||||
<div class="w-12 h-12 bg-white border-2 border-[#fc0e50] rounded-2xl flex items-center justify-center text-[#fc0e50] shadow-sm z-10">
|
||||
<svg class="w-6 h-6" 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 class="flex items-center p-6 bg-[#fc0e50]/5 border-4 border-slate-900 rounded-[2.5rem] relative overflow-hidden group shadow-[8px_8px_0px_0px_rgba(15,23,42,1)]">
|
||||
<div class="w-12 h-12 bg-white border-2 border-slate-900 rounded-2xl flex items-center justify-center text-[#fc0e50] shadow-sm z-10">
|
||||
<svg class="w-6 h-6" 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>
|
||||
<div class="ml-6 z-10">
|
||||
<h4 class="text-xl font-black text-slate-900 uppercase italic leading-none">{{ option.name }}</h4>
|
||||
<p class="text-[10px] font-black text-[#fc0e50] uppercase tracking-widest mt-1">Avantage inclus</p>
|
||||
</div>
|
||||
{# Déco de fond #}
|
||||
<div class="absolute -right-4 -bottom-4 text-6xl opacity-10 grayscale group-hover:rotate-12 transition-transform">🎁</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="h-full flex items-center justify-center p-12 border-2 border-dashed border-slate-100 rounded-[3rem]">
|
||||
<p class="text-slate-300 font-bold uppercase text-[10px] tracking-widest italic">Aucun bonus supplémentaire</p>
|
||||
<div class="h-full flex items-center justify-center p-12 border-4 border-dashed border-slate-200 rounded-[3rem]">
|
||||
<p class="text-slate-300 font-bold uppercase text-[10px] tracking-widest italic text-center">Aucun bonus supplémentaire inclus dans cette offre.</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -227,7 +227,11 @@
|
||||
{# Badge Type de Formule (ex: PACK) #}
|
||||
<div class="absolute top-5 left-5 bg-[#0782bc] text-white px-4 py-1.5 rounded-full shadow-lg border border-white/20">
|
||||
<p class="text-[9px] font-black uppercase tracking-[0.2em] italic">
|
||||
{{ formule.type|default('Pack') }}
|
||||
{% if formule.type == "free" %}
|
||||
Personnalisable
|
||||
{% else %}
|
||||
{{ formule.type|default('Pack') }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user