From 4a2f9d95472da32a492ac007649829785ef82c30 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 16 Jul 2025 15:12:45 +0200 Subject: [PATCH] Read User information --- .idea/php.xml | 1 - assets/icons/fa6-regular/circle-down.svg | 1 + assets/icons/fa6-regular/circle-up.svg | 1 + assets/icons/fa6-regular/circle-user.svg | 1 + assets/icons/fa6-regular/eye.svg | 1 + assets/styles/app.css | 36 ++++++ composer.lock | 110 ++---------------- config/packages/security.yaml | 6 +- migrations/Version20250716130850.php | 31 +++++ src/Controller/UserController.php | 48 ++++++++ src/Repository/UserRepository.php | 33 ++---- .../UsersOrganizationsRepository.php | 40 +++---- src/Service/UserOrganizationService.php | 103 ++++++++++++++++ templates/elements/menu.html.twig | 7 +- templates/elements/userInformation.html.twig | 15 +++ .../userOrganizationInformation.html.twig | 85 ++++++++++++++ templates/user/index.html.twig | 48 ++++++++ templates/user/profile.html.twig | 33 ++++++ 18 files changed, 445 insertions(+), 155 deletions(-) create mode 100644 assets/icons/fa6-regular/circle-down.svg create mode 100644 assets/icons/fa6-regular/circle-up.svg create mode 100644 assets/icons/fa6-regular/circle-user.svg create mode 100644 assets/icons/fa6-regular/eye.svg create mode 100644 migrations/Version20250716130850.php create mode 100644 src/Controller/UserController.php create mode 100644 src/Service/UserOrganizationService.php create mode 100644 templates/elements/userInformation.html.twig create mode 100644 templates/elements/userOrganizationInformation.html.twig create mode 100644 templates/user/index.html.twig create mode 100644 templates/user/profile.html.twig diff --git a/.idea/php.xml b/.idea/php.xml index aa5ce23..f0ca9da 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -141,7 +141,6 @@ - diff --git a/assets/icons/fa6-regular/circle-down.svg b/assets/icons/fa6-regular/circle-down.svg new file mode 100644 index 0000000..06d5e06 --- /dev/null +++ b/assets/icons/fa6-regular/circle-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/fa6-regular/circle-up.svg b/assets/icons/fa6-regular/circle-up.svg new file mode 100644 index 0000000..787ffb0 --- /dev/null +++ b/assets/icons/fa6-regular/circle-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/fa6-regular/circle-user.svg b/assets/icons/fa6-regular/circle-user.svg new file mode 100644 index 0000000..447566f --- /dev/null +++ b/assets/icons/fa6-regular/circle-user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/fa6-regular/eye.svg b/assets/icons/fa6-regular/eye.svg new file mode 100644 index 0000000..d561750 --- /dev/null +++ b/assets/icons/fa6-regular/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/styles/app.css b/assets/styles/app.css index 712cb41..42a7c59 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -1,3 +1,13 @@ +/*variable*/ +:root{ + --primary-blue-light : #086572; + --primary-blue-dark : #094754; + --black-font: #1D1E1C; + --delete : #E42E31; + --disable : #A3A3A3; + --check : #80F20E; +} + html { margin: 0; padding: 0; @@ -77,4 +87,30 @@ body { font-family: "Nunito", sans-serif; font-weight: 400; border-top: 1px solid rgba(0, 0, 0, 0.06); +} + +.btn-primary{ + background: var(--primary-blue-light); + color : #FFFFFF; + border: var(--primary-blue-dark); + border-radius: 1rem; +} +.btn-primary:hover{ + background: var(--primary-blue-dark); + color : #FFFFFF; + border: var(--primary-blue-light); +} + +.btn-danger{ + background: var(--delete); + color : #FFFFFF; + border: var(--delete); + border-radius: 1rem; +} + +.color-primary{ + color: var(--primary-blue-light); +} +.color-primary-dark{ + color: var(--primary-blue-dark); } \ No newline at end of file diff --git a/composer.lock b/composer.lock index 6379dbf..68ba93a 100644 --- a/composer.lock +++ b/composer.lock @@ -154,99 +154,6 @@ }, "time": "2023-06-19T06:10:36+00:00" }, - { - "name": "doctrine/cache", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.2.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2022-05-20T20:07:39+00:00" - }, { "name": "doctrine/collections", "version": "2.3.0", @@ -335,28 +242,31 @@ }, { "name": "doctrine/dbal", - "version": "3.9.5", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "4a4e2eed3134036ee36a147ee0dac037dfa17868" + "reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/4a4e2eed3134036ee36a147ee0dac037dfa17868", - "reference": "4a4e2eed3134036ee36a147ee0dac037dfa17868", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/1cf840d696373ea0d58ad0a8875c0fadcfc67214", + "reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3|^1", "doctrine/event-manager": "^1|^2", "php": "^7.4 || ^8.0", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, + "conflict": { + "doctrine/cache": "< 1.11" + }, "require-dev": { + "doctrine/cache": "^1.11|^2.0", "doctrine/coding-standard": "13.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", @@ -426,7 +336,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.9.5" + "source": "https://github.com/doctrine/dbal/tree/3.10.0" }, "funding": [ { @@ -442,7 +352,7 @@ "type": "tidelift" } ], - "time": "2025-06-15T22:40:05+00:00" + "time": "2025-07-10T21:11:04+00:00" }, { "name": "doctrine/deprecations", diff --git a/config/packages/security.yaml b/config/packages/security.yaml index a525496..c4e3033 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -11,8 +11,10 @@ security: property: email role_hierarchy: - ROLE_ADMIN: ROLE_USER - ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] + ROLE_SUDALYS: ROLE_USER + ROLE_ADMIN: ROLE_USER + ROLE_SUDALYS_ADMIN: [ROLE_SUDALYS, ROLE_ALLOWED_TO_SWITCH, ROLE_ADMIN] + firewalls: dev: diff --git a/migrations/Version20250716130850.php b/migrations/Version20250716130850.php new file mode 100644 index 0000000..c0996b9 --- /dev/null +++ b/migrations/Version20250716130850.php @@ -0,0 +1,31 @@ +addSql('CREATE SCHEMA public'); + } +} diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..0dadb33 --- /dev/null +++ b/src/Controller/UserController.php @@ -0,0 +1,48 @@ +isGranted('ROLE_SUDALYS_ADMIN')) { + $users = $entityManager->getRepository(User::class)->getAllActiveUsers(); + } else { + $users = 'Not Super Admin'; + } + return $this->render('user/index.html.twig', [ + 'users' => $users, + 'controller_name' => 'IndexController', + ]); + } + + #[Route('/{id}', name: 'view')] + public function userProfile(Request $request, EntityManagerInterface $entityManager): Response + { + if ($this->isGranted('ROLE_SUDALYS_ADMIN')) { + $userId = $request->attributes->get('id'); + $user = $entityManager->getRepository(User::class)->find($userId); + $userOrganizations = $this->userOrganizationService->getUserOrganizations($user); + } + return $this->render('user/profile.html.twig', [ + 'user' => $user, + 'userOrganizations' => $userOrganizations, + ]); + } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 4f2804e..2b8150d 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -33,28 +33,13 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader $this->getEntityManager()->flush(); } - // /** - // * @return User[] Returns an array of User objects - // */ - // public function findByExampleField($value): array - // { - // return $this->createQueryBuilder('u') - // ->andWhere('u.exampleField = :val') - // ->setParameter('val', $value) - // ->orderBy('u.id', 'ASC') - // ->setMaxResults(10) - // ->getQuery() - // ->getResult() - // ; - // } - - // public function findOneBySomeField($value): ?User - // { - // return $this->createQueryBuilder('u') - // ->andWhere('u.exampleField = :val') - // ->setParameter('val', $value) - // ->getQuery() - // ->getOneOrNullResult() - // ; - // } + public function getAllActiveUsers(): array{ + $queryBuilder = $this->createQueryBuilder('u') + ->select('u.surname', 'u.email', 'u.id', 'u.isActive', 'u.name', 'u.pictureUrl') + // Remove this line: ->from(User::class, 'u') + ->where('u.isActive = :isActive') // Also fixed the concatenation + ->orderBy('u.surname', 'ASC'); + $queryBuilder->setParameter('isActive', true); + return $queryBuilder->getQuery()->getResult(); + } } diff --git a/src/Repository/UsersOrganizationsRepository.php b/src/Repository/UsersOrganizationsRepository.php index 920141a..d7bd828 100644 --- a/src/Repository/UsersOrganizationsRepository.php +++ b/src/Repository/UsersOrganizationsRepository.php @@ -6,9 +6,7 @@ use App\Entity\UsersOrganizations; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; -/** - * @extends ServiceEntityRepository - */ + class UsersOrganizationsRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) @@ -16,28 +14,18 @@ class UsersOrganizationsRepository extends ServiceEntityRepository parent::__construct($registry, UsersOrganizations::class); } - // /** - // * @return UsersOrganizations[] Returns an array of UsersOrganizations objects - // */ - // public function findByExampleField($value): array - // { - // return $this->createQueryBuilder('u') - // ->andWhere('u.exampleField = :val') - // ->setParameter('val', $value) - // ->orderBy('u.id', 'ASC') - // ->setMaxResults(10) - // ->getQuery() - // ->getResult() - // ; - // } + public function findAllDistinctOrganizationsByUserId(int $userId): array + { + return $this->createQueryBuilder('uo') + ->select('DISTINCT uo') + ->leftJoin('uo.organization', 'o') + ->leftJoin('uo.role', 'r') + ->addSelect('o', 'r') + ->where('uo.users = :userId', 'uo.isActive = :isActive') + ->setParameter('userId', $userId) + ->setParameter('isActive', true) + ->getQuery() + ->getResult(); + } - // public function findOneBySomeField($value): ?UsersOrganizations - // { - // return $this->createQueryBuilder('u') - // ->andWhere('u.exampleField = :val') - // ->setParameter('val', $value) - // ->getQuery() - // ->getOneOrNullResult() - // ; - // } } diff --git a/src/Service/UserOrganizationService.php b/src/Service/UserOrganizationService.php new file mode 100644 index 0000000..f927ee8 --- /dev/null +++ b/src/Service/UserOrganizationService.php @@ -0,0 +1,103 @@ + + */ + public function getUserOrganizations(User $user): array + { + $userOrganizations = $this->entityManager + ->getRepository(UsersOrganizations::class) + ->findAllDistinctOrganizationsByUserId($user->getId()); + $organizations = []; + + foreach ($userOrganizations as $uo) { + $orgId = $uo->getOrganization()->getId(); + + // Initialize the organization entry if it doesn't exist + $organizations[$orgId] = $organizations[$orgId] ?? $this->createEmptyOrganizationBucket($uo); + + // Aggregate roles & apps + $this->addRole($organizations[$orgId]['roles'], $uo->getRole()); + $this->addApps($organizations[$orgId]['apps'], $uo->getApps()); + } + + $this->normalizeAppsIndexes($organizations); + + return array_values($organizations); + } + + /** + * Build the initial array structure for a fresh organization entry. + * + * @param UsersOrganizations $link + * @return array{organization:object, roles:Roles[], apps:array} + */ + private function createEmptyOrganizationBucket(UsersOrganizations $link): array + { + return [ + 'organization' => $link->getOrganization(), + 'roles' => [], + 'apps' => [], + ]; + } + + /** + * Add a Role entity to the roles array only if it is not already present (by ID). + * + * @param Roles[] &$roles + * @param Roles|null $role + */ + private function addRole(array &$roles, ?Roles $role): void + { + if ($role === null) { + return; + } + foreach ($roles as $existingRole) { + if ($existingRole->getId() === $role->getId()) { + return; // Already present + } + } + $roles[] = $role; + } + + /** + * Merge one or many apps into the apps map, keeping only one entry per id. + * + * @param array &$apps + * @param iterable $appsToAdd Collection returned by $userOrganizations->getApps() + */ + private function addApps(array &$apps, iterable $appsToAdd): void + { + foreach ($appsToAdd as $app) { + $apps[$app->getId()] = $apps[$app->getId()] ?? $app; + } + } + + /** + * Convert apps from associative maps (keyed by id) to plain indexed arrays, + * so the final output is clean JSON-able. + * + * @param array &$organizations + */ + private function normalizeAppsIndexes(array &$organizations): void + { + foreach ($organizations as &$org) { + $org['apps'] = array_values($org['apps']); + } + } +} diff --git a/templates/elements/menu.html.twig b/templates/elements/menu.html.twig index dfcdf7a..8192e32 100644 --- a/templates/elements/menu.html.twig +++ b/templates/elements/menu.html.twig @@ -22,11 +22,14 @@ +{# if user is Super Admin#} + {% if is_granted('ROLE_SUDALYS_ADMIN') %} + {% endif %} \ No newline at end of file diff --git a/templates/elements/userInformation.html.twig b/templates/elements/userInformation.html.twig new file mode 100644 index 0000000..8dbe968 --- /dev/null +++ b/templates/elements/userInformation.html.twig @@ -0,0 +1,15 @@ +{% block body %} + +
+
+

{{ user.surname|capitalize }} {{ user.name|capitalize }}

+
+
+

Email: {{ user.email }}

+

Dernière connection: {{ user.lastConnection|date('d/m/Y') }} à {{ user.lastConnection|date('H:m:s') }}

+

Compte crée le: {{ user.createdAt|date('d/m/Y') }}

+

Numéro de téléphone: {{ user.phoneNumber ? user.phoneNumber : 'Non renseigné' }}

+
+
+ +{% endblock %} \ No newline at end of file diff --git a/templates/elements/userOrganizationInformation.html.twig b/templates/elements/userOrganizationInformation.html.twig new file mode 100644 index 0000000..9d90d09 --- /dev/null +++ b/templates/elements/userOrganizationInformation.html.twig @@ -0,0 +1,85 @@ +{% block body %} + + + + +{% endblock %} + +{% block javascript %} + +{% endblock %} \ No newline at end of file diff --git a/templates/user/index.html.twig b/templates/user/index.html.twig new file mode 100644 index 0000000..ea39181 --- /dev/null +++ b/templates/user/index.html.twig @@ -0,0 +1,48 @@ +{% extends 'base.html.twig' %} + +{% block title %}User Profile{% endblock %} + +{% block body %} +
+
+

Gestion Utilisateurs

+ Ajouter un utilisateur +
+ + + + + + + + + + + + + + {% for user in users %} + + + + + + + + {% endfor %} + {% if users|length == 0 %} + + + + {% endif %} + +
PictureSurnameNameEmailVisualiser
{{ user.surname }}{{ user.name }}{{ user.email }} + + + {{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }} + +
Aucun utilisateur trouvé.
+
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/user/profile.html.twig b/templates/user/profile.html.twig new file mode 100644 index 0000000..3a8d9a1 --- /dev/null +++ b/templates/user/profile.html.twig @@ -0,0 +1,33 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+

Gestion Utilisateur

+ Désactiver +
+ {% include 'elements/userInformation.html.twig' %} + +

Organisations

+
+ {% if userOrganizations is empty %} +
+

Aucune organisation associée à cet utilisateur.

+
+ {% else %} + {% for organization in userOrganizations %} + {% include 'elements/userOrganizationInformation.html.twig' + with {'organization': organization.organization, 'roles': organization.roles, 'apps': organization.apps} %} + {% endfor %} + {% endif %} +
+ + +
+{% endblock %} + +{% block title %} + +{% endblock %} +