feat(EsyWebController): Ajoute création et affichage de sites internet

Ce commit implémente l'ajout et la visualisation des sites internet.
Il inclut la gestion de formulaires, l'enregistrement en base de données,
et l'affichage des détails.
This commit is contained in:
Serreau Jovann
2025-10-17 10:45:11 +02:00
parent ae4ea12af3
commit da028d4f39
22 changed files with 868 additions and 17 deletions

View File

@@ -10,6 +10,7 @@ import {LockdownWall} from './class/LockdownWall'
import {SecurityWall} from './class/SecurityWall'
import {IpWall} from './class/IpWall'
import {ConfirmModal} from './class/ConfirmModal'
import {SearchCustomer} from './class/SearchCustomer'
import preactCustomElement from './functions/preact'
import * as Sentry from "@sentry/browser";
@@ -25,6 +26,7 @@ function script() {
customElements.define('ip-wall',IpWall,{})
customElements.define('order-ctrl',OrderCtrl,{extends:'div'})
customElements.define("register-payment",RegisterPayment,{extends:'button'})
customElements.define("search-customer",SearchCustomer,{extends:'select'})
}
function full() {

View File

@@ -1,5 +1,6 @@
@import "tailwindcss";
@import "editor/ckeditor5.css";
@import "editor/tom-select.scss";
@import url('https://fonts.googleapis.com/css2?family=Intel+One+Mono:ital,wght@0,300..700;1,300..700&display=swap');
h1,h2,h3,h4,h5,h6,
@@ -440,3 +441,34 @@ confirm-modal{
color: orange;
font-weight: bolder;
}
.ts-control {
padding: 0;
}
.ts-wrapper,
.ts-control{
border: unset;
background: oklch(21% 0.034 264.665deg) !important;
.item {
color: white;
font-weight: bolder;
}
input {
color: white;
font-weight: bolder;
}
}
.ts-dropdown {
background: var(--color-gray-800) !important;
border: unset !important;
.option{
&:hover {
background: var(--color-gray-600) !important;
}
background: var(--color-gray-800) !important;
color: white;
&.selected {
background: var(--color-gray-600) !important;
}
}
}

View File

@@ -0,0 +1,10 @@
import TomSelect from "tom-select";
export class SearchCustomer extends HTMLSelectElement{
connectedCallback() {
let element = this;
new TomSelect(element,{
create: false
});
}
}

View File

@@ -0,0 +1,409 @@
/**
* tom-select.css (v2.4.3)
* Copyright (c) contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
*/
.ts-control {
border: 1px solid #d0d0d0;
padding: 8px 8px;
width: 100%;
overflow: hidden;
position: relative;
z-index: 1;
box-sizing: border-box;
box-shadow: none;
border-radius: 3px;
display: flex;
flex-wrap: wrap;
}
.full .ts-control {
background-color: #fff;
}
.disabled .ts-control, .disabled .ts-control * {
cursor: default !important;
}
.focus .ts-control {
box-shadow: none;
}
.ts-control > * {
vertical-align: baseline;
display: inline-block;
}
.ts-wrapper.multi .ts-control > div {
cursor: pointer;
margin: 0 3px 3px 0;
padding: 2px 6px;
background: #f2f2f2;
color: #303030;
border: 0 solid #d0d0d0;
}
.ts-wrapper.multi .ts-control > div.active {
background: #e8e8e8;
color: #303030;
border: 0 solid #cacaca;
}
.ts-wrapper.multi.disabled .ts-control > div, .ts-wrapper.multi.disabled .ts-control > div.active {
color: rgb(124.5, 124.5, 124.5);
background: white;
border: 0 solid white;
}
.ts-control > input {
flex: 1 1 auto;
min-width: 7rem;
display: inline-block !important;
padding: 0 !important;
min-height: 0 !important;
max-height: none !important;
max-width: 100% !important;
margin: 0 !important;
text-indent: 0 !important;
border: 0 none !important;
background: none !important;
line-height: inherit !important;
-webkit-user-select: auto !important;
-moz-user-select: auto !important;
-ms-user-select: auto !important;
user-select: auto !important;
box-shadow: none !important;
}
.ts-control > input::-ms-clear {
display: none;
}
.ts-control > input:focus {
outline: none !important;
}
.has-items .ts-control > input {
margin: 0 4px !important;
}
.ts-control.rtl {
text-align: right;
}
.ts-control.rtl.single .ts-control:after {
left: 15px;
right: auto;
}
.ts-control.rtl .ts-control > input {
margin: 0 4px 0 -2px !important;
}
.disabled .ts-control {
opacity: 0.5;
background-color: #fafafa;
}
.input-hidden .ts-control > input {
opacity: 0;
position: absolute;
left: -10000px;
}
.ts-dropdown {
position: absolute;
top: 100%;
left: 0;
width: 100%;
z-index: 10;
border: 1px solid #d0d0d0;
background: var(--color-gray-600) !important;
margin: 0.25rem 0 0;
border-top: 0 none;
box-sizing: border-box;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
border-radius: 0 0 3px 3px;
}
.ts-dropdown [data-selectable] {
cursor: pointer;
overflow: hidden;
}
.ts-dropdown [data-selectable] .highlight {
background: rgba(125, 168, 208, 0.2);
border-radius: 1px;
}
.ts-dropdown .option,
.ts-dropdown .optgroup-header,
.ts-dropdown .no-results,
.ts-dropdown .create {
padding: 5px 8px;
}
.ts-dropdown .option, .ts-dropdown [data-disabled], .ts-dropdown [data-disabled] [data-selectable].option {
cursor: inherit;
opacity: 0.5;
}
.ts-dropdown [data-selectable].option {
opacity: 1;
cursor: pointer;
}
.ts-dropdown .optgroup:first-child .optgroup-header {
border-top: 0 none;
}
.ts-dropdown .optgroup-header {
color: #303030;
background: #fff;
cursor: default;
}
.ts-dropdown .active {
background-color: #f5fafd;
color: #495c68;
}
.ts-dropdown .active.create {
color: #495c68;
}
.ts-dropdown .create {
color: rgba(48, 48, 48, 0.5);
}
.ts-dropdown .spinner {
display: inline-block;
width: 30px;
height: 30px;
margin: 5px 8px;
}
.ts-dropdown .spinner::after {
content: " ";
display: block;
width: 24px;
height: 24px;
margin: 3px;
border-radius: 50%;
border: 5px solid #d0d0d0;
border-color: #d0d0d0 transparent #d0d0d0 transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.ts-dropdown-content {
overflow: hidden auto;
max-height: 200px;
scroll-behavior: smooth;
}
.ts-wrapper.plugin-drag_drop .ts-dragging {
color: transparent !important;
}
.ts-wrapper.plugin-drag_drop .ts-dragging > * {
visibility: hidden !important;
}
.plugin-checkbox_options:not(.rtl) .option input {
margin-right: 0.5rem;
}
.plugin-checkbox_options.rtl .option input {
margin-left: 0.5rem;
}
/* stylelint-disable function-name-case */
.plugin-clear_button {
--ts-pr-clear-button: 1em;
}
.plugin-clear_button .clear-button {
opacity: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: calc(8px - 6px);
margin-right: 0 !important;
background: transparent !important;
transition: opacity 0.5s;
cursor: pointer;
}
.plugin-clear_button.form-select .clear-button, .plugin-clear_button.single .clear-button {
right: max(var(--ts-pr-caret), 8px);
}
.plugin-clear_button.focus.has-items .clear-button, .plugin-clear_button:not(.disabled):hover.has-items .clear-button {
opacity: 1;
}
.ts-wrapper .dropdown-header {
position: relative;
padding: 10px 8px;
border-bottom: 1px solid #d0d0d0;
background: color-mix(#fff, #d0d0d0, 85%);
border-radius: 3px 3px 0 0;
}
.ts-wrapper .dropdown-header-close {
position: absolute;
right: 8px;
top: 50%;
color: #303030;
opacity: 0.4;
margin-top: -12px;
line-height: 20px;
font-size: 20px !important;
}
.ts-wrapper .dropdown-header-close:hover {
color: black;
}
.plugin-dropdown_input.focus.dropdown-active .ts-control {
box-shadow: none;
border: 1px solid #d0d0d0;
}
.plugin-dropdown_input .dropdown-input {
border: 1px solid #d0d0d0;
border-width: 0 0 1px;
display: block;
padding: 8px 8px;
box-shadow: none;
width: 100%;
background: transparent;
}
.plugin-dropdown_input .items-placeholder {
border: 0 none !important;
box-shadow: none !important;
width: 100%;
}
.plugin-dropdown_input.has-items .items-placeholder, .plugin-dropdown_input.dropdown-active .items-placeholder {
display: none !important;
}
.ts-wrapper.plugin-input_autogrow.has-items .ts-control > input {
min-width: 0;
}
.ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control > input {
flex: none;
min-width: 4px;
}
.ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control > input::-ms-input-placeholder {
color: transparent;
}
.ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control > input::placeholder {
color: transparent;
}
.ts-dropdown.plugin-optgroup_columns .ts-dropdown-content {
display: flex;
}
.ts-dropdown.plugin-optgroup_columns .optgroup {
border-right: 1px solid #f2f2f2;
border-top: 0 none;
flex-grow: 1;
flex-basis: 0;
min-width: 0;
}
.ts-dropdown.plugin-optgroup_columns .optgroup:last-child {
border-right: 0 none;
}
.ts-dropdown.plugin-optgroup_columns .optgroup::before {
display: none;
}
.ts-dropdown.plugin-optgroup_columns .optgroup-header {
border-top: 0 none;
}
.ts-wrapper.plugin-remove_button .item {
display: inline-flex;
align-items: center;
}
.ts-wrapper.plugin-remove_button .item .remove {
color: inherit;
text-decoration: none;
vertical-align: middle;
display: inline-block;
padding: 0 6px;
border-radius: 0 2px 2px 0;
box-sizing: border-box;
}
.ts-wrapper.plugin-remove_button .item .remove:hover {
background: rgba(0, 0, 0, 0.05);
}
.ts-wrapper.plugin-remove_button.disabled .item .remove:hover {
background: none;
}
.ts-wrapper.plugin-remove_button .remove-single {
position: absolute;
right: 0;
top: 0;
font-size: 23px;
}
.ts-wrapper.plugin-remove_button:not(.rtl) .item {
padding-right: 0 !important;
}
.ts-wrapper.plugin-remove_button:not(.rtl) .item .remove {
border-left: 1px solid #d0d0d0;
margin-left: 6px;
}
.ts-wrapper.plugin-remove_button:not(.rtl) .item.active .remove {
border-left-color: #cacaca;
}
.ts-wrapper.plugin-remove_button:not(.rtl).disabled .item .remove {
border-left-color: white;
}
.ts-wrapper.plugin-remove_button.rtl .item {
padding-left: 0 !important;
}
.ts-wrapper.plugin-remove_button.rtl .item .remove {
border-right: 1px solid #d0d0d0;
margin-right: 6px;
}
.ts-wrapper.plugin-remove_button.rtl .item.active .remove {
border-right-color: #cacaca;
}
.ts-wrapper.plugin-remove_button.rtl.disabled .item .remove {
border-right-color: white;
}
:root {
--ts-pr-clear-button: 0px;
--ts-pr-caret: 0px;
--ts-pr-min: .75rem;
}
.ts-wrapper.single .ts-control, .ts-wrapper.single .ts-control input {
cursor: pointer;
}
.ts-control:not(.rtl) {
padding-right: max(var(--ts-pr-min), var(--ts-pr-clear-button) + var(--ts-pr-caret)) !important;
}
.ts-control.rtl {
padding-left: max(var(--ts-pr-min), var(--ts-pr-clear-button) + var(--ts-pr-caret)) !important;
}
.ts-wrapper {
position: relative;
}
.ts-dropdown,
.ts-control,
.ts-control input {
color: #303030;
font-family: inherit;
font-size: 13px;
line-height: 18px;
}
.ts-control,
.ts-wrapper.single.input-active .ts-control {
background: var(--color-gray-600);
cursor: text;
}
.ts-hidden-accessible {
border: 0 !important;
clip: rect(0 0 0 0) !important;
-webkit-clip-path: inset(50%) !important;
clip-path: inset(50%) !important;
overflow: hidden !important;
padding: 0 !important;
position: absolute !important;
width: 1px !important;
white-space: nowrap !important;
}
/*# sourceMappingURL=tom-select.css.map */

BIN
bun.lockb

Binary file not shown.

View File

@@ -0,0 +1,36 @@
<?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 Version20251017075007 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 website ADD revendeur_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE website ADD CONSTRAINT FK_476F5DE7F4218D56 FOREIGN KEY (revendeur_id) REFERENCES revendeur (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_476F5DE7F4218D56 ON website (revendeur_id)');
}
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 website DROP CONSTRAINT FK_476F5DE7F4218D56');
$this->addSql('DROP INDEX IDX_476F5DE7F4218D56');
$this->addSql('ALTER TABLE website DROP revendeur_id');
}
}

