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.
```
This commit is contained in:
Serreau Jovann
2026-01-29 10:40:03 +01:00
parent 9a4d7b6ae1
commit e530538af8
9 changed files with 603 additions and 377 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 Version20260129091410 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 contrats_option ADD details TEXT 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 contrats_option DROP details');
}
}

View File

@@ -7,15 +7,19 @@ use App\Entity\ContratsLine;
use App\Entity\ContratsOption;
use App\Entity\ContratsPayments;
use App\Entity\Devis;
use App\Entity\Product;
use App\Event\Signature\ContratEvent;
use App\Form\Type\ContratsType;
use App\Logger\AppLogger;
use App\Repository\AccountRepository;
use App\Repository\DevisRepository;
use App\Repository\ContratsRepository;
use App\Service\Mailer\Mailer;
use App\Service\Pdf\ContratPdfService;
use App\Service\Pdf\PlPdf;
use App\Service\Signature\Client;
use Doctrine\ORM\EntityManagerInterface;
use Illuminate\Support\Facades\Mail;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -85,6 +89,7 @@ class ContratsController extends AbstractController
AppLogger $appLogger,
EventDispatcherInterface $eventDispatcher,
KernelInterface $kernel,
Mailer $mailer,
): Response {
if (!$contrat) {
throw $this->createNotFoundException('Contrat non trouvé.');
@@ -131,37 +136,182 @@ class ContratsController extends AbstractController
$solde = $totalHt - $dejaPaye;
if($request->query->has('act') && $request->query->get('act') === 'cautionCapture') {
$amount = $request->query->get('amountToCapture');
$paiementCaution = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
if($request->query->has('type') && $request->query->get('type') === 'accompte') {
$paiementAccompte = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
'contrat' => $contrat,
'type' => 'caution',
'type' => 'accompte',
]);
$result = $stripeClient->capture($paiementCaution->getPaymentId());
if($result['state']) {
$contrat->setCautionState("recover");
$entityManager->persist($contrat);
$entityManager->flush();
$this->addFlash("success","Caution restitué");
} else {
$this->addFlash("error",$result['message']);
if(!$paiementAccompte) {
$paiementAccompte = new ContratsPayments();
$paiementAccompte->setContrat($contrat);
$paiementAccompte->setType('accompte');
}
}
if($request->query->has('act') && $request->query->get('act') === 'cautionRelease') {
$paiementAccompte->setValidateAt(new \DateTimeImmutable());
$paiementAccompte->setUpdateAt(new \DateTimeImmutable());
$paiementAccompte->setPaymentAt(new \DateTimeImmutable());
$paiementAccompte->setAmount( $totalHt * 0.25);
$paiementAccompte->setState("complete");
$paiementAccompte->setPaymentId("");
$paiementAccompte->setCard([
'type' => 'manuel'
]);
$pdf = new PlPdf($kernel, $paiementAccompte, $contrat);
$pdf->generate();
$content = $pdf->Output('S');
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
file_put_contents($tmpSigned, $content);
$paiementCaution = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
$paiementAccompte->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($paiementAccompte);
$entityManager->flush();
$data = $client->autoSignConfirmedPayment($paiementAccompte);
// 1. Gestion du PDF SIGNÉ
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
$signedContent = file_get_contents($data);
file_put_contents($tmpSigned, $signedContent);
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
$paiementAccompte->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($paiementAccompte);
$entityManager->flush();
$customer = $contrat->getCustomer();
$subjectCustomer = "[Ludikevent] Confirmation de votre acompte - #" . $contrat->getNumReservation();
$mailer->send(
$customer->getEmail(),
$customer->getSurname() . ' ' . $customer->getName(),
$subjectCustomer,
"mails/customer/accompte_confirmation.twig",
[
'contrat' => $contrat,
'payment' => $paiementAccompte,
'customer' => $customer,
'reservationLink' => "https://reservation.ludikevent.fr" . $this->generateUrl('gestion_contrat_view', ['num' => $contrat->getNumReservation()])
]
);
$appLogger->record('PAYMENT','Validation accompte manuel pour contrat #' . $contrat->getNumReservation());
$this->addFlash("success","Validation accompte effectuée");
return $this->redirectToRoute('app_crm_contrats_view', ['id' => $contrat->getId()]);
}
if($request->query->has('type') && $request->query->get('type') === 'caution') {
$paiementAccompte = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
'contrat' => $contrat,
'type' => 'caution',
]);
$result = $stripeClient->cancelPayment($paiementCaution->getPaymentId());
if($result['state']) {
$contrat->setCautionState("restitue");
$entityManager->persist($contrat);
$entityManager->flush();
$this->addFlash("success","Caution restitué");
} else {
$this->addFlash("error",$result['message']);
if(!$paiementAccompte) {
$paiementAccompte = new ContratsPayments();
$paiementAccompte->setContrat($contrat);
$paiementAccompte->setType('caution');
}
$paiementAccompte->setValidateAt(new \DateTimeImmutable());
$paiementAccompte->setUpdateAt(new \DateTimeImmutable());
$paiementAccompte->setPaymentAt(new \DateTimeImmutable());
$paiementAccompte->setAmount( $totalCaution);
$paiementAccompte->setState("complete");
$paiementAccompte->setPaymentId("");
$paiementAccompte->setCard([
'type' => 'manuel'
]);
$pdf = new PlPdf($kernel, $paiementAccompte, $contrat);
$pdf->generate();
$content = $pdf->Output('S');
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
file_put_contents($tmpSigned, $content);
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
$paiementAccompte->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($paiementAccompte);
$entityManager->flush();
$data = $client->autoSignConfirmedPayment($paiementAccompte);
// 1. Gestion du PDF SIGNÉ
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
$signedContent = file_get_contents($data);
file_put_contents($tmpSigned, $signedContent);
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
$paiementAccompte->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($paiementAccompte);
$entityManager->flush();
$customer = $contrat->getCustomer();
$subjectCustomer = "[Ludikevent] Confirmation de votre caution - #" . $contrat->getNumReservation();
$mailer->send(
$customer->getEmail(),
$customer->getSurname() . ' ' . $customer->getName(),
$subjectCustomer,
"mails/customer/accompte_confirmation.twig",
[
'contrat' => $contrat,
'payment' => $paiementAccompte,
'customer' => $customer,
'reservationLink' => "https://reservation.ludikevent.fr" . $this->generateUrl('gestion_contrat_view', ['num' => $contrat->getNumReservation()])
]
);
$appLogger->record('PAYMENT','Validation caution manuel pour contrat #' . $contrat->getNumReservation());
$this->addFlash("success","Validation caution effectuée");
return $this->redirectToRoute('app_crm_contrats_view', ['id' => $contrat->getId()]);
}
if($request->query->has('type') && $request->query->get('type') === 'solde') {
$paiementAccompte = $entityManager->getRepository(ContratsPayments::class)->findOneBy([
'contrat' => $contrat,
'type' => 'solde',
]);
if(!$paiementAccompte) {
$paiementAccompte = new ContratsPayments();
$paiementAccompte->setContrat($contrat);
$paiementAccompte->setType('solde');
}
$paiementAccompte->setValidateAt(new \DateTimeImmutable());
$paiementAccompte->setUpdateAt(new \DateTimeImmutable());
$paiementAccompte->setPaymentAt(new \DateTimeImmutable());
$paiementAccompte->setAmount( $totalHt);
$paiementAccompte->setState("complete");
$paiementAccompte->setPaymentId("");
$paiementAccompte->setCard([
'type' => 'manuel'
]);
$pdf = new PlPdf($kernel, $paiementAccompte, $contrat);
$pdf->generate();
$content = $pdf->Output('S');
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
file_put_contents($tmpSigned, $content);
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
$paiementAccompte->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($paiementAccompte);
$entityManager->flush();
$data = $client->autoSignConfirmedPayment($paiementAccompte);
// 1. Gestion du PDF SIGNÉ
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
$signedContent = file_get_contents($data);
file_put_contents($tmpSigned, $signedContent);
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
$paiementAccompte->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $paiementAccompte->getId() . ".pdf", "application/pdf", null, true));
$paiementAccompte->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($paiementAccompte);
$entityManager->flush();
$customer = $contrat->getCustomer();
$subjectCustomer = "[Ludikevent] Votre réservation est désormais soldée - #" . $contrat->getNumReservation();
$mailer->send(
$customer->getEmail(),
$customer->getSurname() . ' ' . $customer->getName(),
$subjectCustomer,
"mails/customer/accompte_confirmation.twig",
[
'contrat' => $contrat,
'payment' => $paiementAccompte,
'customer' => $customer,
'reservationLink' => "https://reservation.ludikevent.fr" . $this->generateUrl('gestion_contrat_view', ['num' => $contrat->getNumReservation()])
]
);
$appLogger->record('PAYMENT','Validation solde manuel pour contrat #' . $contrat->getNumReservation());
$this->addFlash("success","Validation solde effectuée");
return $this->redirectToRoute('app_crm_contrats_view', ['id' => $contrat->getId()]);
}
return $this->render('dashboard/contrats/view.twig', [
@@ -192,7 +342,7 @@ class ContratsController extends AbstractController
$c = new Contrats();
$lines = [['id' => 0, 'name' => '', 'priceHt1Day' => 0, 'priceHtSupDay' => 0, 'caution' => 0]];
$options = [['id' => 0, 'name' => '', 'priceHt' => 0]];
$options = [['id' => 0, 'name' => '', 'priceHt' => 0,'details'=>'']];
if ($devis instanceof Devis) {
$c->setDateAt($devis->getStartAt());
@@ -200,6 +350,7 @@ class ContratsController extends AbstractController
$c->setCustomer($devis->getCustomer());
$c->setDevis($devis);
// Mapping adresse de l'événement
if ($devis->getAddressShip()) {
$c->setAddressEvent($devis->getAddressShip()->getAddress());
@@ -213,19 +364,22 @@ class ContratsController extends AbstractController
$options = [];
foreach ($devis->getDevisLines() as $line) {
$p = $entityManager->getRepository(Product::class)->findOneBy(['name'=>$line->getProduct()]);
$lines[] = [
'id' => $line->getId(),
'name' => $line->getProduct()->getName() . " - " . $line->getProduct()->getRef(),
'name' =>$p->getName() . " - " . $p->getRef(),
'priceHt1Day' => $line->getPriceHt(),
'priceHtSupDay' => $line->getPriceHtSup(),
'caution' => $line->getProduct()->getCaution(),
'caution' => $p->getCaution(),
];
}
foreach ($devis->getDevisOptions() as $line) {
$options[] = [
'id' => $line->getId(),
'name' => $line->getOption()->getName(),
'name' => $line->getOption(),
'details' => $line->getDetails(),
'priceHt' => $line->getPriceHt(),
];
}
@@ -257,6 +411,7 @@ class ContratsController extends AbstractController
$vc = new ContratsOption();
$vc->setContrat($c);
$vc->setName($line['name']);
$vc->setDetails($line['details']);
$vc->setPrice($line['priceHt']);
$entityManager->persist($vc);
}

View File

@@ -158,18 +158,6 @@ class SignatureController extends AbstractController
// 4. Sauvegarde en base de données
$devis->setUpdateAt(new \DateTimeImmutable());
foreach ($devis->getDevisLines() as $line) {
$product = $line->getProduct();
$productReserve = new ProductReserve();
$productReserve->setProduct($product);
$productReserve->setCustomer($devis->getCustomer());
$productReserve->setStartAt($devis->getStartAt());
$productReserve->setEndAt($devis->getEndAt());
$productReserve->setDevis($devis);
$entityManager->persist($productReserve);
}
$entityManager->persist($devis);
$entityManager->flush();

View File

@@ -3,6 +3,7 @@
namespace App\Entity;
use App\Repository\ContratsOptionRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ContratsOptionRepository::class)]
@@ -22,6 +23,9 @@ class ContratsOption
#[ORM\Column]
private ?float $price = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $details = null;
public function getId(): ?int
{
return $this->id;
@@ -62,4 +66,16 @@ class ContratsOption
return $this;
}
public function getDetails(): ?string
{
return $this->details;
}
public function setDetails(?string $details): static
{
$this->details = $details;
return $this;
}
}

View File

@@ -134,7 +134,12 @@ class PlPdf extends Fpdf
$this->SetFont('Arial', 'B', 10);
$this->Cell(50, 8, $this->clean("Mode de règlement :"), 0, 0);
$this->SetFont('Arial', '', 10);
$this->Cell(0, 8, $this->clean($this->contratsPayments->getCard()['type'] ?? 'Carte Bancaire'), 0, 1);
if($this->contratsPayments->getCard()['type'] == "manuel") {
$this->Cell(0, 8, $this->clean("Paiement valider par Ludikevent"), 0, 1);
} else {
$this->Cell(0, 8, $this->clean($this->contratsPayments->getCard()['type'] ?? 'Carte Bancaire'), 0, 1);
}
$this->SetFont('Arial', 'B', 10);
$this->Cell(50, 8, $this->clean("Référence transaction :"), 0, 0);

View File

@@ -224,7 +224,7 @@
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 items-end">
{# 1. PRODUIT AVEC BOUTON RECHERCHE #}
<div class="lg:col-span-8">
<div class="lg:col-span-6">
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-1 mb-2 block">Produit / Prestation</label>
<div class="relative flex items-center">
<input type="text" name="options[{{ key }}][name]" value="{{ line.name }}" required class="w-full bg-slate-950/50 border-white/5 rounded-2xl text-white focus:ring-purple-500/20 focus:border-purple-500 transition-all py-3 pl-5 pr-12 text-sm">
@@ -235,7 +235,10 @@
</button>
</div>
</div>
<div class="lg:col-span-2">
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-1 mb-2 block">Détails</label>
<input type="text" name="options[{{ key }}][details]" value="{{ line.details }}" required class="w-full bg-slate-950/50 border-white/5 rounded-2xl text-white focus:ring-purple-500/20 focus:border-purple-500 transition-all py-3 px-5 text-sm font-mono">
</div>
{# 2. PRIX 1J #}
<div class="lg:col-span-3">
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-1 mb-2 block">Prix 1J HT</label>

View File

@@ -3,138 +3,186 @@
{% block title %}Contrats de locations{% endblock %}
{% block title_header %}Contrats de locations{% endblock %}
{% block body %}
<div class="space-y-6 pb-20">
{% block actions %}
<div class="flex items-center gap-3">
<a href="{{ path('app_crm_contrats_create') }}"
class="group relative flex items-center gap-2 px-6 py-3 bg-gradient-to-br from-blue-600 to-indigo-700 hover:from-blue-500 hover:to-indigo-600 rounded-2xl text-white text-xs font-bold uppercase tracking-wider transition-all shadow-[0_8px_20px_-6px_rgba(37,99,235,0.5)]">
<svg class="w-4 h-4 transition-transform group-hover:rotate-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Créer un contrat
</a>
</div>
{% endblock %}
{# --- BARRE DE RECHERCHE --- #}
<div class="relative group mb-12">
<div class="absolute inset-y-0 left-0 pl-6 flex items-center pointer-events-none">
<svg class="w-5 h-5 text-slate-500 group-focus-within:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
{% block body %}
<div class="space-y-8 pb-20">
{# --- RECHERCHE STYLE NÉO-GLASS --- #}
<div class="relative group max-w-2xl mx-auto">
<div class="absolute -inset-1 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-[2rem] blur opacity-20 group-focus-within:opacity-40 transition duration-1000"></div>
<div class="relative flex items-center">
<div class="absolute inset-y-0 left-0 pl-6 flex items-center pointer-events-none">
<svg class="w-5 h-5 text-slate-400 group-focus-within:text-blue-400 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
<input type="text" id="searchContrat"
placeholder="Rechercher un contrat, un client ou une ville..."
class="w-full bg-slate-900/40 border border-white/10 backdrop-blur-2xl text-white text-sm rounded-[1.8rem] pl-14 pr-6 py-5 focus:outline-none focus:ring-2 focus:ring-blue-500/50 transition-all placeholder:text-slate-500">
</div>
<input type="text"
id="searchContrat"
placeholder="Rechercher un contrat (Nom, N° ou Ville)..."
class="w-full bg-white/5 border border-white/10 backdrop-blur-xl text-white text-sm rounded-[1.5rem] pl-14 pr-6 py-5 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition-all placeholder:text-slate-500 font-medium">
</div>
<div id="contratsList" class="space-y-6">
<div id="contratsList" class="grid gap-6">
{% for contrat in contrats %}
{# CALCUL DES ÉTATS FINANCIERS VIA TES FILTRES TWIG #}
{% set acompteOk = contratPaymentPay(contrat, 'accompte') %}
{% set cautionOk = contratPaymentPay(contrat, 'caution') %}
{% set soldeOk = contratPaymentPay(contrat, 'solde') %}
<div class="contrat-card bg-white/5 border border-white/10 backdrop-blur-md rounded-[2rem] overflow-hidden hover:border-blue-500/40 transition-all group">
<div class="grid grid-cols-1 lg:grid-cols-12">
<div class="contrat-card relative overflow-hidden group">
{# Background Glow Effect #}
<div class="absolute -inset-px bg-gradient-to-r from-transparent via-white/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-[2rem]"></div>
<div class="relative bg-white/[0.03] border border-white/10 backdrop-blur-md rounded-[2rem] transition-all duration-300 group-hover:bg-white/[0.06] group-hover:translate-y-[-2px] group-hover:shadow-2xl group-hover:shadow-blue-500/10">
<div class="grid grid-cols-1 lg:grid-cols-12 items-center">
{# 1. REF & STATUS #}
<div class="lg:col-span-2 p-8 border-b lg:border-b-0 lg:border-r border-white/5 text-center lg:text-left">
<span class="text-[10px] font-bold text-blue-400 uppercase tracking-[0.2em] mb-1 block">Contrat</span>
<h3 class="text-white font-black text-xl tracking-tighter mb-3">{{ contrat.numReservation }}</h3>
{# --- COLONNE 1 : RÉFÉRENCE & SIGNATURE (2/12) --- #}
<div class="lg:col-span-2 p-8 bg-white/[0.02] flex flex-col justify-center border-r border-white/5">
<span class="text-[9px] font-black text-blue-500 uppercase tracking-widest mb-2 block text-search">Référence</span>
<h3 class="text-white font-black italic text-lg tracking-tighter text-search">{{ contrat.numReservation }}</h3>
<div class="mt-4">
{% if contrat.isSigned %}
<span class="px-3 py-1 bg-emerald-500/10 text-emerald-500 text-[8px] font-black uppercase rounded-lg border border-emerald-500/20 inline-flex items-center gap-1">
<svg class="w-2.5 h-2.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>
Signé
</span>
<div class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-[9px] font-bold uppercase tracking-wider">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span> Signé
</div>
{% else %}
<span class="px-3 py-1 bg-amber-500/10 text-amber-500 text-[8px] font-black uppercase rounded-lg border border-amber-500/20 inline-flex items-center gap-1">
<svg class="w-2.5 h-2.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path></svg>
En attente
</span>
<div class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-amber-500/10 border border-amber-500/20 text-amber-400 text-[9px] font-bold uppercase tracking-wider">
<span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span> Attente
</div>
{% endif %}
</div>
</div>
{# --- COLONNE 2 : CLIENT (3/12) --- #}
<div class="lg:col-span-3 p-8 border-r border-white/5">
<div class="flex items-start gap-4">
<div class="w-10 h-10 bg-blue-600/10 rounded-xl flex items-center justify-center text-blue-500 shrink-0">
<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="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg>
</div>
<div>
<span class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-1 block">Locataire</span>
<p class="text-white font-bold text-base uppercase italic text-search">{{ contrat.customer.surname }} {{ contrat.customer.name }}</p>
<p class="text-slate-400 text-[11px] mt-1">{{ contrat.customer.email }}</p>
{# 2. CLIENT #}
<div class="lg:col-span-3 p-8 border-b lg:border-b-0 lg:border-r border-white/5">
<div class="flex items-center gap-4">
<div class="relative">
<div class="w-12 h-12 bg-gradient-to-tr from-blue-600/20 to-indigo-600/20 rounded-2xl flex items-center justify-center border border-white/10 group-hover:scale-110 transition-transform">
<svg class="w-6 h-6 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" stroke-width="1.5"></path></svg>
</div>
</div>
<div>
<p class="text-white font-bold text-base tracking-tight uppercase">{{ contrat.customer.surname }} {{ contrat.customer.name }}</p>
<p class="text-slate-500 text-xs font-medium">{{ contrat.customer.email }}</p>
</div>
</div>
</div>
</div>
{# --- COLONNE 3 : LIEU & VILLE (2/12) --- #}
<div class="lg:col-span-2 p-8 border-r border-white/5">
<span class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-2 block">Lieu</span>
<p class="text-white font-bold text-sm leading-tight text-search">
{{ contrat.townEvent }}
</p>
<p class="text-amber-500 font-black text-[10px] text-search">{{ contrat.zipCodeEvent }}</p>
</div>
{# --- COLONNE 4 : STATUT FINANCIER (3/12) --- #}
<div class="lg:col-span-3 p-8 border-r border-white/5 bg-white/[0.01]">
<span class="text-[9px] font-black text-slate-500 uppercase tracking-widest mb-4 block">Suivi Paiements</span>
<div class="space-y-3">
{# ACOMPTE #}
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<div class="w-1.5 h-1.5 rounded-full {{ acompteOk ? 'bg-emerald-500 shadow-[0_0_8px_#10b981]' : 'bg-slate-600' }}"></div>
<span class="text-[10px] {{ acompteOk ? 'text-slate-200' : 'text-slate-500' }}">Acompte</span>
</div>
{% if acompteOk %}
<svg class="w-3 h-3 text-emerald-500" fill="currentColor" viewBox="0 0 20 20"><path d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"></path></svg>
{% endif %}
</div>
{# CAUTION #}
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<div class="w-1.5 h-1.5 rounded-full {{ cautionOk ? 'bg-blue-500 shadow-[0_0_8px_#3b82f6]' : 'bg-rose-500 animate-pulse' }}"></div>
<span class="text-[10px] {{ cautionOk ? 'text-slate-200' : 'text-slate-500' }}">Caution</span>
</div>
<span class="text-[8px] font-black {{ cautionOk ? 'text-blue-400' : 'text-rose-500' }} uppercase">{{ cautionOk ? 'OK' : 'REQUISE' }}</span>
</div>
{# SOLDE #}
<div class="pt-2 border-t border-white/10 flex items-center justify-between">
<span class="text-[9px] font-black {{ soldeOk ? 'text-emerald-400' : 'text-white' }} uppercase italic">{{ soldeOk ? 'SOLDÉ' : 'À PERCEVOIR' }}</span>
{% if soldeOk %}
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
</span>
{% endif %}
{# 3. LIEU #}
<div class="lg:col-span-2 p-8 border-b lg:border-b-0 lg:border-r border-white/5">
<div class="flex flex-col">
<span class="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-1 text-search">Destination</span>
<p class="text-slate-200 font-bold text-sm">{{ contrat.townEvent }}</p>
<p class="text-blue-500 font-black text-[11px]">{{ contrat.zipCodeEvent }}</p>
</div>
</div>
</div>
{# --- COLONNE 5 : ACTIONS (2/12) --- #}
<div class="lg:col-span-2 p-6 flex flex-row lg:flex-col justify-center gap-2">
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id}) }}"
title="Détails"
class="flex-1 lg:flex-none py-3 bg-white/5 hover:bg-blue-600 text-white rounded-xl flex items-center justify-center transition-all border border-white/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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><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"></path></svg>
</a>
{# 4. PAIEMENTS #}
<div class="lg:col-span-3 p-8 bg-black/10 lg:bg-transparent">
<div class="grid grid-cols-3 gap-2">
<a data-turbo="false" download="contrat-{{ contrat.numReservation }}"
href="{{ vich_uploader_asset(contrat,'devisFile') }}"
class="flex-1 lg:flex-none py-3 bg-white/5 hover:bg-emerald-600 text-white rounded-xl flex items-center justify-center transition-all border border-white/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="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"></path></svg>
</a>
{# --- ACOMPTE --- #}
<div class="flex flex-col items-center gap-2">
<div class="w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-300
{{ acompteOk
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
: 'bg-rose-500/10 text-rose-500 border border-rose-500/20 shadow-[0_0_15px_rgba(244,63,94,0.1)]'
}}">
{% if acompteOk %}
<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.5" d="M5 13l4 4L19 7"></path></svg>
{% else %}
<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.5" d="M6 18L18 6M6 6l12 12"></path></svg>
{% endif %}
</div>
<span class="text-[8px] font-black uppercase tracking-tighter {{ acompteOk ? 'text-emerald-500' : 'text-rose-500' }}">
Acompte
</span>
</div>
<a data-turbo="false" href="{{ path('app_crm_contrats', {idSend: contrat.id}) }}"
onclick="return confirm('Envoyer à {{ contrat.customer.email }} ?')"
class="flex-1 lg:flex-none py-3 bg-white/5 hover:bg-indigo-500 text-white rounded-xl flex items-center justify-center transition-all border border-white/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="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg>
</a>
{# --- CAUTION --- #}
<div class="flex flex-col items-center gap-2">
<div class="w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-300
{{ cautionOk
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
: 'bg-rose-500/20 text-rose-500 border border-rose-500/30 animate-pulse shadow-[0_0_15px_rgba(244,63,94,0.2)]'
}}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{% if cautionOk %}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" 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"></path>
{% else %}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" 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"></path>
{% endif %}
</svg>
</div>
<span class="text-[8px] font-black uppercase tracking-tighter {{ cautionOk ? 'text-emerald-500' : 'text-rose-500' }}">
Caution
</span>
</div>
{# --- SOLDE --- #}
<div class="flex flex-col items-center gap-2">
<div class="w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-300
{{ soldeOk
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
: 'bg-rose-500/10 text-rose-500 border border-rose-500/20'
}}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{% if soldeOk %}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path>
{% else %}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
{% endif %}
</svg>
</div>
<span class="text-[8px] font-black uppercase tracking-tighter {{ soldeOk ? 'text-emerald-500' : 'text-rose-500' }}">
Solde
</span>
</div>
</div>
</div>
{# 5. ACTIONS #}
<div class="lg:col-span-2 p-6 flex lg:flex-col items-center justify-center gap-3">
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id}) }}"
class="w-full lg:w-12 h-12 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-white hover:bg-blue-600 hover:border-blue-400 transition-all group/btn" title="Voir">
<svg class="w-5 h-5 group-hover/btn:scale-110 transition-transform" 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><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"></path></svg>
</a>
<a data-turbo="false" href="{{ path('app_crm_contrats', {idSend: contrat.id}) }}"
onclick="return confirm('Envoyer à {{ contrat.customer.email }} ?')"
class="w-full lg:w-12 h-12 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-white hover:bg-indigo-600 hover:border-indigo-400 transition-all group/btn" title="Envoyer">
<svg class="w-5 h-5 group-hover/btn:translate-x-1 group-hover/btn:-translate-y-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg>
</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="mt-12 glass-pagination">
{{ knp_pagination_render(contrats) }}
</div>
</div>
{{ knp_pagination_render(contrats) }}
<style>
/* Optionnel : Custom style pour la pagination KNP pour matcher le glassmorphism */
.glass-pagination nav ul { @apply flex justify-center gap-2; }
.glass-pagination nav ul li span,
.glass-pagination nav ul li a {
@apply px-4 py-2 bg-white/5 border border-white/10 rounded-xl text-white text-sm transition-all backdrop-blur-md;
}
.glass-pagination nav ul li.active span { @apply bg-blue-600 border-blue-500 font-bold; }
.glass-pagination nav ul li a:hover { @apply bg-white/10 border-white/20; }
</style>
{% endblock %}

View File

@@ -4,311 +4,286 @@
{% block actions %}
<div class="flex items-center gap-3">
{% if contrat.devis %}
<a href="{{ vich_uploader_asset(contrat.devis,'devisFile') }}"
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-xs font-black uppercase italic transition-all group">
<svg class="w-4 h-4 text-red-500 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="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 Devis PDF
</a>
{% endif %}
{% if contrat.signed %}
<a href="{{ vich_uploader_asset(contrat,'devisSignFile') }}"
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-xs font-black uppercase italic transition-all group">
<svg class="w-4 h-4 text-red-500 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="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" />
<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>
Télécharger Contrat Signée PDF
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-xs font-black uppercase italic transition-all group">
<svg class="w-4 h-4 text-red-500 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="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" />
<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>
Télécharger Audit Signée PDF
Audit Signature
</a>
{% else %}
<a href="{{ vich_uploader_asset(contrat,'devisFile') }}"
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-xs font-black uppercase italic transition-all group">
<svg class="w-4 h-4 text-red-500 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="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>
<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 basés sur les variables du contrôleur #}
{# 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">
{# --- GRILLE DES 4 CARTES DE STATUT --- #}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{# --- 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>
{# 1. SIGNATURE #}
<div class="relative overflow-hidden bg-white/5 border border-white/10 backdrop-blur-xl p-6 rounded-[2rem] transition-all hover:border-white/20">
<div class="flex flex-col gap-4">
<div class="w-10 h-10 rounded-xl flex items-center justify-center {{ contrat.isSigned ? 'bg-emerald-500/20 text-emerald-400' : 'bg-amber-500/20 text-amber-500 animate-pulse' }}">
<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.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/></svg>
{# --- 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>
<p class="text-slate-500 text-[9px] font-black uppercase tracking-widest">Contrat</p>
{% if contrat.isSigned %}
<p class="text-white font-bold italic uppercase">Signé</p>
{% else %}
<a href="{{ signUrl }}" target="_blank" class="text-amber-500 font-black italic hover:underline">À SIGNER ICI</a>
{% endif %}
<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>
{# 2. ARRHES (ACOMPTE) #}
<div class="relative overflow-hidden bg-white/5 border border-white/10 backdrop-blur-xl p-6 rounded-[2rem]">
<div class="flex flex-col gap-4">
<div class="w-10 h-10 rounded-xl flex items-center justify-center {{ acompteOk ? 'bg-blue-500/20 text-blue-400' : 'bg-slate-500/20 text-slate-400' }}">
<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="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"/></svg>
{# 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>
<p class="text-slate-500 text-[9px] font-black uppercase tracking-widest">Acompte (Arrhes)</p>
{% if acompteOk %}
<p class="text-white font-bold italic uppercase">Encaissé</p>
{% else %}
<a href="{{ path('gestion_contrat_view', {num: contrat.numReservation, act: 'accomptePay'}) }}" class="text-blue-400 font-black italic hover:underline uppercase">Régler {{ arrhes|number_format(2, ',', ' ') }}€</a>
{% endif %}
<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>
{# 3. CAUTION (MODE ADMINISTRATION) #}
<div class="relative overflow-hidden bg-white/5 border border-white/10 backdrop-blur-xl p-6 rounded-[2rem]">
<div class="flex flex-col gap-4">
{# Icône dynamique : Pulse si non déposée, fixe si OK #}
<div class="w-10 h-10 rounded-xl flex items-center justify-center {{ cautionOk ? 'bg-purple-500/20 text-purple-400' : 'bg-rose-500/20 text-rose-500 animate-pulse' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path 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" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</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>
<p class="text-slate-500 text-[9px] font-black uppercase tracking-widest">Garantie (Caution)</p>
{% if not cautionOk %}
{# ÉTAT 1 : ATTENTE DE DÉPÔT #}
<div class="mt-1">
<p class="text-rose-500 font-black italic uppercase text-sm leading-tight">Non Déposée</p>
<p class="text-[9px] text-slate-500 uppercase font-bold italic">Attendu : {{ totalCaution|number_format(2, ',', ' ') }}€</p>
</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 %}
{# ÉTAT 2 : CAUTION DÉPOSÉE #}
{% if contrat.cautionState == null %}
{# ACTIONS DISPONIBLES #}
<div class="space-y-3 mt-2">
<p class="text-white font-bold italic uppercase text-[10px] mb-2 opacity-70">Empreinte active</p>
<div class="flex flex-col gap-2">
{# LIBÉRER #}
<a href="{{ path('app_crm_contrats_view', {id: contrat.id, act: 'cautionRelease'}) }}"
data-turbo="false"
onclick="return confirm('Confirmer la libération de la caution ?')"
class="w-full py-1.5 bg-emerald-500/10 hover:bg-emerald-500/20 border border-emerald-500/30 rounded-lg text-emerald-400 text-[9px] font-black uppercase italic text-center transition-all">
Libérer la caution
</a>
<p class="mt-2 text-[10px] text-emerald-500/60 font-bold uppercase italic">Encaissé</p>
{% endif %}
</div>
</div>
{# ENCAISSER #}
<form action="{{ path('app_crm_contrats_view', {id: contrat.id}) }}" method="GET" data-turbo="false" class="space-y-1">
<input type="hidden" name="act" value="cautionCapture">
<div class="flex items-center bg-rose-500/5 rounded-lg border border-rose-500/20 px-2 py-1">
<input type="number" name="amountToCapture" step="0.01" max="{{ totalCaution }}" value="{{ totalCaution }}"
class="bg-transparent border-none text-rose-500 text-[10px] font-black w-14 p-0 focus:ring-0">
<button type="submit" onclick="return confirm('Confirmer l\'encaissement ?')"
class="ml-auto text-rose-500 text-[9px] font-black uppercase italic hover:text-white transition-colors">
Encaisser
</button>
</div>
</form>
</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 %}
{# ÉTAT 3 : ARCHIVÉ (RESTITUÉ OU RÉCUPÉRÉ) #}
<div class="mt-2">
{% if contrat.cautionState == 'restitue' %}
<span class="px-3 py-1 bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-[10px] font-black uppercase italic rounded-full">
Restituée
</span>
{% elseif contrat.cautionState == 'recover' %}
<span class="px-3 py-1 bg-amber-500/10 border border-amber-500/20 text-amber-400 text-[10px] font-black uppercase italic rounded-full">
Encaissée
</span>
{% else %}
<span class="text-white font-bold text-xs uppercase opacity-50">{{ contrat.cautionState }}</span>
{% endif %}
<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 %}
{% endif %}
</div>
</div>
</div>
</div>
{# 4. SOLDE #}
<div class="relative overflow-hidden bg-white/5 border border-white/10 backdrop-blur-xl p-6 rounded-[2rem]">
<div class="flex flex-col gap-4">
<div class="w-10 h-10 rounded-xl flex items-center justify-center {{ soldeOk ? 'bg-emerald-500 text-white' : 'bg-amber-500/20 text-amber-500' }}">
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
<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>
<p class="text-slate-500 text-[9px] font-black uppercase tracking-widest">État du solde</p>
{% if soldeOk %}
<p class="text-emerald-400 font-black italic uppercase">Dossier Soldé</p>
<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 %}
<form action="{{ path('gestion_contrat_view', {num: contrat.numReservation}) }}" method="GET" class="flex flex-col gap-2">
<input type="hidden" name="act" value="soldePay">
<div class="flex items-center bg-white/5 rounded-lg border border-white/10 px-2 py-1">
<input type="number" name="amountToPay" step="0.01" max="{{ solde }}" value="{{ solde }}"
class="bg-transparent border-none text-white text-xs font-black w-20 p-0 focus:ring-0">
<button type="submit" class="text-amber-500 text-[10px] font-black uppercase italic hover:text-white">Payer</button>
</div>
<span class="text-[8px] text-slate-500 uppercase">Reste : {{ solde|number_format(2, ',', ' ') }}€</span>
</form>
<p class="mt-2 text-[10px] text-emerald-500/60 font-bold uppercase italic">Totalité payée</p>
{% endif %}
</div>
</div>
</div>
</div>
{# --- SECTION DÉTAILS : CLIENT / LOGISTIQUE / FINANCES --- #}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
{# CARD 1 : CLIENT #}
<div class="bg-white/5 border border-white/10 backdrop-blur-md rounded-[2.5rem] p-8 relative overflow-hidden group">
<div class="absolute -top-10 -right-10 w-32 h-32 bg-blue-500/5 rounded-full blur-3xl group-hover:bg-blue-500/10 transition-all"></div>
<h2 class="text-blue-500 font-black text-[10px] uppercase tracking-[0.3em] mb-6 flex items-center gap-2">
<svg class="w-3 h-3" 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>
Locataire
</h2>
<div class="space-y-1">
<p class="text-white font-black text-2xl uppercase italic tracking-tighter">{{ contrat.customer.surname }}</p>
<p class="text-white font-medium text-xl uppercase italic opacity-80">{{ contrat.customer.name }}</p>
</div>
<div class="mt-6 pt-6 border-t border-white/5 space-y-3">
<div class="flex items-center gap-3 text-slate-400 text-xs font-medium">
<svg class="w-4 h-4 text-blue-500/50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>
{{ contrat.customer.email }}
</div>
<div class="flex items-center gap-3 text-slate-400 text-xs font-medium">
<svg class="w-4 h-4 text-blue-500/50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>
{{ contrat.customer.phone }}
</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>
{# CARD 2 : LOGISTIQUE #}
<div class="bg-white/5 border border-white/10 backdrop-blur-md rounded-[2.5rem] p-8 relative overflow-hidden group">
<h2 class="text-amber-500 font-black text-[10px] uppercase tracking-[0.3em] mb-6 flex items-center gap-2">
<svg class="w-3 h-3" 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>
Lieu de l'événement
</h2>
<div class="space-y-4">
<div class="p-4 bg-white/[0.03] border border-white/5 rounded-2xl">
<p class="text-slate-500 text-[10px] font-bold uppercase mb-2 text-white/50">Adresse</p>
<p class="text-white text-sm leading-relaxed font-bold italic">
{{ contrat.addressEvent }}<br>
{{ contrat.zipCodeEvent }} {{ contrat.townEvent|upper }}
</p>
</div>
<div class="flex items-center gap-4">
<div class="flex-1 p-3 bg-white/[0.03] border border-white/5 rounded-2xl">
<p class="text-[9px] font-bold uppercase text-white/50">Du</p>
<p class="text-white font-bold text-xs italic">{{ contrat.dateAt|date('d/m/Y') }}</p>
{# 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>
<div class="flex-1 p-3 bg-white/[0.03] border border-white/5 rounded-2xl">
<p class="text-[9px] font-bold uppercase text-white/50">Au</p>
<p class="text-white font-bold text-xs italic">{{ contrat.endAt|date('d/m/Y') }}</p>
</div>
</div>
</div>
</div>
{# CARD 3 : RÉCAPITULATIF FINANCIER #}
<div class="bg-slate-900/40 border border-white/10 backdrop-blur-2xl rounded-[2.5rem] p-8 relative overflow-hidden group">
<div class="absolute top-0 right-0 w-32 h-32 bg-blue-600/10 blur-[80px]"></div>
<h2 class="text-white font-black text-[10px] uppercase tracking-[0.3em] mb-6 flex items-center gap-2">
<svg class="w-3 h-3 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01"/></svg>
Détail Règlement
</h2>
<div class="space-y-5">
<div class="flex items-end justify-between">
<span class="text-slate-400 text-xs font-bold uppercase italic">Total Prestation</span>
<span class="text-2xl font-black text-white italic tracking-tighter">{{ totalHT|number_format(2, ',', ' ') }}€</span>
</div>
<div class="p-4 bg-white/5 border border-white/10 rounded-2xl space-y-3">
<div class="flex items-center justify-between">
<p class="text-blue-400 text-[9px] font-black uppercase tracking-wider">Acompte Requis</p>
<p class="text-white font-black text-sm italic tracking-tighter">{{ arrhes|number_format(2, ',', ' ') }}€</p>
</div>
<div class="h-px bg-white/5"></div>
<div class="flex items-center justify-between">
<p class="text-purple-400 text-[9px] font-black uppercase tracking-wider">Montant Caution</p>
<p class="text-white font-black text-sm italic tracking-tighter">{{ totalCaution|number_format(2, ',', ' ') }}€</p>
</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>
{# --- HISTORIQUE DES PAIEMENTS --- #}
<div class="mt-12">
<h2 class="text-white font-black text-xl italic uppercase tracking-tighter mb-6 flex items-center gap-3">
<span class="w-8 h-px bg-white/20"></span> Historique des transactions
</h2>
<div class="bg-white/5 border border-white/10 backdrop-blur-xl rounded-[2.5rem] overflow-hidden">
<table class="w-full text-left border-collapse">
{# --- 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.02]">
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest">Date</th>
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest">Type</th>
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest">Montant</th>
<th class="px-8 py-5 text-[10px] font-black text-slate-500 uppercase tracking-widest text-right">Action</th>
<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-medium">{{ payment.paymentAt|date('d/m/Y H:i') }}</td>
<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-3 py-1 rounded-full text-[9px] 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>
<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-[10px] font-black uppercase italic text-slate-400 hover:text-white group transition-all">
<svg class="w-4 h-4 text-red-500 group-hover:scale-110 transition-transform" 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" stroke-linecap="round" stroke-linejoin="round"/></svg>
Reçu
<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="px-8 py-10 text-center text-slate-500 italic text-xs uppercase opacity-50">Aucune transaction enregistrée</td>
</tr>
<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 %}

View File

@@ -49,7 +49,11 @@
<mj-column width="50%">
<mj-text font-size="10px" font-weight="800" color="#94a3b8" text-transform="uppercase" align="right">Méthode</mj-text>
<mj-text font-size="12px" font-weight="700" color="#475569" align="right" padding-top="10px">
{% if datas.payment.card.type is defined and datas.payment.card.type == "manuel" %}
Paiement effectuer par Ludikevent
{% else %}
{{ datas.payment.card.method_label|default('Carte Bancaire') }}
{% endif %}
</mj-text>
</mj-column>
</mj-section>