✨ feat(gitignore): Ajoute Catalogue.pdf aux fichiers ignorés. 🎨 style(templates): Ajoute un lien vers le catalogue PDF dans la page produits. ♻️ refactor(pwa): Met à jour l'URL du catalogue PDF dans le fichier PWA. ♻️ refactor(templates): Met à jour l'URL du catalogue PDF dans la base de réservation. ✨ feat(ProductController): Ajoute une route pour mettre à jour le catalogue PDF. ```
213 lines
16 KiB
Twig
213 lines
16 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">
|
|
{# LIEN VERS LE CATALOGUE PDF EXISTANT #}
|
|
<a href="/images/Catalogue.pdf" target="_blank" class="flex items-center space-x-2 px-5 py-3 bg-white/5 hover:bg-white/10 text-slate-300 text-[10px] font-black uppercase tracking-[0.2em] rounded-xl transition-all border border-white/10 group">
|
|
<svg class="w-4 h-4 text-rose-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
|
</svg>
|
|
<span>Voir le Catalogue PDF</span>
|
|
</a>
|
|
|
|
<a data-turbo="false" 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 %}
|
|
{# SECTION PRODUITS #}
|
|
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] overflow-hidden shadow-2xl animate-in fade-in duration-700">
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-left border-collapse">
|
|
<thead>
|
|
<tr class="border-b border-white/5 bg-black/20">
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em]">Visuel & Réf</th>
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em]">Désignation</th>
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em]">Catégorie</th>
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em]">Stripe</th>
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] text-center">Tarif J1</th>
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] text-right">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-white/5">
|
|
{% for product in products %}
|
|
<tr class="group hover:bg-white/[0.02] transition-colors">
|
|
<td class="px-6 py-4">
|
|
<div class="flex items-center space-x-4">
|
|
<div class="h-12 w-12 rounded-xl overflow-hidden border border-white/10 bg-slate-900 flex-shrink-0">
|
|
{% if product.imageName %}
|
|
<img src="{{ vich_uploader_asset(product, 'imageFile') | imagine_filter('webp') }}" class="h-full w-full object-cover">
|
|
{% else %}
|
|
<div class="h-full w-full flex items-center justify-center bg-slate-800 text-slate-600">
|
|
<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="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>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<span class="text-[10px] font-mono font-bold text-blue-500 tracking-wider">{{ product.ref }}</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<span class="text-sm font-bold text-white group-hover:text-blue-400 transition-colors capitalize">{{ product.name }}</span>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<span class="px-3 py-1 bg-white/5 border border-white/10 rounded-lg text-[9px] font-black text-slate-400 uppercase tracking-widest">{{ product.category }}</span>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
{% if product.productId %}
|
|
<div class="flex items-center text-[8px] font-black text-emerald-400 uppercase tracking-widest">
|
|
<span class="h-1.5 w-1.5 rounded-full bg-emerald-500 mr-2 shadow-[0_0_8px_rgba(16,185,129,0.5)]"></span>
|
|
Synchronisé
|
|
</div>
|
|
{% else %}
|
|
<div class="flex items-center text-[8px] font-black text-rose-500 uppercase tracking-widest opacity-60">
|
|
<span class="h-1.5 w-1.5 rounded-full bg-rose-500 mr-2 animate-pulse"></span>
|
|
En attente
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
<td class="px-6 py-4 text-center">
|
|
<span class="text-sm font-black text-emerald-400">{{ product.priceDay|number_format(2, ',', ' ') }}€</span>
|
|
</td>
|
|
<td class="px-6 py-4 text-right">
|
|
<div class="flex items-center justify-end space-x-2">
|
|
<a data-turbo="false" href="{{ path('app_crm_product_edit', {id: product.id}) }}" class="p-2 bg-blue-600/10 hover:bg-blue-600 text-blue-500 hover:text-white rounded-xl transition-all border border-blue-500/20 shadow-lg shadow-blue-600/5">
|
|
<svg class="w-4 h-4" 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>
|
|
</a>
|
|
<a data-turbo="false" href="{{ path('app_crm_product_delete', {id: product.id}) }}?_token={{ csrf_token('delete' ~ product.id) }}" data-turbo-method="post" data-turbo-confirm="Confirmer la suppression ?" class="p-2 bg-rose-500/10 hover:bg-rose-500 text-rose-500 hover:text-white rounded-xl transition-all border border-rose-500/20 shadow-lg shadow-rose-500/5">
|
|
<svg class="w-4 h-4" 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>
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr><td colspan="6" class="py-24 text-center italic text-slate-500 text-[10px] font-black uppercase tracking-[0.2em]">Aucun produit</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{# PAGINATION PRODUITS #}
|
|
{% if products.getTotalItemCount is defined and products.getTotalItemCount > products.getItemNumberPerPage %}
|
|
<div class="mt-8 flex justify-center custom-pagination">{{ knp_pagination_render(products) }}</div>
|
|
{% endif %}
|
|
|
|
{# SECTION OPTIONS #}
|
|
<div class="mt-16 flex items-end justify-between mb-8 pb-6 border-b border-white/5">
|
|
<h2 class="text-4xl font-extrabold text-white">Gestion des <span class="text-blue-500">Options</span></h2>
|
|
<a data-turbo="false" href="{{ path('app_crm_product_options_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>Nouvelle Option</span>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] overflow-hidden shadow-2xl">
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-left border-collapse">
|
|
<thead>
|
|
<tr class="border-b border-white/5 bg-black/20">
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em]">Visuel</th>
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em]">Désignation</th>
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em]">Stripe</th>
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] text-center">Tarif</th>
|
|
<th class="px-6 py-5 text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] text-right">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-white/5">
|
|
{% for option in options %}
|
|
<tr class="group hover:bg-white/[0.02] transition-colors">
|
|
<td class="px-6 py-4">
|
|
<div class="h-12 w-12 rounded-xl overflow-hidden border border-white/10 bg-slate-900">
|
|
{% if option.imageName %}
|
|
<img src="{{ vich_uploader_asset(option, 'imageFile') | imagine_filter('webp') }}" class="h-full w-full object-cover">
|
|
{% else %}
|
|
<div class="h-full w-full flex items-center justify-center bg-slate-800 text-slate-600">
|
|
<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="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>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<span class="text-sm font-bold text-white group-hover:text-blue-400 transition-colors capitalize">{{ option.name }}</span>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
{% if option.stripeId != "" %}
|
|
<div class="flex items-center text-[8px] font-black text-emerald-400 uppercase tracking-widest">
|
|
<span class="h-1.5 w-1.5 rounded-full bg-emerald-500 mr-2"></span> Synchronisé
|
|
</div>
|
|
{% else %}
|
|
<div class="flex items-center text-[8px] font-black text-rose-500 uppercase tracking-widest opacity-60">
|
|
<span class="h-1.5 w-1.5 rounded-full bg-rose-500 mr-2 animate-pulse"></span> En attente
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
<td class="px-6 py-4 text-center">
|
|
<span class="text-sm font-black text-emerald-400">{{ option.priceHt|number_format(2, ',', ' ') }}€</span>
|
|
</td>
|
|
<td class="px-6 py-4 text-right">
|
|
<div class="flex items-center justify-end space-x-2">
|
|
<a data-turbo="false" href="{{ path('app_crm_product_options_edit', {id: option.id}) }}" class="p-2 bg-blue-600/10 hover:bg-blue-600 text-blue-500 hover:text-white rounded-xl transition-all border border-blue-500/20"><svg class="w-4 h-4" 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></a>
|
|
<a data-turbo="false" href="{{ path('app_crm_product_option_delete', {id: option.id}) }}?_token={{ csrf_token('delete' ~ option.id) }}" data-turbo-method="post" data-turbo-confirm="Supprimer ?" class="p-2 bg-rose-500/10 hover:bg-rose-500 text-rose-500 hover:text-white rounded-xl transition-all border border-rose-500/20"><svg class="w-4 h-4" 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>
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr><td colspan="5" class="py-24 text-center italic text-slate-500 text-[10px] font-black uppercase tracking-[0.2em]">Aucune option</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{# SECTION MISE À JOUR CATALOGUE PDF (BOT) #}
|
|
<div class="mt-20">
|
|
<div class="mb-6">
|
|
<h3 class="text-2xl font-extrabold text-white flex items-center space-x-3">
|
|
<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
<span>Mise à jour du <span class="text-blue-500">catalogue PDF</span></span>
|
|
</h3>
|
|
<p class="text-slate-400 text-xs mt-2 uppercase tracking-widest font-bold">Remplacez le fichier PDF accessible par les clients</p>
|
|
</div>
|
|
|
|
<form data-turbo="false" action="{{ path('app_crm_catalogue_upload') }}" method="POST" enctype="multipart/form-data" class="relative group">
|
|
<div class="flex items-center space-x-4 p-4 bg-[#1e293b]/60 border border-white/10 rounded-2xl hover:border-blue-500/30 transition-all">
|
|
<div class="flex-grow">
|
|
<input type="file" name="catalogue_pdf" accept="application/pdf" required
|
|
class="block w-full text-[10px] text-slate-400 font-bold uppercase tracking-widest
|
|
file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0
|
|
file:text-[10px] file:font-black file:uppercase file:tracking-widest
|
|
file:bg-blue-600/20 file:text-blue-400 file:cursor-pointer
|
|
hover:file:bg-blue-600/30 transition-all">
|
|
</div>
|
|
<button type="submit" class="px-6 py-2 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">
|
|
Mettre à jour
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<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 %}
|