Files
ludikevent_crm/templates/dashboard/contrats/view.twig
Serreau Jovann e530538af8 ```text
 feat(contrats): Ajoute détails option, actions paiements et style liste

Ajoute un champ détails pour les options de contrat, permet la validation
manuelle des paiements (accompte, caution, solde) et améliore le style
de la liste des contrats.
```
2026-01-29 10:40:03 +01:00

290 lines
22 KiB
Twig

{% extends 'dashboard/base.twig' %}
{% block title %}Contrat N°{{ contrat.numReservation }}{% endblock %}
{% block actions %}
<div class="flex items-center gap-3">
{% if contrat.signed %}
<a href="{{ vich_uploader_asset(contrat,'devisSignFile') }}" download
class="flex items-center gap-2 px-6 py-2.5 bg-emerald-500/10 hover:bg-emerald-500/20 border border-emerald-500/20 backdrop-blur-md rounded-xl text-emerald-400 text-[10px] font-black uppercase italic transition-all group">
<svg class="w-4 h-4 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Contrat Signé PDF
</a>
<a href="{{ vich_uploader_asset(contrat,'devisAuditFile') }}" download
class="flex items-center gap-2 px-6 py-2.5 bg-white/5 hover:bg-white/10 border border-white/10 backdrop-blur-md rounded-xl text-white text-[10px] font-black uppercase italic transition-all group">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2a4 4 0 00-4-4H5m11 9a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h11a2 2 0 012 2v11z" />
</svg>
Audit Signature
</a>
{% else %}
<a href="{{ vich_uploader_asset(contrat,'devisFile') }}" download
class="flex items-center gap-2 px-6 py-2.5 bg-rose-500/10 hover:bg-rose-500/20 border border-rose-500/20 backdrop-blur-md rounded-xl text-rose-500 text-[10px] font-black uppercase italic transition-all group">
<svg class="w-4 h-4 group-hover:rotate-12 transition-transform" 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>
Télécharger Contrat PDF
</a>
{% endif %}
</div>
{% endblock %}
{% block body %}
{# Définition des états de paiement #}
{% set acompteOk = contratPaymentPay(contrat, 'accompte') %}
{% set cautionOk = contratPaymentPay(contrat, 'caution') %}
{% set soldeOk = (solde <= 0.05) %}
<div class="space-y-8 pb-20">
{# --- 1. BANDEAU DE STATUT RAPIDE --- #}
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="p-4 rounded-[1.5rem] bg-white/5 border border-white/10 backdrop-blur-md flex items-center justify-between">
<span class="text-[10px] font-black uppercase text-slate-500 tracking-widest">État Juridique</span>
{% if contrat.signed %}
<span class="px-3 py-1 bg-emerald-500/10 text-emerald-400 text-[9px] font-black rounded-lg border border-emerald-500/20">SIGNÉ</span>
{% else %}
<span class="px-3 py-1 bg-amber-500/10 text-amber-400 text-[9px] font-black rounded-lg border border-amber-500/20 animate-pulse">EN ATTENTE</span>
{% endif %}
</div>
<div class="p-4 rounded-[1.5rem] bg-white/5 border border-white/10 backdrop-blur-md flex items-center justify-between">
<span class="text-[10px] font-black uppercase text-slate-500 tracking-widest">Reste à percevoir</span>
<span class="text-xs font-black {{ soldeOk ? 'text-emerald-400' : 'text-rose-500' }} italic">
{{ soldeOk ? 'CONTRAT SOLDÉ' : (solde|number_format(2, ',', ' ') ~ ' €') }}
</span>
</div>
<div class="p-4 rounded-[1.5rem] bg-white/5 border border-white/10 backdrop-blur-md flex items-center justify-between">
<span class="text-[10px] font-black uppercase text-slate-500 tracking-widest">Référence</span>
<span class="text-xs font-black text-blue-400 italic">#{{ contrat.numReservation }}</span>
</div>
</div>
{# --- 2. INFOS CLIENT & ÉVÉNEMENT --- #}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
{# CARTE CLIENT #}
<div class="relative group">
<div class="absolute -inset-0.5 bg-gradient-to-br from-blue-500/20 to-purple-500/20 rounded-[2.5rem] blur opacity-50"></div>
<div class="relative h-full p-8 bg-slate-900/40 border border-white/10 backdrop-blur-xl rounded-[2.5rem]">
<div class="flex items-center gap-4 mb-8">
<div class="w-14 h-14 bg-blue-600/20 rounded-2xl flex items-center justify-center border border-blue-500/30">
<svg class="w-7 h-7 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
</div>
<div>
<h2 class="text-xl font-black text-white italic tracking-tighter uppercase">{{ contrat.customer.surname }} {{ contrat.customer.name }}</h2>
<p class="text-slate-500 font-bold text-[10px] uppercase tracking-widest">Informations Locataire</p>
</div>
</div>
<div class="space-y-3">
<div class="flex items-center gap-3 p-4 rounded-2xl bg-white/[0.02] border border-white/5 text-slate-300 text-sm">
<svg class="w-4 h-4 text-blue-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" stroke-width="2"/></svg>
{{ contrat.customer.email }}
</div>
<div class="flex items-center gap-3 p-4 rounded-2xl bg-white/[0.02] border border-white/5 text-slate-300 text-sm">
<svg class="w-4 h-4 text-blue-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" stroke-width="2"/></svg>
{{ contrat.customer.phone }}
</div>
</div>
</div>
</div>
{# CARTE ÉVÉNEMENT #}
<div class="relative group">
<div class="absolute -inset-0.5 bg-gradient-to-br from-emerald-500/20 to-teal-500/20 rounded-[2.5rem] blur opacity-50"></div>
<div class="relative h-full p-8 bg-slate-900/40 border border-white/10 backdrop-blur-xl rounded-[2.5rem]">
<div class="flex items-center gap-4 mb-8">
<div class="w-14 h-14 bg-emerald-600/20 rounded-2xl flex items-center justify-center border border-emerald-500/30">
<svg class="w-7 h-7 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" /></svg>
</div>
<div>
<h2 class="text-xl font-black text-white italic tracking-tighter uppercase">Lieu de l'événement</h2>
<p class="text-slate-500 font-bold text-[10px] uppercase tracking-widest">{{ contrat.dateAt|date('d/m/Y') }} - {{ contrat.endAt|date('d/m/Y') }}</p>
</div>
</div>
<div class="p-5 rounded-2xl bg-white/[0.02] border border-white/5">
<span class="text-[9px] font-black text-slate-500 uppercase tracking-widest block mb-1">Adresse de livraison</span>
<p class="text-white text-base font-bold">{{ contrat.addressEvent }}</p>
<p class="text-emerald-400 font-black text-sm uppercase italic">{{ contrat.zipCodeEvent }} {{ contrat.townEvent }}</p>
</div>
</div>
</div>
</div>
{# --- 3. ÉTAT DES PAIEMENTS AVEC ACTIONS DE GESTION --- #}
<div class="p-8 bg-white/[0.02] border border-white/10 rounded-[2.5rem] backdrop-blur-md shadow-2xl">
<div class="flex flex-col md:flex-row gap-8 justify-around items-start">
{# --- ACOMPTE --- #}
<div class="flex flex-col items-center gap-4 text-center w-full">
<div class="w-16 h-16 rounded-2xl flex items-center justify-center border transition-all duration-500
{{ acompteOk ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30 shadow-[0_0_20px_rgba(16,185,129,0.1)]' : 'bg-rose-500/10 text-rose-500 border-rose-500/20' }}">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{% if acompteOk %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/>{% else %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>{% endif %}
</svg>
</div>
<div>
<span class="block text-[9px] font-black uppercase tracking-widest {{ acompteOk ? 'text-emerald-500' : 'text-rose-500' }}">Acompte</span>
{% if not acompteOk %}
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id, type: 'accompte'}) }}"
class="mt-3 inline-flex px-4 py-1.5 bg-rose-500/20 hover:bg-rose-500/30 border border-rose-500/30 rounded-lg text-[10px] font-black text-rose-400 uppercase transition-all">
Marquer réglé
</a>
{% else %}
<p class="mt-2 text-[10px] text-emerald-500/60 font-bold uppercase italic">Encaissé</p>
{% endif %}
</div>
</div>
<div class="hidden md:block w-px h-20 bg-white/5 self-center"></div>
{# --- CAUTION --- #}
<div class="flex flex-col items-center gap-4 text-center w-full">
<div class="w-16 h-16 rounded-2xl flex items-center justify-center border transition-all duration-500
{{ cautionOk ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30 shadow-[0_0_20px_rgba(16,185,129,0.1)]' : 'bg-rose-500/20 text-rose-500 border-rose-500/30 animate-pulse' }}">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{% if cautionOk %}<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"/>{% else %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>{% endif %}
</svg>
</div>
<div>
<span class="block text-[9px] font-black uppercase tracking-widest {{ cautionOk ? 'text-emerald-500' : 'text-rose-500' }}">Caution</span>
<div class="flex flex-col gap-2 mt-3">
{% if not cautionOk %}
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id, type: 'caution'}) }}"
class="px-4 py-1.5 bg-rose-500/20 hover:bg-rose-500/30 border border-rose-500/30 rounded-lg text-[10px] font-black text-rose-400 uppercase transition-all">
Marquer reçue
</a>
{% else %}
<div class="flex gap-2">
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id, action: 'encaisser'}) }}"
class="px-3 py-1.5 bg-amber-500/20 hover:bg-amber-500/30 border border-amber-500/30 rounded-lg text-[9px] font-black text-amber-500 uppercase transition-all">
Encaisser
</a>
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id, action: 'liberer'}) }}"
class="px-3 py-1.5 bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/30 rounded-lg text-[9px] font-black text-emerald-400 uppercase transition-all">
Libérer
</a>
</div>
{% endif %}
</div>
</div>
</div>
<div class="hidden md:block w-px h-20 bg-white/5 self-center"></div>
{# --- SOLDE --- #}
<div class="flex flex-col items-center gap-4 text-center w-full">
<div class="w-16 h-16 rounded-2xl flex items-center justify-center border transition-all duration-500
{{ soldeOk ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30 shadow-[0_0_20px_rgba(16,185,129,0.1)]' : 'bg-rose-500/10 text-rose-500 border-rose-500/20' }}">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{% if soldeOk %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>{% else %}<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/>{% endif %}
</svg>
</div>
<div>
<span class="block text-[9px] font-black uppercase tracking-widest {{ soldeOk ? 'text-emerald-500' : 'text-rose-500' }}">Solde Final</span>
{% if not soldeOk %}
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id, type: 'solde'}) }}"
class="px-4 py-1.5 bg-rose-500/20 hover:bg-rose-500/30 border border-rose-500/30 rounded-lg text-[10px] font-black text-rose-400 uppercase transition-all">
Régler le solde
</a>
{% else %}
<p class="mt-2 text-[10px] text-emerald-500/60 font-bold uppercase italic">Totalité payée</p>
{% endif %}
</div>
</div>
</div>
</div>
{# --- 4. PRODUITS ET OPTIONS --- #}
<div class="grid grid-cols-1 xl:grid-cols-2 gap-8">
{# PRODUITS #}
<div class="space-y-4">
<h3 class="px-2 text-sm font-black text-slate-400 uppercase tracking-[0.3em]">Équipements loués</h3>
<div class="bg-white/[0.02] border border-white/10 rounded-[2rem] divide-y divide-white/5 overflow-hidden">
{% for product in contrat.contratsLines %}
<div class="p-6 hover:bg-white/[0.03] transition-colors">
<div class="flex justify-between items-start">
<div>
<h4 class="text-white font-bold text-base uppercase italic">{{ product.name }}</h4>
<p class="text-[10px] text-slate-500 font-bold uppercase mt-1">Caution : {{ product.caution }}€</p>
</div>
<div class="text-right">
<span class="text-blue-400 font-black italic">{{ product.price1DayHt }}€</span>
<span class="block text-[8px] text-slate-500 uppercase font-black">HT / Jour</span>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{# OPTIONS #}
<div class="space-y-4">
<h3 class="px-2 text-sm font-black text-slate-400 uppercase tracking-[0.3em]">Options & Services</h3>
<div class="bg-white/[0.02] border border-white/10 rounded-[2rem] divide-y divide-white/5 overflow-hidden">
{% for product in contrat.contratsOptions %}
<div class="p-6 hover:bg-white/[0.03] transition-colors">
<div class="flex justify-between items-center">
<div class="max-w-[70%]">
<h4 class="text-white font-bold text-sm uppercase">{{ product.name }}</h4>
<p class="text-slate-500 text-[10px] italic mt-1">{{ product.details }}</p>
</div>
<div class="bg-purple-500/10 border border-purple-500/20 px-3 py-1 rounded-xl">
<span class="text-purple-400 font-black text-xs italic">{{ product.price }}€</span>
</div>
</div>
</div>
{% else %}
<div class="p-10 text-center text-slate-600 text-[10px] font-black uppercase tracking-widest opacity-30">Aucun supplément</div>
{% endfor %}
</div>
</div>
</div>
{# --- 5. HISTORIQUE DES PAIEMENTS --- #}
<div class="space-y-4">
<h3 class="px-2 text-sm font-black text-slate-400 uppercase tracking-[0.3em]">Historique Financier</h3>
<div class="bg-white/[0.02] border border-white/10 rounded-[2rem] overflow-hidden">
<table class="w-full text-left">
<thead>
<tr class="border-b border-white/5 bg-white/[0.03]">
<th class="px-8 py-4 text-[9px] font-black text-slate-500 uppercase tracking-widest">Transaction</th>
<th class="px-8 py-4 text-[9px] font-black text-slate-500 uppercase tracking-widest">Type</th>
<th class="px-8 py-4 text-[9px] font-black text-slate-500 uppercase tracking-widest">Montant</th>
<th class="px-8 py-4 text-[9px] font-black text-slate-500 uppercase tracking-widest text-right">Justificatif</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
{% for payment in contrat.contratsPayments %}
{% if payment.state == 'complete' %}
<tr class="group hover:bg-white/[0.02] transition-colors">
<td class="px-8 py-5 text-xs text-white font-bold tracking-tight">{{ payment.paymentAt|date('d/m/Y H:i') }}</td>
<td class="px-8 py-5">
<span class="px-2 py-1 rounded-lg text-[8px] font-black uppercase italic
{% if payment.type == 'caution' %}bg-purple-500/10 text-purple-400 border border-purple-500/20
{% elseif payment.type == 'accompte' %}bg-blue-500/10 text-blue-400 border border-blue-500/20
{% else %}bg-emerald-500/10 text-emerald-400 border border-emerald-500/20{% endif %}">
{{ payment.type|replace({'_': ' '}) }}
</span>
</td>
<td class="px-8 py-5 text-sm text-white font-black italic">{{ payment.amount|number_format(2, ',', ' ') }}€</td>
<td class="px-8 py-5 text-right">
<a href="{{ path('app_crm_contrats_view', {id: contrat.id, idPaymentPdf: payment.id}) }}" target="_blank"
class="inline-flex items-center gap-2 text-[9px] font-black uppercase text-slate-400 hover:text-white transition-all">
<svg class="w-4 h-4 text-rose-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path 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" stroke-width="2"/></svg>
REÇU PDF
</a>
</td>
</tr>
{% endif %}
{% else %}
<tr><td colspan="4" class="py-12 text-center text-slate-600 text-[10px] font-black uppercase opacity-40">Aucun paiement effectué</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}