```
✨ feat(Product): Ajoute description et quantité aux produits, et formulaire associé.
```
This commit is contained in:
32
migrations/Version20260121132344.php
Normal file
32
migrations/Version20260121132344.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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 Version20260121132344 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 description TEXT DEFAULT NULL');
|
||||
}
|
||||
|
||||
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 description');
|
||||
}
|
||||
}
|
||||
32
migrations/Version20260121132406.php
Normal file
32
migrations/Version20260121132406.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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 Version20260121132406 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 qt INT DEFAULT NULL');
|
||||
}
|
||||
|
||||
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 qt');
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use App\Repository\ProductRepository;
|
||||
use Cocur\Slugify\Slugify;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Vich\UploaderBundle\Mapping\Attribute\Uploadable;
|
||||
@@ -68,6 +69,12 @@ class Product
|
||||
#[ORM\OneToMany(targetEntity: ProductReserve::class, mappedBy: 'product')]
|
||||
private Collection $productReserves;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $description = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?int $qt = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->devisLines = new ArrayCollection();
|
||||
@@ -285,4 +292,28 @@ class Product
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setDescription(?string $description): static
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQt(): ?int
|
||||
{
|
||||
return $this->qt;
|
||||
}
|
||||
|
||||
public function setQt(?int $qt): static
|
||||
{
|
||||
$this->qt = $qt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TelType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
@@ -27,9 +28,21 @@ class ProductType extends AbstractType
|
||||
'label' => 'Reference du produit',
|
||||
'required' => true,
|
||||
])
|
||||
->add('category',TextType::class,[
|
||||
->add('description',TextareaType::class,[
|
||||
'label' => 'Description du produit',
|
||||
'required' => false,
|
||||
])
|
||||
->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',
|
||||
]
|
||||
])
|
||||
->add('caution',NumberType::class,[
|
||||
'label' => 'Caution du produit',
|
||||
|
||||
@@ -73,6 +73,21 @@
|
||||
{{ form_widget(form.ref, {'attr': {'placeholder': 'REF-000', '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 px-5'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
{# À placer juste après le bloc Référence Interne #}
|
||||
<div class="md:col-span-2 mt-6">
|
||||
{{ form_label(form.description, 'Description détaillée', {'label_attr': {'class': 'text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
|
||||
{{ form_widget(form.description, {
|
||||
'attr': {
|
||||
'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 px-5 min-h-[150px]',
|
||||
'placeholder': 'Décrivez les dimensions, la capacité, les points forts...'
|
||||
}
|
||||
}) }}
|
||||
{% if form_errors(form.description) %}
|
||||
<div class="text-rose-500 text-[10px] font-bold mt-2 ml-2 uppercase tracking-widest">
|
||||
{{ form_errors(form.description) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
</button>
|
||||
|
||||
{% set categories_list = [
|
||||
{'id': '3-15 ans', 'label': '3-15 ANS', 'hover': 'hover:border-blue-600 hover:text-blue-600'},
|
||||
{'id': '2-7 ans', 'label': '2-7 ANS', 'hover': 'hover:border-amber-500 hover:text-amber-500'},
|
||||
{'id': '3-15 ans', 'label': '3-15 ANS', 'hover': 'hover:border-blue-600 hover:text-blue-600'},
|
||||
{'id': '3-99 ans', 'label': '3-99 ANS', 'hover': 'hover:border-indigo-600 hover:text-indigo-600'},
|
||||
{'id': 'barnums', 'label': 'BARNUMS', 'hover': 'hover:border-slate-800 hover:text-slate-800'},
|
||||
{'id': 'alimentaire', 'label': 'ALIMENTAIRE', 'hover': 'hover:border-rose-500 hover:text-rose-500'},
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<a href="#" class="block w-full py-4 bg-gray-900 text-white text-center rounded-[1.5rem] font-black uppercase text-sm tracking-widest hover:bg-blue-600 transition-all shadow-xl hover:shadow-blue-200 active:scale-95">
|
||||
<a href="{{ path('reservation_product_show',{id:product.slug}) }}" class="block w-full py-4 bg-gray-900 text-white text-center rounded-[1.5rem] font-black uppercase text-sm tracking-widest hover:bg-blue-600 transition-all shadow-xl hover:shadow-blue-200 active:scale-95">
|
||||
Réserver ce bonheur
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,41 @@
|
||||
{% extends 'revervation/base.twig' %}
|
||||
|
||||
{# --- SEO DYNAMIQUE & META-DONNÉES --- #}
|
||||
{% block title %}{{ product.name }} - Location Ludikevent{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
Louez {{ product.name }} chez Ludikevent. Idéal pour les enfants ({{ product.category }}),
|
||||
cette structure est disponible à partir de {{ product.priceDay }}€ la journée.
|
||||
{{ product.description|striptags|slice(0, 150) }}...
|
||||
Vérifiez la disponibilité en ligne !
|
||||
{% endblock %}
|
||||
{% block jsonld %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org/",
|
||||
"@type": "Product",
|
||||
"name": "{{ product.name }}",
|
||||
"image": [
|
||||
"{% 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 }}",
|
||||
"brand": {
|
||||
"@type": "Brand",
|
||||
"name": "Ludikevent"
|
||||
},
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"url": "{{ app.request.uri }}",
|
||||
"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>
|
||||
{% endblock %}
|
||||
{% block breadcrumb_json %}
|
||||
,{
|
||||
"@type": "ListItem",
|
||||
@@ -17,6 +51,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{# --- TRACKING ÉVÉNEMENT --- #}
|
||||
<utm-event event="view_product" data="{{ product.json }}"></utm-event>
|
||||
|
||||
<div class="min-h-screen bg-white font-sans antialiased">
|
||||
@@ -78,8 +113,8 @@
|
||||
|
||||
<div class="space-y-6">
|
||||
{# Prix principal #}
|
||||
<div class="flex items-baseline gap-4">f
|
||||
<span class="text-6xl font-black text-blue-600 italic">{{ product.priceDay }}€</span>
|
||||
<div class="flex items-baseline gap-4">
|
||||
<span class="text-6xl font-black text-blue-600 ">{{ product.priceDay }}€</span>
|
||||
<span class="text-sm font-bold text-slate-400 uppercase tracking-widest italic">La première journée</span>
|
||||
</div>
|
||||
|
||||
@@ -91,6 +126,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- DESCRIPTION --- #}
|
||||
<div class="prose prose-slate prose-lg max-w-none mb-12 text-slate-600 leading-relaxed">
|
||||
{{ product.description|raw }}
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-10 mb-12">
|
||||
<div class="grid grid-cols-1 gap-8">
|
||||
{# Badge Âge #}
|
||||
@@ -116,7 +156,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- ACTION --- #}
|
||||
{# --- ACTION FINALE --- #}
|
||||
<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-blue-600 transition-all shadow-2xl hover:scale-[1.02] active:scale-95">
|
||||
@@ -132,7 +172,7 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{# --- SECTION SUGGESTIONS --- #}
|
||||
{# --- SECTION SUGGESTIONS (CROSS-SELLING) --- #}
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user