Création d'organisation

This commit is contained in:
Charles 2025-08-06 11:15:53 +02:00
parent c55e9fa039
commit 7021b28163
12 changed files with 165 additions and 65 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -6,6 +6,7 @@
parameters:
aws_url: '%env(AWS_ENDPOINT)%'
aws_public_url: '%env(AWS_ENDPOINT)%'
logos_directory: '%kernel.project_dir%/public/uploads/logos'
services:
# default configuration for services in *this* file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -5,35 +5,36 @@ namespace App\Controller;
use App\Entity\Apps;
use App\Entity\Roles;
use App\Entity\UsersOrganizations;
use App\Form\OrganizationForm;
use App\Service\ActionService;
use App\Service\OrganizationsService;
use App\Service\UserOrganizationService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use App\Entity\Organizations;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Service\Attribute\Required;
#[Route(path: '/organization', name: 'organization_')]
class OrganizationController extends AbstractController
{
private const NOT_FOUND = 'Entity not found';
private const ACCESS_DENIED = 'Access denied';
public function __construct(private readonly EntityManagerInterface $entityManager,
private readonly OrganizationsService $organizationsService,
private readonly UserOrganizationService $usersOrganizationService)
public function __construct(private readonly EntityManagerInterface $entityManager,
private readonly OrganizationsService $organizationsService,
private readonly UserOrganizationService $usersOrganizationService)
{
}
#[Route(path: '/' , name: 'index', methods: ['GET'])]
public function index():Response
#[Route('/', name: 'index', methods: ['GET'])]
public function index(): Response
{
if($this->isGranted('ROLE_SUPER_ADMIN'))
{
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$organizations = $this->entityManager->getRepository(Organizations::class)->findBy(['isActive' => true]);
} else{
} else {
$user = $this->getUser();
if (!$user) {
return $this->redirectToRoute('app_login');
@ -41,7 +42,7 @@ class OrganizationController extends AbstractController
$userIdentifier = $user->getUserIdentifier();
$organizations = $this->entityManager->getRepository(UsersOrganizations::class)->findOrganizationsByUserEmailAndRoleName($userIdentifier, 'ADMIN');
if(!$organizations) {
if (!$organizations) {
// if user is not admin in any organization, throw access denied
throw $this->createNotFoundException(self::ACCESS_DENIED);
}
@ -52,7 +53,44 @@ class OrganizationController extends AbstractController
]);
}
#[Route(path: '/{id}', name: 'show', methods: ['GET'])]
#[Route('/new', name: 'new', methods: ['GET', 'POST'])]
public function new(Request $request): Response
{
if (!$this->isGranted('ROLE_SUPER_ADMIN')) {
throw $this->createNotFoundException(self::ACCESS_DENIED);
}
$form = $this->createForm(OrganizationForm::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$organization = $form->getData();
// dd($form);
$logoFile = $form->get('logoUrl')->getData();
if ($logoFile) {
$currentDate = (new \DateTime())->format('Y-m-d');
$organizationName = preg_replace('/[^a-zA-Z0-9]/', '_', $organization->getName());
$extension = $logoFile->guessExtension();
$newFilename = $currentDate . '_' . $organizationName . $extension;
// Move the file to the directory where logos are stored
$logoFile->move(
$this->getParameter('logos_directory'),
$newFilename
);
// Update the 'logoUrl' property to store the file name
$organization->setLogoUrl($newFilename);
}
$this->entityManager->persist($organization);
$this->entityManager->flush();
$this->addFlash('success', 'Organization created successfully');
return $this->redirectToRoute('organization_index');
}
return $this->render('organization/new.html.twig', [
'form' => $form->createView(),
]);
}
#[Route('/{id}', name: 'show',requirements: ['id' => '\d+'], methods: ['GET'])]
public function show(int $id, ActionService $actionService): Response
{
if ($this->isGranted('ROLE_ADMIN')) {
@ -60,15 +98,7 @@ class OrganizationController extends AbstractController
if (!$user) {
return $this->redirectToRoute('app_login');
}
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']);
$uo = $this->entityManager->getRepository(UsersOrganizations::class)->findOneBy([
'users' => $user,
'organization' => $id,
'role' => $roleAdmin
]);
if (!$uo) {
throw $this->createNotFoundException(self::ACCESS_DENIED);
}
//Don't care about the null pointer because if no UO found, it won't pass the previous check
$organization = $this->entityManager->getRepository(Organizations::class)->find($id);
$newUsers = $this->entityManager->getRepository(UsersOrganizations::class)->getLastNewActiveUsersByOrganization($organization);
@ -80,12 +110,12 @@ class OrganizationController extends AbstractController
$applications = $this->organizationsService->getApplicationsWithAccessStatus($organization);
$actions = $organization->getActions()->toArray();
usort($actions, static function($a, $b) {
usort($actions, static function ($a, $b) {
return $b->getDate() <=> $a->getDate();
});
//get the last 10 activities
$actions = array_slice($actions, 0, 10);
$activities = array_map(static function($activity) use ($actionService) {
$activities = array_map(static function ($activity) use ($actionService) {
return [
'date' => $activity->getDate(), // or however you access the date
'actionType' => $activity->getActionType(),
@ -93,7 +123,7 @@ class OrganizationController extends AbstractController
'color' => $actionService->getActivityColor($activity->getDate())
];
}, $actions);
}else{
} else {
throw $this->createNotFoundException(self::ACCESS_DENIED);
}
@ -101,10 +131,12 @@ class OrganizationController extends AbstractController
'organization' => $organization,
'adminUsers' => $adminUsers,
'newUsers' => $newUsers,
'org' => $org[0],
'org' => !empty($org) ? $org[0] : null,
'applications' => $applications,
'activities' => $activities
]);
}
}

View File

@ -31,10 +31,10 @@ class Organizations
private ?\DateTimeImmutable $createdAt = null;
#[ORM\Column(options: ['default' => false])]
private ?bool $isDeleted = null;
private ?bool $isDeleted = false;
#[ORM\Column(options: ['default' => true])]
private ?bool $isActive = null;
private ?bool $isActive = true;
/**
* @var Collection<int, Apps>
@ -55,6 +55,7 @@ class Organizations
{
$this->apps = new ArrayCollection();
$this->actions = new ArrayCollection();
$this->createdAt = new \DateTimeImmutable();
}
public function getId(): ?int

View File

@ -0,0 +1,36 @@
<?php
namespace App\Form;
use App\Entity\Organizations;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OrganizationForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email', EmailType::class, ['required' => true, 'label' => 'Email*'])
->add('name', TextType::class, ['required' => true, 'label' => 'Nom de l\'organisation*'])
->add('address', TextType::class, ['required' => false, 'label' => 'Adresse'])
->add('number', TextType::class, ['required' => false, 'label' => 'Numéro de téléphone'])
->add('logoUrl', FileType::class, [
'required' => false,
'label' => 'Logo',
'mapped' => true, // Important if the entity property is not directly mapped
'attr' => ['accept' => 'image/*'],
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Organizations::class,
]);
}
}

View File

@ -408,12 +408,12 @@ readonly class UserOrganizationService
*/
public function findActiveUsersByOrganizations(array $organizations): array
{
if (empty($organizations)) {
return [];
}
$userOrgs = $this->entityManager->getRepository(UsersOrganizations::class)->getAllActiveUserOrganizationLinks($organizations);
$usersByOrg = [];
foreach ($userOrgs as $uo) {
$org = $uo->getOrganization();

View File

@ -6,11 +6,14 @@
<div class="w-100 h-100 p-5 m-auto" data-controller="organization">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Gestion des organisations</h1>
{# <a href="{{ path('organization_new') }}" class="btn btn-primary">Ajouter une organisation</a>#}
<a href="{{ path('organization_new') }}" class="btn btn-primary">Ajouter une organisation</a>
</div>
{% if organizations|length == 0 %}
<tr>
<td colspan="4" class="text-center">Aucune organisation trouvée.</td>
<td colspan="4" class="text-center">
<a href="{{ path('organization_new') }}" class="btn btn-primary">Créer une organisation</a>
</td>
</tr>
{% else %}
<table class="table align-middle shadow">
@ -27,7 +30,7 @@
<tr>
<td>
{% if organization.logoUrl %}
<img src="{{ asset(organization.logoUrl) }}" alt="Organization logo" class="rounded-circle" style="width:40px; height:40px;">
<img src="{{ asset('uploads/logos/' ~ organization.logoUrl) }}" alt="Organization logo" class="rounded-circle" style="width:40px; height:40px;">
{% endif %}
</td>
<td>{{ organization.name }}</td>

View File

@ -0,0 +1,21 @@
{% extends 'base.html.twig' %}
{% block title %}Ajouter une organisation{% endblock %}
{% block body %}
<div class=" col-md-10 m-auto p-5">
<div class="card">
<div class="card-title shadow-sm p-3 d-flex justify-content-between align-items-center">
<h1>Ajouter une organisation</h1>
</div>
<div class="card-body">
<form method="post" action="{{ path('organization_new') }}" enctype="multipart/form-data">
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" class="btn btn-primary">Enregistrer</button>
{{ form_end(form) }}
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -31,7 +31,8 @@
<div class="m-auto">
{% include 'user/userList.html.twig' with {
title: 'Mes utilisateurs',
organizationId: organization.id
organizationId: organization.id,
empty_message: 'Aucun utilisateurs trouvé.'
} %}
</div>
{# APPLICATION ROW #}

View File

@ -22,45 +22,50 @@
</tr>
</thead>
<tbody>
{% if org.users|length == 0 %}
{% if org|length == 0 %}
<tr>
<td colspan="6" class="text-center">Aucun utilisateur trouvé.</td>
</tr>
{% elseif org.users|length == 0 %}
<tr>
<td colspan="6" class="text-center">Aucun utilisateur trouvé.</td>
</tr>
{% else %}
{% for user in org.users %}
<tr>
<td>
{% if user.users.pictureUrl %}
<img src="{{ asset(user.users.pictureUrl) }}" alt="User profile pic"
class="rounded-circle"
style="width:40px; height:40px;">
{% endif %}
</td>
<td>{{ user.users.surname }}</td>
<td>{{ user.users.name }}</td>
<td>{{ user.users.email }}</td>
<td>
{% if user.is_connected %}
<span class="badge bg-success">Actif</span>
{% else %}
<span class="badge bg-secondary">Inactif</span>
{% endif %}
</td>
<td>
{% if organizationId is defined %}
<a href="{{ path('user_show', {'id': user.users.id, 'organizationId': organizationId}) }}"
class="p-3 align-middle color-primary">
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
</a>
{% else %}
<a href="{{ path('user_show', {'id': user.users.id}) }}"
class="p-3 align-middle color-primary">
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
</a>
{% endif %}
</td>
</tr>
{% endfor %}
{% endif %}
{% for user in org.users %}
<tr>
<td>
{% if user.users.pictureUrl %}
<img src="{{ asset(user.users.pictureUrl) }}" alt="User profile pic"
class="rounded-circle"
style="width:40px; height:40px;">
{% endif %}
</td>
<td>{{ user.users.surname }}</td>
<td>{{ user.users.name }}</td>
<td>{{ user.users.email }}</td>
<td>
{% if user.is_connected %}
<span class="badge bg-success">Actif</span>
{% else %}
<span class="badge bg-secondary">Inactif</span>
{% endif %}
</td>
<td>
{% if organizationId is defined %}
<a href="{{ path('user_show', {'id': user.users.id, 'organizationId': organizationId}) }}"
class="p-3 align-middle color-primary">
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
</a>
{% else %}
<a href="{{ path('user_show', {'id': user.users.id}) }}"
class="p-3 align-middle color-primary">
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>