Shopping : l’administration
Nous voici arrivés dans la partie cachée de la boutique, celle de l’administration. Il y a des tas de façons de mettre en place une administration avec même des systèmes tout prêts. Mais quel que soit le système existant bien souvent on a plus de travail pour l’adapter à ses besoins que si on codait tout nous-même. C’est donc ce que je vous propose. Pour la structure de base j’ai opté pour AdminLTE. J’ai d’ailleurs écrit récemment un article pour montrer comment l’intégrer dans Laravel 7. Mais pour être complet je vais reprendre les différentes étapes de l’intégration, d’autant que je vais procéder différemment, et ajouter quelques helpers et un composant pour simplifier le codage.
Vous pouvez télécharger un ZIP du projet ici.
AdminLTE
On trouve AdminLTE sur ce site :
On va le télécharger avec le bouton DOWNLOAD. On se retrouve avec un fichier compressé et après décompression on obtient tout ça :
Au moment où j’écris cet article la dernière version est la 3.0.4.
Plutôt que d’utiliser les assets proposés on va plutôt charger les librairies avec des CDN, ce qui améliorera les performances (les serveurs sont performants et on travaille en parallèle).
On va par contre utiliser la page de démarrage starter.html.
On va prendre le code de la page starter.html, changer son nom pour layout.blade.php et ranger le fichier dans un dossier back des vues :
On va créer une route provisoire pour accéder au layout :
Route::view('admin', 'back.layout');
Pour le moment on a aucun style parce qu’on n’a pas les assets alors on va arranger ça avec des CDN :
... <!-- Font Awesome Icons --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css"> <!-- Theme style --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/admin-lte/3.0.4/css/adminlte.min.css"> ... <!-- jQuery --> <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> <!-- Bootstrap 4 --> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.bundle.min.js"></script> <!-- AdminLTE App --> <script src="https://cdnjs.cloudflare.com/ajax/libs/admin-lte/3.0.4/js/adminlte.min.js"></script>
Maintenant on a quelque chose de correct mis à part les images, mais ça on en aura pas besoin :
On va faire un grand ménage maintenant :
<!DOCTYPE html> <html lang="fr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="csrf-token" content="{{ csrf_token() }}" /> <title>Administration</title> <!-- Font Awesome Icons --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css"> <!-- Theme style --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/admin-lte/3.0.4/css/adminlte.min.css"> <!-- Google Font: Source Sans Pro --> <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet"> </head> <body class="hold-transition sidebar-mini"> <div class="wrapper"> <!-- Navbar --> <nav class="main-header navbar navbar-expand navbar-white navbar-light"> <!-- Left navbar links --> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a> </li> <li class="nav-item d-none d-sm-inline-block"> <a href="{{ route('home') }}" class="nav-link">Voir la boutique</a> </li> </ul> </nav> <!-- /.navbar --> <!-- Main Sidebar Container --> <aside class="main-sidebar sidebar-dark-primary elevation-4"> <!-- Sidebar --> <div class="sidebar"> <!-- Sidebar Menu --> <nav class="mt-2"> <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false"> </ul> </nav> <!-- /.sidebar-menu --> </div> <!-- /.sidebar --> </aside> <!-- Content Wrapper. Contains page content --> <div class="content-wrapper"> <!-- Content Header (Page header) --> <div class="content-header"> <div class="container-fluid"> <div class="row mb-2"> <div class="col-sm-12"> <h1 class="m-0 text-dark">Les titres ici</h1> </div><!-- /.col --> </div><!-- /.row --> </div><!-- /.container-fluid --> </div> <!-- /.content-header --> <!-- Main content --> <div class="content"> <div class="container-fluid"> @yield('main') <!-- /.row --> </div><!-- /.container-fluid --> </div> <!-- /.content --> </div> <!-- /.content-wrapper --> <!-- Main Footer --> <footer class="main-footer"> <!-- Default to the left --> <strong>Copyright ici.</strong> </footer> </div> <!-- ./wrapper --> <!-- REQUIRED SCRIPTS --> <!-- jQuery --> <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> <!-- Bootstrap 4 --> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.bundle.min.js"></script> <!-- AdminLTE App --> <script src="https://cdnjs.cloudflare.com/ajax/libs/admin-lte/3.0.4/js/adminlte.min.js"></script> </body> </html>
Bon il ne reste plus grand chose !
Un composant pour le menu latéral
Comme le code va être répétitif on va créer un composant pour les items du menu latéral :
php artisan make:component MenuItem
<?php namespace App\View\Components; use Illuminate\View\Component; class MenuItem extends Component { public $sub; public $subsub; public $href; public $icon; public $active; /** * Create a new component instance. * * @return void */ public function __construct($href, $active, $icon = false, $sub = false, $subsub = false) { $this->sub = $sub; $this->href = $href; $this->icon = $icon; $this->active = $active; $this->subsub = $subsub; } /** * Get the view / contents that represent the component. * * @return \Illuminate\View\View|string */ public function render() { return view('components.menu-item'); } }
On a eu aussi création de la vue :
<li class="nav-item"> <a href="{{ $href }}" class="nav-link @if($active) active @endif"> <i class=" @if($sub) far fa-circle @elseif($subsub) far fa-dot-circle @endif nav-icon @if($icon) fas fa-{{ $icon }} @endif "></i> <p>{{ $slot }}</p> </a> </li>
Des helpers
Pour simplifier le codage on va aussi créer des helpers. On crée un fichier pour ces helpers :
On en crée deux :
<?php if (!function_exists('menuOpen')) { function menuOpen(...$routes) { foreach ($routes as $route) { if(Route::currentRouteName() === $route) return 'menu-open'; } } } if (!function_exists('currentRouteActive')) { function currentRouteActive(...$routes) { foreach ($routes as $route) { if(Route::currentRouteName() === $route) return 'active'; } } }
Mais pour que ces helpers soient connus il faut déclarer le fichier dans composer.json :
"autoload": { ... "files": [ "app/helpers.php" ], ... },
Pour raffraîchir l’autoload utilisez cette commande :
composer dumpautoload
On pourra ainsi facilement coder le menu latéral.
Un middleware
La partie administration ne devra être accessible qu’aux administrateurs. On crée un middleware :
php artisan make:middleware Admin
<?php namespace App\Http\Middleware; use Closure; class Admin { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $user = $request->user(); if ($user && $user->admin) { return $next($request); } return redirect()->route('home'); } }
On le déclare dans le Kernel :
protected $routeMiddleware = [ ... 'admin' => \App\Http\Middleware\Admin::class, ];
Contrôleur et route
On va créer un contrôleur pour entrer dans l’administration :
php artisan make:controller Back\AdminController
<?php namespace App\Http\Controllers\Back; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class AdminController extends Controller { /** * Show admin dashboard * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function index(Request $request) { $notifications = $request->user()->unreadNotifications()->get(); $newUsers = 0; $newOrders = 0; foreach($notifications as $notification) { if($notification->type === 'App\Notifications\NewUser') { ++$newUsers; } elseif($notification->type === 'App\Notifications\NewOrder'){ ++$newOrders; } } return view('back.index', compact('notifications', 'newUsers', 'newOrders')); } }
On récupère les notifications non lues (nouveaux inscrits et nouvelles commandes) et on ouvre un vue index qu’on n’a pas encore créée.
On crée la route :
// Administration Route::prefix('admin')->middleware('admin')->namespace('Back')->group(function () { Route::name('admin')->get('/', 'AdminController@index'); });
Vue index
On crée la vue index :
@extends('back.layout') @section('main') <div class="container-fluid"> @if(app()->isDownForMaintenance()) <div class="alert alert-danger alert-dismissible fade show" role="alert"> La boutique est en mode maintenance ! </div> @endif @if($notifications->count()) <div class="row"> @if($newOrders) <div class="col-6"> <div class="small-box bg-info"> <div class="inner"> <h3>{{ $newOrders }}</h3> <p>@if($newOrders === 1) Nouvelle commande @else Nouvelles commandes @endif</p> </div> <div class="icon"> <i class="fas fa-shopping-bag"></i> </div> <a href="#" class="small-box-footer">Plus d'informations <i class="fas fa-arrow-circle-right"></i></a> <form action="#" method="POST"> @csrf @method('PUT') <button type="submit" class="btn btn-info btn-block text-warning">Purger</button> </form> </div> </div> @endif @if($newUsers) <div class="col-6"> <div class="small-box bg-success"> <div class="inner"> <h3>{{ $newUsers }}</h3> <p>@if($newUsers === 1) Nouvel inscrit @else Nouveaux inscrits @endif</p> </div> <div class="icon"> <i class="fas fa-user"></i> </div> <a href="#" class="small-box-footer">Plus d'informations <i class="fas fa-arrow-circle-right"></i></a> <form action="#" method="POST"> @csrf @method('PUT') <button type="submit" class="btn btn-success btn-block text-warning">Purger</button> </form> </div> </div> @endif </div> @endif </div> @endsection
On y prévoit un message pour quand la boutique est en mode maintenance, c’est toujours une bonne idée. On ajoute une visualisation des notifications non lues. pour le moment les liens sont inactifs.
On avance ! On peut aussi mettre à jour le copyright dans le layout :
<strong>Copyright © 2020 {{ $shop->name }}.</strong>
Les titres
Pour les titres on va créer un fichier de configuration qui va faire correspondre une route avec un titre :
Pour le moment on n’a pas grand chose :
return [ /* |-------------------------------------------------------------------------- | Titles for routes names |-------------------------------------------------------------------------- | | Set Titles for each admin routes names */ 'admin' => 'Tableau de bord', ];
Dans AppServiceProvider on envoie les titres avec un composeur :
public function boot() { ... View::composer('back.layout', function ($view) { $title = config('titles.' . Route::currentRouteName()); $view->with(compact('title')); }); }
Dans le layout on peut maintenant afficher le titre :
<div class="content-header"> <div class="container-fluid"> <div class="row mb-2"> <div class="col-sm-12"> <h1 class="m-0 text-dark">{{ $title }}</h1> </div><!-- /.col --> </div><!-- /.row --> </div><!-- /.container-fluid --> </div>
Le menu latéral
Maintenant qu’on a une route on peut créer un premier item dans le menu latéral à l’aide du composant et des helpers qu’on a créés :
<nav class="mt-2"> <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false"> <x-menu-item :href="route('admin')" icon="tachometer-alt" :active="currentRouteActive('admin')"> Tableau de bord </x-menu-item> </ul> </nav>
Accès à l’administration
Pour accéder à l’administration on ajoute un lien dans le menu de la boutique (layouts/app.blade.php) :
@else <li><a class="tooltipped" href="{{ route('account') }}" data-position="bottom" data-tooltip="Voir mon compte client">{{ auth()->user()->firstname . ' ' . auth()->user()->name }}</a></li> @if(auth()->user()->admin) <li><a href="{{ route('admin') }}"><i class="material-icons left">dashboard</i>Administration</a></li> @endif <li><a href="{{ route('logout') }}" ... </a></li> @endguest
Pensez à placer ce code aux deux emplacements (mais sans l’icône en mode mobile) !
La purge des notifications
On va terminer en codant la purge des notifications. Dans le contrôleur AdminController on ajoute la méthode read :
public function read(Request $request, $type) { if($type === 'orders') { $type = 'App\Notifications\NewOrder'; } else if($type === 'users') { $type = 'App\Notifications\NewUser'; } $request->user()->unreadNotifications->where('type', $type)->markAsRead(); return redirect(route('admin')); }
On ajoute la route :
// Administration Route::prefix('admin')->middleware('admin')->namespace('Back')->group(function () { ... Route::name('read')->put('read/{type}', 'AdminController@read'); });
Et les liens dans la vue index :
... <form action="{{ route('read', 'orders') }}" method="POST"> @csrf @method('PUT') <button type="submit" class="btn btn-info btn-block text-warning">Purger</button> </form> ... <form action="{{ route('read', 'users') }}" method="POST"> @csrf @method('PUT') <button type="submit" class="btn btn-success btn-block text-warning">Purger</button> </form>
On peut constater le changement dans la colonne read_at de la base :
Et évidemment on a la visualisation en direct sur l’accueil de l’adminsitration.
Au lieu de les marquer comme lues on pourrait aussi les supprimer définitivement…
Conclusion
Notre administration est maintenant en place il ne nous reste plus qu’à la remplir !
20 commentaires
louma
Bonjour,
J’avais une question :
Dans cette partie
« Le menu latéral
Maintenant qu’on a une route on peut créer un premier item dans le menu latéral à l’aide du composant et des helpers qu’on a créés »
j’obtient cet erreur :
« Symfony\Component\Routing\Exception\RouteNotFoundException
Route [admin] not defined. (View: C:\laragon\www\shopping\resources\views\back\layout.blade.php) »
c’est quoi la solution svp
merci.
bestmomo
Bonjour,
Dans l’article je précise de créer cette route :
Route::view('admin', 'back.layout');
louma
j’ai deja fait mais toujour la meme erreur
bestmomo
Liste les routes avec « php artisan route:list », tu verras bien si elle existe cette route.
Djomab
Bonjour BESTMOMO.
Merci pour ce très bon tuto.
Je n’arrive pas à accéder à la partie admin parceque le lien « Administration » tout en haut n’apparaît pas.
Pourtant j’arrive à m’authentfier avec succès avec un compte admin.
J’ai fais {{ dd(Auth()->user()) }} pour vérifier et là je vois que le résultat est null.
Ai-je louper quelque chose?
bestmomo
Salut,
Tu as essayé avec {{ dd(auth()->user()) }} ?
Djomab
Salut,
Oui les deux; {{ dd(Auth::user()) }} et {{ dd(auth()->user()) }} mais tous les 2 retournent null.
bestmomo
Si ça renvoie null c’est que l’authentification n’a pas marché ou alors c’est vraiment très étrange…
king
Bonsoir bestmomo,
Est-il possible d’ajouter un titre dynamique, qui va faire correspondre une route avec un titre avec symfony 5?
Merci
bestmomo
Bonjour,
Je ne comprends pas ce que vient faire symfony 5 ici.
fatou
Bonjour rien ne s’enregistre dans ma base dans la colonne read_at alors que j’ai bien suivi correctement
thijo
Bonjour BestMomo ,
Pour les notifications : J’ai une navBar qui se repete partout dans mon dashboard, et j’aimerais récupérer le count() des mes notifications dans mon ampoule qui est dans le navbar. comment puis je mettre cette ligne $notifications = $request->user()->unreadNotifications()->get(); dans l’AppServiceProvider pour qu’il soit accessible partout dans mes différentes vues ? sur l’index c’est bon car j’envoie la collections dans la vue mais quand je vais dans la vue create ou edit la la variable est inconnue par la 😉
help please
Merci d’avance 🙂
thijo
Trouvé 😉
riftone07
Bonsoir chapitre très intèrressant et y’aurai t’il une possibilité d’avoir les notifications sonores pour amelioration et aussi meme les notifications en temps réelles
bestmomo
Salut,
Pour les notifications en temps réel regarde la documentation de Laravel ici. Ça nécessite d’avoir une routine PHP qui tourne en permanence sur le serveur.
Pour les notifications sonores je ne me suis jamais posé la question, on devrait pouvoir lancer un fichier son avec Javascript.
thijo
Bonjour,
Je fais le fichier titles.php dans config et modifie AppSerciceProvider , mais j’arrive pas à afficher le {{$title}} dans mon masterlayout.
je pense que j’ai un probleme avec les routes . on peut mettre par ex à la place de ‘admin’ => » .. une route du style ‘admin/post’ => » merci d’avance
Ps: il me dit pas le propriete title est introuvable , pas d erreur , mais les titres de mes differentes pages ne s’affiche pas .
Merci
bestmomo
Salut,
Déjà dans le provider est-ce que tu récupères bien le nom de la route avec
Route::currentRouteName()
?thijo
Bonjour,
Bravo déjà pour mettre à jour le cours à chaque nouvelle version de Laravel 😉 merci tu m’aides beaucoup.
J’avais une question : quand je met le View::composer …. dans AppServiceProvider la route Route::currentRouteName() provoque une erreur il me dit que ca currentRouteName doesnt exist . et là je suis bloqué .
Help Please
bestmomo
Salut,
Est-ce que tu as bien déclaré en tête du provider :
use Illuminate\Support\Facades\Route;
?thijo
oui ca marche merci 😉