Ajax call to create + edit organizations

This commit is contained in:
Charles 2026-03-03 11:28:02 +01:00
parent 4f7c1eb5de
commit d5e1ad057e
9 changed files with 346 additions and 99 deletions

View File

@ -2,17 +2,18 @@ import {Controller} from '@hotwired/stimulus'
// Important: include a build with Ajax + pagination (TabulatorFull is simplest) // Important: include a build with Ajax + pagination (TabulatorFull is simplest)
import {TabulatorFull as Tabulator} from 'tabulator-tables'; import {TabulatorFull as Tabulator} from 'tabulator-tables';
import {eyeIconLink, TABULATOR_FR_LANG} from "../js/global.js"; import {eyeIconLink, TABULATOR_FR_LANG} from "../js/global.js";
import { Modal } from "bootstrap";
export default class extends Controller { export default class extends Controller {
static values = { static values = {
id: String, id: Number,
activities: Boolean, activities: Boolean,
table: Boolean, table: Boolean,
sadmin: Boolean, sadmin: Boolean,
user: Number user: Number
}; };
static targets = ["activityList", "emptyMessage"] static targets = ["activityList", "emptyMessage", "modal", "modalTitle", "nameInput", "emailInput", "numberInput", "addressInput"];
connect() { connect() {
if(this.activitiesValue){ if(this.activitiesValue){
this.loadActivities(); this.loadActivities();
@ -23,6 +24,10 @@ export default class extends Controller {
if (this.tableValue && this.sadminValue) { if (this.tableValue && this.sadminValue) {
this.table(); this.table();
} }
if (this.hasModalTarget) {
this.modal = new Modal(this.modalTarget);
this.currentOrgId = null;
}
} }
@ -153,4 +158,69 @@ export default class extends Controller {
this.activityListTarget.innerHTML = html; this.activityListTarget.innerHTML = html;
} }
openCreateModal() {
this.currentOrgId = null;
this.modalTitleTarget.textContent = "Créer une organisation";
this.resetForm();
this.modal.show();
}
async openEditModal(event) {
this.currentOrgId = event.currentTarget.dataset.id;
this.modalTitleTarget.textContent = "Modifier l'organisation";
try {
const response = await fetch(`/organization/${this.currentOrgId}`);
const data = await response.json();
// Fill targets
this.nameInputTarget.value = data.name;
this.emailInputTarget.value = data.email;
this.numberInputTarget.value = data.number || '';
this.addressInputTarget.value = data.address || '';
this.modal.show();
} catch (error) {
alert("Erreur lors du chargement des données.");
}
}
async submitForm(event) {
event.preventDefault();
const formData = new FormData(event.target);
const method = this.currentOrgId ? 'PUT' : 'POST';
const url = this.currentOrgId ? `/organization/${this.currentOrgId}` : `/organization/`;
if (this.currentOrgId) {
formData.append('_method', 'PUT');
}
try {
const response = await fetch(url, {
method: 'POST',
body: formData,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
if (response.ok) {
this.modal.hide();
location.reload();
} else {
const result = await response.json();
alert(result.error || "Une erreur est survenue.");
}
} catch (e) {
alert("Erreur réseau.");
}
}
resetForm() {
this.nameInputTarget.value = "";
this.emailInputTarget.value = "";
this.numberInputTarget.value = "";
this.addressInputTarget.value = "";
}
} }

View File

@ -0,0 +1,38 @@
<?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 Version20260302105113 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 organizations ALTER number DROP NOT NULL');
$this->addSql('ALTER TABLE project ALTER bdd_name SET NOT NULL');
$this->addSql('ALTER TABLE project ALTER timestamp_precision SET NOT NULL');
$this->addSql('ALTER TABLE project ALTER deletion_allowed SET NOT 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 organizations ALTER number SET NOT NULL');
$this->addSql('ALTER TABLE project ALTER bdd_name DROP NOT NULL');
$this->addSql('ALTER TABLE project ALTER timestamp_precision DROP NOT NULL');
$this->addSql('ALTER TABLE project ALTER deletion_allowed DROP NOT NULL');
}
}

View File

@ -4,9 +4,6 @@ namespace App\Controller;
use App\Entity\Actions; use App\Entity\Actions;
use App\Entity\Apps; use App\Entity\Apps;
use App\Entity\Roles;
use App\Entity\User;
use App\Entity\UserOrganizationApp;
use App\Entity\UsersOrganizations; use App\Entity\UsersOrganizations;
use App\Form\OrganizationForm; use App\Form\OrganizationForm;
use App\Repository\OrganizationsRepository; use App\Repository\OrganizationsRepository;
@ -17,12 +14,8 @@ use App\Service\LoggerService;
use App\Service\OrganizationsService; use App\Service\OrganizationsService;
use App\Service\UserOrganizationService; use App\Service\UserOrganizationService;
use App\Service\UserService; use App\Service\UserService;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Exception; use Exception;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -82,88 +75,113 @@ class OrganizationController extends AbstractController
return $this->redirectToRoute('app_index'); return $this->redirectToRoute('app_index');
} }
#[Route(path: '/', name: 'create', methods: ['POST'])]
#[Route(path: '/create', name: 'create', methods: ['GET', 'POST'])] public function create(Request $request): JsonResponse
public function new(Request $request): Response
{ {
$this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN'); $this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN');
$actingUser = $this->getUser(); $actingUser = $this->getUser();
if ($request->isMethod('POST')) {
$data = $request->request->all();
// 2. Access file data specifically
$logoFile = $request->files->get('logoUrl');
$organization = new Organizations(); $organization = new Organizations();
$form = $this->createForm(OrganizationForm::class, $organization); $existingOrg = $this->organizationsRepository->findOneBy(['email' => $data['email']]);
$form->handleRequest($request); if ($existingOrg) {
if ($form->isSubmitted() && $form->isValid()) { return $this->json(['error' => 'Une organisation avec cet email existe déjà.'], 400);
$logoFile = $form->get('logoUrl')->getData(); }
$form = $this->createForm(OrganizationForm::class, $organization,[
'csrf_protection' => false,
'allow_extra_fields' => true,
]);
$form->submit(array_merge($data, ['logoUrl' => $logoFile]));
if ($form->isValid()) {
try {
if ($logoFile) { if ($logoFile) {
$this->organizationsService->handleLogo($organization, $logoFile); $this->organizationsService->handleLogo($organization, $logoFile);
} }
try {
$organization->setProjectPrefix($this->organizationsService->generateUniqueProjectPrefix()); $organization->setProjectPrefix($this->organizationsService->generateUniqueProjectPrefix());
$this->entityManager->persist($organization); $this->entityManager->persist($organization);
$this->entityManager->flush(); $this->entityManager->flush();
$this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getUserIdentifier(), "Organization Created");
$this->loggerService->logSuperAdmin($actingUser->getUserIdentifier(), $actingUser->getUserIdentifier(), "Organization Created", $organization->getId()); // Loggers...
$this->actionService->createAction("Create Organization", $actingUser, $organization, $organization->getName()); $this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getUserIdentifier(), "Organization Created via ajax");
$this->addFlash('success', 'Organisation crée avec succès.');
return $this->redirectToRoute('organization_index'); return $this->json([
'message' => 'Organisation créée avec succès.',
'id' => $organization->getId()
], Response::HTTP_CREATED);
} catch (Exception $e) { } catch (Exception $e) {
$this->addFlash('danger', 'Erreur lors de la création de l\'organization'); return $this->json(['error' => $e->getMessage()], 500);
$this->loggerService->logError('Error creating organization', ['acting_user_id' => $actingUser->getUserIdentifier(), 'error' => $e->getMessage()]);
} }
} }
return $this->render('organization/new.html.twig', [
'form' => $form->createView(),
]);
}
$form = $this->createForm(OrganizationForm::class); // 4. Return specific validation errors to help debugging
return $this->render('organization/new.html.twig', [ $errors = [];
'form' => $form->createView(), foreach ($form->getErrors(true) as $error) {
]); $errors[] = $error->getMessage();
} }
#[Route(path: '/edit/{id}', name: 'edit', methods: ['GET', 'POST'])] return $this->json(['error' => 'Validation failed', 'details' => $errors], 400);
public function edit(Request $request, $id): Response }
#[Route(path: '/{id}', name: 'edit', methods: ['PUT'])]
public function edit(Request $request, int $id): JsonResponse
{ {
$this->denyAccessUnlessGranted('ROLE_ADMIN'); $this->denyAccessUnlessGranted('ROLE_USER');
$actingUser = $this->getUser(); $actingUser = $this->getUser();
$organization = $this->organizationsRepository->find($id); $organization = $this->organizationsRepository->find($id);
if (!$organization) { if (!$organization) {
$this->loggerService->logEntityNotFound('Organization', [ $this->loggerService->logEntityNotFound('Organization', [
'org_id' => $id, 'org_id' => $id,
'message' => 'Organization not found for edit'], $actingUser->getUserIdentifier() 'message' => 'Organization not found for get endpoint'
); ], $actingUser->getUserIdentifier());
$this->addFlash('danger', 'Erreur, l\'organization est introuvable.'); return $this->json(['error' => self::NOT_FOUND], Response::HTTP_NOT_FOUND);
return $this->redirectToRoute('organization_index');
} }
if (!$this->userService->isAdminOfOrganization($organization) && !$this->isGranted("ROLE_ADMIN")) {
$form = $this->createForm(OrganizationForm::class, $organization); $this->loggerService->logAccessDenied($actingUser->getUserIdentifier());
$form->handleRequest($request); return $this->json(['error' => self::ACCESS_DENIED], Response::HTTP_FORBIDDEN);
}
$data = $request->request->all();
$logoFile = $request->files->get('logoUrl');
$form = $this->createForm(OrganizationForm::class, $organization,[
'csrf_protection' => false,
'allow_extra_fields' => true,
]);
if ($logoFile) {
$form->submit(array_merge($data, ['logoUrl' => $logoFile]), false);
}
$form->submit(array_merge($request->request->all(), $request->files->all()));
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$logoFile = $form->get('logoUrl')->getData(); try {
if ($logoFile) { if ($logoFile) {
$this->organizationsService->handleLogo($organization, $logoFile); $this->organizationsService->handleLogo($organization, $logoFile);
} }
try {
$this->entityManager->persist($organization); $this->entityManager->persist($organization);
$this->entityManager->flush(); $this->entityManager->flush();
$this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getUserIdentifier(), "Organization Edited");
if ($this->isGranted("ROLE_SUPER_ADMIN")) {
$this->loggerService->logSuperAdmin($actingUser->getUserIdentifier(), $actingUser->getUserIdentifier(), "Organization Edited", $organization->getId());
}
$this->actionService->createAction("Edit Organization", $actingUser, $organization, $organization->getName()); $this->actionService->createAction("Edit Organization", $actingUser, $organization, $organization->getName());
$this->addFlash('success', 'Organisation modifiée avec succès.'); $this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getUserIdentifier(), "Organization Edited via ajax");
return $this->redirectToRoute('organization_index'); return $this->json(['message' => 'Organisation modifiée avec succès.']);
}catch (Exception $e) { } catch (Exception $e) {
$this->addFlash('danger', 'Erreur lors de la modification de l\'organization'); return $this->json(['error' => $e->getMessage()], 500);
$this->loggerService->logError('Error editing organization', ['acting_user_id' => $actingUser->getUserIdentifier(), 'error' => $e->getMessage()]); }
} else {
$errors = [];
foreach ($form->getErrors(true) as $error) {
$errors[] = $error->getMessage();
}
return $this->json(['error' => 'Validation failed', 'details' => $errors], 400);
} }
} }
return $this->render('organization/edit.html.twig', [
'form' => $form->createView(),
'organization' => $organization,
]);
}
#[Route(path: '/view/{id}', name: 'show', methods: ['GET'])] #[Route(path: '/view/{id}', name: 'show', methods: ['GET'])]
public function view($id): Response public function view($id): Response
@ -286,7 +304,7 @@ class OrganizationController extends AbstractController
return $this->redirectToRoute('organization_index'); return $this->redirectToRoute('organization_index');
} }
// API endpoint to fetch organization data for Tabulator // API endpoint to fetch organizations data for Tabulator
#[Route(path: '/data', name: 'data', methods: ['GET'])] #[Route(path: '/data', name: 'data', methods: ['GET'])]
public function data(Request $request): JsonResponse public function data(Request $request): JsonResponse
{ {
@ -325,6 +343,7 @@ class OrganizationController extends AbstractController
]); ]);
} }
/* Ajax route to get users of an organization ( used in select field for admin of an org ) */
#[Route(path: '/{id}/users', name: 'users', methods: ['GET'])] #[Route(path: '/{id}/users', name: 'users', methods: ['GET'])]
public function users($id): JsonResponse{ public function users($id): JsonResponse{
$this->denyAccessUnlessGranted("ROLE_USER"); $this->denyAccessUnlessGranted("ROLE_USER");
@ -351,4 +370,32 @@ class OrganizationController extends AbstractController
}, $uos); }, $uos);
return $this->json(['users' => $users]); return $this->json(['users' => $users]);
} }
/*
* Path used to get data on an organization for the edit modal
*/
#[Route(path: '/{id}', name: 'get', methods: ['GET'])]
public function get(int $id): JsonResponse{
$this->denyAccessUnlessGranted('ROLE_USER');
$actingUser = $this->getUser();
$organization = $this->organizationsRepository->find($id);
if (!$organization) {
$this->loggerService->logEntityNotFound('Organization', [
'org_id' => $id,
'message' => 'Organization not found for get endpoint'
], $actingUser->getUserIdentifier());
return $this->json(['error' => self::NOT_FOUND], Response::HTTP_NOT_FOUND);
}
if (!$this->userService->isAdminOfOrganization($organization) && !$this->isGranted("ROLE_ADMIN")) {
$this->loggerService->logAccessDenied($actingUser->getUserIdentifier());
return $this->json(['error' => self::ACCESS_DENIED], Response::HTTP_FORBIDDEN);
}
return $this->json([
'id' => $organization->getId(),
'name' => $organization->getName(),
'email' => $organization->getEmail(),
'logoUrl' => $organization->getLogoUrl() ?: null,
'active' => $organization->isActive(),
]);
}
} }

