✨ feat(templates-points-controle): Ajoute la gestion et l'application de modèles de points de contrôle aux produits.
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
|
||||
export class FlowReserve extends HTMLAnchorElement {
|
||||
constructor() {
|
||||
super();
|
||||
@@ -165,55 +164,56 @@ export class FlowReserve extends HTMLAnchorElement {
|
||||
}
|
||||
}
|
||||
|
||||
ensureSidebarExists() {
|
||||
if (document.getElementById(this.sidebarId)) return;
|
||||
ensureSidebarExists() {
|
||||
if (document.getElementById(this.sidebarId)) return;
|
||||
|
||||
const template = `
|
||||
<div id="${this.sidebarId}" class="fixed inset-0 z-[100] flex justify-end pointer-events-none">
|
||||
<!-- Backdrop -->
|
||||
<div class="backdrop absolute inset-0 bg-slate-900/60 backdrop-blur-sm opacity-0 transition-opacity duration-300"></div>
|
||||
const template = `
|
||||
<div id="${this.sidebarId}" class="fixed inset-0 z-[100] flex justify-end pointer-events-none">
|
||||
<!-- Backdrop -->
|
||||
<div class="backdrop absolute inset-0 bg-slate-900/60 backdrop-blur-sm opacity-0 transition-opacity duration-300"></div>
|
||||
|
||||
<!-- Panel -->
|
||||
<div class="panel w-full max-w-md bg-white shadow-2xl translate-x-full transition-transform duration-300 flex flex-col relative z-10">
|
||||
<!-- Panel -->
|
||||
<div class="panel w-full max-w-md bg-white shadow-2xl translate-x-full transition-transform duration-300 flex flex-col relative z-10">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="p-6 border-b border-gray-100 flex items-center justify-between bg-white">
|
||||
<div>
|
||||
<h2 class="text-2xl font-black text-slate-900 uppercase italic tracking-tighter">Ma Super <br><span class="text-[#f39e36]">Future Réservation</span></h2>
|
||||
</div>
|
||||
<button id="flow-reserve-close" class="p-2 hover:bg-gray-100 rounded-full transition-colors">
|
||||
<svg class="w-6 h-6 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>
|
||||
<!-- Header -->
|
||||
<div class="p-6 border-b border-gray-100 flex items-center justify-between bg-white">
|
||||
<div>
|
||||
<h2 class="text-2xl font-black text-slate-900 uppercase italic tracking-tighter">Ma Super <br><span class="text-[#f39e36]">Future Réservation</span></h2>
|
||||
</div>
|
||||
<button id="flow-reserve-close" class="p-2 hover:bg-gray-100 rounded-full transition-colors">
|
||||
<svg class="w-6 h-6 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Content (Loader/List/Empty) -->
|
||||
<div id="flow-reserve-content" class="flex-1 overflow-y-auto p-6 space-y-6 bg-gray-50/50 relative">
|
||||
<!-- Content (Loader/List/Empty) -->
|
||||
<div id="flow-reserve-content" class="flex-1 overflow-y-auto p-6 space-y-6 bg-gray-50/50 relative">
|
||||
<!-- Content injected via JS -->
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div id="flow-reserve-footer" class="p-6 border-t border-gray-100 bg-white">
|
||||
<!-- Content injected via JS -->
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div id="flow-reserve-footer" class="p-6 border-t border-gray-100 bg-white">
|
||||
<!-- Content injected via JS -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.insertAdjacentHTML('beforeend', template);
|
||||
</div>
|
||||
`;
|
||||
document.body.insertAdjacentHTML('beforeend', template);
|
||||
|
||||
// Bind events
|
||||
const sidebar = document.getElementById(this.sidebarId);
|
||||
// Bind events
|
||||
const sidebar = document.getElementById(this.sidebarId);
|
||||
|
||||
const closeHandler = (e) => {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
const closeHandler = (e) => {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
|
||||
sidebar.querySelector('.backdrop').addEventListener('click', closeHandler);
|
||||
sidebar.querySelector('#flow-reserve-close').addEventListener('click', closeHandler);
|
||||
}
|
||||
|
||||
sidebar.querySelector('.backdrop').addEventListener('click', closeHandler);
|
||||
sidebar.querySelector('#flow-reserve-close').addEventListener('click', closeHandler);
|
||||
}
|
||||
async refreshContent() {
|
||||
const container = document.getElementById('flow-reserve-content');
|
||||
const footer = document.getElementById('flow-reserve-footer');
|
||||
@@ -284,7 +284,6 @@ export class FlowReserve extends HTMLAnchorElement {
|
||||
if (currentList.length !== initialLength) {
|
||||
sessionStorage.setItem(this.storageKey, JSON.stringify(currentList));
|
||||
window.dispatchEvent(new CustomEvent('cart:updated'));
|
||||
// We don't recurse here to avoid infinite loops, but the UI will update next time or we could just use the returned 'products' which already excludes them.
|
||||
console.warn('Certains produits ont été retirés car ils n\'existent plus:', data.unavailable_products_ids);
|
||||
}
|
||||
}
|
||||
|
||||
34
migrations/Version20260209062832.php
Normal file
34
migrations/Version20260209062832.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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 Version20260209062832 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('CREATE TABLE product_promo (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, percentage DOUBLE PRECISION NOT NULL, date_start TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, date_end TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, product_id INT NOT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE INDEX IDX_114FE1A74584665A ON product_promo (product_id)');
|
||||
$this->addSql('ALTER TABLE product_promo ADD CONSTRAINT FK_114FE1A74584665A FOREIGN KEY (product_id) REFERENCES product (id) NOT DEFERRABLE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE product_promo DROP CONSTRAINT FK_114FE1A74584665A');
|
||||
$this->addSql('DROP TABLE product_promo');
|
||||
}
|
||||
}
|
||||
36
migrations/Version20260209065100.php
Normal file
36
migrations/Version20260209065100.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 Version20260209065100 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('CREATE TABLE promotion (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, percentage DOUBLE PRECISION NOT NULL, date_start TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, date_end TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('ALTER TABLE product_promo DROP CONSTRAINT fk_114fe1a74584665a');
|
||||
$this->addSql('DROP TABLE product_promo');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE product_promo (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, percentage DOUBLE PRECISION NOT NULL, date_start TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, date_end TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, product_id INT NOT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE INDEX idx_114fe1a74584665a ON product_promo (product_id)');
|
||||
$this->addSql('ALTER TABLE product_promo ADD CONSTRAINT fk_114fe1a74584665a FOREIGN KEY (product_id) REFERENCES product (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('DROP TABLE promotion');
|
||||
}
|
||||
}
|
||||
@@ -403,7 +403,10 @@ class ReserverController extends AbstractController
|
||||
$session->setState('created');
|
||||
}
|
||||
|
||||
$session->setProducts($data ?? []);
|
||||
// Save promos along with other data
|
||||
$sessionData = $data ?? [];
|
||||
// Ensure promos key exists if sent (it should be in $data if frontend sends it)
|
||||
$session->setProducts($sessionData);
|
||||
|
||||
$user = $this->getUser();
|
||||
if ($user instanceof Customer) {
|
||||
|
||||
81
src/Entity/Promotion.php
Normal file
81
src/Entity/Promotion.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\PromotionRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PromotionRepository::class)]
|
||||
class Promotion
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $percentage = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
|
||||
private ?\DateTimeInterface $dateStart = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
|
||||
private ?\DateTimeInterface $dateEnd = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPercentage(): ?float
|
||||
{
|
||||
return $this->percentage;
|
||||
}
|
||||
|
||||
public function setPercentage(float $percentage): static
|
||||
{
|
||||
$this->percentage = $percentage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDateStart(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->dateStart;
|
||||
}
|
||||
|
||||
public function setDateStart(\DateTimeInterface $dateStart): static
|
||||
{
|
||||
$this->dateStart = $dateStart;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDateEnd(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->dateEnd;
|
||||
}
|
||||
|
||||
public function setDateEnd(\DateTimeInterface $dateEnd): static
|
||||
{
|
||||
$this->dateEnd = $dateEnd;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
28
src/Repository/PromotionRepository.php
Normal file
28
src/Repository/PromotionRepository.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Promotion;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Promotion>
|
||||
*/
|
||||
class PromotionRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Promotion::class);
|
||||
}
|
||||
|
||||
public function findActivePromotions(\DateTimeInterface $date): array
|
||||
{
|
||||
return $this->createQueryBuilder('p')
|
||||
->where('p.dateStart <= :date')
|
||||
->andWhere('p.dateEnd >= :date')
|
||||
->setParameter('date', $date)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user