L'administration de Save France est construite avec EasyAdmin 4, un générateur d'administration moderne pour les applications Symfony.
graph TD
A[Tableau de bord] --> B[Demandes]
A --> C[Utilisateurs]
A --> D[Équipements]
A --> E[Offres]
A --> F[Dépannages]
B --> G[Créer/Modifier/Supprimer]
C --> H[Gérer les rôles]
D --> I[Suivi du parc]
L'interface d'administration est accessible à l'adresse :
/admin
ROLE_ADMIN : Accès completROLE_MANAGER : Gestion des demandes et équipementsROLE_TECH : Vue technique (dépannages, maintenances)Le tableau de bord fournit une vue d'ensemble avec :
Le CRUD des types d'équipements permet de gérer les types (Mural, Console, Gainable, etc.) avec notamment :
pac_air_air, pac_air_eau). Permet de regrouper plusieurs catégories dans une même section sur le front (ex. Air/Air hors gainable + Gainable → section « PAC Air/Air »). Optionnel ; si non renseigné, le front utilise la catégorie.// src/Controller/Admin/RequestCrudController.php
namespace App\Controller\Admin;
use App\Entity\Request;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
class RequestCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Request::class;
}
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id')->hideOnForm(),
TextField::new('reference'),
TextField::new('status'),
AssociationField::new('user'),
AssociationField::new('equipment'),
];
}
}
// src/Filter/RequestStatusFilter.php
namespace App\Filter;
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Filter\FilterInterface;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDataDto;
use EasyCorp\Bundle\EasyAdminBundle\Filter\FilterTrait;
class RequestStatusFilter implements FilterInterface
{
use FilterTrait;
public static function new(string $propertyName, $label = null): self
{
return (new self())
->setFilterFqcn(__CLASS__)
->setProperty($propertyName)
->setLabel($label);
}
public function apply(
QueryBuilder $queryBuilder,
FilterDataDto $filterDataDto,
?FieldDto $fieldDto,
EntityDto $entityDto
): void {
$queryBuilder
->andWhere(sprintf('%s.status = :status', $filterDataDto->getEntityAlias()))
->setParameter('status', $filterDataDto->getValue());
}
}
// src/Controller/Admin/CustomDashboardController.php
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class CustomDashboardController extends AbstractDashboardController
{
#[Route('/admin/custom', name: 'admin_custom')]
public function index(): Response
{
$adminUrlGenerator = $this->container->get(AdminUrlGenerator::class);
return $this->render('admin/custom_dashboard.html.twig', [
'requestsUrl' => $adminUrlGenerator->setController(RequestCrudController::class)->generateUrl(),
'usersUrl' => $adminUrlGenerator->setController(UserCrudController::class)->generateUrl(),
]);
}
}
{# templates/admin/custom_dashboard.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title %}Tableau de bord personnalisé{% endblock %}
{% block main %}
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">Statistiques des demandes</div>
<div class="card-body">
<canvas id="requestsChart"></canvas>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ctx = document.getElementById('requestsChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Nouvelles', 'En cours', 'Résolues'],
datasets: [{
label: 'Demandes par statut',
data: [12, 19, 3],
backgroundColor: ['#3498db', '#f1c40f', '#2ecc71']
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
}
}
});
</script>
{% endblock %}
security.yaml// src/Security/Voter/AdminVoter.php
namespace App\Security\Voter;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class AdminVoter extends Voter
{
protected function supports(string $attribute, $subject): bool
{
return in_array($attribute, ['ADMIN_ACCESS', 'MANAGER_ACCESS']);
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
switch ($attribute) {
case 'ADMIN_ACCESS':
return $this->canAccessAdmin($user);
case 'MANAGER_ACCESS':
return $this->canAccessManager($user);
}
return false;
}
private function canAccessAdmin(User $user): bool
{
return in_array('ROLE_ADMIN', $user->getRoles());
}
private function canAccessManager(User $user): bool
{
return in_array('ROLE_MANAGER', $user->getRoles()) || $this->canAccessAdmin($user);
}
}
# config/packages/security.yaml
security:
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/admin/requests, roles: ROLE_MANAGER }
- { path: ^/admin/profile, roles: IS_AUTHENTICATED_FULLY }
// assets/styles/admin.scss
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--danger-color: #e74c3c;
}
/* Surcharger les styles d'EasyAdmin */
.sidebar {
background-color: #2c3e50;
}
/* Ajouter des styles personnalisés */
.custom-badge {
padding: 0.35em 0.65em;
border-radius: 50rem;
font-weight: 600;
}
.badge-new { @extend .custom-badge; background-color: #3498db; color: white; }
.badge-in-progress { @extend .custom-badge; background-color: #f1c40f; color: #000; }
.badge-resolved { @extend .custom-badge; background-color: #2ecc71; color: white; }
yarn encore dev
# config/packages/easy_admin.yaml
easy_admin:
design:
assets:
css: ['build/admin.css']
js: ['build/admin.js']
L'administration EasyAdmin offre une interface complète pour gérer votre application. N'hésitez pas à la personnaliser selon vos besoins spécifiques et à explorer la documentation officielle pour des fonctionnalités avancées.