View File

@ -21,7 +21,7 @@ class Organizations
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
private ?string $email = null; private ?string $email = null;
#[ORM\Column] #[ORM\Column(nullable: true)]
private ?int $number = null; private ?int $number = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
@ -101,7 +101,7 @@ class Organizations
return $this->number; return $this->number;
} }
public function setNumber(int $number): static public function setNumber(?int $number): static
{ {
$this->number = $number; $this->number = $number;

View File

@ -7,6 +7,7 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@ -17,8 +18,8 @@ class OrganizationForm extends AbstractType
$builder $builder
->add('email', EmailType::class, ['required' => true, 'label' => 'Email*','empty_data' => '']) ->add('email', EmailType::class, ['required' => true, 'label' => 'Email*','empty_data' => ''])
->add('name', TextType::class, ['required' => true, 'label' => 'Nom de l\'organisation*','empty_data' => '']) ->add('name', TextType::class, ['required' => true, 'label' => 'Nom de l\'organisation*','empty_data' => ''])
->add('address', TextType::class, ['required' => true, 'label' => 'Adresse','empty_data' => '']) ->add('address', TextType::class, [ 'label' => 'Adresse','empty_data' => ''])
->add('number', TextType::class, ['required' => true, 'label' => 'Numéro de téléphone','empty_data' => '']) ->add('number', IntegerType::class, [ 'label' => 'Numéro de téléphone'])
->add('logoUrl', FileType::class, [ ->add('logoUrl', FileType::class, [
'required' => false, 'required' => false,
'label' => 'Logo', 'label' => 'Logo',

View File

@ -11,24 +11,72 @@
</div> </div>
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
<div class="card no-header-bg p-3 m-3 border-0"> <div class="card no-header-bg p-3 m-3 border-0"
data-controller="organization"
>
<div class="card-header d-flex justify-content-between align-items-center border-0"> <div class="card-header d-flex justify-content-between align-items-center border-0">
<div class="card-title"> <div class="card-title">
<h1>Gestion des organisations</h1> <h1>Gestion des organisations</h1>
</div> </div>
{% if is_granted("ROLE_SUPER_ADMIN") %} {% if is_granted("ROLE_SUPER_ADMIN") %}
<a href="{{ path('organization_create') }}" class="btn btn-primary">Ajouter une organisation</a> <button type="button" class="btn btn-primary" data-action="click->organization#openCreateModal">
Créer une organisation
</button>
{% endif %} {% endif %}
{# New organization modal #}
<div class="modal fade" id="createOrganizationModal" tabindex="-1" aria-labelledby="createOrganizationModalLabel"
aria-hidden="true"
data-organization-target="modal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createOrganizationModalLabel">Créer une organisation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form data-action="submit->organization#createOrganization">
<div class="mb-3">
<label for="organizationName" class="form-label">Nom de l'organisation</label>
<input type="text" class="form-control" id="organizationName" name="name" required>
</div>
<div class="mb-3">
<label for="organizationEmail" class="form-label">Email</label>
<input class="form-control" id="organizationEmail" type="email" name="email" required>
</div>
<div class="mb-3">
<label for="organizationPhone" class="form-label">Téléphone</label>
<input class="form-control" type="number" id="organizationPhone" name="phone">
</div>
<div class="mb-3">
<label for="organizationAddress" class="form-label">Adresse</label>
<input class="form-control" id="organizationAddress" name="address">
</div>
<div class="mb-3">
<label class="form-label" for="organizationLogo">Logo de l'organisation</label>
<input type="file" name="logoUrl" id="organizationLogo" class="form-control"
accept="image/*">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Annuler
</button>
<button type="submit" class="btn btn-primary">Créer l'organisation</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div> </div>
<div class="card-body"> <div class="card-body">
{% if is_granted('ROLE_SUPER_ADMIN') and not hasOrganizations %} {% if is_granted('ROLE_SUPER_ADMIN') and not hasOrganizations %}
<div class="div text-center my-5 py-5"> <div class="div text-center my-5 py-5">
<h1 class="my-5 ty-5"> Aucune organisation trouvée. </h1> <h1 class="my-5 ty-5"> Aucune organisation trouvée. </h1>
<a href="{{ path('organization_create') }}" class="btn btn-primary">Créer une organisation</a> <a href="{{ path('organization_create') }}" class="btn btn-primary">Créer une
organisation</a>
</div> </div>
{% else %} {% else %}

View File

@ -0,0 +1,38 @@
<div class="modal fade" id="organizationModal" tabindex="-1" data-organization-target="modal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" data-organization-target="modalTitle">Créer une organisation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form data-action="submit->organization#submitForm">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Nom de l'organisation</label>
<input type="text" name="name" data-organization-target="nameInput" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" name="email" data-organization-target="emailInput" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Téléphone</label>
<input type="text" name="number" data-organization-target="numberInput" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Adresse</label>
<input type="text" name="address" data-organization-target="addressInput" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Logo</label>
<input type="file" name="logoUrl" class="form-control" accept="image/*">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -10,7 +10,7 @@
</div> </div>
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
<div class="col d-flex justify-content-between align-items-center"> <div class="col d-flex justify-content-between align-items-center" data-controller="organization">
<div class="d-flex "> <div class="d-flex ">
{% if organization.logoUrl %} {% if organization.logoUrl %}
<img src="{{ asset(organization.logoUrl) }}" alt="Organization logo" <img src="{{ asset(organization.logoUrl) }}" alt="Organization logo"
@ -20,8 +20,13 @@
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
{% if isSA %} {% if isSA %}
<a href="{{ path('organization_edit', {'id': organization.id}) }}" class="btn btn-primary">Gérer <button type="button"
l'organisation</a> class="btn btn-primary"
data-action="click->organization#openEditModal"
data-id="{{ organization.id }}">
Gérer mon organisation
</button>
{{ include('organization/organizationModal.html.twig') }}
<form method="POST" action="{{ path('organization_delete', {'id': organization.id}) }}" <form method="POST" action="{{ path('organization_delete', {'id': organization.id}) }}"
onsubmit="return confirm('Vous allez supprimer cette organisation, êtes vous sûre?');" onsubmit="return confirm('Vous allez supprimer cette organisation, êtes vous sûre?');"
style="display: inline-block;"> style="display: inline-block;">
@ -41,8 +46,13 @@
</form> </form>
{% endif %} {% endif %}
{% elseif is_granted("ROLE_ADMIN") %} {% elseif is_granted("ROLE_ADMIN") %}
<a href="{{ path('organization_edit', {'id': organization.id}) }}" class="btn btn-primary">Gérer mon <button type="button"
organisation</a> class="btn btn-primary"
data-action="click->organization#openEditModal"
data-id="{{ organization.id }}">
Gérer mon organisation
</button>
{{ include('organization/organizationModal.html.twig') }}
{% endif %} {% endif %}
</div> </div>

View File

@ -1,5 +0,0 @@
{% extends 'base.html.twig' %}
{% block body %}
{% endblock %}