From a893c09fcf0844ee35e6001bc1eb69f4746537ee Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 17 Feb 2026 11:02:47 +0100 Subject: [PATCH] changed creating logic to modal --- assets/controllers/base_controller.js | 27 +++ assets/controllers/project_controller.js | 11 +- assets/controllers/user_controller.js | 39 +++- src/Controller/UserController.php | 244 +++++++++++++++-------- src/Service/UserService.php | 34 +++- templates/organization/show.html.twig | 72 ++++++- 6 files changed, 320 insertions(+), 107 deletions(-) create mode 100644 assets/controllers/base_controller.js diff --git a/assets/controllers/base_controller.js b/assets/controllers/base_controller.js new file mode 100644 index 0000000..379f069 --- /dev/null +++ b/assets/controllers/base_controller.js @@ -0,0 +1,27 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + async fetchAndRenderApplications(targetElement) { + try { + const response = await fetch('/application/data/all'); + const apps = await response.json(); + + targetElement.innerHTML = apps.map(app => ` +
+
+ + +
+
+ `).join(''); + + return apps; + } catch (error) { + targetElement.innerHTML = '
Erreur de chargement.
'; + console.error("App load error:", error); + } + } +} \ No newline at end of file diff --git a/assets/controllers/project_controller.js b/assets/controllers/project_controller.js index bcf2e12..e7bf00f 100644 --- a/assets/controllers/project_controller.js +++ b/assets/controllers/project_controller.js @@ -2,9 +2,10 @@ import {Controller} from '@hotwired/stimulus'; import { Modal } from "bootstrap"; import {TabulatorFull as Tabulator} from 'tabulator-tables'; import {eyeIconLink, pencilIcon, TABULATOR_FR_LANG, trashIcon} from "../js/global.js"; +import base_controller from "./base_controller.js"; -export default class extends Controller { +export default class extends base_controller { static values = { listProject : Boolean, orgId: Number, @@ -175,13 +176,13 @@ export default class extends Controller { this.currentProjectId = projectId; this.modal.show(); + this.nameInputTarget.disabled = true; this.formTitleTarget.textContent = "Modifier le projet"; try { // 1. Ensure checkboxes are loaded first - await this.loadApplications(); - + const apps = await this.fetchAndRenderApplications(this.appListTarget); // 2. Fetch the project data const response = await fetch(`/project/data/${projectId}`); const project = await response.json(); @@ -203,13 +204,13 @@ export default class extends Controller { } } // Update your openCreateModal to reset the state - openCreateModal() { + async openCreateModal() { this.currentProjectId = null; this.modal.show(); this.nameInputTarget.disabled = false; this.nameInputTarget.value = ""; this.formTitleTarget.textContent = "Nouveau Projet"; - this.loadApplications(); + await this.fetchAndRenderApplications(); } async deleteProject(event) { diff --git a/assets/controllers/user_controller.js b/assets/controllers/user_controller.js index e7834b0..4a71920 100644 --- a/assets/controllers/user_controller.js +++ b/assets/controllers/user_controller.js @@ -10,9 +10,10 @@ import { trashIconForm } from "../js/global.js"; import { Modal } from "bootstrap"; +import base_controller from "./base_controller.js"; -export default class extends Controller { +export default class extends base_controller { static values = { rolesArray: Array, selectedRoleIds: Array, @@ -26,7 +27,7 @@ export default class extends Controller { orgId: Number } - static targets = ["select", "statusButton", "modal", "userSelect"]; + static targets = ["select", "statusButton", "modal", "userSelect", "appList"]; connect() { this.roleSelect(); @@ -1018,4 +1019,38 @@ export default class extends Controller { alert("Une erreur réseau est survenue."); } } + + async openNewUserModal() { + this.modal.show(); + // Call the shared logic and pass the target + await this.fetchAndRenderApplications(this.appListTarget); + } + + async submitNewUser(event) { + event.preventDefault(); + const form = event.currentTarget; + const formData = new FormData(form); + + try { + const response = await fetch('/user/new/ajax', { // Adjust path if prefix is different + method: 'POST', + body: formData, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }); + + const result = await response.json(); + + if (response.ok) { + this.modal.hide(); + form.reset(); // Clear the form + location.reload(); + } else { + alert("Erreur: " + (result.error || "Une erreur est survenue lors de la création.")); + } + } catch (error) { + alert("Erreur réseau."); + } + } } \ No newline at end of file diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index d7cf277..5efcdc2 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -195,88 +195,88 @@ class UserController extends AbstractController } - #[Route('/new', name: 'new', methods: ['GET', 'POST'])] - public function new(Request $request): Response - { - $this->denyAccessUnlessGranted('ROLE_USER'); - try { - $actingUser =$this->getUser(); - - $user = new User(); - $form = $this->createForm(UserForm::class, $user); - $form->handleRequest($request); - - $orgId = $request->query->get('organizationId') ?? $request->request->get('organizationId'); - if ($orgId) { - $org = $this->organizationRepository->find($orgId); - if (!$org) { - $this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getUserIdentifier()); - $this->addFlash('danger', "L'organisation n'existe pas."); - throw $this->createNotFoundException(self::NOT_FOUND); - } - if (!$this->isGranted('ROLE_ADMIN') && !$this->userService->isAdminOfOrganization($org)) { - $this->loggerService->logAccessDenied($actingUser->getUserIdentifier()); - $this->addFlash('danger', "Accès non autorisé."); - throw $this->createAccessDeniedException(self::ACCESS_DENIED); - } - } else{ - $this->loggerService->logAccessDenied($actingUser->getUserIdentifier()); - $this->addFlash('danger', "Accès non autorisé."); - throw $this->createAccessDeniedException(self::ACCESS_DENIED); - } - - if ($form->isSubmitted() && $form->isValid()) { - $existingUser = $this->userRepository->findOneBy(['email' => $user->getEmail()]); - - // Case : User exists -> link him to given organization if not already linked, else error message - if ($existingUser && $org) { - $this->userService->addExistingUserToOrganization( - $existingUser, - $org, - ); - - if ($this->isGranted('ROLE_ADMIN')) { - $this->loggerService->logSuperAdmin( - $existingUser->getId(), - $actingUser->getUserIdentifier(), - "Super Admin linked user to organization", - $org->getId(), - ); - } - $this->addFlash('success', 'Utilisateur ajouté avec succès à l\'organisation. '); - return $this->redirectToRoute('organization_show', ['id' => $orgId]); - } - - // Case : user doesn't already exist - - $picture = $form->get('pictureUrl')->getData(); - $this->userService->createNewUser($user, $actingUser, $picture); - - $this->userService->linkUserToOrganization( - $user, - $org, - ); - $this->addFlash('success', 'Nouvel utilisateur créé et ajouté à l\'organisation avec succès. '); - return $this->redirectToRoute('organization_show', ['id' => $orgId]); - } - - return $this->render('user/new.html.twig', [ - 'user' => $user, - 'form' => $form->createView(), - 'organizationId' => $orgId, - ]); - - } catch (\Exception $e) { - $this->errorLogger->critical($e->getMessage()); - - if ($orgId) { - $this->addFlash('danger', 'Une erreur est survenue lors de la création de l\'utilisateur pour l\'organisation .'); - return $this->redirectToRoute('organization_show', ['id' => $orgId]); - } - $this->addFlash('danger', 'Une erreur est survenue lors de la création de l\'utilisateur.'); - return $this->redirectToRoute('user_index'); - } - } +// #[Route('/new', name: 'new', methods: ['GET', 'POST'])] +// public function new(Request $request): Response +// { +// $this->denyAccessUnlessGranted('ROLE_USER'); +// try { +// $actingUser =$this->getUser(); +// +// $user = new User(); +// $form = $this->createForm(UserForm::class, $user); +// $form->handleRequest($request); +// +// $orgId = $request->query->get('organizationId') ?? $request->request->get('organizationId'); +// if ($orgId) { +// $org = $this->organizationRepository->find($orgId); +// if (!$org) { +// $this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getUserIdentifier()); +// $this->addFlash('danger', "L'organisation n'existe pas."); +// throw $this->createNotFoundException(self::NOT_FOUND); +// } +// if (!$this->isGranted('ROLE_ADMIN') && !$this->userService->isAdminOfOrganization($org)) { +// $this->loggerService->logAccessDenied($actingUser->getUserIdentifier()); +// $this->addFlash('danger', "Accès non autorisé."); +// throw $this->createAccessDeniedException(self::ACCESS_DENIED); +// } +// } else{ +// $this->loggerService->logAccessDenied($actingUser->getUserIdentifier()); +// $this->addFlash('danger', "Accès non autorisé."); +// throw $this->createAccessDeniedException(self::ACCESS_DENIED); +// } +// +// if ($form->isSubmitted() && $form->isValid()) { +// $existingUser = $this->userRepository->findOneBy(['email' => $user->getEmail()]); +// +// // Case : User exists -> link him to given organization if not already linked, else error message +// if ($existingUser && $org) { +// $this->userService->addExistingUserToOrganization( +// $existingUser, +// $org, +// ); +// +// if ($this->isGranted('ROLE_ADMIN')) { +// $this->loggerService->logSuperAdmin( +// $existingUser->getId(), +// $actingUser->getUserIdentifier(), +// "Super Admin linked user to organization", +// $org->getId(), +// ); +// } +// $this->addFlash('success', 'Utilisateur ajouté avec succès à l\'organisation. '); +// return $this->redirectToRoute('organization_show', ['id' => $orgId]); +// } +// +// // Case : user doesn't already exist +// +// $picture = $form->get('pictureUrl')->getData(); +// $this->userService->createNewUser($user, $actingUser, $picture); +// +// $this->userService->linkUserToOrganization( +// $user, +// $org, +// ); +// $this->addFlash('success', 'Nouvel utilisateur créé et ajouté à l\'organisation avec succès. '); +// return $this->redirectToRoute('organization_show', ['id' => $orgId]); +// } +// +// return $this->render('user/new.html.twig', [ +// 'user' => $user, +// 'form' => $form->createView(), +// 'organizationId' => $orgId, +// ]); +// +// } catch (\Exception $e) { +// $this->errorLogger->critical($e->getMessage()); +// +// if ($orgId) { +// $this->addFlash('danger', 'Une erreur est survenue lors de la création de l\'utilisateur pour l\'organisation .'); +// return $this->redirectToRoute('organization_show', ['id' => $orgId]); +// } +// $this->addFlash('danger', 'Une erreur est survenue lors de la création de l\'utilisateur.'); +// return $this->redirectToRoute('user_index'); +// } +// } /** * Endpoint to activate/deactivate a user (soft delete) @@ -832,6 +832,86 @@ class UserController extends AbstractController throw $this->createNotFoundException(self::NOT_FOUND); } + #[Route('/new/ajax', name: 'new_ajax', methods: ['POST'])] + public function newUserAjax(Request $request): JsonResponse + { + $this->denyAccessUnlessGranted('ROLE_USER'); + $actingUser = $this->getUser(); + try { + $data = $request->request->all(); + $orgId = $data['organizationId'] ?? null; + $selectedApps = $data['applications'] ?? []; + + //unset data that are not part of the User entity to avoid form errors + unset($data['organizationId'], $data['applications']); + $user = new User(); + + $form = $this->createForm(UserForm::class, $user, [ + 'csrf_protection' => false, + 'allow_extra_fields' => true, + ]); + + $form->submit($data, false); + if (!$orgId) { + return $this->json(['error' => 'ID Organisation manquant.'], 400); + } + + $org = $this->organizationRepository->find($orgId); + if (!$org) { + return $this->json(['error' => "L'organisation n'existe pas."], 404); + } + + // 3. Permissions Check + if (!$this->isGranted('ROLE_ADMIN') && !$this->userService->isAdminOfOrganization($org)) { + $this->loggerService->logAccessDenied($actingUser->getUserIdentifier()); + return $this->json(['error' => "Accès non autorisé."], 403); + } + + if ($form->isSubmitted() && $form->isValid()) { + $email = $user->getEmail(); + $existingUser = $this->userRepository->findOneBy(['email' => $email]); + + // CASE A: User exists -> Add to org + if ($existingUser) { + // Check if already in org to avoid logic errors or duplicate logs + $this->userService->addExistingUserToOrganization($existingUser, $org, $selectedApps); + + if ($this->isGranted('ROLE_ADMIN')) { + $this->loggerService->logSuperAdmin( + $existingUser->getId(), + $actingUser->getUserIdentifier(), + "Super Admin linked user to organization via AJAX", + $org->getId(), + ); + } + + return $this->json([ + 'success' => true, + 'message' => 'Utilisateur existant ajouté à l\'organisation.' + ]); + } + + // CASE B: New User -> Create + // Fetch picture from $request->files since it's a multipart request + $picture = $request->files->get('pictureUrl'); + + $this->userService->createNewUser($user, $actingUser, $picture); + $this->userService->linkUserToOrganization($user, $org, $selectedApps); + + return $this->json([ + 'success' => true, + 'message' => 'Nouvel utilisateur créé et ajouté.' + ]); + } + + // If form is invalid, return the specific errors + return $this->json(['error' => 'Données de formulaire invalides.'], 400); + + } catch (\Exception $e) { + $this->errorLogger->critical("AJAX User Creation Error: " . $e->getMessage()); + return $this->json(['error' => 'Une erreur interne est survenue.'], 500); + } + } } diff --git a/src/Service/UserService.php b/src/Service/UserService.php index a6429c6..dbe6e6b 100644 --- a/src/Service/UserService.php +++ b/src/Service/UserService.php @@ -3,6 +3,7 @@ namespace App\Service; +use App\Entity\Apps; use App\Entity\Organizations; use App\Entity\Roles; use App\Entity\User; @@ -533,17 +534,17 @@ class UserService * * @param User $user * @param Organizations $organization - * @return void + * @param array $selectedApps + * @return int + * @throws Exception */ - public function handleExistingUser(User $user, Organizations $organization): int + public function reactivateUser(User $user, Organizations $organization, array $selectedApps): int { if (!$user->isActive()) { $user->setIsActive(true); $this->entityManager->persist($user); } - $uo = $this->linkUserToOrganization($user, $organization); - - return $uo->getId(); + return $this->linkUserToOrganization($user, $organization, $selectedApps)->getId(); } /** @@ -554,6 +555,8 @@ class UserService * Handle picture if provided * * @param User $user + * @param $picture + * @param bool $setPassword * @return void */ public function formatUserData(User $user, $picture, bool $setPassword = false): void @@ -589,11 +592,12 @@ class UserService public function addExistingUserToOrganization( User $existingUser, Organizations $org, + array $selectedApps ): int { + $actingUser = $this->getUserByIdentifier($this->security->getUser()->getUserIdentifier()); try { - $uoId = $this->handleExistingUser($existingUser, $org); - $actingUser = $this->getUserByIdentifier($this->security->getUser()->getUserIdentifier()); + $uoId = $this->reactivateUser($existingUser, $org, $selectedApps); $this->loggerService->logExistingUserAddedToOrg( $existingUser->getId(), $org->getId(), @@ -647,6 +651,7 @@ class UserService public function linkUserToOrganization( User $user, Organizations $org, + array $selectedApps ): UsersOrganizations { $actingUser = $this->getUserByIdentifier($this->security->getUser()->getUserIdentifier()); @@ -660,6 +665,7 @@ class UserService $uo->setRole($roleUser); $uo->setModifiedAt(new \DateTimeImmutable('now')); $this->entityManager->persist($uo); + $this->linkUOToApps($uo, $selectedApps); $this->entityManager->flush(); $this->loggerService->logUserOrganizationLinkCreated( @@ -731,4 +737,18 @@ class UserService } } + private function linkUOToApps(UsersOrganizations $uo, array $selectedApps):void + { + foreach ($selectedApps as $appId){ + $uoa = new UserOrganizationApp(); + $uoa->setUserOrganization($uo); + $app = $this->entityManager->getRepository(Apps::class)->find($appId); + if ($app) { + $uoa->setApplication($app); + $this->entityManager->persist($uoa); + } + } + $this->entityManager->flush(); + } + } diff --git a/templates/organization/show.html.twig b/templates/organization/show.html.twig index 60ce8e1..a54ce55 100644 --- a/templates/organization/show.html.twig +++ b/templates/organization/show.html.twig @@ -52,20 +52,70 @@ {# User tables #}
-
+
+
-

- Nouveaux utilisateurs -

- Ajouter un utilisateur +

Nouveaux utilisateurs

+ {# Button to trigger modal #} +
-
+
+
+ + {# New User Modal #} +