How to implement crud using client, issues table with laravel livewire

Step 01: running artisan commands for creating project, installing bootstrap, scaffolding auth and installing livewire

composer create-project laravel/laravel livewire 
composer require laravel/ui
php artisan ui bootstrap
php artisan ui bootstrap --auth
npm install
composer require livewire/livewire

Step 02: Setting up vite.config.js like following

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import path from 'path';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/scss/app.scss',
                'resources/js/app.js',
            ],
            refresh: true,
        }),
    ],
    resolve: {
        alias: {
            '~bootstrap': path.resolve(__dirname, 'node_modules/bootstrap'),
        }
    }    
});

Step 03: now importing bootstrap css inside resources/scss/app.scss

@import "~bootstrap/scss/bootstrap";

Step 04: Setting up resources/views/components/layouts/app.blade.php adding $slot variable, @livewireStyles, @livewireScripts for livewire component, vite directive with css and js at the top inside head tag like below

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Clients Issues Crud with Livewire</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  @vite(['resources/scss/app.scss', 'resources/js/app.js'])
  @livewireStyles 
</head>
<body>

<div class="p-5 bg-primary text-white text-center">
  <h1>Clients Issues Crud with livewire</h1>
</div>

<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
  <div class="container">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link active" href="/">Home</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="{{ route('issues') }}">Issues</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="{{ route('issues.create') }}">Create an Issue</a>
      </li>                  
    </ul>
  </div>
</nav>

<div class="container mt-5">
  <div class="row">
    <div class="col-sm-12">
      {{ $slot }}
    </div>
  </div>
</div>

<div class="mt-5 p-4 bg-dark text-white text-center">
  <p>Footer</p>
</div>
@livewireScripts 
</body>
</html>

Step 05: Seeding clients and issues data

Following this link seeding clients and issues data will be implemented.

Step 06: Now, Issues component list with livewire

// with artisan command creating first livewire for showing issue list 
php artisan make:livewire Issues/IssueList

// inside routes/web.php adding routes for issues list 
// at the top 
use App\Livewire\Issues\IssueList;

Route::get('issues', IssueList::class)->name('issues'); 

//Inside app/Livewire/Issues/IssueList making changes like below 

namespace App\Livewire\Issues;

use App\Models\Issue;
use Livewire\Component;
use Illuminate\Contracts\View\View;

class IssueList extends Component
{
    public $page;
    protected $queryString = ['page'];

