feat(Newsletter/ContactController): Ajoute la gestion d'édition des listes de contacts.

This commit is contained in:
Serreau Jovann
2025-08-01 14:09:03 +02:00
parent bae8c67837
commit 6c026b00cb
6 changed files with 712 additions and 6 deletions

View File

@@ -29,6 +29,7 @@
"nelmio/cors-bundle": "^2.5",
"ovh/ovh": "*",
"phpdocumentor/reflection-docblock": "^5.6.2",
"phpoffice/phpspreadsheet": "*",
"phpstan/phpdoc-parser": "^2.2",
"presta/sitemap-bundle": "^4.1",
"sentry/sentry-symfony": "^5.3",

423
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8bdb67f55f7688464c0ac8ffc06fbbe5",
"content-hash": "5069feeb5ac1e553a0a6c92eff275abd",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -430,6 +430,85 @@
],
"time": "2024-07-16T11:13:48+00:00"
},
{
"name": "composer/pcre",
"version": "3.3.2",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<1.11.10"
},
"require-dev": {
"phpstan/phpstan": "^1.12 || ^2",
"phpstan/phpstan-strict-rules": "^1 || ^2",
"phpunit/phpunit": "^8 || ^9"
},
"type": "library",
"extra": {
"phpstan": {
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
"PCRE",
"preg",
"regex",
"regular expression"
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.3.2"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2024-11-12T16:29:46+00:00"
},
{
"name": "composer/semver",
"version": "3.4.3",
@@ -4490,6 +4569,191 @@
},
"time": "2025-06-26T07:49:55+00:00"
},
{
"name": "maennchen/zipstream-php",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/maennchen/ZipStream-PHP.git",
"reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416",
"reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-zlib": "*",
"php-64bit": "^8.3"
},
"require-dev": {
"brianium/paratest": "^7.7",
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^3.16",
"guzzlehttp/guzzle": "^7.5",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5",
"phpunit/phpunit": "^12.0",
"vimeo/psalm": "^6.0"
},
"suggest": {
"guzzlehttp/psr7": "^2.4",
"psr/http-message": "^2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"ZipStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paul Duncan",
"email": "pabs@pablotron.org"
},
{
"name": "Jonatan Männchen",
"email": "jonatan@maennchen.ch"
},
{
"name": "Jesse Donat",
"email": "donatj@gmail.com"
},
{
"name": "András Kolesár",
"email": "kolesar@kolesar.hu"
}
],
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
"keywords": [
"stream",
"zip"
],
"support": {
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0"
},
"funding": [
{
"url": "https://github.com/maennchen",
"type": "github"
}
],
"time": "2025-07-17T11:15:13+00:00"
},
{
"name": "markbaker/complex",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPComplex.git",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Complex\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with complex numbers",
"homepage": "https://github.com/MarkBaker/PHPComplex",
"keywords": [
"complex",
"mathematics"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
},
"time": "2022-12-06T16:21:08+00:00"
},
{
"name": "markbaker/matrix",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpdocumentor/phpdocumentor": "2.*",
"phploc/phploc": "^4.0",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"sebastian/phpcpd": "^4.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@demon-angel.eu"
}
],
"description": "PHP Class for working with matrices",
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"keywords": [
"mathematics",
"matrix",
"vector"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
},
"time": "2022-12-02T22:17:43+00:00"
},
{
"name": "meyfa/php-svg",
"version": "v0.9.1",
@@ -5368,6 +5632,112 @@
},
"time": "2024-11-09T15:12:26+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "4.5.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "2ea9786632e6fac1aee601b6e426bcc723d8ce13"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/2ea9786632e6fac1aee601b6e426bcc723d8ce13",
"reference": "2ea9786632e6fac1aee601b6e426bcc723d8ce13",
"shasum": ""
},
"require": {
"composer/pcre": "^1||^2||^3",
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": "^8.1",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1 || ^2.0",
"phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0",
"phpstan/phpstan-phpunit": "^1.0 || ^2.0",
"phpunit/phpunit": "^10.5",
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/4.5.0"
},
"time": "2025-07-24T05:15:59+00:00"
},
{
"name": "phpstan/phpdoc-parser",
"version": "2.2.0",
@@ -5949,6 +6319,57 @@
},
"time": "2024-09-11T13:17:53+00:00"
},
{
"name": "psr/simple-cache",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/simple-cache.git",
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\SimpleCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interfaces for simple caching",
"keywords": [
"cache",
"caching",
"psr",
"psr-16",
"simple-cache"
],
"support": {
"source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
},
"time": "2021-10-29T13:26:27+00:00"
},
{
"name": "ralouphie/getallheaders",
"version": "3.0.3",

View File

@@ -3,9 +3,12 @@
namespace App\Controller\Artemis\Newsletter;
use App\Entity\Newsletter\Contact;
use App\Entity\Newsletter\ContactLine;
use App\Form\Artemis\Newsletter\ContactLineType;
use App\Form\Artemis\Newsletter\ContactListType;
use App\Repository\Newsletter\ContactRepository;
use Doctrine\ORM\EntityManagerInterface;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -53,13 +56,127 @@ class ContactController extends AbstractController
]);
}
#[Route(path: '/artemis/newsletter/contact/edit',name: 'artemis_newsletter_contact_edit',methods: ['GET', 'POST'])]
public function contactsEdit(ContactRepository $contactRepository): Response
#[Route(path: '/artemis/newsletter/contact/edit/{id}',name: 'artemis_newsletter_contact_edit',methods: ['GET', 'POST'])]
public function contactsEdit(?Contact $contact,Request $request,EntityManagerInterface $entityManager): Response
{
if(!$contact instanceof Contact){
return $this->redirectToRoute('artemis_newsletter_contact');
}
$form = $this->createForm(ContactListType::class, $contact);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->persist($contact);
$entityManager->flush();
$this->addFlash('success', 'La liste a été mise à jour');
// Redirige vers la liste des listes (à adapter selon ta route)
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contact->getId()]);
}
if($request->files->has('contacts_file')) {
$contacts_file = $request->files->get('contacts_file');
$spreadsheet = IOFactory::load($contacts_file);
// Récupérer la feuille active (première feuille)
$sheet = $spreadsheet->getActiveSheet();
// Lire la première ligne (en-têtes)
$headerRow = $sheet->rangeToArray('A1:' . $sheet->getHighestColumn() . '1')[0];
// Normalisation : tout en minuscule, supprimer espaces avant/après
$headerRowNormalized = array_map(function($header) {
return mb_strtolower(trim((string) $header));
}, $headerRow);
// Colonnes attendues (nom, prénom, email)
$requiredColumns = ['nom','prenom', 'email'];
// Vérifier que chaque colonne attendue est bien présente dans l'en-tête
$missingColumns = [];
foreach ($requiredColumns as $col) {
// Pour "prénom" supporter aussi "prenom" (sans accent)
if (!in_array($col, $headerRowNormalized, true)) {
$missingColumns[] = $col;
}
}
if (count($missingColumns) > 0) {
// Colonnes manquantes : gérer l'erreur (throw, flash message, etc.)
$missingStr = implode(', ', $missingColumns);
$this->addFlash("error_import","Le fichier importé ne contient pas les colonnes obligatoires $missingStr");
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contact->getId()]);
}
$indexNom = array_search('nom', $headerRowNormalized, true);
$indexPrenom = array_search('prenom', $headerRowNormalized, true);
$indexEmail = array_search('email', $headerRowNormalized, true);
$contacts = 0;
// Parcourir les lignes à partir de la 2e (données)
$highestRow = $sheet->getHighestRow();
for ($row = 2; $row <= $highestRow; $row++) {
$rowData = $sheet->rangeToArray("A{$row}:" . $sheet->getHighestColumn() . $row)[0];
$nom = isset($rowData[$indexNom]) ? trim($rowData[$indexNom]) : null;
$prenom = isset($rowData[$indexPrenom]) ? trim($rowData[$indexPrenom]) : null;
$email = isset($rowData[$indexEmail]) ? trim($rowData[$indexEmail]) : null;
// Optionnel : valider email
if ($email && filter_var($email, FILTER_VALIDATE_EMAIL)) {
$contactExist = $entityManager->getRepository(ContactLine::class)->findOneBy(['list'=>$contact,'email'=>$email]);
if(!$contactExist instanceof ContactLine) {
$contactLine = new ContactLine();
$contactLine->setName($nom);
$contactLine->setEmail($email);
$contactLine->setSurname($prenom);
$contactLine->setUuid(Uuid::v4());
$entityManager->persist($contactLine);
$contact->addContactLine($contactLine);
$entityManager->persist($contact);
$contacts++;
}
}
$entityManager->flush();;
}
$this->addFlash('success', $contacts . ' contacts importés correctement.');
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contact->getId()]);
}
if($request->query->has('deleteContact')) {
$c = $entityManager->getRepository(ContactLine::class)->find($request->query->get('deleteContact'));
if($c instanceof ContactLine) {
$entityManager->remove($c);
$entityManager->flush();
}
$this->addFlash("success","Suppression effectuée");
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contact->getId()]);
}
$contactLine = new ContactLine();
$contactLine->setList($contact);
$contactLine->setUuid(Uuid::v4());
$formContact= $this->createForm(ContactLineType::class,$contactLine);
$formContact->handleRequest($request);
if ($formContact->isSubmitted() && $formContact->isValid()) {
$entityManager->persist($contactLine);
$entityManager->flush();
$this->addFlash("success","Création effectuée");
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contact->getId()]);
}
//import
return $this->render('artemis/newsletter/contact/edit.twig', [
'form' => $form->createView(),
'formContact' => $formContact->createView(),
'contact' => $contact,
]);
}
#[Route(path: '/artemis/newsletter/contact/delete',name: 'artemis_newsletter_contact_delete',methods: ['GET', 'POST'])]
public function contactsDelete(ContactRepository $contactRepository): Response
public function contactsDelete(?Contact $contact): Response
{
}

