Laravel 8

Créer un blog – le tableau des articles

Nous avons dans le précédent article installé notre interface d’administration avec comme choix AdminLTE. Pour se simplifier la vie on a prévu d’automatiser les titres et le menu latéral en ajoutant deux fichiers de configuration qu’on va lire pour générer les éléments correspondants. On a aussi prévu d’afficher sur le tableau de bord les nouveaux enregistrements : utilisateurs, articles, commentaires et contacts. De cette manière on a une vue globale de la vie du blog sur une même page.

Dans le présent article on va se pencher sur la gestion des articles. Pour le moment on sait juste afficher l’image et le résumé sur la page d’accueil du blog et l’article complet sur une page. Mais on doit pouvoir également créer et modifier les articles. De même il doit être possible de les supprimer et aussi les dupliquer.

Pour la gestion de toutes les entités du blog on va faire appel systématiquement à des tableaux. Comme je l’avais fait pour mon exemple de commerce en ligne je vais utiliser le package laravel-datatables qui me semble le plus performant en la matière.

Vous pouvez télécharger le code final de cet article ici.

Edit au 22/02/2021 : j’ai modifié la Datatable pour supprimer le bouton de duplication pour l’administrateur pour les articles dont il n’est pas l’auteur.

Laravel Datatables

Pour l’administration on va gérer tous les tableaux avec le package laravel-datatables, on va donc l’installer :

composer require yajra/laravel-datatables

Une fois le package installé on dispose d’une commande pour créer une datatable, on l’utilise pour les articles :

php artisan datatables:make Posts

Pour le moment on garde le code de base, on y reviendra plus loin.

Contrôleur et routes

On crée un contrôleur pour la gestion des articles :

php artisan make:controller Back\PostController --resource --model=Post

On aura besoin de toutes les méthodes sauf show puisque l’affichage d’un article se fait dans le frontend.

Dans le contrôleur on prévoit l’injection de la Datatable créée ci-dessus :

use App\DataTables\PostsDataTable;

...

public function index(PostsDataTable $dataTable)
{
    return $dataTable->render('back.shared.index');
}

On va créer la vue plus loin.

Pour les routes on peut aussi grouper :

use App\Http\Controllers\Back\{
    AdminController,
    PostController as BackPostController
};

...

Route::prefix('admin')->group(function () {

    Route::middleware('redac')->group(function () {
        ...
        Route::resource('posts', BackPostController::class)->except('show');
    });

    Route::middleware('admin')->group(function () {
        Route::name('posts.indexnew')->get('newposts', [BackPostController::class, 'index']);
    });
});

Je crée une route spéciale réservée à l’administrateur pour afficher seulement les nouveaux articles. On pointe sur la méthode index, il faudra prévoir de distinguer les deux situations : tous les articles ou juste les nouveaux, c’est le nom de la route utilisée qui nous renseignera.

Une vue partagée pour les tableaux

Le code pour l’affichage des tableaux avec Laravel Datatable va être quelque peu systématique. On ne crée donc qu’une vue qu’on adaptera si nécessaire pour les différences rencontrées :

Voici le code de base en prévoyant les textes en français :

@extends('back.layout')

@section('css')
  <link rel="stylesheet" href="https://cdn.datatables.net/1.10.23/css/dataTables.bootstrap4.min.css">
  <style>
    a > * { pointer-events: none; }
  </style>
@endsection

@section('main') 
  {{ $dataTable->table(['class' => 'table table-bordered table-hover table-sm'], true) }}
@endsection

