
Dans notre précédent article, nous avons codé la gestion des commandes. On a ainsi créé un tableau avec les éléments principaux pour chaque commande. On a ensuite donné la possibilité d'obtenir le détail de chaque commande sur une page dédiée avec la possibilité, sur cette page, de générer la facture ou changer l'état. À présent, nous allons faire quelque chose de similaire pour la gestion des clients et de leurs adresses..
Vous pouvez trouver le code dans ce dépôt Github.
Conception du tableau de gestion des clients
Le tableau
Pour faciliter la gestion des clients, nous allons créer un tableau intuitif et complet. Ce tableau rassemblera les informations clés de chaque client en un coup d'œil, permettant aux administrateurs de gérer efficacement le contenu. Voici les éléments que nous inclurons dans notre tableau :
- Nom
- Prénom
- Date d'inscription
- Statut pour la lettre d'information
- Facture
Fonctionnalités avancées
En plus de ces informations de base, nous allons envisager d'ajouter des fonctionnalités supplémentaires pour améliorer l'expérience utilisateur :
- Envoie d'un email au client
- Suppression du client
Un composant pour le tableau
Il nous faut maintenant un composant Volt pour afficher le tableau :
php artisan make:volt admin/customers/index --class
Avec ce code :
<?php
use Livewire\Volt\Component;
use Livewire\Attributes\{Layout, Title};
use App\Models\User;
use Mary\Traits\Toast;
use Livewire\WithPagination;
use Illuminate\Database\Eloquent\Builder;
new
#[Title('Customers')]
#[Layout('components.layouts.admin')]
class extends Component
{
use Toast, WithPagination;
public int $perPage = 10;
public string $search = '';
public bool $deleteButton = true;
public array $sortBy = [
'column' => 'created_at',
'direction' => 'desc',
];
public function headers(): array
{
return [
['key' => 'name', 'label' => __('Name')],
['key' => 'firstname', 'label' => __('Firstname')],
['key' => 'email', 'label' => __('Email')],
['key' => 'created_at', 'label' => __('Registration')],
['key' => 'newsletter', 'label' => __('Newsletter')],
];
}
public function deleteUser(User $user): void
{
$user->delete();
$this->success(__('User deleted successfully.'));
}
public function with(): array
{
return [
'users' => User::orderBy(...array_values($this->sortBy))
->when($this->search, function (Builder $query)
{
$query->where('name', 'like', "%{$this->search}%");
})
->paginate($this->perPage),
'headers' => $this->headers(),
];
}
}; ?>
<div>
<x-header title="{{ __('Customers') }}" separator progress-indicator >
<x-slot:actions>
<x-input
placeholder="{{ __('Search a name...') }}"
wire:model.live.debounce="search"
clearable
icon="o-magnifying-glass"
/>
<x-button
icon="s-building-office-2"
label="{{ __('Dashboard') }}"
class="btn-outline lg:hidden"
link="{{ route('admin') }}"
/>
</x-slot:actions>
</x-header>
<x-card>
<x-table
striped
:headers="$headers"
:rows="$users"
:sort-by="$sortBy"
per-page="perPage"
with-pagination
link="/admin/customers/{id}"
>
@scope('cell_newsletter', $user)
@if ($user->newsletter)
<x-icon name="o-check-circle" />
@endif
@endscope
@scope('cell_created_at', $user)
<span class="whitespace-nowrap">
{{ $user->created_at->isoFormat('LL') }}
@if(!$user->created_at->isSameDay($user->updated_at))
<p>@lang('Change') : {{ $user->updated_at->isoFormat('LL') }}</p>
@endif
</span>
@endscope
@scope('actions', $user, $deleteButton)
<div class="flex">
<x-popover>
<x-slot:trigger>
<x-button icon="o-envelope" link="mailto:{{ $user->email }}" no-wire-navigate spinner
class="text-blue-500 btn-ghost btn-sm" />
</x-slot:trigger>
<x-slot:content class="pop-small">
@lang('Send an email')
</x-slot:content>
</x-popover>
@if($deleteButton)
<x-popover>
<x-slot:trigger>
<x-button
icon="o-trash"
wire:click="deleteUser({{ $user->id }})"
wire:confirm="{{ __('Are you sure you want to delete this user?') }}"
spinner
class="text-red-500 btn-ghost btn-sm"
/>
</x-slot:trigger>
<x-slot:content class="pop-small">
@lang('Delete')
</x-slot:content>
</x-popover>
@endif
</div>
@endscope
</x-table>
</x-card>
</div>
On ajoute ces traductions :
"Firstname": "Prénom",
"Registration": "Inscription",
"Send an email": "Envoyer un email",
"Search a name...": "Rechercher un nom...",
La route
On ajoute une route pour l'atteindre :
Volt::route('/customers', 'admin.customers.index')->name('admin.customers.index');
La navigation
On ajoute un lien dans la barre latérale de l'administration (on prévoit un sous-menu parce qu'on ajoutera plus loin les adresses) :
<x-menu-sub title="{{ __('Customers') }}" icon="s-users">
<x-menu-item title="{{ __('Datas') }}" icon="s-list-bullet" link="{{ route('admin.customers.index') }}" />
</x-menu-sub>
Une petite traduction :
"Datas": "Données",
Et on a notre tableau :
Avec la pagination en partie inférieure :
J'avais oublié dans les précédents article de compléter le fichier resources.css pour avoir une bonne présentation de la pagination :
/* Table pagination: active page highlight */
.mary-table-pagination span[aria-current="page"] > span {
@apply bg-primary text-base-100
}
/* Table pagination: for dark mode*/
.mary-table-pagination span[aria-disabled="true"] span {
@apply bg-inherit
}
/* Table pagination: for dark mode */
.mary-table-pagination button {
@apply bg-base-100
}
Vérifiez que cette pagination fonctionne, ainsi que la recherche qui s'effectue sur les noms. On peut aussi trier par nom, prénom, email, date d'inscription et statut pour la lettre d'information.
On va aussi en profiter pour renseigner le lien dans le dashboard :
<a href="{{ route('admin.customers.index') }}" class="flex-grow">
Ainsi, on ira directement au tableau des clients en cliquant sur l'élément statistique :
Le détail d'un client
Lorsqu'on clique sur la ligne d'un client dans le tableau, on doit ouvrir une page avec le détail du client concerné. Il nous faut un composant Volt pour afficher ce détail :
php artisan make:volt admin/customers/show --class
Avec ce code plus léger que pour les commandes :
<?php
use Livewire\Volt\Component;
use Livewire\Attributes\{Layout, Title};
use App\Models\User;
use Mary\Traits\Toast;
new
#[Title('Customer')]
#[Layout('components.layouts.admin')]
class extends Component
{
use Toast;
public User $user;
public function headers(): array
{
return [
['key' => 'reference', 'label' => __('Reference')],
['key' => 'created_at', 'label' => __('Date')],
['key' => 'total', 'label' => __('Total price')],
['key' => 'state', 'label' => __('State')],
];
}
public function mount(User $user): void
{
$this->user = $user;
$this->user->load('addresses', 'orders', 'orders.state');
}
public function with(): array
{
return [
'headers' => $this->headers(),
];
}
}; ?>
<div>
<x-header title="{{ __('Customer details') }}" separator progress-indicator>
<x-slot:actions>
<x-button icon="s-building-office-2" label="{{ __('Dashboard') }}" class="btn-outline lg:hidden"
link="{{ route('admin') }}" />
</x-slot:actions>
</x-header>
<x-card
title="{{ __('Identity') }}"
shadow
class="h-full"
>
<div class="space-y-4">
<div class="flex items-center">
<x-icon name="o-user" class="w-6 h-6 mr-3 text-primary" />
<span>
<strong>@lang('Name') :</strong>
{{ $user->name }}
</span>
</div>
<div class="flex items-center">
<x-icon name="o-user" class="w-6 h-6 mr-3 text-primary" />
<span>
<strong>@lang('Firstname') :</strong>
{{ $user->firstname }}
</span>
</div>
<div class="flex items-center">
<x-button icon="o-envelope" link="mailto:{{ $user->email }}" no-wire-navigate spinner
class="w-6 h-6 mr-3 text-primary btn-ghost btn-sm" tooltip="{{ __('Send an email') }}" />
<span>
<strong>@lang('Email') :</strong>
{{ $user->email }}
</span>
</div>
<div class="flex items-center">
<x-icon name="o-calendar" class="w-6 h-6 mr-3 text-primary" />
<span>
<strong>@lang('Date of registration') :</strong>
{{ $user->created_at->isoFormat('LL') }}
</span>
</div>
<div class="flex items-center">
<x-icon name="o-calendar" class="w-6 h-6 mr-3 text-primary" />
<span>
<strong>@lang('Last update') :</strong>
{{ $user->updated_at->isoFormat('LL') }}
</span>
</div>
<div class="flex items-center">
<x-icon name="o-newspaper" class="w-6 h-6 mr-3 text-primary" />
<span>
<strong>@lang('Newsletter') :</strong>
{{ $user->newsletter ? __('Yes') : __('No') }}
</span>
</div>
</div>
</x-card>
<br>
<x-card
title="{{ __('Orders') }}"
shadow
class="h-full"
>
<x-table
:headers="$headers"
:rows="$user->orders"
link="/admin/orders/{id}"
>
@scope('cell_reference', $order)
<strong>{{ $order->reference }}</strong>
@endscope
@scope('cell_created_at', $order)
{{ $order->created_at->isoFormat('LL') }}
@endscope
@scope('cell_total', $order)
{{ $order->total }} €
@endscope
@scope('cell_state', $order)
<x-badge value="{{ $order->state->name }}" class="p-3 bg-{{ $order->state->color }}-400 self-start sm:self-center" />
@endscope
</x-table>
</x-card><br>
<x-card
title="{{ __('Adresses') }}"
shadow
class="h-full"
>
<div class="container mx-auto">
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
@foreach ($user->addresses as $address)
<x-card
class="w-full shadow-md"
title="">
<x-address :address="$address" />
</x-card>
@endforeach
</div>
</div>
</x-card>
</div>
On a besoin de nouvelles traductions :
"Customer details": "Détails du client",
"Identity": "Identité",
"Last update": "Dernière mise à jour",
On ajoute la route :
Volt::route('/customers/{user}', 'admin.customers.show')->name('admin.customers.show');
Dans la partie supérieure, on a les données de l'identité :
Puis les commandes (un clic sur une ligne envoie sur la page du détail de la commande) :
Et enfin les adresses :
Et évidemment, un clic sur la ligne d'une commande conduit directement sur le détail correspondant.
On en profite pour ajouter le lien vers le détail du client dans la page du détail de la commande à l'aide de ce bouton dont on avait pas encore renseigné le lien :
<x-slot:actions>
<x-button label="{{ __('View customer details') }}" class="btn-primary" link="{{ route('admin.customers.show', $order->user) }}" />
</x-slot:actions>
Les adresses
Le tableau
Pour faciliter la gestion des adresses, nous allons créer aussi un tableau. Ce tableau rassemblera les informations clés de chaque adresse en un coup d'œil, permettant aux administrateurs de gérer efficacement le contenu. Voici les éléments que nous inclurons dans notre tableau :
- Nom
- Prénom
- Raison sociale
- Adresse
- Code postal
- Ville
- Pays
Un composant pour le tableau
Il nous faut maintenant un composant Volt pour afficher le tableau :
php artisan make:volt admin/customers/addresses --class
Avec ce code :
<?php
use Livewire\Volt\Component;
use Livewire\Attributes\{Layout, Title};
use App\Models\Address;
use Mary\Traits\Toast;
use Livewire\WithPagination;
use Illuminate\Database\Eloquent\Builder;
new
#[Title('Addresses')]
#[Layout('components.layouts.admin')]
class extends Component
{
use Toast, WithPagination;
public int $perPage = 10;
public string $search = '';
public array $sortBy = [
'column' => 'name',
'direction' => 'asc',
];
public function headers(): array
{
return [
['key' => 'name', 'label' => __('Name')],
['key' => 'firstname', 'label' => __('Firstname')],
['key' => 'company', 'label' => __('Company name')],
['key' => 'address', 'label' => __('Address')],
['key' => 'postal', 'label' => __('Postcode')],
['key' => 'city', 'label' => __('City')],
['key' => 'country', 'label' => __('Country')],
];
}
public function with(): array
{
return [
'addresses' => Address::with('country')
->when($this->sortBy['column'] === 'country', function (Builder $query) {
$query->join('countries', 'addresses.country_id', '=', 'countries.id')
->orderBy('countries.name', $this->sortBy['direction']);
}, function (Builder $query) {
$query->orderBy($this->sortBy['column'], $this->sortBy['direction']);
})
->when($this->search, function (Builder $query) {
$query->where('addresses.name', 'like', "%{$this->search}%")
->orWhere('company', 'like', "%{$this->search}%")
->orWhere('address', 'like', "%{$this->search}%")
->orWhere('city', 'like', "%{$this->search}%");
})
->paginate($this->perPage),
'headers' => $this->headers(),
];
}
}; ?>
<div>
<x-header title="{{ __('Addresses') }}" separator progress-indicator >
<x-slot:actions>
<x-input
placeholder="{{ __('Search...') }}"
wire:model.live.debounce="search"
clearable
icon="o-magnifying-glass"
/>
<x-button
icon="s-building-office-2"
label="{{ __('Dashboard') }}"
class="btn-outline lg:hidden"
link="{{ route('admin') }}"
/>
</x-slot:actions>
</x-header>
<x-card>
<x-table
striped
:headers="$headers"
:rows="$addresses"
:sort-by="$sortBy"
per-page="perPage"
with-pagination
>
@scope('cell_country', $address)
{{ $address->country->name }}
@endscope
</x-table>
</x-card>
</div>
La route
On ajoute une route pour l'atteindre :
Volt::route('/addresses', 'admin.customers.addresses')->name('admin.addresses');
La navigation
On ajoute un lien dans la barre latérale de l'administration (on prévoit un sous-menu parce qu'on ajoutera plus loin les adresses) :
<x-menu-sub title="{{ __('Customers') }}" icon="s-users">
<x-menu-item title="{{ __('Datas') }}" icon="s-list-bullet" link="{{ route('admin.customers.index') }}" />
<x-menu-item title="{{ __('Addresses') }}" icon="c-map-pin" link="{{ route('admin.addresses') }}" />
</x-menu-sub>
Et voici notre nouveau tableau avec pagination, recherche et tri :
Conclusion
On en a fini avec la gestion des clients et de leurs adresses. Dans la prochaine étape, on codera le catalogue des produits.
Par bestmomo
Aucun commentaire