feat(Form/CustomerAddType): Ajoute formulaire pour créer un nouveau client.
 feat(Form/CustomerAddAddressType): Crée un formulaire pour gérer les adresses client.
 feat(template/customer): Affiche et permet l'édition des infos client et adresses.
♻️ refactor(Form/CustomerType): Simplifie le formulaire client.
🐛 fix(template/customer): Corrige l'affichage de la fiche client.
```
This commit is contained in:
Serreau Jovann
2026-01-16 14:52:30 +01:00
parent cb5fdba2f9
commit bd99d1af43
6 changed files with 373 additions and 108 deletions

View File

@@ -3,8 +3,12 @@
namespace App\Controller\Dashboard;
use App\Entity\Customer;
use App\Entity\CustomerAddress;
use App\Form\CustomerAddAddressType;
use App\Form\CustomerAddType;
use App\Form\CustomerType;
use App\Logger\AppLogger;
use App\Repository\CustomerAddressRepository;
use App\Repository\CustomerRepository;
use App\Service\Search\Client;
use Doctrine\ORM\EntityManagerInterface;
@@ -50,11 +54,23 @@ class CustomerController extends AbstractController
$this->appLogger->record('VIEW', 'Consultation de la page de création client');
$customer = new Customer();
$form = $this->createForm(CustomerType::class, $customer);
$form = $this->createForm(CustomerAddType::class, $customer);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data= $request->request->all()['customer_add'];
$userAddresse = new CustomerAddress();
$userAddresse->setCustomer($customer);
$userAddresse->setAddress($data['adresse']);
$userAddresse->setCity($data['city']);
$userAddresse->setZipcode($data['zipcode']);
$userAddresse->setCountry($data['country']);
$userAddresse->setAddress2($data['adresse2']);
$userAddresse->setAddress3($data['adresse3']);
$entityManager->persist($userAddresse);
// 1. Sauvegarde en base de données
$entityManager->persist($customer);
$entityManager->flush();
@@ -84,6 +100,7 @@ class CustomerController extends AbstractController
public function edit(
int $id,
CustomerRepository $customerRepository,
CustomerAddressRepository $addressRepository, // Injecté pour charger l'adresse
Request $request,
EntityManagerInterface $entityManager,
\App\Service\Stripe\Client $stripeClient,
@@ -95,40 +112,75 @@ class CustomerController extends AbstractController
throw $this->createNotFoundException('Client introuvable');
}
// --- LOGIQUE ADRESSE (ADD OU EDIT) ---
$idAddr = $request->query->get('idAddr');
if ($idAddr) {
// Mode ÉDITION d'adresse : on cherche l'adresse existante
$address = $addressRepository->findOneBy([
'id' => $idAddr,
'customer' => $customer // Sécurité : on vérifie que l'adresse appartient bien au client
]);
if (!$address) {
$this->addFlash('error', 'Adresse introuvable ou non associée à ce client.');
return $this->redirectToRoute('app_crm_customer_edit', ['id' => $customer->getId()]);
}
} else {
// Mode AJOUT : nouvelle instance
$address = new CustomerAddress();
$address->setCustomer($customer);
}
$formAddress = $this->createForm(CustomerAddAddressType::class, $address);
$formAddress->handleRequest($request);
if ($formAddress->isSubmitted() && $formAddress->isValid()) {
// Si c'est une nouvelle adresse, on doit faire persist
if (!$address->getId()) {
$entityManager->persist($address);
$logAction = 'Ajout';
} else {
$logAction = 'Modification';
}
$entityManager->flush();
$appLogger->record($idAddr ? 'EDIT' : 'CREATE', sprintf('%s adresse pour le client : %s', $logAction, $customer->getName()));
$this->addFlash('success', sprintf('L\'adresse a été %s avec succès.', $idAddr ? 'modifiée' : 'ajoutée'));
// On redirige vers la fiche sans le paramètre idAddr pour nettoyer l'URL
return $this->redirectToRoute('app_crm_customer_edit', ['id' => $customer->getId()]);
}
// --- LOGIQUE FORMULAIRE CLIENT (Reste identique) ---
$form = $this->createForm(CustomerType::class, $customer);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// 1. Log de l'action
$appLogger->record('EDIT', sprintf('Modification du client : %s %s', $customer->getSurname(), $customer->getName()));
// 2. Mise à jour sur Stripe (si le client possède un CustomerId)
if ($customer->getCustomerId()) {
// Tu peux créer une méthode updateCustomer dans ton service Stripe
// ou simplement utiliser le client natif ici pour l'exemple :
try {
$stripeClient->updateCustomer($customer);
} catch (\Exception $e) {
$this->addFlash('warning', 'Modifié localement, mais erreur de synchro Stripe : ' . $e->getMessage());
$this->addFlash('warning', 'Erreur synchro Stripe : ' . $e->getMessage());
}
}
// 3. Sauvegarde en base de données
$entityManager->flush();
$appLogger->record('EDIT', sprintf('Modification du client : %s %s', $customer->getSurname(), $customer->getName()));
$this->addFlash('success', 'Les informations du client ont été mises à jour.');
return $this->redirectToRoute('app_crm_customer_edit', ['id' => $customer->getId()]);
}
// Si c'est juste une consultation (GET), on log la vue
if (!$form->isSubmitted()) {
if ($request->isMethod('GET')) {
$appLogger->record('VIEW', sprintf('Consultation de la fiche client : %s', $customer->getName()));
}
return $this->render('dashboard/customer/show.twig', [
'customer' => $customer,
'formAddress' => $formAddress->createView(),
'form' => $form->createView(),
'editingAddress' => (bool)$idAddr // Pour changer le texte du bouton dans le Twig
]);
}
#[Route(path: '/crm/customer/delete/{id}', name: 'app_crm_customer_delete', methods: ['GET', 'POST'])]

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Form;
use App\Entity\Customer;
use App\Entity\CustomerAddress;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CustomerAddAddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('address', TextType::class, [
'label' => 'Adresse',
'required' => true,
])
->add('address2', TextType::class, [
'label' => 'Adresse 2',
'required' => false,
])
->add('address3', TextType::class, [
'label' => 'Adresse 3',
'required' => false,
])
->add('zipcode', TextType::class, [
'label' => 'Code postale',
'required' => true,
])
->add('city', TextType::class, [
'label' => 'Ville',
'required' => true,
])
->add('country', ChoiceType::class, [
'label' => 'Pays',
'required' => true,
'choices' => [
'France' => 'fr',
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => CustomerAddress::class,
]);
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Form;
use App\Entity\Customer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CustomerAddType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('civ', ChoiceType::class, [
'label' => 'Civilité',
'choices' => [
'Monsieur' => 'M.',
'Madame' => 'Mme',
],
'expanded' => false,
'multiple' => false,
])
->add('surname', TextType::class, [
'label' => 'Prénom',
'attr' => ['placeholder' => 'ex: Jean'],
'required' => true,
])
->add('name', TextType::class, [
'label' => 'Nom',
'attr' => ['placeholder' => 'ex: DUPONT'],
'required' => true,
])
->add('type', ChoiceType::class, [
'label' => 'Type de client',
'choices' => [
'Particulier' => 'personal',
'Entreprise' => 'company',
'Association' => 'association',
'Mairie / Collectivité' => 'mairie',
],
])
->add('email', EmailType::class, [
'label' => 'Adresse Email',
'attr' => ['placeholder' => 'contact@exemple.fr'],
'required' => true,
])
->add('phone', TelType::class, [ // TelType est plus adapté pour mobile
'label' => 'Téléphone',
'attr' => ['placeholder' => '06 .. .. .. ..'],
'required' => true,
])
->add('siret', TextType::class, [
'label' => 'Numéro SIRET',
'required' => false,
'attr' => ['placeholder' => '14 chiffres'],
'help' => 'Obligatoire pour les entreprises et mairies',
])
->add('adresse', TextType::class, [
'label' => 'Adresse',
'required' => true,
'mapped' => false,
])
->add('adresse2', TextType::class, [
'label' => 'Adresse 2',
'required' => false,
'mapped' => false,
])
->add('adresse3', TextType::class, [
'label' => 'Adresse 3',
'required' => false,
'mapped' => false,
])
->add('zipcode', TextType::class, [
'label' => 'Code postale',
'required' => true,
'mapped' => false,
])
->add('city', TextType::class, [
'label' => 'Ville',
'required' => true,
'mapped' => false,
])
->add('country', ChoiceType::class, [
'label' => 'Pays',
'required' => true,
'mapped' => false,
'choices' => [
'France' => 'fr',
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Customer::class,
]);
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Form;
use App\Entity\Customer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextType;

View File

@@ -80,6 +80,32 @@
{{ form_widget(form.phone) }}
</div>
</div>
<div class="mt-2 grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="w-full">
{{ form_label(form.adresse) }}
{{ form_widget(form.adresse) }}
</div>
<div class="w-full">
{{ form_label(form.adresse2) }}
{{ form_widget(form.adresse2) }}
</div>
<div class="w-full">
{{ form_label(form.adresse3) }}
{{ form_widget(form.adresse3) }}
</div>
<div class="w-full">
{{ form_label(form.zipcode) }}
{{ form_widget(form.zipcode) }}
</div>
<div class="w-full">
{{ form_label(form.city) }}
{{ form_widget(form.city) }}
</div>
<div class="w-full">
{{ form_label(form.country) }}
{{ form_widget(form.country) }}
</div>
</div>
</div>
{# ACTIONS : Boutons espacés #}

View File

@@ -1,13 +1,12 @@
{% extends 'dashboard/base.twig' %}
{% block title %}Modifier le Client{% endblock %}
{% block title_header %}Fiche Client{% endblock %}
{% block title_header %}Fiche <span class="text-blue-500">Client</span>{% endblock %}
{% block actions %}
{# Bouton Retour #}
<a href="{{ path('app_crm_customer') }}" class="flex items-center px-4 py-2 text-[10px] font-black text-slate-400 hover:text-white uppercase tracking-widest transition-all">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
Annuler
<a href="{{ path('app_crm_customer') }}" class="flex items-center px-4 py-2 text-[10px] font-black text-slate-400 hover:text-white uppercase tracking-widest transition-all group">
<svg class="w-4 h-4 mr-2 transform group-hover:-translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
Retour au listing
</a>
{% endblock %}
@@ -15,158 +14,178 @@
<div class="w-full max-w-full mx-auto space-y-8 animate-in fade-in duration-700">
{{ form_start(form) }}
{# HEADER DE LA FICHE - GLASSMORPHISM #}
<div class="w-full backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] p-8 flex flex-col md:flex-row items-center justify-between mb-8 gap-6">
{# HEADER DE LA FICHE #}
<div class="w-full backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] p-8 flex flex-col md:flex-row items-center justify-between mb-8 gap-6 shadow-2xl">
<div class="flex items-center space-x-6">
<div class="h-20 w-20 rounded-3xl bg-gradient-to-br from-blue-500 to-emerald-500 flex items-center justify-center text-white text-2xl font-black shadow-2xl">
<div class="h-20 w-20 rounded-3xl bg-gradient-to-br from-blue-600 to-indigo-600 flex items-center justify-center text-white text-2xl font-black shadow-2xl ring-4 ring-white/5">
{{ customer.surname|first|upper }}{{ customer.name|first|upper }}
</div>
<div>
<h2 class="text-3xl font-black text-white tracking-tight">
<span class="text-slate-500 text-sm uppercase tracking-[0.2em] block mb-1">Fiche Client</span>
<span class="text-slate-500 text-sm uppercase tracking-[0.2em] block mb-1">Dossier Personnel</span>
{{ customer.surname|upper }} {{ customer.name }}
</h2>
<div class="flex items-center mt-2 space-x-4">
<span class="text-[10px] font-bold text-blue-400 uppercase tracking-widest">ID Interne: #{{ customer.id }}</span>
<span class="text-[10px] font-bold text-blue-400 uppercase tracking-widest">ID #{{ customer.id }}</span>
{% if customer.customerId %}
<span class="flex items-center text-[10px] font-bold text-emerald-400 uppercase tracking-widest bg-emerald-500/10 px-2 py-0.5 rounded-lg border border-emerald-500/20">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 mr-2"></span>
Stripe ID: {{ customer.customerId }}
</span>
{% else %}
<span class="flex items-center text-[10px] font-bold text-rose-500 uppercase tracking-widest bg-rose-500/10 px-2 py-0.5 rounded-lg border border-rose-500/20 italic">
Non synchronisé Stripe
</span>
<span class="flex items-center text-[10px] font-bold text-emerald-400 uppercase tracking-widest bg-emerald-500/10 px-2 py-1 rounded-lg border border-emerald-500/20">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 mr-2 shadow-[0_0_8px_rgba(16,185,129,0.5)]"></span>
Stripe Sync
</span>
{% endif %}
</div>
</div>
</div>
<button type="submit" class="w-full md:w-auto px-10 py-4 bg-blue-600 hover:bg-blue-500 text-white text-[10px] font-black uppercase tracking-widest rounded-2xl shadow-lg shadow-blue-600/20 transition-all hover:scale-105">
Enregistrer les modifications
<button type="submit" class="w-full md:w-auto px-10 py-4 bg-blue-600 hover:bg-blue-500 text-white text-[10px] font-black uppercase tracking-widest rounded-2xl shadow-lg shadow-blue-600/20 transition-all active:scale-95">
Mettre à jour le profil
</button>
</div>
{# GRILLE PRINCIPALE #}
{# GRILLE PRINCIPALE INFOS #}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
{# COLONNE GAUCHE : ÉDITION DES DONNÉES #}
<div class="lg:col-span-2 space-y-8">
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] p-10">
<div class="flex items-center space-x-4 mb-10">
<span class="w-8 h-px bg-blue-500/30"></span>
<span class="text-[10px] font-black text-blue-500 uppercase tracking-[0.3em]">Identité du client</span>
<span class="text-[10px] font-black text-blue-500 uppercase tracking-[0.3em]">Coordonnées de base</span>
</div>
<div class="space-y-8">
{# LIGNE 1 : CIV / NOM / PRENOM #}
<div class="grid grid-cols-1 md:grid-cols-6 gap-8">
<div class="md:col-span-1">
{{ form_label(form.civ) }}
{{ form_widget(form.civ) }}
</div>
<div class="md:col-span-2">
{{ form_label(form.surname) }}
{{ form_widget(form.surname) }}
</div>
<div class="md:col-span-3">
{{ form_label(form.name) }}
{{ form_widget(form.name) }}
</div>
<div class="md:col-span-1">{{ form_row(form.civ) }}</div>
<div class="md:col-span-2">{{ form_row(form.surname) }}</div>
<div class="md:col-span-3">{{ form_row(form.name) }}</div>
</div>
<div class="flex items-center space-x-4 py-4">
<span class="w-8 h-px bg-blue-500/30"></span>
<span class="text-[10px] font-black text-blue-500 uppercase tracking-[0.3em]">Contact & Société</span>
</div>
{# LIGNE 2 : EMAIL / TEL #}
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
{{ form_label(form.email) }}
{{ form_widget(form.email) }}
</div>
<div>
{{ form_label(form.phone) }}
{{ form_widget(form.phone) }}
</div>
<div>{{ form_row(form.email) }}</div>
<div>{{ form_row(form.phone) }}</div>
</div>
{# LIGNE 3 : TYPE / SIRET #}
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
{{ form_label(form.type) }}
{{ form_widget(form.type) }}
</div>
<div>
{{ form_label(form.siret) }}
{{ form_widget(form.siret) }}
</div>
<div>{{ form_row(form.type) }}</div>
<div>{{ form_row(form.siret) }}</div>
</div>
</div>
</div>
</div>
{# COLONNE DROITE : STATUT & DANGER ZONE #}
<div class="space-y-8">
{# ACTIVITÉ #}
<div class="backdrop-blur-xl bg-emerald-500/5 border border-emerald-500/10 rounded-[2.5rem] p-8">
<h3 class="text-[10px] font-black text-emerald-500 uppercase tracking-widest mb-6 flex items-center">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Statut du compte
Résumé
</h3>
<div class="space-y-4">
<div class="flex justify-between items-center border-b border-white/5 pb-3">
<span class="text-xs text-slate-400 font-medium">Type Client</span>
{% set types = {
'personal': 'Particulier',
'company': 'Entreprise',
'association': 'Association',
'mairie': 'Mairie'
} %}
{# Puis plus bas dans ton code : #}
<span class="text-xs text-white font-bold">
{{ types[customer.type] ?? customer.type|upper }}
</span> </div>
<span class="text-xs text-slate-400 font-medium">Contrat</span>
<span class="text-xs text-white font-bold uppercase tracking-widest">
{{ customer.type|default('Standard') }}
</span>
</div>
</div>
</div>
{# ZONE DE DANGER #}
<div class="backdrop-blur-xl bg-rose-500/5 border border-rose-500/10 rounded-[2.5rem] p-8">
<h4 class="text-[10px] font-black text-rose-500 uppercase tracking-widest mb-4">Danger Zone</h4>
<p class="text-[10px] text-slate-500 mb-6 leading-relaxed">
La suppression d'un client entraînera la suppression immédiate de ses informations sur l'Intranet et sur son compte Stripe.
<h4 class="text-[10px] font-black text-rose-500 uppercase tracking-widest mb-4">Zone sensible</h4>
<p class="text-[9px] text-slate-500 mb-6 leading-relaxed uppercase font-bold tracking-tight">
La suppression est définitive sur l'intranet et Stripe.
</p>
<a href="{{ path('app_crm_customer_delete', {id: customer.id}) }}?_token={{ csrf_token('delete' ~ customer.id) }}"
data-turbo-method="post"
data-turbo-confirm="Confirmer la suppression définitive de {{ customer.surname }} {{ customer.name }} ? Cette action est irréversible."
class="flex items-center justify-center w-full py-3 border border-rose-500/20 hover:bg-rose-600 text-rose-500 hover:text-white text-[9px] font-black uppercase tracking-tighter rounded-xl transition-all duration-300 shadow-lg shadow-rose-500/5">
<svg class="w-3.5 h-3.5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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>
Supprimer le compte
data-turbo-confirm="Confirmer la suppression définitive ?"
class="flex items-center justify-center w-full py-3 border border-rose-500/20 hover:bg-rose-600 text-rose-500 hover:text-white text-[9px] font-black uppercase tracking-widest rounded-xl transition-all shadow-lg shadow-rose-500/5">
Désactiver le client
</a>
</div>
</div>
</div>
{{ form_end(form) }}
{# SECTION ADRESSES #}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-12">
{# LISTE DES ADRESSES #}
<div class="lg:col-span-2 space-y-6">
<h3 class="text-lg font-bold text-white mb-6 flex items-center ml-4">
<span class="w-2 h-2 rounded-full bg-blue-500 mr-3"></span>
Carnet d'adresses ({{ customer.customerAddresses|length }})
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% for address in customer.customerAddresses %}
<div class="backdrop-blur-xl bg-white/[0.02] border border-white/5 rounded-3xl p-6 hover:border-blue-500/30 transition-all group relative">
<div class="flex items-start justify-between">
<div class="space-y-1">
<p class="text-white text-sm font-bold">{{ address.address }}</p>
{% if address.address2 %}<p class="text-slate-400 text-xs">{{ address.address2 }}</p>{% endif %}
<p class="text-slate-500 text-[11px] font-mono tracking-tighter uppercase">
{{ address.zipcode }} {{ address.city }}{{ address.country }}
</p>
</div>
<div class="flex space-x-2">
<a href="{{ path('app_crm_customer_edit', {id: customer.id, idAddr: address.id}) }}" class="p-2 bg-blue-500/10 text-blue-400 rounded-lg hover:bg-blue-500 hover:text-white transition-all">
<svg class="w-3.5 h-3.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>
</a>
</div>
</div>
</div>
{% else %}
<div class="col-span-full p-12 text-center border-2 border-dashed border-white/5 rounded-[2.5rem]">
<p class="text-[10px] font-black text-slate-600 uppercase tracking-widest">Aucune adresse enregistrée</p>
</div>
{% endfor %}
</div>
</div>
{# FORMULAIRE D'ADRESSE (AJOUT/EDIT) #}
<div class="lg:col-span-1">
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/10 rounded-[2.5rem] p-8 shadow-2xl sticky top-8">
<div class="mb-8">
<h3 class="text-sm font-black text-white uppercase tracking-widest flex items-center">
<svg class="w-4 h-4 mr-2 text-blue-500" 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"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
{{ editingAddress ? 'Modifier l\'adresse' : 'Ajouter une adresse' }}
</h3>
</div>
{{ form_start(formAddress) }}
<div class="space-y-4">
{{ form_row(formAddress.address) }}
{{ form_row(formAddress.address2) }}
{{ form_row(formAddress.address3) }}
<div class="grid grid-cols-2 gap-4">
{{ form_row(formAddress.zipcode) }}
{{ form_row(formAddress.city) }}
</div>
{{ form_row(formAddress.country) }}
<div class="pt-4 flex flex-col gap-3">
<button type="submit" class="w-full py-4 bg-emerald-600 hover:bg-emerald-500 text-white text-[10px] font-black uppercase tracking-widest rounded-2xl shadow-lg shadow-emerald-600/10 transition-all">
{{ editingAddress ? 'Enregistrer les modifications' : 'Ajouter au carnet' }}
</button>
{% if editingAddress %}
<a href="{{ path('app_crm_customer_edit', {id: customer.id}) }}" class="text-center py-2 text-[9px] font-black text-slate-500 uppercase tracking-widest hover:text-white transition-colors">
Annuler la modification
</a>
{% endif %}
</div>
</div>
{{ form_end(formAddress) }}
</div>
</div>
</div>
</div>
<style>
label { @apply block text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] mb-3 ml-2; }
label { @apply block text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] mb-2 ml-2; }
input, select, textarea {
@apply w-full bg-slate-900/40 border border-white/10 rounded-2xl px-6 py-4 text-sm text-white outline-none focus:border-blue-500/50 focus:bg-slate-900/60 transition-all duration-300 !important;
@apply w-full bg-slate-900/60 border border-white/5 rounded-2xl px-5 py-3.5 text-xs text-white outline-none focus:border-blue-500/30 focus:bg-slate-900/80 transition-all duration-300 !important;
}
/* Style spécifique pour les selects ChoiceType */
select {
@apply appearance-none cursor-pointer;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23475569'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1.5rem center;
background-size: 1rem;
background-position: right 1.25rem center;
background-size: 0.8rem;
}
</style>
{% endblock %}