@section('js') 
  <script src="https://cdn.datatables.net/1.10.23/js/jquery.dataTables.min.js"></script> 
  <script src="https://cdn.datatables.net/1.10.23/js/dataTables.bootstrap4.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>

  @if(config('app.locale') == 'fr')
    <script>
      (($, DataTable) => {
        $.extend(true, DataTable.defaults, {
          language: {
            "sEmptyTable":     "Aucune donnée disponible dans le tableau",
            "sInfo":           "Affichage des éléments _START_ à _END_ sur _TOTAL_ éléments",
            "sInfoEmpty":      "Affichage de l'élément 0 à 0 sur 0 élément",
            "sInfoFiltered":   "(filtré à partir de _MAX_ éléments au total)",
            "sInfoPostFix":    "",
            "sInfoThousands":  ",",
            "sLengthMenu":     "Afficher _MENU_ éléments",
            "sLoadingRecords": "Chargement...",
            "sProcessing":     "Traitement...",
            "sSearch":         "Rechercher :",
            "sZeroRecords":    "Aucun élément correspondant trouvé",
            "oPaginate": {
              "sFirst":    "Premier",
              "sLast":     "Dernier",
              "sNext":     "Suivant",
              "sPrevious": "Précédent"
            },
            "oAria": {
              "sSortAscending":  ": activer pour trier la colonne par ordre croissant",
              "sSortDescending": ": activer pour trier la colonne par ordre décroissant"
            },
            "select": {
              "rows": {
                "_": "%d lignes sélectionnées",
                "0": "Aucune ligne sélectionnée",
                "1": "1 ligne sélectionnée"
              }  
            }
          }
        });
      })(jQuery, jQuery.fn.dataTable);
    </script>
  @endif

  {{ $dataTable->scripts() }}

@endsection

Comme on n’a pas encore créé de section CSS et pour le javascript dans back.layout on va le faire. On en profite pour mettre à jour le titre de la page :

<title>@lang('Administration')</title>

...

<!-- Theme style -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/admin-lte/3.0.5/css/adminlte.min.css" />
@yield('css')

...

<!-- AdminLTE App -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/admin-lte/3.0.5/js/adminlte.min.js"></script>
@yield('js')
</body>

Le tableau des articles

Revenons-en maintenant à notre Datatable.

Les données

Quelles sont les données qu’on va afficher dans le tableau ?

  • le titre
  • l’auteur
  • les catégories
  • le nombre de commentaires
  • la date de publication ou de dernière modification
  • le statut : publié ou pas
  • des boutons d’action :
    • voir l’article
    • le modifier
    • le cloner
    • le supprimer

Dans le Datatable c’est la méthode query qui s’occupe de récupérer les données. Par défaut on a juste une amorce de requête :

public function query(Post $model)
{
    return $model->newQuery();
}

On complète pour aller chercher toutes les données nécessaires :

use Illuminate\Support\Facades\Route;

...

public function query(Post $post)
{
    $query = isRole('redac') ? auth()->user()->posts() : $post->newQuery();

    if(Route::currentRouteNamed('posts.indexnew')) {
        $query->has('unreadNotifications');
    }

    return $query->select(
                    'posts.id',
                    'slug',
                    'title',
                    'active',
                    'posts.created_at',
                    'posts.updated_at',
                    'user_id')
                ->with(
                    'user:id,name',
                    'categories:title')
                ->withCount('comments');
}

On doit tenir compte du fait que :

  • les rédacteurs ne doivent voir que leurs articles
  • si on a la route posts.indexnew on doit avoir seulement les nouveaux articles
  • on doit éviter de charger trop de données en utilisant un SELECT (en particulier on évite de charger le corps des articles)
  • on doit charger les relations
  • on doit compter les commentaires

Le colonnes

On va un peu épurer la méthode html :

public function html()
{
    return $this->builder()
                ->setTableId('posts-table')
                ->columns($this->getColumns())
                ->minifiedAjax()
                ->dom('Blfrtip')
                ->lengthMenu();
}

Les colonnes sont définies dans la méthode getColumns, on ajoute les colonnes en fonction des données qu’on a définies ci-dessus :

protected function getColumns()
{
    $columns = [
        Column::make('title')->title(__('Title'))
    ];
    
    if(auth()->user()->role === 'admin') {
        array_push($columns, 
            Column::make('user.name')->title(__('Author'))
        );
    }

    array_push($columns,
        Column::computed('categories')->title(__('Categories')),
        Column::computed('comments_count')->title(__('Comments'))->addClass('text-center align-middle'),
        Column::make('created_at')->title(__('Date')),
        Column::computed('action')->title(__('Action'))->addClass('align-middle text-center')
    );

    return $columns;
}

On n’ajoute la colonne de l’auteur que si c’est l’administrateur, sinon ce n’est pas la peine.

On dispose de la méthode computed pour une colonne calculée. C’est le cas par exemple des catégories parce qu’on va mettre toutes les catégories auxquelles apartient l’article.

Un trait

Comme on aura à afficher des badges et boutons dans plusieurs tableaux on crée un trait :

