organization dashboard User management

This commit is contained in:
Charles 2025-07-28 15:19:59 +02:00
parent e17e8e0eb2
commit 05d8ca0499
6 changed files with 212 additions and 11 deletions

View File

@ -3,6 +3,7 @@
namespace App\Controller;
use App\Entity\UsersOrganizations;
use App\Service\OrganizationsService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
@ -16,7 +17,8 @@ class OrganizationController extends AbstractController
private const NOT_FOUND = 'Entity not found';
private const ACCESS_DENIED = 'Access denied';
public function __construct(private readonly EntityManagerInterface $entityManager)
public function __construct(private readonly EntityManagerInterface $entityManager,
private readonly OrganizationsService $organizationsService)
{
}
@ -48,14 +50,40 @@ class OrganizationController extends AbstractController
#[Route(path: '/{id}', name: 'show', methods: ['GET'])]
public function show(int $id): Response
{
$organization = $this->entityManager->getRepository(Organizations::class)->find($id);
if (!$this->isGranted('ROLE_ADMIN')) {
$user = $this->getUser();
if (!$user) {
return $this->redirectToRoute('app_login');
}
$userIdentifier = $user->getUserIdentifier();
$organization = $this->entityManager->getRepository(UsersOrganizations::class)->findOneBy([
'userEmail' => $userIdentifier,
'organization' => $id,
'roleName' => 'ADMIN'
]);
if (!$organization) {
throw $this->createNotFoundException(self::ACCESS_DENIED);
}
}
$organization = $this->entityManager->getRepository(Organizations::class)->find($id);
if (!$organization) {
throw $this->createNotFoundException(self::NOT_FOUND);
}
$newUsers = $this->entityManager->getRepository(UsersOrganizations::class)->getLastNewActiveUsersByOrganization($organization);
$adminUsers = $this->entityManager->getRepository(UsersOrganizations::class)->getAdminUsersByOrganization($organization);
// reusing the method to avoid code duplication even though it returns an array of UsersOrganizations
$org = $this->entityManager->getRepository(UsersOrganizations::class)->findActiveUsersByOrganizations([$organization]);
return $this->render('organization/show.html.twig', [
'organization' => $organization,
'adminUsers' => $adminUsers,
'newUsers' => $newUsers,
'org' => $org[0],
]);
}

View File

