feat(Product): Ajoute un champ productId pour la synchronisation Stripe.

🎨 style(product/products.twig): Affiche l'état de synchronisation Stripe.
```
This commit is contained in:
Serreau Jovann
2026-01-16 13:55:11 +01:00
parent 1304260c1b
commit 85b3f631d1
3 changed files with 72 additions and 15 deletions

View 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 Version20260116125355 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 product_id VARCHAR(255) 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 product_id');
}
}

View File

@@ -49,6 +49,9 @@ class Product
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $updatedAt = null; private ?\DateTimeImmutable $updatedAt = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $productId = null;
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@@ -173,4 +176,16 @@ class Product
{ {
return $this->imageSize; return $this->imageSize;
} }
public function getProductId(): ?string
{
return $this->productId;
}
public function setProductId(?string $productId): static
{
$this->productId = $productId;
return $this;
}
} }

View File

@@ -44,28 +44,43 @@
{# CONTENU #} {# CONTENU #}
<div class="p-8 flex-1 flex flex-col"> <div class="p-8 flex-1 flex flex-col">
{# REF, NOM #} {# REF, NOM & STRIPE SYNC #}
<div class="mb-8"> <div class="mb-8">
<p class="text-[10px] font-black text-blue-500 uppercase tracking-[0.3em] mb-1">{{ product.ref }}</p> <p class="text-[10px] font-black text-blue-500 uppercase tracking-[0.3em] mb-1">{{ product.ref }}</p>
<h3 class="text-2xl font-bold text-white tracking-tight group-hover:text-blue-400 transition-colors"> <h3 class="text-2xl font-bold text-white tracking-tight group-hover:text-blue-400 transition-colors mb-2">
{{ product.name }} {{ product.name }}
</h3> </h3>
{# ETAT SYNCHRO STRIPE (Même style que Client) #}
<div class="flex items-center">
{% if product.productId %}
<div class="flex items-center text-[8px] font-black text-emerald-400 uppercase tracking-[0.1em] bg-emerald-500/10 px-2 py-0.5 rounded-md border border-emerald-500/30 shadow-sm shadow-emerald-500/10">
<span class="flex h-1.5 w-1.5 rounded-full bg-emerald-500 mr-2"></span>
Stripe synchronisé
</div>
{% else %}
<div class="flex items-center text-[8px] font-black text-rose-500 uppercase tracking-[0.1em] bg-rose-500/10 px-2 py-0.5 rounded-md border border-rose-500/30 shadow-sm shadow-rose-500/10">
<span class="flex h-1.5 w-1.5 rounded-full bg-rose-500 mr-2 animate-pulse"></span>
Non synchronisé Stripe
</div>
{% endif %}
</div>
</div> </div>
{# GRILLE TARIFS (priceDay & priceSup) #} {# GRILLE TARIFS (priceDay & priceSup) #}
<div class="grid grid-cols-2 gap-4 mb-8"> <div class="grid grid-cols-2 gap-4 mb-8">
<div class="p-4 bg-slate-900/40 rounded-2xl border border-white/5"> <div class="p-4 bg-slate-900/40 rounded-2xl border border-white/5 text-center">
<p class="text-[8px] font-bold text-slate-500 uppercase tracking-widest mb-1">Prix Journée</p> <p class="text-[8px] font-bold text-slate-500 uppercase tracking-widest mb-1">Prix Journée</p>
<p class="text-lg font-black text-emerald-400">{{ product.priceDay|number_format(2, ',', ' ') }}€</p> <p class="text-lg font-black text-emerald-400">{{ product.priceDay|number_format(2, ',', ' ') }}€</p>
</div> </div>
<div class="p-4 bg-slate-900/40 rounded-2xl border border-white/5"> <div class="p-4 bg-slate-900/40 rounded-2xl border border-white/5 text-center">
<p class="text-[8px] font-bold text-slate-500 uppercase tracking-widest mb-1">Jour Sup.</p> <p class="text-[8px] font-bold text-slate-500 uppercase tracking-widest mb-1">Jour Sup.</p>
<p class="text-lg font-black text-blue-400">{{ product.priceSup|number_format(2, ',', ' ') }}€</p> <p class="text-lg font-black text-blue-400">{{ product.priceSup|number_format(2, ',', ' ') }}€</p>
</div> </div>
</div> </div>
{# CAUTION & INSTALLATION #} {# CAUTION & INSTALLATION #}
<div class="space-y-4 mb-8"> <div class="space-y-4 mb-8 bg-black/10 p-5 rounded-[1.5rem] border border-white/5">
<div class="flex items-center justify-between text-[11px]"> <div class="flex items-center justify-between text-[11px]">
<span class="text-slate-500 font-bold uppercase tracking-widest flex items-center"> <span class="text-slate-500 font-bold uppercase tracking-widest flex items-center">
<svg class="w-3.5 h-3.5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg> <svg class="w-3.5 h-3.5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
@@ -86,19 +101,19 @@
{# ACTIONS #} {# ACTIONS #}
<div class="mt-auto pt-6 border-t border-white/5 flex items-center justify-between"> <div class="mt-auto pt-6 border-t border-white/5 flex items-center justify-between">
{# Bouton Modifier (Redirection vers la fiche) #} {# Bouton Modifier #}
<a href="{{ path('app_crm_product_edit', {id: product.id}) }}" class="flex items-center text-[10px] font-black text-slate-500 hover:text-blue-400 uppercase tracking-widest transition-all group/btn"> <a href="{{ path('app_crm_product_edit', {id: product.id}) }}" class="flex items-center text-[10px] font-black text-slate-500 hover:text-blue-400 uppercase tracking-widest transition-all group/btn">
<svg class="w-4 h-4 mr-2 transition-transform group-hover/btn:scale-110" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 mr-2 transition-transform group-hover/btn:scale-110" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg> </svg>
Modifier le produit Modifier
</a> </a>
{# Bouton Supprimer (Identique aux clients) #} {# Bouton Supprimer #}
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<a href="{{ path('app_crm_product_delete', {id: product.id}) }}?_token={{ csrf_token('delete' ~ product.id) }}" <a href="{{ path('app_crm_product_delete', {id: product.id}) }}?_token={{ csrf_token('delete' ~ product.id) }}"
data-turbo-method="post" data-turbo-method="post"
data-turbo-confirm="Voulez-vous vraiment supprimer le produit '{{ product.name }}' ? Cette action est irréversible." data-turbo-confirm="Supprimer définitivement '{{ product.name }}' ?"
class="p-2.5 text-slate-500 hover:text-rose-500 hover:bg-rose-500/10 rounded-xl transition-all border border-transparent hover:border-rose-500/20" class="p-2.5 text-slate-500 hover:text-rose-500 hover:bg-rose-500/10 rounded-xl transition-all border border-transparent hover:border-rose-500/20"
title="Supprimer"> title="Supprimer">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -110,12 +125,7 @@
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="col-span-full py-24 text-center backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[3rem]"> {# ... vide ... #}
<div class="w-20 h-20 bg-slate-800 rounded-3xl flex items-center justify-center mx-auto mb-6 border border-white/5">
<svg class="w-10 h-10 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" /></svg>
</div>
<p class="text-slate-500 italic uppercase tracking-[0.2em] text-xs font-black">Le catalogue est actuellement vide</p>
</div>
{% endfor %} {% endfor %}
</div> </div>