✨ feat(vich_uploader): Ajoute la configuration VichUploader pour les factures.
✨ feat(order/f.twig): Affiche les factures du client avec actions. ✨ feat(CustomerController): Gère l'affichage et l'envoi des factures. 🆕 feat(FacturePdf): Crée un service PDF pour les factures clients. 🆕 feat(ContactListType): Ajoute un formulaire pour créer une liste de contacts. 🆕 feat(ContactController): Gère les listes de contacts pour la newsletter. ✨ feat(base.twig): Ajoute un menu pour la gestion de la newsletter. ✨ feat(CustomerOrder): Ajoute les champs et annotations pour l'upload de facture. 🆕 feat(contact.twig): Affiche la liste des contacts. 🆕 feat(BillingEventSusbriber): Gère la génération de la facture PDF. 🆕 feat(TemplateController): Initialise le controller des templates de newsletter. 🆕 feat(CompaignController): Crée un controller pour les campagnes newsletter. 🎨 style(admin.scss): Ajoute le style css pour la card contact newsletter. 🆕 feat(add.twig): Ajoute le formulaire de création de liste de contact.
This commit is contained in:
@@ -26,3 +26,13 @@ input {
|
||||
.bg-opacity-70{
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.card-contact{
|
||||
border: 1px solid #1a202c;
|
||||
background: var(--color-gray-600);
|
||||
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
.flex{
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,14 @@ vich_uploader:
|
||||
inject_on_load: false
|
||||
delete_on_update: true
|
||||
delete_on_remove: true
|
||||
facture:
|
||||
uri_prefix: /storage/facture
|
||||
upload_destination: '%kernel.project_dir%/public/storage/facture'
|
||||
namer: App\VichUploader\Namer\Customer\DevisName # Replaced namer
|
||||
directory_namer: App\VichUploader\DirectoryNamer\Customer\DevisName
|
||||
inject_on_load: false
|
||||
delete_on_update: true
|
||||
delete_on_remove: true
|
||||
#mappings:
|
||||
# products:
|
||||
# uri_prefix: /images/products
|
||||
|
||||
32
migrations/Version20250731081945.php
Normal file
32
migrations/Version20250731081945.php
Normal 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 Version20250731081945 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 customer_order ADD state VARCHAR(255) 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 customer_order DROP state');
|
||||
}
|
||||
}
|
||||
40
migrations/Version20250731082547.php
Normal file
40
migrations/Version20250731082547.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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 Version20250731082547 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 customer_order ADD facture_file_name VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_order ADD facture_dimensions JSON DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_order ADD facture_size VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_order ADD facture_mine_type VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_order ADD facture_original_name VARCHAR(255) 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 customer_order DROP facture_file_name');
|
||||
$this->addSql('ALTER TABLE customer_order DROP facture_dimensions');
|
||||
$this->addSql('ALTER TABLE customer_order DROP facture_size');
|
||||
$this->addSql('ALTER TABLE customer_order DROP facture_mine_type');
|
||||
$this->addSql('ALTER TABLE customer_order DROP facture_original_name');
|
||||
}
|
||||
}
|
||||
33
migrations/Version20250801075326.php
Normal file
33
migrations/Version20250801075326.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?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 Version20250801075326 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('CREATE TABLE contact (id SERIAL NOT NULL, name VARCHAR(255) NOT NULL, uuid UUID NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('COMMENT ON COLUMN contact.uuid IS \'(DC2Type:uuid)\'');
|
||||
}
|
||||
|
||||
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('DROP TABLE contact');
|
||||
}
|
||||
}
|
||||
@@ -137,12 +137,14 @@ class CustomerController extends AbstractController
|
||||
$order->setNumOrder($numFinal);
|
||||
$order->setAvisPayment($customerAdvertPayment);
|
||||
$order->setCustomer($customer);
|
||||
$order->setState("f-created");
|
||||
$order->setCreateAt(new \DateTimeImmutable());
|
||||
$order->setUpdateAt(new \DateTimeImmutable());
|
||||
|
||||
$entityManager->persist($order);
|
||||
foreach ($customerAdvertPayment->getCustomerAdvertPaymentLines() as $line) {
|
||||
$orderLine = new CustomerOrderLine();
|
||||
$orderLine->setPo($line->getPo());
|
||||
$orderLine->setPo($line->getPos());
|
||||
$orderLine->setPriceHt($line->getPriceHt());
|
||||
$orderLine->setName($line->getName());
|
||||
$orderLine->setContent($line->getContent());
|
||||
@@ -276,12 +278,14 @@ class CustomerController extends AbstractController
|
||||
|
||||
$orderDevis = $entityManager->getRepository(CustomerDevis::class)->findBy(['customer'=>$customer],['id'=>'ASC']);
|
||||
$orderAdvert = $entityManager->getRepository(CustomerAdvertPayment::class)->findBy(['customer'=>$customer],['id'=>'ASC']);
|
||||
$orderOrder = $entityManager->getRepository(CustomerOrder::class)->findBy(['customer'=>$customer],['id'=>'ASC']);
|
||||
|
||||
return $this->render('artemis/intranet/customer/edit.twig',[
|
||||
'form' => $form->createView(),
|
||||
'formNdd' => $formNdd->createView(),
|
||||
'customer' => $customer,
|
||||
'orderDevis' => $paginator->paginate($orderDevis,$request->get('page',1),20),
|
||||
'orderOrders' => $paginator->paginate($orderOrder,$request->get('page',1),20),
|
||||
'orderAdverts' => $paginator->paginate($orderAdvert,$request->get('page',1),20),
|
||||
'current' => $request->get('current','main'),
|
||||
'currentOrder' => $request->get('currentOrder','f'),
|
||||
|
||||
16
src/Controller/Artemis/Newsletter/CompaignController.php
Normal file
16
src/Controller/Artemis/Newsletter/CompaignController.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Artemis\Newsletter;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class CompaignController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/artemis/newsletter/campaign',name: 'artemis_newsletter_campaign',methods: ['GET', 'POST'])]
|
||||
public function artemis(): Response
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
66
src/Controller/Artemis/Newsletter/ContactController.php
Normal file
66
src/Controller/Artemis/Newsletter/ContactController.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Artemis\Newsletter;
|
||||
|
||||
use App\Entity\Newsletter\Contact;
|
||||
use App\Form\Artemis\Newsletter\ContactListType;
|
||||
use App\Repository\Newsletter\ContactRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
class ContactController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/artemis/newsletter/contact',name: 'artemis_newsletter_contact',methods: ['GET', 'POST'])]
|
||||
public function contacts(ContactRepository $contactRepository): Response
|
||||
{
|
||||
$r = new Contact();
|
||||
$r->setUuid(Uuid::v4());
|
||||
$r->setName("list 1");
|
||||
// Récupération des contacts triés par id (ordre croissant)
|
||||
$contacts = $contactRepository->findBy([],['id'=>'ASC']);
|
||||
|
||||
|
||||
// Affiche dans un template Twig (à créer)
|
||||
return $this->render('artemis/newsletter/contact.twig', [
|
||||
'lists' => $contacts,
|
||||
]);
|
||||
}
|
||||
#[Route(path: '/artemis/newsletter/contact/add', name: 'artemis_newsletter_contact_add', methods: ['GET', 'POST'])]
|
||||
public function contactsAdd(Request $request, EntityManagerInterface $em): Response
|
||||
{
|
||||
$contactList = new Contact();
|
||||
$form = $this->createForm(ContactListType::class, $contactList);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$contactList->setUuid(Uuid::v4());
|
||||
$em->persist($contactList);
|
||||
$em->flush();
|
||||
|
||||
$this->addFlash('success', 'La liste a été créée avec succès.');
|
||||
|
||||
// Redirige vers la liste des listes (à adapter selon ta route)
|
||||
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contactList->getId()]);
|
||||
}
|
||||
|
||||
return $this->render('artemis/newsletter/contact/add.twig', [
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[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/delete',name: 'artemis_newsletter_contact_delete',methods: ['GET', 'POST'])]
|
||||
public function contactsDelete(ContactRepository $contactRepository): Response
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
16
src/Controller/Artemis/Newsletter/TemplateController.php
Normal file
16
src/Controller/Artemis/Newsletter/TemplateController.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Artemis\Newsletter;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class TemplateController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/artemis/newsletter/template',name: 'artemis_newsletter_template',methods: ['GET', 'POST'])]
|
||||
public function artemis(): Response
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,11 @@ use App\Repository\CustomerOrderRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Vich\UploaderBundle\Mapping\Annotation as Vich;
|
||||
|
||||
#[ORM\Entity(repositoryClass: CustomerOrderRepository::class)]
|
||||
#[Vich\Uploadable()]
|
||||
class CustomerOrder
|
||||
{
|
||||
#[ORM\Id]
|
||||
@@ -36,6 +39,21 @@ class CustomerOrder
|
||||
#[ORM\OneToMany(targetEntity: CustomerOrderLine::class, mappedBy: 'customerOrder')]
|
||||
private Collection $customerOrderLines;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $state = null;
|
||||
|
||||
#[Vich\UploadableField(mapping: 'facture',fileNameProperty: 'factureFileName', size: 'factureSize', mimeType: 'factureMineType', originalName: 'factureOriginalName',dimensions: 'factureDimensions')]
|
||||
private ?File $facture = null;
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?string $factureFileName = null;
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?array $factureDimensions = [];
|
||||
#[ORM\Column(length: 255,nullable: true)]
|
||||
private ?string $factureSize = null;
|
||||
#[ORM\Column(length: 255,nullable: true)]
|
||||
private ?string $factureMineType = null;
|
||||
#[ORM\Column(length: 255,nullable: true)]
|
||||
private ?string $factureOriginalName = null;
|
||||
public function __construct()
|
||||
{
|
||||
$this->customerOrderLines = new ArrayCollection();
|
||||
@@ -101,7 +119,7 @@ class CustomerOrder
|
||||
|
||||
public function setUpdateAt(?\DateTimeImmutable $updateAt): static
|
||||
{
|
||||
$this->updateAt = $updateAt<EFBFBD>;
|
||||
$this->updateAt = $updateAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -135,4 +153,121 @@ class CustomerOrder
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getState(): ?string
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
public function setState(?string $state): static
|
||||
{
|
||||
$this->state = $state;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param File|null $facture
|
||||
*/
|
||||
public function setFacture(?File $facture): void
|
||||
{
|
||||
$this->facture = $facture;
|
||||
if($facture !== null)
|
||||
$this->updateAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return File|null
|
||||
*/
|
||||
public function getFacture(): ?File
|
||||
{
|
||||
return $this->facture;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getFactureDimensions(): ?array
|
||||
{
|
||||
return $this->factureDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFactureFileName(): ?string
|
||||
{
|
||||
return $this->factureFileName;
|
||||
}
|
||||
/**
|
||||
* @param int|null $id
|
||||
*/
|
||||
public function setId(?int $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFactureMineType(): ?string
|
||||
{
|
||||
return $this->factureMineType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFactureOriginalName(): ?string
|
||||
{
|
||||
return $this->factureOriginalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFactureSize(): ?string
|
||||
{
|
||||
return $this->factureSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $factureDimensions
|
||||
*/
|
||||
public function setFactureDimensions(?array $factureDimensions): void
|
||||
{
|
||||
$this->factureDimensions = $factureDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $factureFileName
|
||||
*/
|
||||
public function setFactureFileName(?string $factureFileName): void
|
||||
{
|
||||
$this->factureFileName = $factureFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $factureMineType
|
||||
*/
|
||||
public function setFactureMineType(?string $factureMineType): void
|
||||
{
|
||||
$this->factureMineType = $factureMineType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $factureOriginalName
|
||||
*/
|
||||
public function setFactureOriginalName(?string $factureOriginalName): void
|
||||
{
|
||||
$this->factureOriginalName = $factureOriginalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $factureSize
|
||||
*/
|
||||
public function setFactureSize(?string $factureSize): void
|
||||
{
|
||||
$this->factureSize = $factureSize;
|
||||
}
|
||||
}
|
||||
|
||||
51
src/Entity/Newsletter/Contact.php
Normal file
51
src/Entity/Newsletter/Contact.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\Newsletter;
|
||||
|
||||
use App\Repository\Newsletter\ContactRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[ORM\Entity(repositoryClass: ContactRepository::class)]
|
||||
class Contact
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column(type: 'uuid')]
|
||||
private ?Uuid $uuid = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUuid(): ?Uuid
|
||||
{
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function setUuid(Uuid $uuid): static
|
||||
{
|
||||
$this->uuid = $uuid;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
34
src/Form/Artemis/Newsletter/ContactListType.php
Normal file
34
src/Form/Artemis/Newsletter/ContactListType.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form\Artemis\Newsletter;
|
||||
use App\Entity\Newsletter\Contact;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
class ContactListType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'Nom de la liste',
|
||||
'constraints' => [
|
||||
new NotBlank([
|
||||
'message' => 'Le nom de la liste ne peut pas être vide.',
|
||||
]),
|
||||
],
|
||||
'attr' => [
|
||||
'placeholder' => 'Ex : Newsletter clients',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Contact::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
43
src/Repository/Newsletter/ContactRepository.php
Normal file
43
src/Repository/Newsletter/ContactRepository.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository\Newsletter;
|
||||
|
||||
use App\Entity\Newsletter\Contact;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Contact>
|
||||
*/
|
||||
class ContactRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Contact::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Contact[] Returns an array of Contact objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('c')
|
||||
// ->andWhere('c.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('c.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Contact
|
||||
// {
|
||||
// return $this->createQueryBuilder('c')
|
||||
// ->andWhere('c.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use App\Service\Customer\Billing\CreateDevisCustomerEventSend;
|
||||
use App\Service\Docuseal\SignClient;
|
||||
use App\Service\Mailer\Mailer;
|
||||
use App\Service\Pdf\DevisPdf;
|
||||
use App\Service\Pdf\FacturePdf;
|
||||
use App\Service\Pdf\PaymentPdf;
|
||||
use App\Service\Stancer\Client;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -26,6 +27,7 @@ use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
|
||||
#[AsEventListener(event: CreateDevisCustomerEventSend::class, method: 'onBillingEventSend')]
|
||||
#[AsEventListener(event: CreateAvisEvent::class, method: 'onCreatedAvisEvent')]
|
||||
#[AsEventListener(event: CreateAvisEventSend::class, method: 'onCreatedAvisEventSend')]
|
||||
#[AsEventListener(event: CreateFactureEvent::class, method: 'onCreateFactureEvent')]
|
||||
class BillingEventSusbriber
|
||||
{
|
||||
|
||||
@@ -33,6 +35,15 @@ class BillingEventSusbriber
|
||||
{
|
||||
}
|
||||
|
||||
public function onCreateFactureEvent(CreateFactureEvent $event)
|
||||
{
|
||||
$order = $event->getCustomerOrder();
|
||||
$isSend = $event->isSend();
|
||||
|
||||
$pdf = New FacturePdf($this->kernel,$order);
|
||||
$pdf->generate();
|
||||
$pdf->Output('I');
|
||||
}
|
||||
public function onCreatedAvisEventSend(CreateAvisEventSend $createAvisEventSend)
|
||||
{
|
||||
$createAvis = $createAvisEventSend->getCustomerAdvertPayment();
|
||||
@@ -78,21 +89,7 @@ class BillingEventSusbriber
|
||||
foreach ($createAvis->getCustomerAdvertPaymentLines() as $item) {
|
||||
$total = $total + (floatval($item->getPriceHt()) * 1.20);
|
||||
}
|
||||
//$customerStancer = new Customer($customerId);
|
||||
/*//creeat payement link
|
||||
$payment = new Payment();
|
||||
$payment->setAmount($total * 100);
|
||||
$payment->setCurrency("EUR");
|
||||
$payment->setDescription("Paiement de l'avis de paiement - " . $createAvis->getNumAvis());
|
||||
$payment->setCustomer($customerStancer);
|
||||
$payment->setReturnUrl($paymentReturnPath);
|
||||
$payment->setOrderId($createAvis->getNumAvis());
|
||||
$payment->setMethodsAllowed(["card"]);
|
||||
$payment->setCapture(true);
|
||||
$paimentId = $data = $payment->send();
|
||||
$createAvis->setPaymentId($paimentId);
|
||||
$this->entityManager->persist($createAvis);
|
||||
$this->entityManager->flush();*/
|
||||
|
||||
}
|
||||
|
||||
$pdf = New PaymentPdf($this->kernel,$createAvis,$this->urlGenerator->generate('app_payment',[
|
||||
|
||||
175
src/Service/Pdf/FacturePdf.php
Normal file
175
src/Service/Pdf/FacturePdf.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Pdf;
|
||||
|
||||
use App\Entity\CustomerAdvertPayment;
|
||||
use App\Entity\CustomerOrder;
|
||||
use Endroid\QrCode\Builder\Builder;
|
||||
use Endroid\QrCode\Encoding\Encoding;
|
||||
use Endroid\QrCode\Label\Font\OpenSans;
|
||||
use Endroid\QrCode\Label\LabelAlignment;
|
||||
use Endroid\QrCode\Writer\PngWriter;
|
||||
use Fpdf\Fpdf;
|
||||
use IntlDateFormatter;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
define('EURO_FACTURE', chr(128));
|
||||
|
||||
class FacturePdf extends Fpdf
|
||||
{
|
||||
private $items;
|
||||
|
||||
public function __construct(private readonly KernelInterface $kernel, private readonly CustomerOrder $customerDevis)
|
||||
{
|
||||
parent::__construct();
|
||||
$items = [];
|
||||
foreach ($this->customerDevis->getCustomerOrderLines() as $line) {
|
||||
$items[$line->getPo()] = [
|
||||
'title' => $line->getName(),
|
||||
'content' => $line->getContent(),
|
||||
'priceHt' => $line->getPriceHT(),
|
||||
'priceTTC' => (1.20 * $line->getPriceHT()),
|
||||
];
|
||||
}
|
||||
ksort($items);
|
||||
$this->items = $items;
|
||||
$this->SetTitle(mb_convert_encoding("Facture N° ", "ISO-8859-1", "UTF-8") . $this->customerDevis->getNumOrder());
|
||||
}
|
||||
|
||||
function Header()
|
||||
{
|
||||
/*$this->Image($this->kernel->getProjectDir() . "/public/assets/logo_siteconseil.png", 5, 5, 25);
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->Text(30, 10, mb_convert_encoding("SITECONSEIL", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->SetFont('Arial', '', 12);
|
||||
$this->Text(30, 15, mb_convert_encoding("27 rue le sérurier", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(30, 20, mb_convert_encoding("02100 SAINT-QUENTIN", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(30, 25, mb_convert_encoding("s.com@siteconseil.fr", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(30, 30, mb_convert_encoding("03 23 05 62 43", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(8, 35, mb_convert_encoding("SIRET: 41866405800025", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(8, 40, mb_convert_encoding("RCS: RCS St-Quentin 418 664 058", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(8, 45, mb_convert_encoding("TVA: FR05418664058", 'ISO-8859-1', 'UTF-8'));*/
|
||||
$this->SetFont('Arial', '', 10);
|
||||
$formatter = new IntlDateFormatter(
|
||||
'fr_FR', // Locale for French (France)
|
||||
IntlDateFormatter::FULL, // Date style: e.g., jeudi 31 juillet 2025
|
||||
IntlDateFormatter::NONE, // Time style: none
|
||||
'Europe/Paris', // Timezone (important for correct date if not UTC)
|
||||
IntlDateFormatter::GREGORIAN, // Calendar type
|
||||
);
|
||||
|
||||
|
||||
$this->Text(15, 80, mb_convert_encoding("Facture N° " . $this->customerDevis->getNumOrder(), 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(15, 85, mb_convert_encoding("Saint-Quentin, ".$formatter->format( $this->customerDevis->getCreateAt()->getTimestamp()), 'ISO-8859-1', 'UTF-8'));
|
||||
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$y = 60;
|
||||
$this->Text(110, $y, mb_convert_encoding($this->customerDevis->getCustomer()->getRaisonSocial(), 'ISO-8859-1', 'UTF-8'));
|
||||
$y = $y + 5;
|
||||
$this->Text(110, $y, mb_convert_encoding($this->customerDevis->getCustomer()->getAddress(), 'ISO-8859-1', 'UTF-8'));
|
||||
if ($this->customerDevis->getCustomer()->getAddress2() != "") {
|
||||
$y = $y + 5;
|
||||
$this->Text(110, $y, mb_convert_encoding($this->customerDevis->getCustomer()->getAddress2(), 'ISO-8859-1', 'UTF-8'));
|
||||
}
|
||||
if ($this->customerDevis->getCustomer()->getAddress3() != "") {
|
||||
$y = $y + 5;
|
||||
$this->Text(110, $y, mb_convert_encoding($this->customerDevis->getCustomer()->getAddress3(), 'ISO-8859-1', 'UTF-8'));
|
||||
}
|
||||
$y = $y + 5;
|
||||
$this->Text(110, $y, mb_convert_encoding($this->customerDevis->getCustomer()->getZipcode() . " " . $this->customerDevis->getCustomer()->getCity(), 'ISO-8859-1', 'UTF-8'));
|
||||
$this->SetFont('Arial', '', 12);
|
||||
$this->body();
|
||||
}
|
||||
|
||||
public function body()
|
||||
{
|
||||
// Headers for the items table
|
||||
$this->SetFont('Arial','B',10);
|
||||
$this->SetXY(145,100);
|
||||
$this->Cell(40, 5, mb_convert_encoding("PRIX HT", "ISO-8859-1", "UTF-8"), 0, 0, 'C');
|
||||
$this->Line(145, 110, 145, 220);
|
||||
$this->Line(185, 110, 185, 220);
|
||||
|
||||
$this->Line(0,100,5,100);
|
||||
$this->Line(0,200,5,200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the main content of the PDF, including the list of items.
|
||||
* This function has been fixed to correctly display items one after another.
|
||||
*/
|
||||
function generate()
|
||||
{
|
||||
$this->AliasNbPages();
|
||||
$this->AddPage();
|
||||
$this->SetFont('Arial', '', 12);
|
||||
|
||||
// Set a starting Y position for the items list, just below the item table header.
|
||||
$startY = 110;
|
||||
$this->SetY($startY);
|
||||
|
||||
// Define a bottom limit for the content to avoid overwriting the summary/footer.
|
||||
$contentBottomLimit = 220;
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
// A simple check to see if we have enough space for the next item.
|
||||
// 30 is a rough estimate for item height. For more complex content,
|
||||
// you might need a more dynamic height calculation.
|
||||
if ($this->GetY() + 30 > $contentBottomLimit) {
|
||||
$this->AddPage();
|
||||
$this->body(); // Redraw the item table header on the new page
|
||||
$this->SetY($startY); // Reset Y position on the new page
|
||||
}
|
||||
|
||||
// Store the current Y position to align all columns for this item's title line.
|
||||
$current_y = $this->GetY();
|
||||
|
||||
// Set position to the start of the line for the main content.
|
||||
$this->SetX(20);
|
||||
$this->SetFont('Arial', 'B', 11);
|
||||
// Print the title, but don't move to the next line yet (ln=0).
|
||||
$this->Cell(95, 10, mb_convert_encoding($item['title'], 'ISO-8859-1', 'UTF-8'), 0, 0);
|
||||
|
||||
// Now, set the position for the prices on the same line using SetXY.
|
||||
$this->SetFont('Arial', '', 11);
|
||||
$this->SetXY(142, $current_y);
|
||||
$this->SetFont('Arial', 'B', 11);
|
||||
$this->Cell(39, 8, number_format($item['priceHt'], 2, ",") . " " . EURO_FACTURE, 0, 1, 'R');
|
||||
$this->SetFont('Arial', '', 11);
|
||||
// The cursor is now on the line below the title/prices.
|
||||
// Print the item content description.
|
||||
$this->SetX(30); // Ensure we are in the correct column.
|
||||
$this->MultiCell(90, 5, mb_convert_encoding($item['content'], 'ISO-8859-1', 'UTF-8'), 0, 'L');
|
||||
|
||||
// Add a small vertical gap between items for readability.
|
||||
$this->Ln(5);
|
||||
}
|
||||
|
||||
$this->displaySummary();
|
||||
}
|
||||
|
||||
|
||||
function displaySummary()
|
||||
{
|
||||
|
||||
// Calculate totals
|
||||
$totalHT = array_sum(array_column($this->items, 'priceHt'));
|
||||
$totalTVA = $totalHT * 0.20;
|
||||
$totalTTC = $totalHT + $totalTVA;
|
||||
|
||||
// Position the summary block at the bottom of the page
|
||||
$this->SetY(-60);
|
||||
|
||||
// Display the summary
|
||||
$this->SetFont('Arial', '', 12);
|
||||
$this->Cell(135, 10, mb_convert_encoding('Total HT :', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
|
||||
$this->Cell(40, 10, number_format($totalHT, 2, ",") . " " . EURO_FACTURE, 0, 1, 'R');
|
||||
|
||||
$this->Cell(135, 10, mb_convert_encoding('TVA (20%) :', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
|
||||
$this->Cell(40, 10, number_format($totalTVA, 2, ",") . " " . EURO_FACTURE, 0, 1, 'R');
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->Cell(135, 10, mb_convert_encoding('Total :', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
|
||||
$this->Cell(40, 10, number_format($totalTTC, 2, ",") . " " . EURO_FACTURE, 0, 1, 'R');
|
||||
}
|
||||
}
|
||||
@@ -81,22 +81,51 @@
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="px-4 py-2">
|
||||
<button class="flex items-center justify-between w-full p-2 text-base font-normal text-gray-900 dark:text-white rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none" data-submenu-toggle="newsletter">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-6 h-6 text-gray-500 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path>
|
||||
<path d="M12 2.252A8.014 8.014 0 0117.748 12H12V2.252z"></path>
|
||||
</svg>
|
||||
<span class="ml-3">Newsletter</span>
|
||||
</div>
|
||||
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400 arrow-icon" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<ul id="submenu-newsletter" class="submenu ml-6 mt-2 space-y-2">
|
||||
<li>
|
||||
<a href="{{ path('artemis_newsletter_contact') }}" class="flex items-center p-2 text-base font-normal text-gray-900 dark:text-white {% if app.request.get('_route') == 'artemis_newsletter_contact' %}bg-gray-200 dark:bg-gray-700{% endif %} rounded-lg">
|
||||
<span class="ml-3">Liste de contact</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
<li class="px-4 py-2">
|
||||
<button class="flex items-center justify-between w-full p-2 text-base font-normal text-gray-900 dark:text-white rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none" data-submenu-toggle="intranet">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-6 h-6 text-gray-500 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path><path d="M12 2.252A8.014 8.014 0 0117.748 12H12V2.252z"></path></svg>
|
||||
<svg class="w-6 h-6 text-gray-500 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path>
|
||||
<path d="M12 2.252A8.014 8.014 0 0117.748 12H12V2.252z"></path>
|
||||
</svg>
|
||||
<span class="ml-3">Intranet</span>
|
||||
</div>
|
||||
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400 arrow-icon" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
|
||||
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400 arrow-icon" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<ul id="submenu-intranet" class="submenu ml-6 mt-2 space-y-2">
|
||||
<li>
|
||||
<a href="{{ path('artemis_intranet_customer') }}" class="flex items-center p-2 text-base font-normal text-gray-900 dark:text-white {% if app.request.get('_route') == "artemis_intranet_customer"%}bg-gray-200 dark:bg-gray-700{% endif%} rounded-lg">
|
||||
<a href="{{ path('artemis_intranet_customer') }}" class="flex items-center p-2 text-base font-normal text-gray-900 dark:text-white {% if app.request.get('_route') == 'artemis_intranet_customer' %}bg-gray-200 dark:bg-gray-700{% endif %} rounded-lg">
|
||||
<span class="ml-3">Client(s)</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{% if is_granted('ROLE_ADMIN') %}
|
||||
<li class="px-4 py-2">
|
||||
<button class="flex items-center justify-between w-full p-2 text-base font-normal text-gray-900 dark:text-white rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none" data-submenu-toggle="settings">
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<div class="mt-2 overflow-x-auto bg-gray-800 rounded-lg shadow">
|
||||
<table class="min-w-full divide-y divide-gray-700">
|
||||
<thead class="bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Type</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">N°</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Date</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Montant</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Statut</th>
|
||||
<th class="px-6 py-3 text-center text-xs font-medium uppercase tracking-wider">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for orderOrder in orderOrders %}
|
||||
<tr class="hover:bg-gray-700">
|
||||
<td class="px-6 py-4">FACTURE</td>
|
||||
<td class="px-6 py-4">{{ orderOrder.numOrder }}</td>
|
||||
<td class="px-6 py-4">{{ orderOrder.createAt|date('d/m/Y H:i') }}</td>
|
||||
<td class="px-6 py-4">{{ (orderOrder|totalOrder)|format_currency('EUR') }}</td>
|
||||
<td class="px-6 py-4 {% if orderOrder.state == "f-send"%} text-green-400 {% else %}text-orange-400{% endif %}">{{ orderOrder.state|trans }}</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
{% if orderOrder.state == "f-created" %}
|
||||
<a href="{{ path('artemis_intranet_customer_view',{id:customer.id,current:'order',idFacture:orderOrder.id,act:'send'}) }}" class="block bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Envoyée la facture</a>
|
||||
{% endif %}
|
||||
{% if orderOrder.state == "f-send" %}
|
||||
<a href="{{ path('artemis_intranet_customer_view',{id:customer.id,current:'order',idFacture:orderOrder.id,act:'resend'}) }}" class="block bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Réenvoyée la facture</a>
|
||||
{% endif %}}
|
||||
<a href="{{ vich_uploader_asset(orderOrder,'facture') }}" download="facture-{{ orderOrder.numOrder }}.pdf" class="block w-full mt-1 bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Télécharger la facture</a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ knp_pagination_render(orderDevis) }}
|
||||
|
||||
</div>
|
||||
|
||||
34
templates/artemis/newsletter/contact.twig
Normal file
34
templates/artemis/newsletter/contact.twig
Normal file
@@ -0,0 +1,34 @@
|
||||
{% extends 'artemis/base.twig' %}
|
||||
|
||||
{% block title %}Listes de contacts{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-3xl font-semibold text-gray-800 dark:text-gray-200">istes de contacts</h2>
|
||||
<div>
|
||||
<a href="{{ path('artemis_newsletter_contact_add') }}" class="px-4 py-2 bg-blue-600 text-white font-medium rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
|
||||
+ Crée une liste de contact
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
||||
{% for list in lists %}
|
||||
<a href="{{ path('artemis_newsletter_contact_edit',{id:list.id}) }}" class="card-contact mt-6">
|
||||
<div class="card-body flex items-center">
|
||||
|
||||
<div class="px-3 py-2 rounded bg-indigo-600 text-white mr-3">
|
||||
<i class="fad fa-user"></i>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<h1 class="font-semibold">{{ list.name }}</h1>
|
||||
<p class="text-xs"><span class="num-2">0</span> Contact</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="col-span-4 text-center text-gray-400 dark:text-gray-500 font-bold">Aucune liste trouvée.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
22
templates/artemis/newsletter/contact/add.twig
Normal file
22
templates/artemis/newsletter/contact/add.twig
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends 'artemis/base.twig' %}
|
||||
|
||||
{% block title %}Créer une nouvelle liste{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="text-2xl font-semibold mb-6">Créer une nouvelle liste de contacts</h1>
|
||||
|
||||
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6">
|
||||
{{ form_start(form, {'attr': {'class': 'max-w-md'}}) }}
|
||||
<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">
|
||||
Créer la liste
|
||||
</button>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user