Shopping : les statistiques

Pour compléter le projet de boutique en ligne je vous propose dans cet article de mettre en place quelques statistiques : le nombre de commandes et de nouveaux clients. On doit pouvoir choisir l’année concernée.

Vous pouvez télécharger un ZIP du projet ici.

Un package

Il existe quelques packages pour dessiner des graphes mais celui que je préfère est Laravel Charts :

La version 7 vient tout juste d’être lancée avec un remaniement de fond en particulier le choix de Chartisan pour le frontend.

Il est facile à installer :

composer require consoletvs/charts:7.*

Si ça coince commencez par mettre à jour vos librairies (il faut aussi PHP >= 7.4) :

composer update

Contrôleur et route

On va créer un contrôleur :

php artisan make:controller Back\StatisticsController

On le rend invocable :

<?php

namespace App\Http\Controllers\Back;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class StatisticsController extends Controller
{
    public function __invoke(Request $request)
    {  
        // On va coder ici
    }
}

On ajoute une route :

Route::prefix('admin')->middleware('admin')->namespace('Back')->group(function () {
    ...
    Route::name('statistics')->get('statistiques/{year}', 'StatisticsController');

On prévoit le paramètre year pour l’année.

Le menu

On ajoute l’item dans le menu de l’administration (back.layout) avec l’année actuelle par défaut comme paramètre :

<x-menu-item 
  :href="route('statistics', now()->year)" 
  icon="chart-bar"
  :active="currentRouteActive('statistics')">
  Statistiques
</x-menu-item>

Les commandes

On va créer la classe pour le graphe des commandes :

php artisan make:chart OrdersChart

On l’enregistre dans AppServiceProvider :

use ConsoleTVs\Charts\Registrar as Charts;
use App\Charts\OrdersChart;

...

public function boot(Charts $charts)
{
    $charts->register([
        OrdersChart::class
    ]);

On a un code par défaut dans la classe OrdersChart mais plutôt que de tout coder dans cette classe on va en créer une autre abstraite parce qu’on va avoir du code commun avec les nouveaux utilisateurs qu’on va voir plus loin dans cet article.

On crée la classe CommonChart :

Avec ce code :

<?php

namespace App\Charts;

use ConsoleTVs\Charts\BaseChart;
use Chartisan\PHP\Chartisan;

abstract class CommonChart extends BaseChart
{
    protected function chartisan($model, $title)
    {
        $year = request()->year;

        $datas = $this->datas($year, $model);

        return Chartisan::build()
            ->labels($datas->pluck('month_name')->toArray())
            ->dataset($title , $datas->pluck('data')->toArray());
    }

    protected function datas($year, $model)
    {
        return $model->selectRaw('
            count(*) data, 
            month(created_at) month, 
            monthname(created_at) month_name
        ')
        ->whereYear('created_at', $year)
        ->groupBy('month', 'month_name')
        ->orderBy('month', 'asc')
        ->get();
    }
}

Cette classe est abstraite parce qu’elle n’a pas vocation a être instanciée.

On commence par récupérer l’année dans la requête :

$year = request()->year;

Ensuite on récupère les données :

$datas = $this->datas($year, $model);

C’est une fonction qui assure cette tâche :

protected function datas($year, $model)
{
    return $model->selectRaw('
        count(*) data, 
        month(created_at) month, 
        monthname(created_at) month_name
    ')
    ->whereYear('created_at', $year)
    ->groupBy('month', 'month_name')
    ->orderBy('month', 'asc')
    ->get();
}

On a besoin de compter les enregistrements par mois, on a aussi besoin du nom de chaque mois pour l’afficher. On classe aussi par mois.

On peut alors générer les étiquettes et le dataset pour le graphe :

return Chartisan::build()
    ->labels($datas->pluck('month_name')->toArray())
    ->dataset($title , $datas->pluck('data')->toArray());

Du coup la classe OrdersChart est légère :

<?php

declare(strict_types = 1);

namespace App\Charts;

use Chartisan\PHP\Chartisan;
use Illuminate\Http\Request;
use App\Models\Order;

class OrdersChart extends CommonChart
{
    public function handler(Request $request): Chartisan
    {
        return $this->chartisan(new Order, 'Commandes');
    }
}

La vue

On crée la vue :

@extends('back.layout') 

@section('main') 

  <div class="container-fluid">    

    <div class="col-12">
      <div class="card">
        <div class="card-body">
          <div class="form-group">
            <label for="customRange1">Année : &nbsp</label>
            @foreach ($years as $year)
              <div class="custom-control custom-radio custom-control-inline">
                <input type="radio" id="{{ $year }}" name="year" class="custom-control-input" value="{{ $year }}" @if($actualYear == $year) checked @endif>
                <label class="custom-control-label" for="{{ $year }}">{{ $year }}</label>
              </div>               
            @endforeach
          </div>
        </div>
      </div>
    </div>

    <div class="col-12">
      <div class="card">
        <div id="ordersChart" style="height: 300px;" class="card-body">          
        </div>
      </div>
    </div>

  </div>

@endsection

@section('js')
  <script src="https://unpkg.com/chart.js/dist/Chart.min.js"></script>
  <script src="https://unpkg.com/@chartisan/chartjs/dist/chartisan_chartjs.js"></script>
  <script>
    $(function() {

      const OrdersChart = new Chartisan({
        el: '#ordersChart',
        url: "@chart('orders_chart')" + '?year={{ $actualYear }}',
        hooks: new ChartisanHooks()
          .colors(['#c33'])
          .responsive()
          .beginAtZero()
      });
  
      $('input').change(function() { 
        let year = $("input[name='year']:checked").val();
        let param = '?year=' + year;;     
        OrdersChart.update({
          url: "@chart('orders_chart')" + param
        });
        window.history.replaceState('', '', '/admin/statistiques/' + year);
      });
    });
  </script>
@endsection

On ajoute aussi le titre dans config.titles :

return [

    ...

    'statistics' => 'Statistiques',

La librairie est facile à utiliser, on peut ajuster quelques options, ici la couleur, le fait que ça commence à 0…

On met aussi à jour l’adresse pour être cohérent.

On a aussi automatiquement la création d’une route côté serveur :

C’est cette route qu’on appelle pour créer et actualiser le graphe.

On va aussi coder le contrôleur StatisticsController pour appeler et nourrir cette vue :

<?php

namespace App\Http\Controllers\Back;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Order;

class StatisticsController extends Controller
{
    public function __invoke(Request $request)
    {  
        $actualYear = $request->year;

        // Années disponibles
        $years = range(Order::oldest()->first()->created_at->year, now()->year);

        return view('back.statistics.index', compact(
            'years',
            'actualYear'
        ));
    }
}

Et ça devrait marcher :

On peut maintenant obtenir les statistiques des commandes pour chaque année. Le seul problème c’est que les mois sont en anglais alors on va régler ça dans AppSerciveProvider :

use DB;

...

public function boot(Charts $charts)
{

    DB::statement("SET lc_time_names = 'fr_FR'");

On a maintenant les mois en français :

On a un tooltip assez élégant au survol :

Les nouveaux clients

Pour les statistiques des nouveaux clients on crée aussi une classe :

php artisan make:chart UsersChart

Avec ce code :

<?php

declare(strict_types = 1);

namespace App\Charts;

use Chartisan\PHP\Chartisan;
use Illuminate\Http\Request;
use App\Models\User;

class UsersChart extends CommonChart
{
    public function handler(Request $request): Chartisan
    {
        return $this->chartisan(new User, 'Nouveaux clients');
    }
}

On voit ici l’intérêt de la classe CommonChart qu’on a créée précédemment.

On complète ainsi la vue back.statistics.index :

@extends('back.layout') 

@section('main') 

  <div class="container-fluid">    

    ...    

    <div class="col-12">
      <div class="card">
        <div id="usersChart" style="height: 300px;" class="card-body">          
        </div>
      </div>
    </div>

  </div>

@endsection

@section('js')
  
      ...

      const UsersChart = new Chartisan({
        el: '#usersChart',
        url: "@chart('users_chart')" + '?year={{ $actualYear }}',
        hooks: new ChartisanHooks()
          .colors(['#3c3'])
          .responsive()
          .beginAtZero()
      });
    
      $('input').change(function() { 
        let year = $("input[name='year']:checked").val();
        let param = '?year=' + year;;     
        OrdersChart.update({
          url: "@chart('orders_chart')" + param
        });
        UsersChart.update({
          url: "@chart('users_chart')" + param
        });
        window.history.replaceState('', '', '/admin/statistiques/' + year);
      });
    });
  </script>
@endsection

On a maintenant aussi les statistiques pour les nouveaux clients :

Conclusion

On a vu dans cet article qu’il est facile d’ajouter des statistiques à notre boutique, surtout avec ce package et cette librairie. Je n’ai montré qu’une approche sommaire et on peut bien améliorer cet aspect de maintes manières.

Print Friendly, PDF & Email

7 commentaires sur “Shopping : les statistiques

  1. Merci pour cette belle série de tutos, je l’ai suivie depuis le début et tout fonctionne !!
    Vraiment appréciable d’avoir des tutos de cette qualité en français.

    PS : ne pas oublier d’enregistrer dans AppServiceProvider la classe du graphe des utilisateurs, UsersChart 😉

Laisser un commentaire