Files
ludikevent_crm/templates/product/products.twig
Serreau Jovann 85b3f631d1 ```
 feat(Product): Ajoute un champ productId pour la synchronisation Stripe.

🎨 style(product/products.twig): Affiche l'état de synchronisation Stripe.
```
2026-01-16 13:55:11 +01:00

153 lines
10 KiB
Twig

{% extends 'dashboard/base.twig' %}
{% block title %}Catalogue Produits{% endblock %}
{% block title_header %}Gestion du <span class="text-blue-500">Matériel</span>{% endblock %}
{% block actions %}
<div class="flex items-center space-x-3">
<a href="{{ path('app_crm_product_add') }}" class="flex items-center space-x-2 px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white text-[10px] font-black uppercase tracking-[0.2em] rounded-xl transition-all shadow-lg shadow-blue-600/20 group">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M12 4v16m8-8H4" />
</svg>
<span>Nouveau Produit</span>
</a>
</div>
{% endblock %}
{% block body %}
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8 animate-in fade-in duration-700">
{% for product in products %}
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] overflow-hidden group hover:border-blue-500/30 transition-all duration-500 hover:shadow-2xl hover:shadow-blue-500/10 flex flex-col">
{# IMAGE DU PRODUIT #}
<div class="relative h-64 w-full overflow-hidden bg-slate-900/50">
{% if product.imageName is not null %}
<img src="{{ vich_uploader_asset(product, 'imageFile') }}"
alt="{{ product.name }}"
class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110">
{% else %}
<div class="w-full h-full flex flex-col items-center justify-center text-slate-600">
<svg class="w-12 h-12 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span class="text-[10px] font-bold uppercase tracking-widest">Aucun visuel</span>
</div>
{% endif %}
{# BADGE CATEGORIE #}
<div class="absolute top-6 left-6">
<span class="px-4 py-1.5 backdrop-blur-md bg-black/40 border border-white/10 text-white text-[9px] font-black uppercase tracking-widest rounded-lg">
{{ product.category }}
</span>
</div>
</div>
{# CONTENU #}
<div class="p-8 flex-1 flex flex-col">
{# REF, NOM & STRIPE SYNC #}
<div class="mb-8">
<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 mb-2">
{{ product.name }}
</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>
{# GRILLE TARIFS (priceDay & priceSup) #}
<div class="grid grid-cols-2 gap-4 mb-8">
<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-lg font-black text-emerald-400">{{ product.priceDay|number_format(2, ',', ' ') }}€</p>
</div>
<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-lg font-black text-blue-400">{{ product.priceSup|number_format(2, ',', ' ') }}€</p>
</div>
</div>
{# CAUTION & INSTALLATION #}
<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]">
<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>
Caution
</span>
<span class="text-slate-300 font-mono bg-white/5 px-2 py-0.5 rounded">{{ product.caution|number_format(0, ',', ' ') }}€</span>
</div>
<div class="flex items-center justify-between text-[11px]">
<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="M11 4a2 2 0 114 0v1a2 2 0 01-2 2H3m2 4l-2 2m0 0l2 2m-2-2h8m-5-8h4a2 2 0 012 2v1a2 2 0 01-2 2H3" /></svg>
Installation
</span>
<span class="font-bold {{ product.installation ? 'text-emerald-500' : 'text-slate-600' }}">
{{ product.installation ? 'INCLUS' : 'NON INCLUS' }}
</span>
</div>
</div>
{# ACTIONS #}
<div class="mt-auto pt-6 border-t border-white/5 flex items-center justify-between">
{# 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">
<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" />
</svg>
Modifier
</a>
{# Bouton Supprimer #}
<div class="flex items-center space-x-1">
<a href="{{ path('app_crm_product_delete', {id: product.id}) }}?_token={{ csrf_token('delete' ~ product.id) }}"
data-turbo-method="post"
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"
title="Supprimer">
<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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</a>
</div>
</div>
</div>
</div>
{% else %}
{# ... vide ... #}
{% endfor %}
</div>
{# PAGINATION #}
{% if products.getTotalItemCount is defined and products.getTotalItemCount > products.getItemNumberPerPage %}
<div class="mt-12 flex justify-center custom-pagination">
{{ knp_pagination_render(products) }}
</div>
{% endif %}
<style>
.custom-pagination nav ul { @apply flex space-x-2; }
.custom-pagination nav ul li span,
.custom-pagination nav ul li a {
@apply px-4 py-2 rounded-xl bg-[#1e293b]/40 backdrop-blur-md border border-white/5 text-slate-400 text-xs font-bold transition-all;
}
.custom-pagination nav ul li.active span {
@apply bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-600/20;
}
.custom-pagination nav ul li a:hover {
@apply bg-white/10 text-white border-white/20;
}
</style>
{% endblock %}