View File

@@ -33,6 +33,7 @@
"react-email-editor": "^1.7.11",
"sortablejs": "^1.15.6",
"tailwindcss": "^4.1.13",
"tom-select": "^2.4.3",
"vite-plugin-favicon": "^1.0.8"
}
}

View File

@@ -4,7 +4,9 @@ namespace App\Controller\ApiInterne\Intranet;
use App\Entity\CustomerAdvertPayment;
use App\Entity\CustomerDnsEmail;
use App\Repository\CustomerAdvertPaymentRepository;
use App\Repository\CustomerRepository;
use App\Service\Echeance\EventEcheanceCreated;
use App\Service\Logger\LoggerService;
use Doctrine\ORM\EntityManagerInterface;
@@ -23,6 +25,32 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class CustomerController extends AbstractController
{
#[Route(path: '/api-interne/customer',name: 'api-interne-search',methods: ['POST','GET'])]
public function searchCustomer(Request $request,CustomerRepository $customerRepository): Response
{
$request = $request->get('q',null);
if(is_null($request)){
return $this->json([
'items' => []
]);
}
$list = $customerRepository->search($request);
$items =[];
/** @var \App\Entity\Customer $item */
foreach ($list as $item) {
$items[] = [
'id' => $item->getId(),
'title' => $item->getRaisonSocial(),
];
}
return $this->json([
'items' => $items
]);
}
#[Route(path: '/api-interne/intranet/customer/payment/cancel',name: 'api-interne-intranet-customer-payment-cancel')]
public function customerPaymentCancel(EntityManagerInterface $entityManager,Request $request,CustomerAdvertPaymentRepository $customerAdvertPayment): Response
{

View File

@@ -7,8 +7,11 @@ use App\Form\Artemis\EsyWeb\WebsiteType;
use App\Repository\EsyWeb\WebsiteRepository;
use App\Repository\EsyWebTutoRepository;
use App\Service\Logger\LoggerService;
use App\Service\Website\EventCreatedWebsite;
use Cocur\Slugify\Slugify;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Attribute\Route;
@@ -18,29 +21,55 @@ use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
class EsyWebController extends AbstractController
{
#[Route(path: '/artemis/esyweb', name: 'artemis_esyweb', methods: ['GET', 'POST'])]
public function tutos(LoggerService $loggerService,WebsiteRepository $websiteRepository)
public function websites(LoggerService $loggerService,Request $request,EntityManagerInterface $entityManager,WebsiteRepository $websiteRepository)
{
$loggerService->log("VIEW","Affiche la page de site internet",$this->getUser());
if($request->query->has('idValidate')) {
$website = $websiteRepository->find($request->query->get('idValidate'));
$website->setState("validate");
$entityManager->persist($website);
$entityManager->flush();
$loggerService->log("VALIDATE","Validation du site internet",$this->getUser());
return $this->redirectToRoute('artemis_esyweb');
}
return $this->render('artemis/esyweb/website.twig', [
'websites' => $websiteRepository->findAll(),
]);
}
#[Route(path: '/artemis/esyweb/{id}', name: 'artemis_esyweb_view', methods: ['GET', 'POST'])]
public function websiteView(?Website $website,LoggerService $loggerService,Request $request,EntityManagerInterface $entityManager,WebsiteRepository $websiteRepository)
{
if(is_null($website)) {
return $this->redirectToRoute('artemis_esyweb');
}
$loggerService->log("VIEW","Affiche la page de site internet - ".$website->getTitle(),$this->getUser());
return $this->render('artemis/esyweb/website_view.twig', [
'current' => $request->get('current','main'),
'website' => $website
]);
}
#[Route(path: '/artemis/esyweb/add', name: 'artemis_esyweb_add', methods: ['GET', 'POST'])]
public function websiteAdd(LoggerService $loggerService,Request $request)
public function websiteAdd(LoggerService $loggerService,Request $request,EntityManagerInterface $entityManager,EventDispatcherInterface $eventDispatcher)
{
$loggerService->log("VIEW","Affiche la page de création de site internet",$this->getUser());
$webiste = new Website();
$webiste->setState("created");
$webiste->setUuid(Uuid::v4());
$website = new Website();
$website->setState("created");
$website->setUuid(Uuid::v4());
$form = $this->createForm(WebsiteType::class,$webiste);
$form = $this->createForm(WebsiteType::class,$website);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$slug = new Slugify();
$website->setMainDns($slug->slugify($website->getTitle()).".esy-web.dev");
$loggerService->log("CREATED","Création d'un site internet");
$entityManager->persist($website);
$entityManager->flush();
$eventDispatcher->dispatch(new EventCreatedWebsite($website));
return $this->redirectToRoute('artemis_esyweb');
}
return $this->render('artemis/esyweb/add.twig', [
'form' => $form->createView(),

View File

@@ -4,6 +4,7 @@ namespace App\Entity\EsyWeb;
use App\Entity\Customer;
use App\Entity\CustomerDns;
use App\Entity\Revendeur;
use App\Repository\EsyWeb\WebsiteRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
@@ -42,6 +43,9 @@ class Website
#[ORM\Column(length: 255)]
private ?string $type = null;
#[ORM\ManyToOne(inversedBy: 'websites')]
private ?Revendeur $revendeur = null;
public function __construct()
{
$this->ndd = new ArrayCollection();
@@ -153,4 +157,16 @@ class Website
return $this;
}
public function getRevendeur(): ?Revendeur
{
return $this->revendeur;
}
public function setRevendeur(?Revendeur $revendeur): static
{
$this->revendeur = $revendeur;
return $this;
}
}

View File

@@ -2,7 +2,10 @@
namespace App\Entity;
use App\Entity\EsyWeb\Website;
use App\Repository\RevendeurRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: RevendeurRepository::class)]
@@ -34,6 +37,17 @@ class Revendeur
#[ORM\Column(nullable: true)]
private ?int $parent = null;
/**
* @var Collection<int, Website>
*/
#[ORM\OneToMany(targetEntity: Website::class, mappedBy: 'revendeur')]
private Collection $websites;
public function __construct()
{
$this->websites = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
@@ -122,4 +136,34 @@ class Revendeur
return $this;
}
/**
* @return Collection<int, Website>
*/
public function getWebsites(): Collection
{
return $this->websites;
}
public function addWebsite(Website $website): static
{
if (!$this->websites->contains($website)) {
$this->websites->add($website);
$website->setRevendeur($this);
}
return $this;
}
public function removeWebsite(Website $website): static
{
if ($this->websites->removeElement($website)) {
// set the owning side to null (unless already changed)
if ($website->getRevendeur() === $this) {
$website->setRevendeur(null);
}
}
return $this;
}
}

View File

@@ -2,7 +2,11 @@
namespace App\Form\Artemis\EsyWeb;
use App\Entity\Customer;
use App\Entity\EsyWeb\Website;
use App\Entity\Revendeur;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
@@ -15,6 +19,7 @@ class WebsiteType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type',ChoiceType::class,[
'label' => 'Type de site internet',
@@ -25,9 +30,37 @@ class WebsiteType extends AbstractType
]
])
->add('title',TextType::class,[
'label' => 'Titre',
'label' => 'Nom du site internet',
'required' => true
])
->add('customer',EntityType::class,[
'label' => 'Client',
'attr' =>[
'is' => 'search-customer'
],
'class' => Customer::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.raisonSocial', 'ASC');
},
'choice_label' => function (Customer $category): string {
return $category->getRaisonSocial()." - ".$category->mainContact()->getName()." ".$category->mainContact()->getSurname();
}
])
->add('revendeur',EntityType::class,[
'label' => 'Revendeur',
'attr' =>[
'is' => 'search-customer'
],
'class' => Revendeur::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.raisonSocial', 'ASC');
},
'choice_label' => function (Revendeur $category): string {
return $category->getRaisonSocial()." - ".$category->getName()." ".$category->getSurname()." - ".$category->getEmail()." ".$category->getCode();
}
])
;
}

