```
✨ 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:
@@ -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'])]
|
||||
|
||||
59
src/Form/CustomerAddAddressType.php
Normal file
59
src/Form/CustomerAddAddressType.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
108
src/Form/CustomerAddType.php
Normal file
108
src/Form/CustomerAddType.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 #}
|
||||
|
||||
@@ -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 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 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="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>
|
||||
|
||||
<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>{{ form_row(form.email) }}</div>
|
||||
<div>{{ form_row(form.phone) }}</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ form_label(form.phone) }}
|
||||
{{ form_widget(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 %}
|
||||
|
||||
Reference in New Issue
Block a user