Laravel

Un framework qui rend heureux

Voir cette catégorie
Vers le bas
Voir cette série
Mon CMS - Les catégories
Mercredi 25 septembre 2024 16:19

Nous avons précédemment assuré la gestion complète des articles et nous pouvons en créer, en modifier, les lister sous forme de tableau, les supprimer... On a vu que chaque article est rattaché à une catégorie. On doit pouvoir créer des catégories, les modifier, et aussi les supprimer. C'est l'objet du présent article.

Pour rappel, la table des matières est ici.

Un composant pour les catégories

On a à nouveau besoin d'un composant Volt pour gérer le tableau des catégories et le code PHP qui va faire tout le traitement :

php artisan make:volt admin/categories/index --class

On va ajouter la route pour l'atteindre :

use App\Http\Middleware\{IsAdmin, IsAdminOrRedac};

...
Route::middleware('auth')->group(function () {
	...
	Route::middleware(IsAdminOrRedac::class)->prefix('admin')->group(function () {
		...
		Route::middleware(IsAdmin::class)->group(function () {
			Volt::route('/categories/index', 'admin.categories.index')->name('categories.index');
		});
	});
});

Et dans la foulée un item dans la barre latérale (admin.sidebar) :

<x-menu-sub title="{{ __('Posts') }}" icon="s-document-text">
    ...
    @if (Auth::user()->isAdmin())
        <x-menu-item title="{{ __('Categories') }}" link="{{ route('categories.index') }}" />
    @endif
</x-menu-sub>  

On ajoute une traduction :

"Categories": "Catégories",

On peut maintenant atteindre facilement le nouveau composant, il ne nous reste plus qu'à le compléter.

Le tableau des catégories

Vous commencez à être habitués à coder un tableau de MaryUI, voici le code de départ pour les catégories :

<?php

use Livewire\Volt\Component;
use App\Models\Category;
use Livewire\WithPagination;
use Livewire\Attributes\Layout;

new #[Layout('components.layouts.admin')] 
class extends Component {
    use WithPagination;

    public array $sortBy = ['column' => 'title', 'direction' => 'asc'];

    public function headers(): array
	{
		return [['key' => 'title', 'label' => __('Title')], ['key' => 'slug', 'label' => 'Slug']];
	}

	public function with(): array
	{
		return [
			'categories' => Category::orderBy(...array_values($this->sortBy))->paginate(10),
			'headers'    => $this->headers(),
		];
	}

}; ?>

<div>
    <x-header title="{{ __('Categories') }}" separator progress-indicator>
        <x-slot:actions class="lg:hidden">
            <x-button icon="s-building-office-2" label="{{ __('Dashboard') }}" class="btn-outline"
                link="{{ route('admin') }}" />
        </x-slot:actions>
    </x-header>
    <x-card>
        <x-table striped :headers="$headers" :rows="$categories" :sort-by="$sortBy" link="#"
            with-pagination>
        </x-table>
    </x-card>
</div>

Un tableau classique avec pagination, titres de colonnes, données, et tri par colonnes.

Supprimer une catégorie

On doit pouvoir supprimer une catégorie, même si ce sera une action rare, parce qu'à partir du moment où on va avoir des articles rattachés à cette catégorie, on aurait une suppression en chaîne de ces articles, ce qui n'est sans doute pas souhaitable.

Dans le tableau, on ajoute un bouton pour chaque catégorie affichée :

<x-table striped :headers="$headers" :rows="$categories" :sort-by="$sortBy" link="#"
    with-pagination>
        @scope('actions', $category)
        <x-popover>
            <x-slot:trigger>
                <x-button icon="o-trash" wire:click="delete({{ $category->id }})"
                    wire:confirm="{{ __('Are you sure to delete this category?') }}" 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>
    @endscope
</x-table>

Et le code PHP pour gérer cette suppression :

public function delete(Category $category): void
{
    $category->delete();
    $this->success(__('Category deleted with success.'));
}

On a besoin de deux nouvelles traductions :

"Are you sure to delete this category?": "Etes-vous sûr de vouloir supprimer cette catégorie ?",
"Category deleted with success.": "Catégorie supprimée avec succès.",

Créer une catégorie

Le formulaire

Créons à présent le formulaire pour créer une catégorie, on va tout simplement l'ajouter après le tableau. Mais comme ce formulaire sera le même que pour la modification d'une catégorie, on va bien travailler et créer un composant séparé :

Avec ce code :

