From e1227c5d14bdeb1e5af5a740ded4260d148b6789 Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Fri, 30 Jan 2026 11:29:29 +0100 Subject: [PATCH] =?UTF-8?q?```=20=E2=9C=A8=20feat(Product.php):=20Ajoute?= =?UTF-8?q?=20les=20entit=C3=A9s=20ProductPhotos=20et=20ProductVideo.=20?= =?UTF-8?q?=E2=9C=A8=20feat(Product):=20Ajoute=20les=20collections=20photo?= =?UTF-8?q?s=20et=20vid=C3=A9os=20au=20produit.=20=F0=9F=86=95=20feat(Prod?= =?UTF-8?q?uctPhotosType):=20Cr=C3=A9e=20le=20formulaire=20d'upload=20des?= =?UTF-8?q?=20photos.=20=F0=9F=86=95=20feat(ProductVideoType):=20Cr=C3=A9e?= =?UTF-8?q?=20le=20formulaire=20d'upload=20des=20vid=C3=A9os.=20?= =?UTF-8?q?=F0=9F=8E=A8=20refactor(add.twig):=20Ajoute=20les=20formulaires?= =?UTF-8?q?=20et=20affichage=20des=20photos/vid=C3=A9os.=20=F0=9F=8E=A8=20?= =?UTF-8?q?refactor(produit.twig):=20Affiche=20les=20photos=20et=20vid?= =?UTF-8?q?=C3=A9os=20sur=20la=20page=20produit.=20=E2=99=BB=EF=B8=8F=20re?= =?UTF-8?q?factor(vich=5Fuploader.yaml):=20Ajoute=20les=20mappings=20pour?= =?UTF-8?q?=20photos=20et=20vid=C3=A9os.=20=F0=9F=90=9B=20fix(ProductContr?= =?UTF-8?q?oller):=20G=C3=A8re=20l'ajout/suppression=20des=20photos=20et?= =?UTF-8?q?=20vid=C3=A9os.=20```?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/packages/vich_uploader.yaml | 8 + migrations/Version20260130095656.php | 42 +++ .../Dashboard/ProductController.php | 65 ++++- src/Entity/Product.php | 74 +++++ src/Entity/ProductPhotos.php | 114 ++++++++ src/Entity/ProductVideo.php | 115 ++++++++ src/Form/ProductPhotosType.php | 39 +++ src/Form/ProductVideoType.php | 38 +++ src/Repository/ProductPhotosRepository.php | 43 +++ src/Repository/ProductVideoRepository.php | 43 +++ templates/dashboard/products/add.twig | 273 ++++++++++++------ templates/revervation/produit.twig | 62 +++- 12 files changed, 817 insertions(+), 99 deletions(-) create mode 100644 migrations/Version20260130095656.php create mode 100644 src/Entity/ProductPhotos.php create mode 100644 src/Entity/ProductVideo.php create mode 100644 src/Form/ProductPhotosType.php create mode 100644 src/Form/ProductVideoType.php create mode 100644 src/Repository/ProductPhotosRepository.php create mode 100644 src/Repository/ProductVideoRepository.php diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml index 1afe6b4..b4515d0 100644 --- a/config/packages/vich_uploader.yaml +++ b/config/packages/vich_uploader.yaml @@ -9,6 +9,14 @@ vich_uploader: uri_prefix: /images/image_product upload_destination: '%kernel.project_dir%/public/images/image_product' namer: Vich\UploaderBundle\Naming\SmartUniqueNamer + image_product_pic: + uri_prefix: /images/image_product_pic + upload_destination: '%kernel.project_dir%/public/images/image_product_pic' + namer: Vich\UploaderBundle\Naming\SmartUniqueNamer + image_product_video: + uri_prefix: /images/image_product_video + upload_destination: '%kernel.project_dir%/public/images/image_product_video' + namer: Vich\UploaderBundle\Naming\SmartUniqueNamer doc_product: uri_prefix: /images/doc_product upload_destination: '%kernel.project_dir%/public/images/doc_product' diff --git a/migrations/Version20260130095656.php b/migrations/Version20260130095656.php new file mode 100644 index 0000000..e596499 --- /dev/null +++ b/migrations/Version20260130095656.php @@ -0,0 +1,42 @@ +addSql('CREATE TABLE product_photos (id SERIAL NOT NULL, product_id INT DEFAULT NULL, image_name VARCHAR(255) DEFAULT NULL, image_size INT DEFAULT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_6A0AA17D4584665A ON product_photos (product_id)'); + $this->addSql('COMMENT ON COLUMN product_photos.updated_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE TABLE product_video (id SERIAL NOT NULL, product_id INT DEFAULT NULL, image_name VARCHAR(255) DEFAULT NULL, image_size INT DEFAULT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_DD9BA1704584665A ON product_video (product_id)'); + $this->addSql('COMMENT ON COLUMN product_video.updated_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE product_photos ADD CONSTRAINT FK_6A0AA17D4584665A FOREIGN KEY (product_id) REFERENCES product (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE product_video ADD CONSTRAINT FK_DD9BA1704584665A FOREIGN KEY (product_id) REFERENCES product (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + 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_photos DROP CONSTRAINT FK_6A0AA17D4584665A'); + $this->addSql('ALTER TABLE product_video DROP CONSTRAINT FK_DD9BA1704584665A'); + $this->addSql('DROP TABLE product_photos'); + $this->addSql('DROP TABLE product_video'); + } +} diff --git a/src/Controller/Dashboard/ProductController.php b/src/Controller/Dashboard/ProductController.php index 8cc4664..7870e0b 100644 --- a/src/Controller/Dashboard/ProductController.php +++ b/src/Controller/Dashboard/ProductController.php @@ -5,9 +5,13 @@ namespace App\Controller\Dashboard; use App\Entity\Options; use App\Entity\Product; use App\Entity\ProductDoc; +use App\Entity\ProductPhotos; +use App\Entity\ProductVideo; use App\Form\OptionsType; use App\Form\ProductDocType; +use App\Form\ProductPhotosType; use App\Form\ProductType; +use App\Form\ProductVideoType; use App\Logger\AppLogger; use App\Repository\OptionsRepository; use App\Repository\ProductRepository; @@ -126,7 +130,7 @@ class ProductController extends AbstractController #[Route(path: '/crm/products/edit/{id}', name: 'app_crm_product_edit', methods: ['GET', 'POST'])] public function productEdit(Product $product, EntityManagerInterface $em, AppLogger $logger, Request $request, Client $stripe): Response { - // 1. Gestion de la suppression de document + // 1. Suppression de Document if ($idDoc = $request->query->get('idDoc')) { $doc = $em->getRepository(ProductDoc::class)->find($idDoc); if ($doc && $doc->getProduct() === $product) { @@ -139,7 +143,59 @@ class ProductController extends AbstractController return $this->redirectToRoute('app_crm_product_edit', ['id' => $product->getId()]); } - // 2. Formulaire Document + // 2. Suppression de Vidéo + if ($idVideo = $request->query->get('idVideo')) { + $video = $em->getRepository(ProductVideo::class)->find($idVideo); + if ($video && $video->getProduct() === $product) { + $em->remove($video); + $em->flush(); + $logger->record('DELETE', "Vidéo supprimée sur {$product->getName()}"); + $this->addFlash('success', 'Vidéo supprimée.'); + } + return $this->redirectToRoute('app_crm_product_edit', ['id' => $product->getId()]); + } + + // 3. Suppression de Photo + if ($idPhoto = $request->query->get('idPhoto')) { + $photo = $em->getRepository(ProductPhotos::class)->find($idPhoto); + if ($photo && $photo->getProduct() === $product) { + $em->remove($photo); + $em->flush(); + $logger->record('DELETE', "Photo supprimée sur {$product->getName()}"); + $this->addFlash('success', 'Photo supprimée.'); + } + return $this->redirectToRoute('app_crm_product_edit', ['id' => $product->getId()]); + } + + // 4. Formulaire Ajout Photo + $photo = new ProductPhotos(); + $photo->setProduct($product); + $formPhotos = $this->createForm(ProductPhotosType::class, $photo); + $formPhotos->handleRequest($request); + + if ($formPhotos->isSubmitted() && $formPhotos->isValid()) { + $em->persist($photo); + $em->flush(); + $logger->record('UPDATE', "Ajout photo sur : {$product->getName()}"); + $this->addFlash('success', 'Photo ajoutée.'); + return $this->redirectToRoute('app_crm_product_edit', ['id' => $product->getId()]); + } + + // 5. Formulaire Ajout Vidéo + $video = new ProductVideo(); + $video->setProduct($product); + $formVideo = $this->createForm(ProductVideoType::class, $video); + $formVideo->handleRequest($request); + + if ($formVideo->isSubmitted() && $formVideo->isValid()) { + $em->persist($video); + $em->flush(); + $logger->record('UPDATE', "Ajout vidéo sur : {$product->getName()}"); + $this->addFlash('success', 'Vidéo ajoutée.'); + return $this->redirectToRoute('app_crm_product_edit', ['id' => $product->getId()]); + } + + // 6. Formulaire Ajout Document $doc = new ProductDoc(); $doc->setProduct($product); $formDoc = $this->createForm(ProductDocType::class, $doc); @@ -154,7 +210,7 @@ class ProductController extends AbstractController return $this->redirectToRoute('app_crm_product_edit', ['id' => $product->getId()]); } - // 3. Formulaire Produit + // 7. Formulaire Principal Produit $form = $this->createForm(ProductType::class, $product); $form->handleRequest($request); @@ -171,11 +227,12 @@ class ProductController extends AbstractController return $this->render('dashboard/products/add.twig', [ 'form' => $form->createView(), 'formDoc' => $formDoc->createView(), + 'formPhoto' => $formPhotos->createView(), + 'formVideo' => $formVideo->createView(), 'product' => $product, 'is_edit' => true ]); } - #[Route(path: '/crm/products/delete/{id}', name: 'app_crm_product_delete', methods: ['POST'])] public function productDelete(Product $product, EntityManagerInterface $em, Request $request, AppLogger $logger, Client $stripe): Response { diff --git a/src/Entity/Product.php b/src/Entity/Product.php index 25d708b..a112185 100644 --- a/src/Entity/Product.php +++ b/src/Entity/Product.php @@ -87,11 +87,25 @@ class Product #[ORM\OneToMany(targetEntity: FormulesProductInclus::class, mappedBy: 'PRODUCT')] private Collection $formulesProductIncluses; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: ProductPhotos::class, mappedBy: 'product')] + private Collection $productPhotos; + + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: ProductVideo::class, mappedBy: 'product')] + private Collection $productVideos; + public function __construct() { $this->productReserves = new ArrayCollection(); $this->productDocs = new ArrayCollection(); $this->formulesProductIncluses = new ArrayCollection(); + $this->productPhotos = new ArrayCollection(); + $this->productVideos = new ArrayCollection(); } public function slug() { @@ -400,4 +414,64 @@ class Product return $this; } + + /** + * @return Collection + */ + public function getProductPhotos(): Collection + { + return $this->productPhotos; + } + + public function addProductPhoto(ProductPhotos $productPhoto): static + { + if (!$this->productPhotos->contains($productPhoto)) { + $this->productPhotos->add($productPhoto); + $productPhoto->setProduct($this); + } + + return $this; + } + + public function removeProductPhoto(ProductPhotos $productPhoto): static + { + if ($this->productPhotos->removeElement($productPhoto)) { + // set the owning side to null (unless already changed) + if ($productPhoto->getProduct() === $this) { + $productPhoto->setProduct(null); + } + } + + return $this; + } + + /** + * @return Collection + */ + public function getProductVideos(): Collection + { + return $this->productVideos; + } + + public function addProductVideo(ProductVideo $productVideo): static + { + if (!$this->productVideos->contains($productVideo)) { + $this->productVideos->add($productVideo); + $productVideo->setProduct($this); + } + + return $this; + } + + public function removeProductVideo(ProductVideo $productVideo): static + { + if ($this->productVideos->removeElement($productVideo)) { + // set the owning side to null (unless already changed) + if ($productVideo->getProduct() === $this) { + $productVideo->setProduct(null); + } + } + + return $this; + } } diff --git a/src/Entity/ProductPhotos.php b/src/Entity/ProductPhotos.php new file mode 100644 index 0000000..664761c --- /dev/null +++ b/src/Entity/ProductPhotos.php @@ -0,0 +1,114 @@ +id; + } + + public function getProduct(): ?Product + { + return $this->product; + } + + public function setProduct(?Product $product): static + { + $this->product = $product; + + return $this; + } + + /** + * @return int|null + */ + public function getImageSize(): ?int + { + return $this->imageSize; + } + + /** + * @return string|null + */ + public function getImageName(): ?string + { + return $this->imageName; + } + + /** + * @return File|null + */ + public function getImageFile(): ?File + { + return $this->imageFile; + } + + /** + * @return \DateTimeImmutable|null + */ + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + /** + * @param int|null $imageSize + */ + public function setImageSize(?int $imageSize): void + { + $this->imageSize = $imageSize; + } + + /** + * @param string|null $imageName + */ + public function setImageName(?string $imageName): void + { + $this->imageName = $imageName; + } + + /** + * @param File|null $imageFile + */ + public function setImageFile(?File $imageFile): void + { + $this->imageFile = $imageFile; + } + + /** + * @param \DateTimeImmutable|null $updatedAt + */ + public function setUpdatedAt(?\DateTimeImmutable $updatedAt): void + { + $this->updatedAt = $updatedAt; + } +} diff --git a/src/Entity/ProductVideo.php b/src/Entity/ProductVideo.php new file mode 100644 index 0000000..78d28a9 --- /dev/null +++ b/src/Entity/ProductVideo.php @@ -0,0 +1,115 @@ +id; + } + + public function getProduct(): ?Product + { + return $this->product; + } + + public function setProduct(?Product $product): static + { + $this->product = $product; + + return $this; + } + + /** + * @return \DateTimeImmutable|null + */ + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + /** + * @return File|null + */ + public function getImageFile(): ?File + { + return $this->imageFile; + } + + /** + * @return string|null + */ + public function getImageName(): ?string + { + return $this->imageName; + } + + /** + * @return int|null + */ + public function getImageSize(): ?int + { + return $this->imageSize; + } + + /** + * @param \DateTimeImmutable|null $updatedAt + */ + public function setUpdatedAt(?\DateTimeImmutable $updatedAt): void + { + $this->updatedAt = $updatedAt; + } + + /** + * @param File|null $imageFile + */ + public function setImageFile(?File $imageFile): void + { + $this->imageFile = $imageFile; + } + + /** + * @param string|null $imageName + */ + public function setImageName(?string $imageName): void + { + $this->imageName = $imageName; + } + + /** + * @param int|null $imageSize + */ + public function setImageSize(?int $imageSize): void + { + $this->imageSize = $imageSize; + } + +} diff --git a/src/Form/ProductPhotosType.php b/src/Form/ProductPhotosType.php new file mode 100644 index 0000000..0e08a9f --- /dev/null +++ b/src/Form/ProductPhotosType.php @@ -0,0 +1,39 @@ +add('imageFile',FileType::class,[ + 'label' => 'Videos', + 'required' => false, + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => ProductPhotos::class, + ]); + } +} diff --git a/src/Form/ProductVideoType.php b/src/Form/ProductVideoType.php new file mode 100644 index 0000000..87faa32 --- /dev/null +++ b/src/Form/ProductVideoType.php @@ -0,0 +1,38 @@ +add('imageFile',FileType::class,[ + 'label' => 'Videos', + 'required' => false, + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => ProductVideo::class, + ]); + } +} diff --git a/src/Repository/ProductPhotosRepository.php b/src/Repository/ProductPhotosRepository.php new file mode 100644 index 0000000..84ba487 --- /dev/null +++ b/src/Repository/ProductPhotosRepository.php @@ -0,0 +1,43 @@ + + */ +class ProductPhotosRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ProductPhotos::class); + } + + // /** + // * @return ProductPhotos[] Returns an array of ProductPhotos objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('p.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?ProductPhotos + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Repository/ProductVideoRepository.php b/src/Repository/ProductVideoRepository.php new file mode 100644 index 0000000..7bacace --- /dev/null +++ b/src/Repository/ProductVideoRepository.php @@ -0,0 +1,43 @@ + + */ +class ProductVideoRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ProductVideo::class); + } + + // /** + // * @return ProductVideo[] Returns an array of ProductVideo objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('p.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?ProductVideo + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/templates/dashboard/products/add.twig b/templates/dashboard/products/add.twig index 285701b..5f4cfb8 100644 --- a/templates/dashboard/products/add.twig +++ b/templates/dashboard/products/add.twig @@ -6,35 +6,32 @@ {% block actions %} {% endblock %} + {% block body %}
{{ form_start(form) }}
- {# COLONNE GAUCHE : VISUEL & IDENTITÉ #} + {# --- COLONNE GAUCHE --- #}
{# 00. IMAGE DU PRODUIT #} - {# SECTION IMAGE #}

00 @@ -42,14 +39,13 @@

+ {# Preview Image #}
- {# L'image avec un ID spécifique pour le JS #} - {# L'icône de remplacement #} @@ -58,10 +54,9 @@
+ {# Input File #}
{{ form_label(form.imageFile, 'Sélectionner un fichier', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-widest mb-2 block'}}) }} - - {# On enlève le "onchange" inline, on cible via l'ID généré par Symfony ou un ID fixe #} {{ form_widget(form.imageFile, { 'id': 'product_image_input', 'attr': { @@ -71,6 +66,7 @@
+ {# 01. INFORMATIONS GÉNÉRALES #}

@@ -93,26 +89,27 @@ {{ form_label(form.ref, 'Référence Interne (SKU)', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }} {{ 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'}}) }}

-
- {# À placer juste après le bloc Référence Interne #} -
- {{ form_label(form.description, 'Description détaillée', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }} - {{ form_widget(form.description, { - 'attr': { - 'is':'crm-editor', - '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) %} -
- {{ form_errors(form.description) }} -
- {% endif %} + +
+ {{ form_label(form.description, 'Description détaillée', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }} + {{ form_widget(form.description, { + 'attr': { + 'is':'crm-editor', + '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) %} +
+ {{ form_errors(form.description) }} +
+ {% endif %} +
+ {# 02. DIMENSIONS DU PRODUIT #} -
+

02 Dimensions de la Structure @@ -155,52 +152,52 @@

- {# COLONNE DROITE : TARIFICATION #} + {# --- COLONNE DROITE --- #}
+ {# 03. FINANCES & TARIFS #}

- 02 + 03 Finances & Tarifs

- {# PRIX LOCATION #} -
-
- {{ form_label(form.priceDay, 'Tarif 1er Jour (€) Ou Tarif weekend (si Barnums)', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-widest ml-1 mb-2 block'}}) }} -
- {{ form_widget(form.priceDay, {'attr': {'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-emerald-400 font-black text-xl focus:ring-emerald-500/20 focus:border-emerald-500 transition-all py-4 px-5'}}) }} - €HT -
-
- -
- {{ form_label(form.priceSup, 'Tarif Jour Sup. (€)', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-widest ml-1 mb-2 block'}}) }} -
- {{ form_widget(form.priceSup, {'attr': {'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-blue-400 font-black text-xl focus:ring-blue-500/20 focus:border-blue-500 transition-all py-4 px-5'}}) }} - €HT -
+
+
+ {{ form_label(form.priceDay, 'Tarif 1er Jour (€)', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-widest ml-1 mb-2 block'}}) }} +

Ou tarif weekend pour les barnums

+
+ {{ form_widget(form.priceDay, {'attr': {'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-emerald-400 font-black text-xl focus:ring-emerald-500/20 focus:border-emerald-500 transition-all py-4 px-5'}}) }} + €HT
-
- {# CAUTION #} -
- {{ form_label(form.caution, 'Montant de la Caution (€)', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-widest ml-1 mb-2 block'}}) }} +
+ {{ form_label(form.priceSup, 'Tarif Jour Sup. (€)', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-widest ml-1 mb-2 block'}}) }}
- {{ form_widget(form.caution, {'attr': {'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 px-5 text-center'}}) }} + {{ form_widget(form.priceSup, {'attr': {'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-blue-400 font-black text-xl focus:ring-blue-500/20 focus:border-blue-500 transition-all py-4 px-5'}}) }} + €HT
-
-
-
- - - -
-

- Information importante - Cette somme sera prélevée sur le compte bancaire du client. Conformément aux politiques de Stripe, les fonds peuvent être bloqués pour un maximum de 4 jours. Passé ce délai, aucun prélèvement ultérieur ne sera possible. -

+
+
+ +
+ +
+ {{ form_label(form.caution, 'Montant de la Caution (€)', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-widest ml-1 mb-2 block'}}) }} +
+ {{ form_widget(form.caution, {'attr': {'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 px-5 text-center'}}) }} +
+
+
+
+ + +
+

+ Information importante + Cette somme sera prélevée sur le compte bancaire du client. Conformément aux politiques de Stripe, les fonds peuvent être bloqués pour un maximum de 4 jours. +

@@ -221,20 +218,20 @@ {{ form_end(form) }} - {# 03. DOCUMENTS TECHNIQUES (PDF) #} + {# 04. DOCUMENTS TECHNIQUES (PDF) #} {% if formDoc is defined %}

- 03 + 04 Documents & Notices

- {# LISTE DES DOCUMENTS EXISTANTS #} + {# LISTE DES DOCUMENTS #}
{% for doc in product.productDocs %}
-
-
+
+
@@ -248,8 +245,8 @@ -
- {# Ici tu pourrais ajouter un lien de suppression #}
{% else %} @@ -270,7 +266,7 @@
- {# FORMULAIRE D'AJOUT #} + {# FORMULAIRE D'AJOUT DOC #} {{ form_start(formDoc) }}
@@ -302,5 +298,120 @@ {{ form_end(formDoc) }}
{% endif %} + + {# 05. VIDEOS #} + {% if formVideo is defined %} +
+

+ 05 + Vidéos +

+ + {# LISTE DES VIDÉOS #} +
+ {% for video in product.productVideos %} +
+ + {# DELETE BUTTON VIDEO #} +
+
+ + +
+
+
+ {% else %} +
+

Aucune vidéo

+
+ {% endfor %} +
+ +
+ + {# FORMULAIRE D'AJOUT VIDEO #} + {{ form_start(formVideo) }} +
+
+ {{ form_label(formVideo.imageFile, 'Fichier Vidéo (MP4, WebM...)', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-widest mb-4 block text-center'}}) }} + {{ form_widget(formVideo.imageFile, { + 'attr': { + 'class': 'block w-full text-xs text-slate-400 file:mr-4 file:py-3 file:px-6 file:rounded-xl file:border-0 file:text-[10px] file:font-black file:uppercase file:tracking-widest file:bg-rose-600 file:text-white hover:file:bg-rose-500 transition-all cursor-pointer' + } + }) }} +
+
+ +
+ +
+ {{ form_end(formVideo) }} +
+ {% endif %} + + {# 06. PHOTOS #} + {% if formPhoto is defined %} +
+

+ 06 + Photos Supplémentaires +

+ + {# LISTE DES PHOTOS #} +
+ {% for photo in product.productPhotos %} +
+ + {# DELETE BUTTON PHOTO #} +
+
+ + +
+
+
+ {% else %} +
+

Aucune photo

+
+ {% endfor %} +
+ +
+ + {# FORMULAIRE D'AJOUT PHOTO #} + {{ form_start(formPhoto) }} +
+
+ {{ form_label(formPhoto.imageFile, 'Fichier Image (JPG, PNG...)', {'label_attr': {'class': 'text-[10px] font-black text-slate-300 uppercase tracking-widest mb-4 block text-center'}}) }} + {{ form_widget(formPhoto.imageFile, { + 'attr': { + 'class': 'block w-full text-xs text-slate-400 file:mr-4 file:py-3 file:px-6 file:rounded-xl file:border-0 file:text-[10px] file:font-black file:uppercase file:tracking-widest file:bg-cyan-600 file:text-white hover:file:bg-cyan-500 transition-all cursor-pointer' + } + }) }} +
+
+ +
+ +
+ {{ form_end(formPhoto) }} +
+ {% endif %}
{% endblock %} diff --git a/templates/revervation/produit.twig b/templates/revervation/produit.twig index 8b87e66..2314048 100644 --- a/templates/revervation/produit.twig +++ b/templates/revervation/produit.twig @@ -124,14 +124,14 @@ {# Grille de badges Tarifs/Caution #}
{% if product.category != "barnums" %} -
- {% if tvaEnabled %} - + {{ (product.priceSup*1.20)|format_currency('EUR') }} TTC - {% else %} - + {{ product.priceSup|format_currency('EUR') }} - {% endif %} - Jour supplémentaire -
+
+ {% if tvaEnabled %} + + {{ (product.priceSup*1.20)|format_currency('EUR') }} TTC + {% else %} + + {{ product.priceSup|format_currency('EUR') }} + {% endif %} + Jour supplémentaire +
{% endif %}
{{ product.caution|format_currency('EUR') }} @@ -210,14 +210,14 @@
{% endif %} {% if product.category != "barnums" %} -
-
-
- Âge conseillé - {{ product.category }} +
+
+
+ Âge conseillé + {{ product.category }} +
-
{% endif %} {# --- ACTIONS FINALES --- #} @@ -242,6 +242,40 @@
+ {# --- GALERIE PHOTOS --- #} + {% if product.productPhotos|length > 0 %} +
+ Galerie Photos +
+ {% for photo in product.productPhotos %} +
+ Photo {{ product.name }} +
+ {% endfor %} +
+
+ {% endif %} + + {# --- VIDEOS --- #} + {% if product.productVideos|length > 0 %} +
+ Vidéos de présentation +
+ {% for video in product.productVideos %} +
+ +
+ {% endfor %} +
+
+ {% endif %} + {# --- DOCUMENTS PUBLICS --- #} {% set publicDocs = product.productDocs|filter(doc => doc.isPublic) %} {% if publicDocs|length > 0 %}