<?php

namespace App\DataTables;

trait DataTableTrait
{
    public function badge($text, $type, $margin = 0)
    {
        return '<span class="badge badge-' . $type . ' ml-' . $margin . '">' . __($text) . '</span>';
    }

    public function button($route, $param, $type, $title, $icon, $name = '', $target = '_self')
    {
        return '<a 
                    title="'. $title . '" 
                    data-name="' . $name . '" 
                    href="' . route($route, $param) . '" 
                    class="px-3 btn btn-xs btn-' . $type . '" 
                    target="' . $target . '">
                    <i class="far fa-' . $icon . '"></i>
                </a>';
    }
}

Et on l’ajoute dans le Datatable :

class PostsDataTable extends DataTable
{
    use DataTableTrait;

Un helper

On a besoin de mettre en forme l’heure, on l’avait déjà fait pour la date dans le fichier app/helpers, on ajoute la fonction pour l’heure :

if (!function_exists('formatHour')) {
    function formatHour($date)
    {
        return ucfirst(utf8_encode ($date->formatLocalized('%Hh%M')));
    }
}

On va d’ailleurs fixer la locale dans AppServiceProvider :

public function boot()
{
    setlocale(LC_TIME, config('app.locale'));
    ...

La génération

La génération du Datatable se fait dans la méthode dataTable :

public function dataTable($query)
{
    return datatables()
        ->eloquent($query)
        ->editColumn('categories', function ($post) {
            return $this->getCategories($post);
        })
        ->editColumn('created_at', function ($post) {
            return $this->getDate($post);
        })
        ->editColumn('comments_count', function ($post) {
            return $this->badge($post->comments_count, 'secondary');
        })
        ->editColumn('action', function ($post) {

            $buttons = $this->button(
                            'posts.display', 
                            $post->slug, 
                            'success', 
                            __('Show'), 
                            'eye', 
                            '',
                            '_blank'
                        );

            if(Route::currentRouteName() === 'posts.indexnew') {
                return $buttons;
            }

            $buttons .= $this->button(
                'posts.edit', 
                $post->id, 
                'warning', 
                __('Edit'), 
                'edit'
            );

            if($post->user_id === auth()->id()) {
                $buttons .= $this->button(
                    'posts.create', 
                    $post->id, 
                    'info', 
                    __('Clone'), 
                    'clone'
                );
            }
            
            return $buttons . $this->button(
                        'posts.destroy', 
                        $post->id, 
                        'danger', 
                        __('Delete'), 
                        'trash-alt', 
                        __('Really delete this post?')
                    );
        })
        ->rawColumns(['categories', 'comments_count', 'action', 'created_at']);
}

Le traitement des publications, dates et catégories se fait dans deux fonctions distinctes :

protected function getDate($post)
{
    if(!$post->active) {
        return $this->badge('Not published', 'warning');
    }

    $updated = $post->updated_at > $post->created_at;
    $html = $this->badge($updated ? 'Last update' : 'Published', 'success');

    $html .= '<br>' . formatDate($updated ? $post->updated_at : $post->created_at) . __(' at ') . formatHour($updated ? $post->updated_at : $post->created_at);

    return $html;
}

protected function getCategories($post)
{
    $html = '';

    foreach($post->categories as $category) {
        $html .= $category->title . '<br>';
    }

    return $html;
}

Pour la date je regarde s’il y a eu une modification après la création et j’affiche cette date là.

Maintenant avec l’url monblog.ext/admin/posts (et newposts pour uniquement les nouveaux) on obtient le tableau :

Pour le moment au niveau des boutons on n’a que le premier qui fonctionne pour voir l’article.

Menu et titres

Pour les titres on complète le fichier app/config/titles.php :

<?php

return [

    'admin' => 'Dashboard',
    'posts' => [
        'index'    => 'Posts',
        'create'   => 'Post Creation',
        'edit'     => 'Post Edit',
        'indexnew' => 'New Posts',
    ],
];

Pour le menu latéral ça se passe dans app/config/menu.php :

<?php

return [

    'Dashboard' => [
        'role'   => 'redac',
        'route'  => 'admin',
        'icon'   => 'tachometer-alt',
    ],
    'Posts' => [
        'icon' => 'file-alt',
        'role'   => 'redac',
        'children' => [
            [
                'name'  => 'All posts',
                'role'  => 'redac',
                'route' => 'posts.index',
            ],
            [
                'name'  => 'New posts',
                'role'  => 'admin',
                'route' => 'posts.indexnew',
            ],
            [
                'name'  => 'Add',
                'role'  => 'redac',
                'route' => 'posts.create',
            ],
            [
                'name'  => 'fake',
                'role'  => 'redac',
                'route' => 'posts.edit',
            ],
        ],
    ],
];

Si tout se passe bien vous devez avoir menu et titre pour les articles :

Pour les nouveaux articles :

 

Conclusion

On a obtenu l’affichage des articles sous forme de tableau dans l’administration avec les renseignements essentiels. Le code de base nous servira aussi pour les autres entités. Mais on a encore du travail avec les articles : création, modification, duplication, suppression. On continuera dans le prochain article.

Print Friendly, PDF & Email

57 commentaires

  • DIM

    Bonjour Best , j’espère que tout va bien pour toi.

    j’ai un souci au niveau de l’affichage des boutons d’actions , ils ne s’affichent tout simplement pas.
    voici le code de mon datatable :

    namespace App\DataTables;

    use App\Models\Membre;
    use App\Exports\MembresExport;
    use Yajra\DataTables\Html\Button;
    use Yajra\DataTables\Html\Column;
    use App\DataTables\DataTableTrait;
    use Illuminate\Support\Facades\Route;
    use Yajra\DataTables\WithExportQueue;
    use Yajra\DataTables\EloquentDataTable;
    use Yajra\DataTables\Html\Editor\Editor;
    use Yajra\DataTables\Html\Editor\Fields;
    use Yajra\DataTables\Services\DataTable;
    use Yajra\DataTables\Html\Builder as HtmlBuilder;
    use Illuminate\Database\Eloquent\Builder as QueryBuilder;

    class MembresDataTable extends DataTable
    {
    use DataTableTrait;

    /**
    * Build the DataTable class.
    *
    * @param QueryBuilder $query Results from query() method.
    */
    public function dataTable(QueryBuilder $query): EloquentDataTable
    {
    return (new EloquentDataTable($query))
    ->editColumn('created_at', function($membre){
    return $this->getDate($membre);
    })
    ->editColumn('action', function ($membre) {
    $buttons = $this->button(
    'membres.edit',
    $membre->id,
    'warning',
    __('Edit'),
    'edit'
    ). $this->button(
    'membres.destroy',
    $membre->id,
    'danger',
    __('Delete'),
    'trash-alt',
    __('Really delete this post?')
    );
    })
    ->rawColumns(['action', 'created_at']);
    }

    /**
    * Get the query source of dataTable.
    */
    public function query(Membre $model): QueryBuilder
    {
    if(Route::currentRouteNamed('membres.indexnew')) {
    return $model->has('unreadNotifications');
    }
    return $model->newQuery();
    }

    /**
    * Optional method if you want to use the html builder.
    */
    public function html(): HtmlBuilder
    {
    return $this->builder()
    ->setTableId('membres-table')
    ->columns($this->getColumns())
    ->minifiedAjax()
    ->dom('Bfrtip')
    ->orderBy(1)
    ->selectStyleSingle()
    ->buttons([
    Button::make('excel'),
    Button::make('csv'),
    Button::make('pdf'),
    Button::make('print'),
    Button::make('reset'),
    Button::make('reload')
    ]);
    }

    /**
    * Get the dataTable columns definition.
    */
    public function getColumns(): array
    {
    $columns = [
    Column::make('name')->title(__('Nom'))->addClass('align-middle text-center')
    ];

    array_push($columns,

    Column::make('status')->title(__('Status'))->addClass('align-middle text-center'),
    Column::make('year')->title(__('Année'))->addClass('align-middle text-center'),
    Column::make('reason')->title(__('Motif'))->addClass('align-middle text-center'),
    Column::make('church')->title(__('Eglise'))->addClass('align-middle text-center'),
    Column::make('area')->title(__('Quartier'))->addClass('align-middle text-center'),
    Column::make('phone')->title(__('Téléphone'))->addClass('align-middle text-center'),
    Column::make('email')->title(__('Email'))->addClass('align-middle text-center'),
    Column::make('created_at')->title(__('Date')),
    Column::computed('action')->title(__('Action'))->addClass('align-middle text-center')
    );
    return $columns;
    }

    /**
    * Get the filename for export.
    */
    protected function filename(): string
    {
    return 'Membres_' . date('YmdHis');
    }

    protected function getDate($membre)
    {
    $html = formatDate( $membre->created_at) . __(' at ') . formatHour($membre->created_at);

    return $html;
    }
    }

      • DIM

        salut , j’ai une table membres et je veux afficher les membres dans une datatable voici le code qui ne fonctionne pas :

        eloquent($query)
        ->editColumn('action', function ($membre) {

        $buttons .= $this->button(
        'membres.edit',
        $membre->id,
        'warning',
        __('Edit'),
        'edit'
        ). $this->button(
        'posts.destroy',
        $post->id,
        'danger',
        __('Delete'),
        'trash-alt',
        __('Really delete this post?')
        );
        })
        ->rawColumns(['action']);

        }