View File

@@ -34,4 +34,6 @@ class AccountRepository extends ServiceEntityRepository implements PasswordUpgra
}
}

View File

@@ -61,4 +61,20 @@ class CustomerRepository extends ServiceEntityRepository
return $qt->getQuery();
}
public function search($search)
{
$qt = $this->createQueryBuilder('customer');
$qt->innerJoin('customer.customerContacts','customerContacts');
$qt->andWhere('customer.raisonSocial LIKE :search')
->orWhere('customer.siret LIKE :search')
->orWhere('customerContacts.name LIKE :search')
->orWhere('customerContacts.surname LIKE :search')
->orWhere('customerContacts.email LIKE :search')
->orWhere('customerContacts.phone LIKE :search')
->setParameter('search','%'.$search.'%');
return $qt->getQuery()->getResult();
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Service\Website;
use App\Entity\EsyWeb\Website;
class EventCreatedWebsite
{
public function __construct(private readonly Website $website)
{
}
/**
* @return Website
*/
public function getWebsite(): Website
{
return $this->website;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Service\Website;
use App\Repository\AccountRepository;
use App\Service\Customer\CustomerSendPasswordEmail;
use App\Service\Mailer\Mailer;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
#[AsEventListener(event: EventCreatedWebsite::class, method: 'onEventCreatedWebsite', priority: 1)]
class WebsiteCreatedSubscriber
{
public function __construct(
private Mailer $mailer,
private AccountRepository $accountRepository,
private UrlGeneratorInterface $urlGenerator,
)
{
}
public function onEventCreatedWebsite(EventCreatedWebsite $event) {
$website = $event->getWebsite();
$listEmail = [];
foreach ($this->accountRepository->findAll() as $account) {
if(in_array('ROLE_ROOT',$account->getRoles())) {
$listEmail[] = $account->getEmail();
}
if(in_array('ROLE_ADMIN',$account->getRoles())) {
$listEmail[] = $account->getEmail();
}
}
$this->mailer->sendMulti(array_unique($listEmail),"[Mainframe] - Création d'un site internet en attends de validation","mails/esyWeb/website.twig",[
'website' => $website,
'validation_link' => $this->urlGenerator->generate('artemis_esyweb',['idValidate' => $website->getId()],UrlGeneratorInterface::ABSOLUTE_URL),
]);
}
}

View File

@@ -2,6 +2,27 @@
{% block title %}Création d'un site internet{% endblock %}
{% block content %}
{{ form_start(form) }}
{{ form_end(form) }}
<h2 class="text-3xl font-semibold text-gray-800 dark:text-gray-200">Crée un site internet</h2>
<div class="mt-5 bg-gray-800 rounded-lg shadow-lg p-6 space-y-4">
{{ form_start(form) }}
<div class="flex space-x-4">
<div class="flex-1">
{{ form_row(form.title) }}
</div>
<div class="flex-1">
{{ form_row(form.type) }}
</div>
</div>
<div class="flex space-x-4">
<div class="flex-1">
{{ form_row(form.customer) }}
</div>
<div class="flex-1">
{{ form_row(form.revendeur) }}
</div>
</div>
<button type="submit" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-semibold px-4 py-2 rounded">Crée le site internet</button>
{{ form_end(form) }}
</div>
{% endblock %}

View File

@@ -76,6 +76,9 @@
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">
Statut
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody id="websitesTableBody" class="bg-gray-800 divide-y divide-gray-700">
@@ -87,29 +90,40 @@
{% for website in websites %}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">
{{ website.name }}
<span>{{ website.title }}</span><br/>
<span>{{ website.uuid }}</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">
{{ website.client }}
<span>{{ website.customer.raisonSocial }}</span><br>
<span>{{ website.customer.address }}</span><br>
<span>{{ website.customer.zipcode }} {{ website.customer.city }}</span><br>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-blue-400 hover:text-blue-300">
<a href="http://{{ website.ndd }}" target="_blank">{{ website.ndd }}</a>
<a href="http://{{ website.mainDns }}" target="_blank">{{ website.mainDns }}</a>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{% set statusClass = 'bg-gray-600 text-gray-200' %}
{% if website.status == 'Actif' %}
{% if website.state == 'Actif' %}
{% set statusClass = 'bg-green-700/50 text-green-300' %}
{% elseif website.status == 'Suspendu' %}
{% elseif website.state == 'Suspendu' %}
{% set statusClass = 'bg-red-700/50 text-red-300' %}
{% elseif website.status == 'En Développement' %}
{% elseif website.state == 'En Développement' %}
{% set statusClass = 'bg-yellow-700/50 text-yellow-300' %}
{% elseif website.state == 'validate' %}
{% set statusClass = 'bg-zinc-700/50 text-zinc-300' %}
{% elseif website.state == 'created' %}
{% set statusClass = 'bg-stone-700/50 text-stone-300' %}
{% else %}
{% set statusClass = 'bg-indigo-700/50 text-indigo-300' %}
{% endif %}
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ statusClass }}">
{{ website.status }}
{{ ('esyWeb_'~ website.state)|trans }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-blue-400 hover:text-blue-300">
<a href="{{ path('artemis_esyweb_view',{id:website.id}) }}" class="block bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Voir le site internet</a>
</td>
</tr>
{% endfor %}
{% else %}

View File

@@ -0,0 +1,26 @@
{% extends 'artemis/base.twig' %}
{% block title %}
Site Internet - {{ website.title }}
{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-white mb-6"> Site Internet - {{ website.title }}</h1>
</div>
<div class="flex space-x-4 mb-6 border-b border-gray-700">
{% set active = "text-sm border-b-2 border-purple-500" %}
{% set desactive = "text-sm text-gray-400 hover:text-white" %}
<a href="{{ path('artemis_esyweb_view',{id:website.id,current:'main'}) }}" class="px-4 py-2 font-semibold {% if current == "main" %}{{ active }}{% else %}{{ desactive }}{% endif %}">
<i class="fad fa-home"></i>
Informations site internet
</a>
<a href="{{ path('artemis_esyweb_view',{id:website.id,current:'ndd'}) }}" class="px-4 py-2 font-semibold {% if current == "ndd" %}{{ active }}{% else %}{{ desactive }}{% endif %}">
<i class="fad fa-home"></i>
Nom de domaine
</a>
</div>
{% include 'artemis/esyweb/website/'~current~".twig" %}
{% endblock %}

View File

@@ -0,0 +1,71 @@
{% extends 'mails/base.twig' %}
{% block content %}
{# --- Section Principale : En-tête --- #}
<mj-section background-color="#f5f5f5">
<mj-column>
<mj-text font-size="24px" font-weight="bold" color="#1e40af" align="center">
Nouveau Site Web Soumis
</mj-text>
<mj-text font-size="18px" color="#333333" align="center" padding-top="0">
En attente de votre validation
</mj-text>
</mj-column>
</mj-section>
{# --- Section Détails du Site --- #}
<mj-section background-color="#ffffff" padding-top="30px" padding-bottom="20px">
<mj-column>
<mj-text font-size="16px" line-height="24px" color="#333333">
Bonjour,
</mj-text>
<mj-text font-size="16px" line-height="24px" color="#333333">
Un nouveau projet de site internet a été soumis et attend votre approbation finale avant d'être mis en ligne.
</mj-text>
{# Bloc des informations clés #}
<mj-text font-size="16px" line-height="24px" color="#333333" padding-top="20px">
<p style="font-weight: bold; margin-bottom: 5px;">Détails du projet :</p>
<ul>
<li style="list-style-type: none; margin-bottom: 5px;">
<strong>Nom du Site :</strong> {{ datas.website.title }}
</li>
<li style="list-style-type: none; margin-bottom: 5px;">
<strong>Client :</strong> {{ datas.website.customer.raisonSocial }}
</li>
</ul>
</mj-text>
{# Bouton CTA de Validation #}
<mj-button
background-color="#1e40af"
color="#ffffff"
font-size="16px"
padding="20px 0"
href="{{ datas.validation_link }}"
border-radius="8px"
>
Accéder à la Page de Validation
</mj-button>
</mj-column>
</mj-section>
{# --- Section Pied de Page (Rappel) --- #}
<mj-section background-color="#f5f5f5" padding-top="10px" padding-bottom="10px">
<mj-column>
<mj-text font-size="14px" color="#888888" align="center">
Veuillez valider ou rejeter ce site via le lien ci-dessus.
</mj-text>
</mj-column>
</mj-section>
{# --- Clôture --- #}
<mj-section background-color="#ffffff">
<mj-column>
<mj-text font-size="16px" line-height="24px" color="#333333">
Cordialement,<br/>
L'équipe SARL SITECONSEIL
</mj-text>
</mj-column>
</mj-section>
{% endblock %}

View File

@@ -79,3 +79,6 @@ customer_settings: Paramétres
ech_created: Crée - En attends de validation
dns: Nom de domaine
dns_email: Nom de Domaine + Emails
esyWeb_created: Créer en attends de validation
esyWeb_validate: Validation - En Attends de déploiement