@ -193,4 +193,82 @@ class UsersOrganizationsRepository extends ServiceEntityRepository
}
return $userToOrgs;
}
/**
* Get the last 10 new active users for a specific organization.
* Users are ordered by creation date (most recent first).
*
* @param Organizations $organization
* @return array
*/
public function getLastNewActiveUsersByOrganization(Organizations $organization): array
{
$results = $this->createQueryBuilder('uo')
->select('u.id', 'u.surname', 'u.name', 'u.email', 'u.pictureUrl', 'u.isActive', 'uo.createdAt')
->innerJoin('uo.users', 'u')
->innerJoin('uo.organization', 'o')
->where('uo.isActive = :isActive')
->andWhere('u.isActive = :userIsActive')
->andWhere('o.isActive = :orgIsActive')
->andWhere('uo.organization = :organization')
->setParameter('isActive', true)
->setParameter('userIsActive', true)
->setParameter('orgIsActive', true)
->setParameter('organization', $organization)
->orderBy('uo.createdAt', 'DESC')
->setMaxResults(10)
->getQuery()
->getResult();
// Remove duplicates by user ID (in case user has multiple roles)
$uniqueUsers = [];
foreach ($results as $result) {
$userId = $result['id'];
if (!isset($uniqueUsers[$userId])) {
$uniqueUsers[$userId] = $result;
}
}
return array_values($uniqueUsers);
}
/**
* Get all active admin users for a specific organization.
* Returns users who have the 'ADMIN' role in the given organization.
*
* @param Organizations $organization
* @return array
*/
public function getAdminUsersByOrganization(Organizations $organization): array
{
$results = $this->createQueryBuilder('uo')
->select('u.id', 'u.surname', 'u.name', 'u.email', 'u.pictureUrl', 'u.isActive')
->innerJoin('uo.users', 'u')
->innerJoin('uo.organization', 'o')
->innerJoin('uo.role', 'r')
->where('uo.isActive = :isActive')
->andWhere('u.isActive = :userIsActive')
->andWhere('o.isActive = :orgIsActive')
->andWhere('uo.organization = :organization')
->andWhere('r.name = :roleName')
->setParameter('isActive', true)
->setParameter('userIsActive', true)
->setParameter('orgIsActive', true)
->setParameter('organization', $organization)
->setParameter('roleName', 'ADMIN')
->orderBy('u.surname', 'ASC')
->getQuery()
->getResult();
// Remove duplicates by user ID (in case user has multiple admin-related roles)
$uniqueUsers = [];
foreach ($results as $result) {
$userId = $result['id'];
if (!isset($uniqueUsers[$userId])) {
$uniqueUsers[$userId] = $result;
}
}
return array_values($uniqueUsers);
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Service;
use App\Entity\Organizations;
use App\Entity\Roles;
use App\Entity\UsersOrganizations;
use Doctrine\ORM\EntityManagerInterface;
class OrganizationsService
{
public function __construct(private readonly EntityManagerInterface $entityManager)
{
}
public function getAdminUsers(Organizations $organization): array{
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findBy(['name' => 'ADMIN']);
return $this->entityManager->getRepository(UsersOrganizations::class)->findBy(['organization'=>$organization,
'role' => $roleAdmin,
'isActive' => true]);
}
public function getNewUsers(Organizations $organization): array{
return $this->entityManager->getRepository(UsersOrganizations::class)->findBy(['organization'=>$organization,
'isActive' => true]);
}
}

View File

@ -1,10 +1,33 @@
{% extends 'base.html.twig' %}
{% block body %}
<div class="col-md-12 m-auto p-5">
<div class="col d-flex justify-content-between align-items-center ">
<h1 class="mb-4">{{ organization.name|title }} - Dashboard</h1>
{% if is_granted("ROLE_SUER_ADMIN") %}
{# <a href="{{ path('user_deactivate', {'id': user.id}) }}" class="btn btn-danger">Désactiver</a> #}
{% endif %}
</div>
<div class="row mb-4">
<div class="col-sm-6 mb-3 mb-sm-0">
{% include 'user/userListSmall.html.twig' with {
title: 'Nouveaux utilisateurs',
users: newUsers,
empty_message: 'Aucun nouvel utilisateur trouvé.'
} %}
</div>
<div class="col-sm-6 mb-3 mb-sm-0">
{% include 'user/userListSmall.html.twig' with {
title: 'Administrateurs',
users: adminUsers,
empty_message: 'Aucun administrateur trouvé.'
} %}
</div>
</div>
{% include 'user/userList.html.twig' with {
title: 'Mes utilisateurs',
} %}
</div>
{% endblock %}
{% block title %}
{{ organization.name|title }} - Dashboard
{% endblock %}

View File

@ -1,5 +1,8 @@
{% block body %}
{% if title is defined %}
<h3>{{ title }}</h3>
{% endif %}
<table class="table align-middle shadow">
<thead class="table-light shadow-sm">
<tr>
@ -7,10 +10,16 @@
<th>Surname</th>
<th>Name</th>
<th>Email</th>
{# <th>Statut</th>#}
<th>Visualiser</th>
</tr>
</thead>
<tbody>
{% if org.users|length == 0 %}
<tr>
<td colspan="5" class="text-center">Aucun utilisateur trouvé.</td>
</tr>
{% endif %}
{% for user in org.users %}
<tr>
<td>
@ -29,11 +38,7 @@
</td>
</tr>
{% endfor %}
{% if org.users|length == 0 %}
<tr>
<td colspan="5" class="text-center">Aucun utilisateur trouvé.</td>
</tr>
{% endif %}
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,39 @@
<div class="card card-bor">
<div class="card-title p-3 d-flex justify-content-between align-items-center ">
<h3>{{ title }}</h3>
</div>
<div class="card-body">
<table class="table align-middle table-borderless">
<thead class="table-light">
<tr>
<th>Picture</th>
<th>Email</th>
<th>Visualiser</th>
</tr>
</thead>
<tbody>
{% if users|length == 0 %}
<tr>
<td colspan="3" class="text-center">{{ empty_message }}</td>
</tr>
{% else %}
{% for user in users %}
<tr>
<td>
{% if user.pictureUrl %}
<img src="{{ asset(user.pictureUrl) }}" alt="User profile pic" class="rounded-circle" style="width:40px; height:40px;">
{% endif %}
</td>
<td>{{ user.email }}</td>
<td>
<a href="{{ path('user_show', {'id': user.id}) }}" class="p-3 align-middle color-primary">
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
</a>
</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>