```
✨ feat(Product): Ajoute les dimensions largeur, hauteur et profondeur au produit.
```
This commit is contained in:
36
migrations/Version20260127075804.php
Normal file
36
migrations/Version20260127075804.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?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 Version20260127075804 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('ALTER TABLE product ADD dim_h DOUBLE PRECISION DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE product ADD dim_p DOUBLE PRECISION DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE product RENAME COLUMN installation TO dim_w');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE SCHEMA public');
|
||||||
|
$this->addSql('ALTER TABLE product DROP dim_h');
|
||||||
|
$this->addSql('ALTER TABLE product DROP dim_p');
|
||||||
|
$this->addSql('ALTER TABLE product RENAME COLUMN dim_w TO installation');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,6 +78,15 @@ class Product
|
|||||||
#[ORM\OneToMany(targetEntity: ProductDoc::class, mappedBy: 'product')]
|
#[ORM\OneToMany(targetEntity: ProductDoc::class, mappedBy: 'product')]
|
||||||
private Collection $productDocs;
|
private Collection $productDocs;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?float $dimW = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?float $dimH = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?float $dimP = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->devisLines = new ArrayCollection();
|
$this->devisLines = new ArrayCollection();
|
||||||
@@ -354,4 +363,40 @@ class Product
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDimW(): ?float
|
||||||
|
{
|
||||||
|
return $this->dimW;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDimW(float $dimW): static
|
||||||
|
{
|
||||||
|
$this->dimW = $dimW;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDimH(): ?float
|
||||||
|
{
|
||||||
|
return $this->dimH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDimH(?float $dimH): static
|
||||||
|
{
|
||||||
|
$this->dimH = $dimH;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDimP(): ?float
|
||||||
|
{
|
||||||
|
return $this->dimP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDimP(?float $dimP): static
|
||||||
|
{
|
||||||
|
$this->dimP = $dimP;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,22 @@ class ProductType extends AbstractType
|
|||||||
'required' => true,
|
'required' => true,
|
||||||
'html5' => true,
|
'html5' => true,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
->add('dimW',NumberType::class,[
|
||||||
|
'label' => 'Largeur',
|
||||||
|
'required' => true,
|
||||||
|
'html5' => true,
|
||||||
|
])
|
||||||
|
->add('dimP',NumberType::class,[
|
||||||
|
'label' => 'Longeur',
|
||||||
|
'required' => true,
|
||||||
|
'html5' => true,
|
||||||
|
])
|
||||||
|
->add('dimH',NumberType::class,[
|
||||||
|
'label' => 'Hauteur',
|
||||||
|
'required' => true,
|
||||||
|
'html5' => true,
|
||||||
|
])
|
||||||
->add('imageFile',FileType::class,[
|
->add('imageFile',FileType::class,[
|
||||||
'label' => 'Image du produit',
|
'label' => 'Image du produit',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
|||||||
@@ -108,6 +108,48 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# 02. DIMENSIONS DU PRODUIT #}
|
||||||
|
<div class="mt-8 backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] p-8 shadow-2xl">
|
||||||
|
<h3 class="text-lg font-bold text-white mb-6 flex items-center">
|
||||||
|
<span class="w-8 h-8 bg-blue-600/20 text-blue-500 rounded-lg flex items-center justify-center mr-3 text-[10px] font-black">02</span>
|
||||||
|
Dimensions de la Structure
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
{# Largeur #}
|
||||||
|
<div class="relative group">
|
||||||
|
{{ form_label(form.dimW, 'Largeur (m)', {'label_attr': {'class': 'text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
||||||
|
<span class="text-slate-500 text-[10px] font-bold italic">W</span>
|
||||||
|
</div>
|
||||||
|
{{ form_widget(form.dimW, {'attr': {'placeholder': '0.00', 'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white font-mono focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 pl-10 pr-5'}}) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Longueur #}
|
||||||
|
<div class="relative group">
|
||||||
|
{{ form_label(form.dimP, 'Longueur (m)', {'label_attr': {'class': 'text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
||||||
|
<span class="text-slate-500 text-[10px] font-bold italic">L</span>
|
||||||
|
</div>
|
||||||
|
{{ form_widget(form.dimP, {'attr': {'placeholder': '0.00', 'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white font-mono focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 pl-10 pr-5'}}) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Hauteur #}
|
||||||
|
<div class="relative group">
|
||||||
|
{{ form_label(form.dimH, 'Hauteur (m)', {'label_attr': {'class': 'text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
||||||
|
<span class="text-slate-500 text-[10px] font-bold italic">H</span>
|
||||||
|
</div>
|
||||||
|
{{ form_widget(form.dimH, {'attr': {'placeholder': '0.00', 'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white font-mono focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 pl-10 pr-5'}}) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# COLONNE DROITE : TARIFICATION #}
|
{# COLONNE DROITE : TARIFICATION #}
|
||||||
|
|||||||
@@ -8,33 +8,30 @@
|
|||||||
Tarif : {{ product.priceDay }}€/jour. Installation et livraison dans l'Aisne (02).
|
Tarif : {{ product.priceDay }}€/jour. Installation et livraison dans l'Aisne (02).
|
||||||
{{ product.description|striptags|slice(0, 130) }}... Réservez votre date !
|
{{ product.description|striptags|slice(0, 130) }}... Réservez votre date !
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block jsonld %}
|
{% block jsonld %}
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
"@context": "https://schema.org/",
|
"@context": "https://schema.org/",
|
||||||
"@type": "Product",
|
"@type": "Product",
|
||||||
"name": "{{ product.name }}",
|
"name": "{{ product.name }}",
|
||||||
"image": [
|
"image": ["{% if product.imageName %}{{ absolute_url(vich_uploader_asset(product, 'imageFile')) }}{% else %}{{ absolute_url(asset('provider/images/favicon.png')) }}{% endif %}"],
|
||||||
"{% if product.imageName %}{{ absolute_url(vich_uploader_asset(product, 'imageFile')) }}{% else %}{{ absolute_url(asset('provider/images/favicon.png')) }}{% endif %}"
|
"description": "{{ product.description|striptags|slice(0, 160) }}",
|
||||||
],
|
"sku": "{{ product.ref }}",
|
||||||
"description": "{{ product.description|striptags|slice(0, 160) }}",
|
"brand": { "@type": "Brand", "name": "Ludikevent" },
|
||||||
"sku": "{{ product.ref }}",
|
"offers": {
|
||||||
"brand": {
|
"@type": "Offer",
|
||||||
"@type": "Brand",
|
"url": "{{ app.request.uri }}",
|
||||||
"name": "Ludikevent"
|
"priceCurrency": "EUR",
|
||||||
},
|
"price": "{{ product.priceDay }}",
|
||||||
"offers": {
|
"availability": "https://schema.org/InStock",
|
||||||
"@type": "Offer",
|
"itemCondition": "https://schema.org/UsedCondition",
|
||||||
"url": "{{ app.request.uri }}",
|
"priceValidUntil": "{{ "now"|date_modify("+1 year")|date("Y-m-d") }}"
|
||||||
"priceCurrency": "EUR",
|
}
|
||||||
"price": "{{ product.priceDay }}",
|
}
|
||||||
"availability": "https://schema.org/InStock",
|
|
||||||
"itemCondition": "https://schema.org/UsedCondition",
|
|
||||||
"priceValidUntil": "{{ "now"|date_modify("+1 year")|date("Y-m-d") }}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block breadcrumb_json %}
|
{% block breadcrumb_json %}
|
||||||
,{
|
,{
|
||||||
"@type": "ListItem",
|
"@type": "ListItem",
|
||||||
@@ -50,7 +47,6 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{# --- TRACKING ÉVÉNEMENT --- #}
|
|
||||||
<utm-event event="view_product" data="{{ product.json }}"></utm-event>
|
<utm-event event="view_product" data="{{ product.json }}"></utm-event>
|
||||||
|
|
||||||
<div class="min-h-screen bg-white font-sans antialiased">
|
<div class="min-h-screen bg-white font-sans antialiased">
|
||||||
@@ -81,19 +77,19 @@
|
|||||||
|
|
||||||
{# --- COLONNE GAUCHE : VISUEL --- #}
|
{# --- COLONNE GAUCHE : VISUEL --- #}
|
||||||
<div class="sticky top-24">
|
<div class="sticky top-24">
|
||||||
<div class="relative overflow-hidden rounded-[4rem] bg-slate-50 aspect-[4/5]">
|
<div class="relative overflow-hidden rounded-[3rem] md:rounded-[4rem] bg-slate-50 aspect-[4/5] shadow-inner">
|
||||||
{% if product.imageName %}
|
{% if product.imageName %}
|
||||||
<img src="{{ vich_uploader_asset(product,'imageFile') | imagine_filter('webp') }}"
|
<img src="{{ vich_uploader_asset(product,'imageFile') | imagine_filter('webp') }}"
|
||||||
alt="{{ product.name }}"
|
alt="{{ product.name }}"
|
||||||
class="w-full h-full object-contain">
|
class="w-full h-full object-contain p-4 md:p-8">
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="h-full flex flex-col items-center justify-center p-12 text-center opacity-50">
|
<div class="h-full flex flex-col items-center justify-center p-12 text-center opacity-50">
|
||||||
<img src="{{ asset('provider/images/favicon.png') }}" alt="Ludik Event" class="w-75 h-75">
|
<img src="{{ asset('provider/images/favicon.png') }}" alt="Ludik Event" class="w-48">
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="absolute top-8 left-8">
|
<div class="absolute top-6 left-6 md:top-8 md:left-8">
|
||||||
<span class="bg-[#f39e36] text-white px-6 py-2 rounded-2xl text-[10px] font-black uppercase tracking-widest shadow-xl">
|
<span class="bg-[#f39e36] text-white px-4 py-2 md:px-6 md:py-2 rounded-2xl text-[9px] md:text-[10px] font-black uppercase tracking-widest shadow-xl">
|
||||||
{{ product.category }}
|
{{ product.category }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,191 +98,182 @@
|
|||||||
|
|
||||||
{# --- COLONNE DROITE : CONTENU --- #}
|
{# --- COLONNE DROITE : CONTENU --- #}
|
||||||
<div class="flex flex-col h-full py-4">
|
<div class="flex flex-col h-full py-4">
|
||||||
<div class="mb-12">
|
<div class="mb-10 md:mb-12">
|
||||||
<span class="text-[12px] font-black text-slate-300 uppercase tracking-[0.4em] mb-4 block italic">
|
<span class="text-[11px] md:text-[12px] font-black text-slate-300 uppercase tracking-[0.4em] mb-4 block italic text-center md:text-left">
|
||||||
Référence : {{ product.ref }}
|
Référence : {{ product.ref }}
|
||||||
</span>
|
</span>
|
||||||
<h1 class="text-6xl md:text-8xl font-black text-slate-900 uppercase italic tracking-tighter leading-[0.85] mb-10">
|
<h1 class="text-5xl md:text-8xl font-black text-slate-900 uppercase italic tracking-tighter leading-[0.9] mb-10 text-center md:text-left">
|
||||||
{{ product.name }}
|
{{ product.name }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="flex flex-col items-center md:items-start space-y-6">
|
||||||
{# Prix principal #}
|
{# Prix principal #}
|
||||||
<div class="flex items-baseline gap-4">
|
<div class="flex flex-col md:flex-row items-center md:items-baseline gap-2 md:gap-4 text-center md:text-left">
|
||||||
<span class="text-6xl font-black text-[#f39e36] ">{{ product.priceDay }}€</span>
|
<span class="text-6xl md:text-7xl font-black text-[#f39e36] leading-none">{{ product.priceDay }}€</span>
|
||||||
<span class="text-sm font-bold text-slate-400 uppercase tracking-widest italic">La première journée</span>
|
<span class="text-[10px] md:text-sm font-bold text-slate-400 uppercase tracking-widest italic">La première journée</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Prix supplémentaire #}
|
{# Grille de badges Tarifs/Caution #}
|
||||||
<div class="flex items-center gap-3 bg-slate-50 self-start px-5 py-3 rounded-2xl border border-slate-100 w-fit">
|
<div class="flex flex-wrap justify-center md:justify-start gap-3 w-full">
|
||||||
<span class="text-2xl font-black text-slate-900 italic">+ {{ product.priceSup }}€</span>
|
<div class="flex items-center gap-3 bg-slate-50 px-5 py-3 rounded-2xl border border-slate-100 shadow-sm">
|
||||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest italic">Par journée supplémentaire</span>
|
<span class="text-xl md:text-2xl font-black text-slate-900 italic">+ {{ product.priceSup }}€</span>
|
||||||
</div>
|
<span class="text-[9px] font-black text-slate-400 uppercase tracking-widest italic">Jour supplémentaire</span>
|
||||||
<div class="flex items-center gap-3 bg-slate-50 self-start px-5 py-3 rounded-2xl border border-slate-100 w-fit">
|
</div>
|
||||||
<span class="text-2xl font-black text-slate-900 italic">{{ product.caution }}€</span>
|
<div class="flex items-center gap-3 bg-slate-50 px-5 py-3 rounded-2xl border border-slate-100 shadow-sm">
|
||||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest italic">LA CAUTION</span>
|
<span class="text-xl md:text-2xl font-black text-slate-900 italic">{{ product.caution }}€</span>
|
||||||
|
<span class="text-[9px] font-black text-slate-400 uppercase tracking-widest italic">LA CAUTION</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 max-w-sm p-4 rounded-2xl bg-white/40 border border-slate-200/60 backdrop-blur-sm shadow-sm">
|
{# Message Caution Stripe #}
|
||||||
|
<div class="max-w-sm p-4 rounded-2xl bg-white/40 border border-slate-200/60 backdrop-blur-sm shadow-sm">
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<svg class="w-5 h-5 text-blue-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5 text-blue-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p class="text-[11px] leading-relaxed text-slate-500">
|
<p class="text-[10px] md:text-[11px] leading-relaxed text-slate-500">
|
||||||
<strong class="text-slate-900 font-bold uppercase tracking-tight block mb-0.5">Caution</strong>
|
<strong class="text-slate-900 font-bold uppercase tracking-tight block mb-0.5">Caution</strong>
|
||||||
Cette somme sera prélevée et pourra être bloquée pour un maximum de <span class="text-slate-900 font-semibold">4 jours</span> selon les politiques de <span class="font-medium text-blue-600">Stripe</span>.
|
Cette somme sera prélevée sur votre compte bancaire et pourra être bloquée pour un maximum de <span class="text-slate-900 font-semibold">4 jours</span> selon les politiques de <span class="font-medium text-blue-600">Stripe</span>. </p>
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# --- DESCRIPTION --- #}
|
{# --- DESCRIPTION --- #}
|
||||||
<div class="prose prose-slate prose-lg max-w-none mb-12 text-slate-600 leading-relaxed">
|
<div class="prose prose-slate prose-lg max-w-none mb-12 text-slate-600 leading-relaxed text-center md:text-left">
|
||||||
{{ product.description|raw }}
|
{{ product.description|raw }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t border-slate-100 pt-10 mb-12">
|
{# --- DIMENSIONS (Version Responsive) --- #}
|
||||||
<div class="grid grid-cols-1 gap-8">
|
{% if product.dimP is not empty and product.dimP != "" %}
|
||||||
{# Badge Âge #}
|
<div class="mb-12">
|
||||||
<div class="bg-slate-50 p-6 rounded-[2rem] border border-slate-100 flex items-center justify-between">
|
<span class="text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] mb-4 md:mb-6 block italic text-center md:text-left">Dimensions de la structure</span>
|
||||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Âge conseillé</span>
|
<div class="bg-slate-50 rounded-[2.5rem] md:rounded-[3rem] p-6 md:p-8 border border-slate-100 relative overflow-hidden">
|
||||||
<span class="text-lg font-black text-slate-900 uppercase italic">{{ product.category }}</span>
|
<div class="flex flex-col md:grid md:grid-cols-2 gap-8 md:gap-12 items-center">
|
||||||
</div>
|
{# Visuel Schématique #}
|
||||||
|
<div class="relative h-32 md:h-48 flex items-center justify-center w-full">
|
||||||
{# SÉLECTEUR DE PÉRIODE --- #}
|
<div class="relative w-28 h-16 md:w-32 md:h-20 bg-blue-600/10 border-2 border-blue-500/20 rounded-lg transform -rotate-12 skew-x-12 flex items-center justify-center">
|
||||||
<div class="bg-blue-50/50 p-8 rounded-[3rem] border border-blue-100 hidden">
|
<div class="absolute -right-4 -top-6 md:-top-8 h-full border-r-2 border-dashed border-blue-400/40 flex items-center">
|
||||||
<span class="block text-[10px] font-black text-blue-600 uppercase tracking-[0.2em] mb-6 italic text-center">Planifiez votre événement</span>
|
<span class="ml-2 text-[9px] md:text-[10px] font-black text-blue-500 italic">{{ product.dimH }}m</span>
|
||||||
<div class="flex flex-col sm:flex-row gap-4">
|
</div>
|
||||||
<div class="flex-1">
|
<div class="absolute -bottom-5 md:-bottom-6 left-0 w-full border-b-2 border-dashed border-blue-400/40 text-center">
|
||||||
<label class="block text-[8px] font-black uppercase text-slate-400 mb-2 ml-4">Date de début</label>
|
<span class="block mt-1 text-[9px] md:text-[10px] font-black text-blue-500 italic">{{ product.dimW }}m</span>
|
||||||
<input type="date" class="w-full bg-white border-none rounded-2xl px-5 py-4 text-sm font-bold text-slate-900 focus:ring-2 focus:ring-blue-600 shadow-sm">
|
</div>
|
||||||
|
<div class="absolute -bottom-3 -right-6 md:-right-8 w-16 md:w-20 border-b-2 border-dashed border-blue-400/40 transform rotate-[45deg] text-center">
|
||||||
|
<span class="block mt-1 text-[9px] md:text-[10px] font-black text-blue-500 italic">{{ product.dimP }}m</span>
|
||||||
|
</div>
|
||||||
|
<svg class="w-8 h-8 md:w-12 md:h-12 text-blue-500/20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
{# Chiffres #}
|
||||||
<label class="block text-[8px] font-black uppercase text-slate-400 mb-2 ml-4">Date de fin</label>
|
<div class="grid grid-cols-1 gap-3 md:gap-4 w-full">
|
||||||
<input type="date" class="w-full bg-white border-none rounded-2xl px-5 py-4 text-sm font-bold text-slate-900 focus:ring-2 focus:ring-blue-600 shadow-sm">
|
<div class="flex items-center justify-between border-b border-slate-200/60 pb-3">
|
||||||
|
<span class="text-[9px] md:text-[10px] font-black text-slate-400 uppercase italic">Largeur</span>
|
||||||
|
<span class="text-lg md:text-xl font-black text-slate-900 italic">{{ product.dimW }} m</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between border-b border-slate-200/60 pb-3">
|
||||||
|
<span class="text-[9px] md:text-[10px] font-black text-slate-400 uppercase italic">Profondeur</span>
|
||||||
|
<span class="text-lg md:text-xl font-black text-slate-900 italic">{{ product.dimP }} m</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-[9px] md:text-[10px] font-black text-slate-400 uppercase italic">Hauteur</span>
|
||||||
|
<span class="text-lg md:text-xl font-black text-slate-900 italic">{{ product.dimH }} m</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="border-t border-slate-100 pt-10 mb-12">
|
||||||
|
<div class="grid grid-cols-1 gap-8">
|
||||||
|
<div class="bg-slate-50 p-6 rounded-[2rem] border border-slate-100 flex items-center justify-between shadow-sm">
|
||||||
|
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Âge conseillé</span>
|
||||||
|
<span class="text-lg font-black text-slate-900 uppercase italic">{{ product.category }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if product.category == "2-7 ans" %}
|
{# --- ACTIONS FINALES --- #}
|
||||||
{# --- BLOC CONDITION SPÉCIALE (STYLE ÉPURÉ) --- #}
|
<div class="mt-auto space-y-4">
|
||||||
<div class="mt-auto">
|
{% if product.category == "2-7 ans" %}
|
||||||
<div class="mb-6 p-6 border-2 border-slate-100 rounded-[2.5rem] text-center">
|
<div class="p-6 border-2 border-slate-100 rounded-[2.5rem] text-center">
|
||||||
<p class="text-[12px] font-bold text-slate-800 leading-relaxed uppercase tracking-wide">h
|
<p class="text-[11px] font-bold text-slate-800 leading-relaxed uppercase tracking-wide">
|
||||||
La réservation de cette structure est soumise à des conditions spéciales.
|
Structure soumise à des conditions spéciales.
|
||||||
</p>
|
|
||||||
<p class="mt-2 text-[11px] text-slate-500 font-medium">
|
|
||||||
Merci de nous contacter pour la réserver.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<a href="{{ path('reservation_contact', {id: product.id}) }}"
|
<a href="{{ path('reservation_contact', {id: product.id}) }}"
|
||||||
class="flex items-center justify-center w-full py-8 bg-slate-900 text-white rounded-[2.5rem] font-black uppercase text-[12px] tracking-[0.3em] hover:bg-[#f39e36] transition-all shadow-2xl hover:scale-[1.02] active:scale-95">
|
class="flex items-center justify-center w-full py-6 md:py-8 bg-slate-900 text-white rounded-[2.5rem] font-black uppercase text-[11px] md:text-[12px] tracking-[0.3em] hover:bg-[#f39e36] transition-all shadow-2xl hover:scale-[1.02] active:scale-95">
|
||||||
Nous contacter
|
{{ (product.category == "2-7 ans") ? 'Nous contacter' : 'Vérifier la disponibilité' }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p class="text-center mt-6 text-[9px] font-black text-slate-300 uppercase tracking-widest italic">
|
<p class="text-center text-[9px] font-black text-slate-300 uppercase tracking-widest italic">
|
||||||
Devis gratuit • Ludikevent • Qualité Pro
|
Devis gratuit • Ludikevent • Qualité Pro
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
|
||||||
{# --- ACTION FINALE ORIGINALE --- #}
|
|
||||||
<div class="mt-auto">
|
|
||||||
<a href="{{ path('reservation_contact', {id: product.id}) }}"
|
|
||||||
class="flex items-center justify-center w-full py-8 bg-slate-900 text-white rounded-[2.5rem] font-black uppercase text-[12px] tracking-[0.3em] hover:bg-[#f39e36] transition-all shadow-2xl hover:scale-[1.02] active:scale-95">
|
|
||||||
Vérifier la disponibilité
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<p class="text-center mt-6 text-[9px] font-black text-slate-300 uppercase tracking-widest italic">
|
|
||||||
Devis gratuit • Ludikevent • Qualité Pro
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{# --- DOCUMENTS PUBLICS (NOTICES, PDF) --- #}
|
|
||||||
|
{# --- DOCUMENTS PUBLICS --- #}
|
||||||
{% set publicDocs = product.productDocs|filter(doc => doc.isPublic) %}
|
{% set publicDocs = product.productDocs|filter(doc => doc.isPublic) %}
|
||||||
{% if publicDocs|length > 0 %}
|
{% if publicDocs|length > 0 %}
|
||||||
<div class="mt-12 mb-12">
|
<div class="mt-20">
|
||||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] mb-4 block italic">Ressources techniques</span>
|
<span class="text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] mb-6 block italic text-center md:text-left">Ressources techniques</span>
|
||||||
<div class="space-y-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{% for doc in publicDocs %}
|
{% for doc in publicDocs %}
|
||||||
<div class="cta-file">
|
<a href="{{ vich_uploader_asset(doc, 'docProduct') }}"
|
||||||
<utm-event event="click_pdf_product" data="{{ doc.json }}" ></utm-event>
|
download="{{ doc.name }}.pdf"
|
||||||
<a href="{{ vich_uploader_asset(doc, 'docProduct') }}"
|
class="flex items-center justify-between p-5 bg-white border border-slate-100 rounded-3xl hover:border-blue-500 hover:shadow-lg transition-all group">
|
||||||
download="{{ doc.name }}.pdf"
|
<div class="flex items-center gap-4">
|
||||||
class="flex items-center justify-between p-5 bg-white border border-slate-100 rounded-3xl hover:border-blue-500 hover:shadow-lg transition-all group">
|
<div class="w-10 h-10 bg-red-50 text-red-500 rounded-xl flex items-center justify-center group-hover:bg-red-500 group-hover:text-white transition-colors">
|
||||||
<div class="flex items-center gap-4">
|
<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="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"/></svg>
|
||||||
<div class="w-10 h-10 bg-red-50 text-red-500 rounded-xl flex items-center justify-center group-hover:bg-red-500 group-hover:text-white transition-colors">
|
|
||||||
<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="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[11px] font-black text-slate-900 uppercase italic tracking-wider">{{ doc.name }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-slate-300 group-hover:text-blue-600 transition-colors">
|
<p class="text-[11px] font-black text-slate-900 uppercase italic">{{ doc.name }}</p>
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
</div>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
<svg class="w-5 h-5 text-slate-300 group-hover:text-blue-600 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
|
||||||
</svg>
|
</a>
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{# --- SECTION SUGGESTIONS (CROSS-SELLING) --- #}
|
{# --- SUGGESTIONS --- #}
|
||||||
<section class="max-w-7xl mx-auto px-4 py-24 border-t border-slate-100 mt-12">
|
<section class="max-w-7xl mx-auto px-4 py-24 border-t border-slate-100 mt-12">
|
||||||
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-16">
|
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-16 text-center md:text-left">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-[10px] font-black text-[#f39e36] uppercase tracking-[0.3em] mb-3 block italic">Vous pourriez aussi aimer</span>
|
<span class="text-[10px] font-black text-[#f39e36] uppercase tracking-[0.3em] mb-3 block italic">Vous pourriez aussi aimer</span>
|
||||||
<h2 class="text-4xl md:text-6xl font-black text-slate-900 uppercase italic tracking-tighter leading-none">D'autres <span class="text-[#f39e36]">idées ?</span></h2>
|
<h2 class="text-4xl md:text-6xl font-black text-slate-900 uppercase italic tracking-tighter leading-none">D'autres <span class="text-[#f39e36]">idées ?</span></h2>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ path('reservation_catalogue') }}" class="inline-flex items-center gap-2 text-[10px] font-black uppercase tracking-widest border-b-2 border-slate-900 pb-1 hover:text-blue-600 hover:border-blue-600 transition-all">
|
<a href="{{ path('reservation_catalogue') }}" class="inline-flex items-center justify-center gap-2 text-[10px] font-black uppercase tracking-widest border-b-2 border-slate-900 pb-1 hover:text-blue-600 hover:border-blue-600 transition-all">
|
||||||
Tout le catalogue
|
Tout le catalogue
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-8">
|
<div class="grid grid-cols-2 lg:grid-cols-4 gap-6 md:gap-8">
|
||||||
{% for other in otherProducts %}
|
{% for other in otherProducts %}
|
||||||
<div class="group transition-all duration-500">
|
<a href="{{ path('reservation_product_show', {id: (other.slug)}) }}" class="group block">
|
||||||
<a href="{{ path('reservation_product_show', {id: (other.slug)}) }}" class="block">
|
<div class="relative overflow-hidden rounded-[2rem] md:rounded-[2.5rem] bg-slate-50 aspect-square mb-6 shadow-sm group-hover:shadow-2xl transition-all duration-700">
|
||||||
<div class="relative overflow-hidden rounded-[2.5rem] bg-slate-50 aspect-square mb-6 shadow-sm group-hover:shadow-2xl transition-all duration-700">
|
{% if other.imageName %}
|
||||||
{% if other.imageName %}
|
<img src="{{ vich_uploader_asset(other,'imageFile') | imagine_filter('webp') }}" alt="{{ other.name }}" class="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-1000">
|
||||||
<img src="{{ vich_uploader_asset(other,'imageFile') | imagine_filter('webp') }}"
|
{% else %}
|
||||||
alt="{{ other.name }}"
|
<div class="w-full h-full flex items-center justify-center opacity-10"><img src="{{ asset('provider/images/favicon.png') }}" class="w-16"></div>
|
||||||
class="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-1000">
|
{% endif %}
|
||||||
{% else %}
|
<div class="absolute top-4 right-4 bg-white/95 backdrop-blur-md px-3 py-1.5 rounded-xl shadow-sm">
|
||||||
<div class="w-full h-full flex items-center justify-center opacity-10">
|
<p class="text-slate-900 font-black text-[11px] md:text-[12px] italic leading-none">{{ other.priceDay }}€</p>
|
||||||
<img src="{{ asset('provider/images/favicon.png') }}" class="w-16">
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="absolute top-4 right-4 bg-white/95 backdrop-blur-md px-3 py-1.5 rounded-xl shadow-sm">
|
|
||||||
<p class="text-slate-900 font-black text-[12px] italic leading-none">{{ other.priceDay }}€</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="px-2">
|
</div>
|
||||||
<span class="text-[8px] font-black text-slate-300 uppercase tracking-widest mb-1 block italic">{{ other.category }}</span>
|
<div class="px-2 text-center md:text-left">
|
||||||
<h3 class="text-lg font-black text-slate-900 uppercase italic tracking-tighter leading-tight group-hover:text-[#f39e36] transition-colors line-clamp-1">
|
<span class="text-[8px] font-black text-slate-300 uppercase tracking-widest mb-1 block italic">{{ other.category }}</span>
|
||||||
{{ other.name }}
|
<h3 class="text-md md:text-lg font-black text-slate-900 uppercase italic tracking-tighter leading-tight group-hover:text-[#f39e36] transition-colors line-clamp-1">{{ other.name }}</h3>
|
||||||
</h3>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="col-span-full text-center text-slate-400 italic text-sm py-12">Plus de surprises arrivent bientôt...</p>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user