Save France
Backend

Administration avec EasyAdmin

Interface d'administration et gestion du backoffice

Vue d'ensemble

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]

Accès à l'administration

L'interface d'administration est accessible à l'adresse :

/admin

Rôles requis

  • ROLE_ADMIN : Accès complet
  • ROLE_MANAGER : Gestion des demandes et équipements
  • ROLE_TECH : Vue technique (dépannages, maintenances)

Tableau de bord

Le tableau de bord fournit une vue d'ensemble avec :

  • Statistiques des demandes par statut
  • Activité récente
  • Alertes et notifications
  • Accès rapide aux éléments importants

Gestion des demandes

Liste des demandes

  • Filtrage par statut, date, priorité
  • Recherche plein texte
  • Tri personnalisable
  • Export CSV/Excel

Création/Édition

  • Formulaire en plusieurs étapes
  • Validation en temps réel
  • Upload de pièces jointes
  • Historique des modifications

Gestion des utilisateurs

Création d'un utilisateur

  1. Cliquer sur "Utilisateurs"
  2. "Ajouter un utilisateur"
  3. Remplir le formulaire
  4. Définir le rôle
  5. Envoyer l'email d'activation

Actions disponibles

  • Réinitialisation de mot de passe
  • Désactivation de compte
  • Gestion des rôles
  • Historique des connexions

Gestion des équipements

Types d'équipements (EquipmentTypeCrudController)

Le CRUD des types d'équipements permet de gérer les types (Mural, Console, Gainable, etc.) avec notamment :

  • Catégorie : Association à une catégorie (PAC Air/Air hors gainable, Gainable, PAC Air/Eau).
  • Section : Champ choix (ChoiceField) pour la section d'affichage front (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.
  • Nom, label SAV, description, image, tarifs (heure, fournitures, TVA).

Fiche équipement

  • Informations techniques
  • Historique des interventions
  • Documents associés
  • Contrats de maintenance

Actions

  • Création/Modification
  • Association à un utilisateur
  • Planification de la maintenance
  • Génération de QR code

Personnalisation

Personnaliser un CRUD

// 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'),
        ];
    }
}

Filtres personnalisés

// 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());
    }
}

Bonnes pratiques

Sécurité

  • Limiter l'accès par rôle
  • Journaliser les actions sensibles
  • Valider toutes les entrées
  • Utiliser des requêtes paramétrées

Performance

  • Indexer les champs de recherche
  • Utiliser le chargement paresseux
  • Mettre en cache les données fréquemment utilisées
  • Optimiser les requêtes N+1

UX

  • Personnaliser les libellés
  • Ajouter des aides contextuelles
  • Gérer les erreurs élégamment
  • Fournir des retours utilisateur clairs

Personnalisation avancée

Créer un dashboard personnalisé

// 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(),
        ]);
    }
}

Intégrer des graphiques

{# 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 %}

Dépannage

Problèmes courants

  1. Accès refusé
    • Vérifiez les rôles de l'utilisateur
    • Vérifiez les permissions dans security.yaml
  2. Erreurs 500
    • Consultez les logs Symfony
    • Vérifiez les dépendances manquantes
  3. Performances lentes
    • Activez le profiler Symfony
    • Vérifiez les requêtes N+1
    • Optimisez les index de base de données

Extensions recommandées

  • EasyAdmin Profiler : Analyse des performances
  • EasyAdmin Plus : Fonctionnalités avancées
  • EasyAdmin Themes : Personnalisation de l'interface

Sécurité avancée

Vérificateur d'accès personnalisé

// 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);
    }
}

Configuration de sécurité

# 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 }

Personnalisation du thème

  1. Créez un fichier de thème personnalisé :
// 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; }
  1. Compilez les assets :
yarn encore dev
  1. Activez le thème dans la configuration :
# config/packages/easy_admin.yaml
easy_admin:
    design:
        assets:
            css: ['build/admin.css']
            js: ['build/admin.js']

Conclusion

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.