Files
ludikevent_crm/templates/dashboard/customer.twig
Serreau Jovann 890da18c15 ```
 feat(Stripe): Intègre Stripe pour la gestion des paiements et les webhooks

Ajoute Stripe pour la synchronisation des clients et la configuration des webhooks.
Crée une commande pour synchroniser les clients locaux avec Stripe.
Ajoute un champ customerId à l'entité Customer.
```
2026-01-16 13:15:42 +01:00

175 lines
11 KiB
Twig

{% extends 'dashboard/base.twig' %}
{% block title %}Gestion Clients{% endblock %}
{% block title_header %}Annuaire <span class="text-blue-500">Clients</span>{% endblock %}
{% block actions %}
<div class="flex items-center space-x-3">
<a href="{{ path('app_crm_customer_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 Client</span>
</a>
</div>
{% endblock %}
{% block body %}
<div class="w-full backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] shadow-2xl overflow-hidden">
{# HEADER TABLEAU #}
<div class="px-8 py-6 border-b border-white/5 bg-white/5 flex items-center justify-between">
<div>
<h2 class="text-xl font-bold text-white tracking-tight">Liste des clients</h2>
<p class="text-[10px] text-slate-500 font-bold uppercase tracking-widest mt-1">Base de données centralisée</p>
</div>
<span class="px-4 py-1.5 bg-blue-500/10 text-blue-400 text-[10px] font-black uppercase rounded-lg border border-blue-500/20">
{{ customers|length }} CONTACTS
</span>
</div>
<div class="overflow-x-auto custom-scrollbar">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-slate-900/40 border-b border-white/5">
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest">Identité</th>
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest text-center">Type</th>
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest">Coordonnées</th>
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest">SIRET / ID</th>
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
{% for customer in customers %}
<tr class="hover:bg-white/5 transition-all group">
{# 1. IDENTITÉ #}
<td class="px-8 py-6 whitespace-nowrap">
<div class="flex items-center">
{# Avatar avec initiales #}
<div class="h-10 w-10 rounded-xl bg-gradient-to-br from-slate-700 to-slate-800 flex flex-shrink-0 items-center justify-center text-white font-black text-xs border border-white/10 shadow-lg">
{{ customer.surname|first|upper }}{{ customer.name|first|upper }}
</div>
<div class="ml-4">
{# Nom et Civilité #}
<div class="text-sm font-bold text-white">
<span class="text-slate-500 text-[10px] uppercase mr-1">{{ customer.civ }}</span>
{{ customer.surname|upper }} {{ customer.name }}
</div>
{# ID Interne et État Stripe #}
<div class="flex items-center mt-1 space-x-2">
{# Badge ID Interne #}
<span class="text-[9px] font-bold text-slate-500 tracking-tighter">
ID: #{{ customer.id }}
</span>
{% if customer.customerId %}
{# ÉTAT : SYNCHRONISÉ (VERT) #}
<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 %}
{# ÉTAT : NON SYNCHRONISÉ (ROUGE) #}
<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>
Stripe non synchronisé
</div>
{% endif %}
</div>
</div>
</div>
</td>
{# 2. TYPE (Badge dynamique) #}
<td class="px-8 py-6 text-center">
{% set typeStyles = {
'company': 'bg-purple-500/10 text-purple-400 border-purple-500/20',
'personal': 'bg-sky-500/10 text-sky-400 border-sky-500/20',
'association': 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',
'mairie': 'bg-amber-500/10 text-amber-400 border-amber-500/20'
} %}
<span class="px-3 py-1 rounded-lg text-[9px] font-black border uppercase tracking-widest {{ typeStyles[customer.type] ?? 'bg-slate-500/10 text-slate-400' }}">
{{ customer.type|default('Indéfini') }}
</span>
</td>
{# 3. COORDONNÉES #}
<td class="px-8 py-6">
<div class="flex flex-col space-y-1">
<div class="flex items-center text-slate-300 text-xs">
<svg class="w-3.5 h-3.5 mr-2 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>
{{ customer.email }}
</div>
<div class="flex items-center text-slate-400 text-[11px] font-mono">
<svg class="w-3.5 h-3.5 mr-2 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" /></svg>
{{ customer.phone }}
</div>
</div>
</td>
{# 4. SIRET #}
<td class="px-8 py-6">
{% if customer.siret %}
<div class="text-[10px] font-mono text-slate-500 bg-black/20 px-2 py-1 rounded border border-white/5 inline-block">
{{ customer.siret }}
</div>
{% else %}
<span class="text-[10px] text-slate-600 italic">N/A</span>
{% endif %}
</td>
{# 5. ACTIONS #}
<td class="px-8 py-6 text-right">
<div class="flex items-center justify-end space-x-2">
<a href="#" class="p-2 text-slate-400 hover:text-blue-400 hover:bg-blue-400/10 rounded-lg transition-all" title="Modifier">
<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="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 href="#" class="p-2 text-slate-400 hover:text-white hover:bg-white/10 rounded-lg transition-all" title="Voir fiche">
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
</a>
</div>
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="px-8 py-20 text-center italic text-slate-500 font-medium">
Aucun client enregistré dans la base.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{# PAGINATION #}
<div class="px-8 py-8 bg-black/20 border-t border-white/5">
<div class="flex flex-col md:flex-row items-center justify-between gap-6">
<div class="text-[10px] font-black text-slate-500 uppercase tracking-[0.2em]">
Affichage de {{ customers.getItemNumberPerPage }} clients par page
</div>
<div class="navigation custom-pagination">
{{ knp_pagination_render(customers) }}
</div>
</div>
</div>
</div> {# Fin du conteneur principal #}
{# CSS pour styliser KnpPagination aux couleurs de ton dashboard #}
<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-white/5 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 %}