        /**
        * Get the query source of dataTable.
        */
        public function query(Membre $model): QueryBuilder
        {

        if(Route::currentRouteNamed('membres.indexnew')) {
        return $model->has('unreadNotifications');
        }
        return $model->newQuery();
        }

        /**
        * Optional method if you want to use the html builder.
        */
        public function html(): HtmlBuilder
        {
        return $this->builder()
        ->setTableId('membres-table')
        ->columns($this->getColumns())
        ->minifiedAjax()
        ->dom('Bfrtip')
        ->orderBy(1)
        ->selectStyleSingle()
        ->buttons([
        Button::make('excel'),
        Button::make('csv'),
        Button::make('pdf'),
        Button::make('print'),
        Button::make('reset'),
        Button::make('reload')
        ]);
        }

        /**
        * Get the dataTable columns definition.
        */
        public function getColumns(): array
        {

        $columns = [
        Column::make('name')->title(__('Nom'))->addClass('align-middle text-center')
        ];

        array_push($columns,
        Column::make('firstname')->title(__('Prenom'))->addClass('align-middle text-center'),
        Column::make('status')->title(__('Status'))->addClass('align-middle text-center'),
        Column::make('year')->title(__('Année de bapteme'))->addClass('align-middle text-center'),
        Column::make('reason')->title(__('Motif de séjour'))->addClass('align-middle text-center'),
        Column::make('church')->title(__('Eglise'))->addClass('align-middle text-center'),
        Column::make('area')->title(__('Quartier'))->addClass('align-middle text-center'),
        Column::make('phone')->title(__('Téléphone'))->addClass('align-middle text-center'),
        Column::make('email')->title(__('Adresse Email'))->addClass('align-middle text-center'),
        Column::make('created_at')->title(__('Date'))->addClass('align-middle text-center'),
        Column::computed('action')->title(__('Action'))->addClass('align-middle text-center')
        );
        return $columns;
        /* return [
        Column::computed('action')
        ->exportable(false)
        ->printable(false)
        ->width(60)
        ->addClass('text-center'),
        Column::make('id'),
        Column::make('add your columns'),
        Column::make('created_at'),
        Column::make('updated_at'),
        ]; */
        }

