change user view

This commit is contained in:
Charles 2026-02-10 16:02:15 +01:00
parent 709a9f44cb
commit db3a59b389
7 changed files with 165 additions and 220 deletions

View File

@ -4,10 +4,14 @@
--primary-blue-dark : #094754;
--black-font: #1D1E1C;
--delete : #E42E31;
--delete-dark : #aa1618;
--disable : #A3A3A3;
--check : #80F20E;
--check : #5cae09;
--check-dark: #3a6e05;
--secondary : #cc664c;
--secondary-dark : #a5543d;
--warning : #d2b200;
--warning-dark: #c4a600;
}
html {
@ -103,12 +107,41 @@ body {
border: var(--primary-blue-light);
}
.btn-success{
background: var(--check);
color : #FFFFFF;
border: var(--check-dark);
border-radius: 1rem;
}
.btn-success:hover{
background: var(--check-dark);
color : #FFFFFF;
border: var(--check);
}
.btn-warning{
background: var(--warning);
color : #FFFFFF;
border: var(--warning-dark);
border-radius: 1rem;
}
.btn-warning:hover{
background: var(--warning-dark);
color : #FFFFFF;
border: var(--warning);
}
.btn-danger{
background: var(--delete);
color : #FFFFFF;
border: var(--delete);
border: var(--delete-dark);
border-radius: 1rem;
}
.btn-danger:hover{
background: var(--delete-dark);
color : #FFFFFF;
border: var(--delete);
}
.color-primary{
color: var(--primary-blue-light) !important;

View File

@ -194,7 +194,7 @@ class OrganizationController extends AbstractController
return $this->redirectToRoute('organization_index');
}
//check if the user is admin of the organization
if (!$this->userService->isAdminOfOrganization($organization) && !$this->isGranted("ROLE_SUPER_ADMIN")) {
if (!$this->userService->isAdminOfOrganization($organization) && !$this->isGranted("ROLE_ADMIN")) {
$this->loggerService->logAccessDenied($actingUser->getId());
$this->addFlash('error', 'Erreur, accès refusé.');
throw new AccessDeniedHttpException('Access denied');

View File

@ -62,6 +62,15 @@ class UserController extends AbstractController
{
}
#[Route(path: '/', name: 'index', methods: ['GET'])]
public function index(): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$totalUsers = $this->userRepository->count(['isDeleted' => false, 'isActive' => true]);
return $this->render('user/index.html.twig', [
'users' => $totalUsers
]);
}
#[Route('/view/{id}', name: 'show', methods: ['GET'])]
public function view(int $id, Request $request): Response
@ -72,109 +81,27 @@ class UserController extends AbstractController
// Utilisateur courant (acting user) via UserService
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
// Vérification des droits d'accès supplémentaires
// Chargement de l'utilisateur cible à afficher
$user = $this->userRepository->find($id);
if (!$user) {
$this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getId());
$this->addFlash('error', "L'utilisateur demandé n'existe pas.");
$this->addFlash('danger', "L'utilisateur demandé n'existe pas.");
throw $this->createNotFoundException(self::NOT_FOUND);
}
//if hasAccessTo is false, turn to true and denie access
if (!$this->userService->hasAccessTo($user)) {
$this->loggerService->logAccessDenied($actingUser->getId());
$this->addFlash('error', "L'utilisateur demandé n'existe pas.");
$this->addFlash('danger', "Vous n'avez pas accès à cette information.");
throw new AccessDeniedHttpException (self::ACCESS_DENIED);
}
try {
// Paramètre optionnel de contexte organisationnel
$orgId = $request->query->get('organizationId');
// Liste de toutes les applications (pour créer des groupes même si vides)
$apps = $this->appsRepository->findAll();
// Initialisations pour la résolution des UsersOrganizations (UO)
$singleUo = null;
$uoActive = null;
// get uo or uoS based on orgId
if ($orgId) {
// Contexte organisation précis : récupérer l'organisation et les liens UO
$organization = $this->organizationRepository->findBy(['id' => $orgId]);
$uoList = $this->uoRepository->findBy([
'users' => $user,
'organization' => $organization,
'isActive' => true,
]);
if (!$uoList) {
$this->loggerService->logEntityNotFound('UsersOrganization', [
'user_id' => $user->getId(),
'organization_id' => $orgId],
$actingUser->getId());
$this->addFlash('error', "L'utilisateur n'est pas actif dans cette organisation.");
throw $this->createNotFoundException(self::NOT_FOUND);
}
// Si contexte org donné, on retient la première UO (singleUo)
$singleUo = $uoList[0];
$data["singleUo"] = $singleUo;
$uoActive = $singleUo->isActive();
// TODO: afficher les projets de l'organisation
} else {
// Pas de contexte org : récupérer toutes les UO actives de l'utilisateur
$uoList = $this->uoRepository->findBy([
'users' => $user,
'isActive' => true,
]);
if (!$uoList) {
$data['rolesArray'] = $this->userService->getRolesArrayForUser($actingUser, true);
return $this->render('user/show.html.twig', [
'user' => $user,
'organizationId' => $orgId ?? null,
'uoActive' => $uoActive ?? null,
'apps' => $apps ?? [],
'data' => $data ?? [],
'canEdit' => false,
]);
// Afficher tous les projets de l'utilisateur
}
}
// Charger les liens UserOrganizationApp (UOA) actifs pour les UO trouvées
// Load user-organization-app roles (can be empty)
$uoa = $this->entityManager
->getRepository(UserOrganizatonApp::class)
->findBy([
'userOrganization' => $uoList,
'isActive' => true,
]);
// Group UOA by app and ensure every app has a group
$data['uoas'] = $this->userOrganizationAppService
->groupUserOrganizationAppsByApplication(
$uoa,
$apps,
$singleUo ? $singleUo->getId() : null
);
//Build roles based on user permissions.
//Admin can't see or edit a super admin user
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$data['rolesArray'] = $this->rolesRepository->findAll();
} elseif (!$orgId) {
$data['rolesArray'] = $this->userService->getRolesArrayForUser($actingUser, true);
} else {
$data['rolesArray'] = $this->userService->getRolesArrayForUser($actingUser);
}
// -------------------------------------------------------------------
// Calcul du flag de modification : utilisateur admin ET exactement 1 UO
if (empty($uoa) || !$orgId){
$canEdit = false;
}else{
$canEdit = $this->userService->canEditRolesCheck($actingUser, $user, $this->isGranted('ROLE_ADMIN'), $singleUo, $organization);
}
} catch (\Exception $e) {
$this->loggerService->logError('error while loading user information', [
'target_user_id' => $id,
@ -188,10 +115,6 @@ class UserController extends AbstractController
return $this->render('user/show.html.twig', [
'user' => $user,
'organizationId' => $orgId ?? null,
'uoActive' => $uoActive ?? null,
'apps' => $apps ?? [],
'data' => $data ?? [],
'canEdit' => $canEdit ?? false,
]);
}
@ -404,7 +327,10 @@ class UserController extends AbstractController
}
}
/**
* Endpoint to activate/deactivate a user (soft delete)
* If deactivating, also deactivate all org links and revoke tokens
*/
#[Route('/activeStatus/{id}', name: 'active_status', methods: ['GET', 'POST'])]
public function activeStatus(int $id, Request $request): JsonResponse
{
@ -762,23 +688,6 @@ class UserController extends AbstractController
]);
}
#[Route(path: '/', name: 'index', methods: ['GET'])]
public function index(): Response
{
$this->isGranted('ROLE_SUPER_ADMIN');
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
if ($this->userService->hasAccessTo($actingUser, true) && $this->isGranted("ROLE_ADMIN")) {
$totalUsers = $this->userRepository->count(['isDeleted' => false, 'isActive' => true]);
return $this->render('user/index.html.twig', [
'users' => $totalUsers
]);
}
//shouldn't be reached normally
$this->loggerService->logAccessDenied($actingUser->getId());
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
}
/*
* AJAX endpoint for new users listing
* Get the 5 most recently created users for an organization

View File

@ -3,7 +3,7 @@
{% set current_route = app.request.attributes.get('_route') %}
<ul class="nav">
{% if is_granted("ROLE_SUPER_ADMIN") %}
{% if is_granted("ROLE_ADMIN") %}
{# 2. Check if route is 'app_index' #}
<li class="nav-item {{ current_route == 'app_index' ? 'active' : '' }}">
<a class="nav-link" href="{{ path('app_index') }}">
@ -21,7 +21,7 @@
</a>
</li>
{% if is_granted('ROLE_SUPER_ADMIN') %}
{% if is_granted('ROLE_ADMIN') %}
<li class="nav-item {{ current_route starts with 'user_' ? 'active' : '' }}">
<a class="nav-link" href="{{ path('user_index') }}">
<i class="icon-grid menu-icon">{{ ux_icon('fa6-regular:circle-user', {height: '15px', width: '15px'}) }}</i>

View File

@ -3,7 +3,7 @@
{% block title %}User Profile{% endblock %}
{% block body %}
{% if is_granted('ROLE_SUPER_ADMIN') %}
{% if is_granted('ROLE_ADMIN') %}
<div class="w-100 h-100 p-5 m-auto">
{% for type, messages in app.flashes %}
{% for message in messages %}
@ -40,7 +40,7 @@
{% else %}
<div class="w-100 h-100 p-5 m-auto">
<div class="alert alert-warning">
<div class="alert alert-danger">
<h4>Accès limité</h4>
<p>Vous n'avez pas les permissions nécessaires pour voir la liste des utilisateurs.</p>
</div>

View File

@ -11,20 +11,24 @@
</div>
{% endfor %}
{% endfor %}
{% if is_granted("ROLE_ADMIN") %}
<div class="card-header border-0 d-flex justify-content-between align-items-center ">
<div class="card-title">
<h1>Gestion Utilisateur</h1>
</div>
<div class="d-flex gap-2">
{% if is_granted("ROLE_SUPER_ADMIN") %}
{% if is_granted("ROLE_ADMIN") %}
{% if user.active %}
<a href="{{ path('user_active_status', {'id': user.id, 'status':'deactivate'}) }}"
class="btn btn-secondary">Désactiver</a>
{% else %}
<a href="{{ path('user_active_status', {'id': user.id, 'status':'activate'}) }}"
class="btn btn-success">Réactiver</a>
{% endif %}
<a href="{{ path('user_delete', {'id': user.id}) }}"
class="btn btn-secondary">Supprimer</a>
class="btn btn-warning">Supprimer</a>
{% endif %}
</div>
</div>
{% endif %}
<div class="card-body">
@ -34,102 +38,106 @@
<div class="card border-0 no-header-bg ">
<div class="card-header">
<div class="card-title">
<h1>Vos applications</h1>
<h1>Information d'organisation</h1>
</div>
</div>
<div class="card-body ms-4">
{# TODO: dynamic number of project#}
<p><b>Projet : </b>69 projets vous sont attribués</p>
</div>
<div class="card-body">
<div class="row g-2">
{% for app in apps %}
<div class="col-12 col-md-6">
<div class="card h-100">
<div class="card-header d-flex gap-2">
{% if app.logoMiniUrl %}
<img src="{{ asset(application.entity.logoMiniUrl) }}" alt="Logo {{ app.name }}"
class="rounded-circle" style="width:50px; height:50px;">
{% endif %}
<div class="card-title">
<h1>{{ app.name|title }}</h1>
</div>
</div>
{# <div class="card-body">#}
{# <div class="row g-2">#}
{# {% for app in apps %}#}
{# <div class="col-12 col-md-6">#}
{# <div class="card h-100">#}
{# <div class="card-header d-flex gap-2">#}
{# {% if app.logoMiniUrl %}#}
{# <img src="{{ asset(appli.entity.logoMiniUrl) }}" alt="Logo {{ app.name }}"#}
{# class="rounded-circle" style="width:50px; height:50px;">#}
{# {% endif %}#}
{# <div class="card-title">#}
{# <h1>{{ app.name|title }}</h1>#}
{# </div>#}
{# </div>#}
<div class="card-body">
<div class="row">
<p>
<b>Description :</b>
{{ app.descriptionSmall|default('Aucune description disponible.')|raw }}
</p>
</div>
{# <div class="card-body">#}
{# <div class="row">#}
{# <p>#}
{# <b>Description :</b>#}
{# {{ app.descriptionSmall|default('Aucune description disponible.')|raw }}#}
{# </p>#}
{# </div>#}
{# find appGroup once, used in both editable and read-only branches #}
{% set appGroup = data.uoas[app.id]|default(null) %}
{# #}{# find appGroup once, used in both editable and read-only branches #}
{# {% set appGroup = data.uoas[app.id]|default(null) %}#}
{% if canEdit %}
<form method="POST"
action="{{ path('user_application_role', { id: data.singleUo.id }) }}">
<div class="form-group mb-3">
<label for="roles-{{ app.id }}"><b>Rôles :</b></label>
<div class="form-check">
{% if appGroup %}
{% for role in data.rolesArray %}
<input class="form-check-input" type="checkbox"
name="roles[]"
value="{{ role.id }}"
id="role-{{ role.id }}-app-{{ app.id }}"
{% if role.id in appGroup.selectedRoleIds %}checked{% endif %}>
<label class="form-check"
for="role-{{ role.id }}-app-{{ app.id }}">
{% if role.name == 'USER' %}
Accès
{% else %}
{{ role.name|capitalize }}
{% endif %}
</label>
{% endfor %}
{% else %}
<p class="text-muted">Aucun rôle défini pour cette application.</p>
{% endif %}
</div>
<button type="submit" name="appId" value="{{ app.id }}"
class="btn btn-primary mt-2">
Sauvegarder
</button>
</div>
</form>
{% else %}
<div class="form-group mb-3">
<label for="roles-{{ app.id }}"><b>Rôles :</b></label>
<div class="form-check">
{% if appGroup %}
{% for role in data.rolesArray %}
<input class="form-check-input" type="checkbox"
disabled
name="roles[]"
value="{{ role.id }}"
id="role-{{ role.id }}-app-{{ app.id }}"
{% if role.id in appGroup.selectedRoleIds %}checked{% endif %}>
<label class="form-check"
for="role-{{ role.id }}-app-{{ app.id }}">
{% if role.name == 'USER' %}
Accès
{% else %}
{{ role.name|capitalize }}
{% endif %}
</label>
{% endfor %}
{% else %}
<p class="text-muted">Aucun rôle défini pour cette application.</p>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{# {% if canEdit %}#}
{# <form method="POST"#}
{# action="{{ path('user_application_role', { id: data.singleUo.id }) }}">#}
{# <div class="form-group mb-3">#}
{# <label for="roles-{{ app.id }}"><b>Rôles :</b></label>#}
{# <div class="form-check">#}
{# {% if appGroup %}#}
{# {% for role in data.rolesArray %}#}
{# <input class="form-check-input" type="checkbox"#}
{# name="roles[]"#}
{# value="{{ role.id }}"#}
{# id="role-{{ role.id }}-app-{{ app.id }}"#}
{# {% if role.id in appGroup.selectedRoleIds %}checked{% endif %}>#}
{# <label class="form-check"#}
{# for="role-{{ role.id }}-app-{{ app.id }}">#}
{# {% if role.name == 'USER' %}#}
{# Accès#}
{# {% else %}#}
{# {{ role.name|capitalize }}#}
{# {% endif %}#}
{# </label>#}
{# {% endfor %}#}
{# {% else %}#}
{# <p class="text-muted">Aucun rôle défini pour cette application.</p>#}
{# {% endif %}#}
{# </div>#}
{# <button type="submit" name="appId" value="{{ app.id }}"#}
{# class="btn btn-primary mt-2">#}
{# Sauvegarder#}
{# </button>#}
{# </div>#}
{# </form>#}
{# {% else %}#}
{# <div class="form-group mb-3">#}
{# <label for="roles-{{ app.id }}"><b>Rôles :</b></label>#}
{# <div class="form-check">#}
{# {% if appGroup %}#}
{# {% for role in data.rolesArray %}#}
{# <input class="form-check-input" type="checkbox"#}
{# disabled#}
{# name="roles[]"#}
{# value="{{ role.id }}"#}
{# id="role-{{ role.id }}-app-{{ app.id }}"#}
{# {% if role.id in appGroup.selectedRoleIds %}checked{% endif %}>#}
{# <label class="form-check"#}
{# for="role-{{ role.id }}-app-{{ app.id }}">#}
{# {% if role.name == 'USER' %}#}
{# Accès#}
{# {% else %}#}
{# {{ role.name|capitalize }}#}
{# {% endif %}#}
{# </label>#}
{# {% endfor %}#}
{# {% else %}#}
{# <p class="text-muted">Aucun rôle défini pour cette application.</p>#}
{# {% endif %}#}
{# </div>#}
{# </div>#}
{# {% endif %}#}
{# </div>#}
{# </div>#}
{# </div>#}
{# {% endfor %}#}
{# </div>#}
{# </div>#}
</div>

View File

@ -12,18 +12,13 @@
</div>
<div class="d-flex gap-2">
{% if canEdit %}
<a href="{{ path('user_edit', {'id': user.id, 'organizationId': organizationId}) }}"
class="btn btn-primary">Modifier</a>
{% elseif user.id == app.user.id or is_granted("ROLE_SUPER_ADMIN") %}
<a href="{{ path('user_edit', {'id': user.id}) }}"
class="btn btn-primary">Modifier</a>
{% endif %}
</div>
</div>
<div class="card-body ">
<div class="card-body ms-4">
<p><b>Email: </b>{{ user.email }}</p>
<p><b>Dernière connection: </b>{{ user.lastConnection|date('d/m/Y') }}
à {{ user.lastConnection|date('H:m:s') }} </p>