<x-form wire:submit="save">
    <x-input label="{{ __('Title') }}" wire:model.debounce.500ms="title" wire:change="$refresh" />
    <x-input type="text" wire:model="slug" label="{{ __('Slug') }}" />
    <x-slot:actions>
        <x-button label="{{ __('Save') }}" icon="o-paper-airplane" spinner="save" type="submit"
            class="btn-primary" />
    </x-slot:actions>
</x-form>

L'intégration du formulaire

On intègre ce formulaire dans le composant des catégories (categories.index) :

    <x-card title="{{ __('Create a new category') }}">
        @include('livewire.admin.categories.category-form')
    </x-card>
</div>

Et évidemment on complète le PHP pour gérer la création avec la validation :

...

use Livewire\Attributes\{Layout, Validate};
use Illuminate\Support\Str;
use Mary\Traits\Toast;

new #[Layout('components.layouts.admin')] 
class extends Component {
    use Toast, WithPagination;

    ...

    #[Validate('required|max:255|unique:categories,title')]
	public string $title = '';

    #[Validate('required|max:255|unique:posts,slug|regex:/^[a-z0-9]+(?:-[a-z0-9]+)*$/')]
    public string $slug = '';

    public function updatedTitle($value): void
	{
		$this->generateSlug($value);
	}

    private function generateSlug(string $title): void
	{
		$this->slug = Str::of($title)->slug('-');
	}

    public function save(): void
	{
		$data = $this->validate();
		Category::create($data);
		$this->success(__('Category created with success.'));
	}

On ajoute deux nouvelles traductions :

"Create a new category": "Créer une nouvelle catégorie",
"Category created with success.": "Catégorie créée avec succès.",

On a maintenant le formulaire de création juste après le tableau :

Vérifiez que ça fonctionne et que la nouvelle catégorie est bien ajoutée automatiquement dans le tableau au-dessus.

Modifier une catégorie

Il ne nous manque plus que la modification d'une catégorie. Créons un nouveau composant :

php artisan make:volt admin/categories/edit --class

Le code va beaucoup ressembler à ce qu'on a vu précédemment :

<?php

use App\Models\Category;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Volt\Component;
use Mary\Traits\Toast;

new #[Layout('components.layouts.admin')] 
class extends Component {
	use Toast;

	public Category $category;
	public string $title = '';
	public string $slug  = '';

	public function mount(Category $category): void
	{
		$this->category = $category;
		$this->fill($this->category->toArray());
	}

	public function updatedTitle($value): void
	{
		$this->generateSlug($value);
	}

	public function save(): void
	{
		$data = $this->validate($this->rules());
		$this->category->update($data);
		$this->success(__('Category updated successfully.'), redirectTo: '/admin/categories/index');
	}

	protected function rules(): array
	{
		return [
			'title' => 'required|string|max:255',
			'slug'  => ['required', 'string', 'max:255', 'regex:/^[a-z0-9]+(?:-[a-z0-9]+)*$/', Rule::unique('categories')->ignore($this->category->id)],
		];
	}

	private function generateSlug(string $title): void
	{
		$this->slug = Str::of($title)->slug('-');
	}
}; ?>

<div>
    <x-header title="{{ __('Edit a category') }}" separator progress-indicator>
        <x-slot:actions class="lg:hidden">
            <x-button icon="s-building-office-2" label="{{ __('Dashboard') }}" class="btn-outline"
                link="{{ route('admin') }}" />
        </x-slot:actions>
    </x-header>
    <x-card>
        @include('livewire.admin.categories.category-form')
    </x-card>
</div>

On utilise la méthode mount pour récupérer les données de la catégorie à modifier.

On a besoin de deux nouvelles traductions :

"Category updated successfully.": "Catégorie mise à jour avec succès.",
"Edit a category": "Modifier une catégorie",

Dans le tableau des catégories (categories.index), on renseigne le lien pour accéder au formulaire de modification :

<x-table striped :headers="$headers" :rows="$categories" :sort-by="$sortBy" link="/admin/categories/{id}/edit" with-pagination>

On complète tout ça avec la route :

Route::middleware(IsAdmin::class)->group(function () {
	...
	Volt::route('/categories/{category}/edit', 'admin.categories.edit')->name('categories.edit');
});

Et ça devrait bien fonctionner !

Conclusion

Nous en avons fini avec les catégories. La prochaine étape sera la gestion des pages.

Pour vous simplifier la vie, vous pouvez charger le projet dans son état à l’issue de ce chapitre.



Par bestmomo

Aucun commentaire

Article précédent : Mon CMS - Modifier un article
Article suivant : Mon CMS - Les pages