    public function render(): View
    {
        $issues = Issue::with('client')->latest()->paginate(); //dd($this->page);

        session('submit_redirect', '');
       // creating $this->page variable using public property $page 
// and value of protected property $queryString

if
( $this->page > 0) { // if page query string has value greater than 0
session()->put('submit_redirect', $_SERVER['REQUEST_URI']); //getting current query string from url and saving it to session variable } return view('livewire.issues.issue-list', compact('issues')); } public function delete(Issue $issue): void { $issue->delete(); $redirectTo = session()->get('submit_redirect'); // deleting and redirecting to the page from where deletion is happening which may be not in 1 but in 2,3,4 etc if( !empty( $redirectTo ) ) { redirect(url('').$redirectTo)->with( 'success', 'Issue deleted successfully' ); } } } ?>

// now into livewire.issues.issue-list.blade.php file with edit and delete link for issues
<div>
    <h2 class="text-xl font-semibold leading-tight text-gray-800">
        Issues List
    </h2>

    <table class="table">
    <thead class="thead-dark">
      <tr>
        <th>ID</th>
        <th>Client</th>
        <th>Title</th>
        <th>Description</th>
        <th>Created</th>
        <th>Actions</th>
      </tr>
    </thead>
    <tbody>
        @forelse($issues as $issue)    
        <tr >
            <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                {{ $issue->id }}
            </td>
            <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                {{ $issue->client->name }}
            </td>            
            <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                {{ $issue->title }}
            </td>
            <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                {{  substr($issue->description, 0, 50) . '...' }}
            </td>
            <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                {{ $issue->created_at->toDateString() }}
            </td>
            <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                <a href="{{ route('issues.edit', $issue->id) }}" class="btn btn-sm btn-primary">
                    Edit
                </a>
                <button wire:click="delete({{ $issue }})" class="btn btn-sm btn-danger">
                    Delete
                </button>
            </td>            
        </tr>
        @empty
        <tr>
            <td colspan="4" class="px-6 py-4 text-center leading-5 text-gray-900 whitespace-no-wrap">
                No issues were found.
            </td>
        </tr>
        @endforelse

    </tbody>
  </table> 
  <div class="d-flex justify-content-center mt-3">
  {!! $issues->links() !!}
  </div>
</div>
?>
Now for bootstrap links the html will be broken. To fix this we need to take the help of AppServiceProvider.php and Paginator facade like below 
// at the top 
use Illuminate\Pagination\Paginator;

public function boot(): void
{
    //
    ...
    Paginator::useBootstrap();
}
Now bootstrap navigation html link will be shown correctly. But it will be shown left aligned. To make it center aligned we need to use class d-flex and justify-content-center inside div class value like above.

Step 07:
Now for creating and editing issues with client_id as foreign key

// first livewire component 
php artisan make:livewire Issues/IssueForm

// then adding routes for issues create and edit page 
// at the top 
use App\Livewire\Issues\IssueForm;

Route::get('issues/create', IssueForm::class)->name('issues.create'); 
Route::get('issues/{issue}', IssueForm::class)->name('issues.edit'); 

// then inside app/Livewire/Issues/IssueForm.php itself 
// adding mount(), save(), render() and rules() method like below
use App\Models\Client; use App\Models\Issue; use Illuminate\Http\RedirectResponse; use Livewire\Features\SupportRedirects\Redirector; use Livewire\Component; use Illuminate\Contracts\View\View; class IssueForm extends Component { public ?Issue $issue = null; public string $title= ''; public string|null $description = ''; public int|null $client_id = null; public bool $editing = false; // tracking form whether it is in create or edit mode public $clients; // using this method to load issue first if the form is in edit mode public function mount(Issue $issue): void { $this->issue = $issue; //dd($this->issue); if ($this->issue->exists) { $this->editing = true; $this->title = $issue->title; $this->description = $issue->description; $this->client_id = $issue->client_id; } $this->clients = Client::orderBy('name','asc')->get(); } // this will be default load view method for this livewire component public function render(): View { return view('livewire.issues.issue-form'); } // this method will be used after form is submitted in livewire component public function save(): Redirector|RedirectResponse { $this->validate(); //dd('inside save'); if ( !$this->editing ) { $this->issue = Issue::create($this->only(['title', 'description', 'client_id'])); } else { $this->issue->update($this->only(['title', 'description', 'client_id'])); } return to_route('issues'); } // rules that will be used in livewire component during form submission protected function rules(): array { return [ 'title' => [ 'string', 'required', ], 'description' => [ 'string', 'required', ], 'client_id' => [ 'required', ] ]; } }
// Now changing issue-form.blade.php like below for creating or editing issue 
<div>
    <h1>{{ $editing ? 'Edit Client Issue ' : 'Create Client Issue' }}</h1>
    <div class="card mt-3">
        <div class="card-body">
            <form wire:submit="save">
                <div class="mb-3">
                    <label for="title">Issue title</label>
                    <input wire:model="title" id="title" class="form-control" type="text" name="title" required />
                    @if ($errors->has('title'))
                        <span class="text-danger">{{ $errors->first('title') }}</span>
                    @endif
                </div>
                <div class="mb-3">
                    <label for="email">Description:</label>
                    <textarea  class="form-control" rows="5" id="description" name="description" wire:model="description"></textarea>
                    @if ($errors->has('description'))
                        <span class="text-danger">{{ $errors->first('description') }}</span>
                    @endif
                </div>
                <div class="mb-3">
                    <label for="pwd">Client:</label>
                    <select id="client_id" class="form-control" wire:model="client_id">
                        <option value="" selected>-- Choose client --</option>
                        @foreach( $clients as $client )
                            <option value="{{ $client->id }}" >{{ $client->name }}</option>
                        @endforeach
                    </select>
                    @if ($errors->has('client_id'))
                        <span class="text-danger">{{ $errors->first('client_id') }}</span>
                    @endif
                </div>
                <button type="submit" class="btn btn-primary cursor-not-allowed">
                    <span>Save</span>
                </button>
            </form>
        </div>
    </div>
</div>

So, now after implementiation of all the steps above if arisan ( php artisan serve ) and npm commands ( npm run dev ) are run we will be able to see client issues crud with livewire inside this url http://localhost:8000

This tutorial's github repo can be checked in this url.