        /**
        * Get the filename for export.
        */
        protected function filename(): string
        {
        return 'Membres_' . date('YmdHis');
        }
        }

        • bestmomo

          C’est difficile de répondre sans pouvoir faire de débogage mais il me semble qu’il y a un souci dans la fonction query, je verrais plutôt ça :
          public function query(Membre $model): QueryBuilder
          {
          $model->newQuery();
          if(Route::currentRouteNamed('membres.indexnew')) {
          return $model->has('unreadNotifications');
          }
          return $model;
          }

  • jnn

    Re Bonjour Best Momo.

    Je suis un peu à l’ouest actuellement.

    D’abord, mon problème de relation est résolu grâce au « Laravel Eager loading », mais il n’empêche que quand je me connecte comme rédac, ma datatable ne charge pas et j’ai « DataTables warning: table id=posts-table – Ajax error. For more information about this error, please see http://datatables.net/tn/7 » en log et une erreur 500 en console.

    Ensuite, quel que soit mon statut, le clic sur le bouton de création d’article me renvoie une page blanche, vide, déserte, et rien en console, tandis que la duplication marche bien.

    Merci d’avance pour ta sollicitude et ta bonne volonté.

  • jnn

    Bonjour BestMomo.
    La ligne $query = isRole(‘redac’) ? auth()->user()->posts() : $post->newQuery(); me renvoie que la méthode posts() est indéfinie. Elle l’est pourtant bien dans mon model User.
    J ai donc essayé avec d’autres méthodes du model mais le résultat est le même. Elles sont toutes indéfinies.
    Des pistes de solutions s’il te plaît ?

    • DIM

      c’est bon je l’ai retrouvé mais quand je me connecte en tant que redac j’ai l’erreur datatbles net.7 mais en tant qu’admin tout marche toutes les données sont disponibles .

      Best autre comportement bizzare , lors de la création d’un article le champ body où est utilisé ckeditor les données sont enregistrés dans la base de données entourées de la balise p et les accents ne sont pas reconnus

    • bestmomo

      Salut,

      Il faudrait voir la réponse du serveur qui apparemment n’est pas une 200, tu trouves ça dans l’onglet réseaux des outils développeurs du navigateur. Il faut aussi vérifier s’il n’y a pas un module complémentaire du navigateur qui bloque Ajax. Personnellement j’ai eu ce problème avec Privacy Badger et j’ai cherché un moment d’où venait le problème !

    • fabBlab

      J’avais une erreur similaire qui venait en fait d’une erreur de ma part en recopiant le code de PostsDataTable.
      L’erreur php étant « cachée » par JS, ce n’était très facile à debugger.

      J’ai donc téléchargé la version correcte fournie en début d’article pour comparer à mon code.
      Dans mon cas l’erreur venait d’un oubli au début du script PostsDataTable :
      use Illuminate\Support\Facades\Route;
      Cet ajout est bien indiqué, mais j’avais loupé.

      Bref, avec une erreur 500, il y a de fortes chances que cela soit une erreur côté PHP.

    • bestmomo

      Salut,

      Il faudrait quand même plus de détails sur le problème. Page blanche ? Tableau vide ? Ca donne quoi au niveau des requêtes échangées avec le serveur (onglet réseau des outils de développement du navigateur) ? Y-a-t-il des erreurs recensées dans les logs ?

        • bestmomo

          Ca doit être l’Ajax qui passe pas. Dans le déroulement on charge d’abord le tableau vide avec une requête monblog.ext/admin/posts, puis les librairies, puis en dernier une requête Ajax du genre monblog.ext/admin/posts?draw=1&columns[0][data]=title&columns[1][data]=user.name&columns[2][data]=categories&columns[2][searchable]=false&columns[2][orderable]=false&columns[3][data]=comments_count&columns[3][searchable]=false&columns[3][orderable]=false&columns[4][data]=created_at&columns[5][data]=action&columns[5][searchable]=false&columns[5][orderable]=false&order[0][column]=0&order[0][dir]=asc&start=0&length=10&search[value]=&_=1616447757569 qui sert à récupérer du JSON pour remplir le tableau.

    • bestmomo

      Normalement tu a la succession de deux requêtes (sans parler des celles pour charger toutes les librairies) :

      • ../admin/posts en GET
      • ../admin/posts?draw=1&columns[0][data]=title&columns.. avec tous les champs demandés en Ajax

      Est-ce que tu as bien cette succession ?

  • oksam

    Bonsoir Best j’ai un soucis : l’affichage des articles sous forme de tableau dans l’administration avec les renseignements essentiels ne sont pas visible. lorsque j’inspecte la console est vide et le network affiche : methode : GET et status code : 2000 ok avec le bon chemin de url.
    tu aurais une idée du problème!

          • DIM

            Salut,
            voici le message que je reçois : GEThttp://redemption.test/admin/posts?draw=1&columns[0][data]=title&columns[1][data]=user.name&columns[2][data]=categories&columns[2][searchable]=false&columns[2][orderable]=false&columns[3][data]=comments_count&columns[3][searchable]=false&columns[3][orderable]=false&columns[4][data]=created_at&columns[5][data]=action&columns[5][searchable]=false&columns[5][orderable]=false&order[0][column]=0&order[0][dir]=asc&start=0&length=10&search[value]=&_=1676641041395
            [HTTP/1.1 500 Internal Server Error 1108ms]

Laisser un commentaire