View File

@@ -51,9 +51,9 @@ class ContactLine
return $this->name;
}
public function setName(?string $na<EFBFBD>me): static
public function setName(?string $name): static
{
$this->name = $na<EFBFBD>me;
$this->name = $name;
return $this;
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Form\Artemis\Newsletter;
use App\Entity\Newsletter\ContactLine;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ContactLineType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name',TextType::class,[
'label' => 'Nom',
'required' => false,
])
->add('email',EmailType::class,[
'label' => 'Email',
'required' => true,
])
->add('surname',TextType::class,[
'label' => 'Nom',
'required' => false,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('data_class',ContactLine::class);
}
}

View File

@@ -0,0 +1,129 @@
{% extends 'artemis/base.twig' %}
{% block title %}Liste - {{ contact.name }}{% endblock %}
{% block content %}
<h1 class="text-2xl font-semibold mb-6">Liste - {{ contact.name }}</h1>
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6">
{{ form_start(form, {'attr': {'class': 'w-full'}}) }}
<div class="mb-4">
{{ form_label(form.name, null, {'label_attr': {'class': 'block mb-1 font-medium text-gray-700 dark:text-gray-300'}}) }}
{{ form_widget(form.name, {'attr': {
'class': 'w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white'
}}) }}
{{ form_errors(form.name) }}
</div>
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition font-semibold w-full">
Modifié liste
</button>
{{ form_end(form) }}
</div>
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6 flex flex-col items-center justify-center text-center border border-dashed border-blue-400">
<div class="mb-4">
<svg class="w-12 h-12 mx-auto text-blue-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 48 48">
<rect x="8" y="18" width="32" height="22" rx="3" fill="currentColor" class="text-blue-100"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M24 6v22m0 0l-7-7m7 7l7-7" stroke="currentColor"/>
</svg>
</div>
<h2 class="text-lg font-semibold text-gray-100 mb-2">Importer des contacts</h2>
<p class="text-gray-400 mb-4 text-sm">
Téléversez un fichier <span class="font-semibold text-blue-300">CSV</span> ou <span class="font-semibold text-blue-300">Excel</span> pour ajouter plusieurs contacts à cette liste.<br>
<span class="text-xs text-gray-500">Le fichier doit contenir les colonnes: email, nom, prénom…</span>
</p>
<form method="post" enctype="multipart/form-data" action="{{ path('artemis_newsletter_contact_edit', {'id': contact.id}) }}" class="flex flex-col items-center gap-4 w-full">
<input
type="file"
name="contacts_file"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
class="block file:mr-4 file:px-4 file:py-2 text-gray-900 dark:text-gray-100
file:rounded file:border-0 file:text-sm file:bg-blue-50 file:text-blue-700
hover:file:bg-blue-100 focus:file:outline-none
dark:file:bg-gray-700 dark:file:text-blue-300 dark:hover:file:bg-gray-600"
required
>
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-semibold transition w-full max-w-xs">
Importer
</button>
</form>
</div>
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6">
<h3>Ajouter un contact</h3>
{{ form_start(formContact, {'attr': {'class': 'w-full'}}) }}
<div class="grid grid-cols-3">
<div class="m-2">
{{ form_label(formContact.name, null, {'label_attr': {'class': 'block mb-1 font-medium text-gray-700 dark:text-gray-300'}}) }}
{{ form_widget(formContact.name, {'attr': {
'class': 'w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white'
}}) }}
{{ form_errors(formContact.name) }}
</div>
<div class="m-2">
{{ form_label(formContact.surname, null, {'label_attr': {'class': 'block mb-1 font-medium text-gray-700 dark:text-gray-300'}}) }}
{{ form_widget(formContact.surname, {'attr': {
'class': 'w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white'
}}) }}
{{ form_errors(formContact.surname) }}
</div>
<div class="m-2">
{{ form_label(formContact.email, null, {'label_attr': {'class': 'block mb-1 font-medium text-gray-700 dark:text-gray-300'}}) }}
{{ form_widget(formContact.email, {'attr': {
'class': 'w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white'
}}) }}
{{ form_errors(formContact.email) }}
</div>
</div>
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition font-semibold w-full">
Crée le contact
</button>
{{ form_end(formContact) }}
</div>
<div class="bg-gray-800 rounded-lg shadow mt-2">
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
Email
</th>
<th scope="col" class="px-6 py-3">
Nom
</th>
<th scope="col" class="px-6 py-3">
Prénom
</th>
<th scope="col" class="px-6 py-3">
Status
</th>
<th scope="col" class="px-6 py-3">
Actions
</th>
</tr>
</thead>
<tbody>
{% for line in contact.contactLines %}
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200">
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
{{ line.email }}
</th>
<td class="px-6 py-4">
{{ line.name }}
</td>
<td class="px-6 py-4">
{{ line.surname }}
</td>
<td class="px-6 py-4">
<i class="fad fa-check-circle text-green-500"></i>
</td>
<td class="px-6 py-4">
<a class="button bg-red-900 text-white p-2" href="{{ app.request.pathInfo }}?deleteContact={{ line.id }}">Supprimer</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}