What Files are Inside Livewire+Tailwind CRUD?

When you create a CRUD in Livewire+Tailwind QuickAdminPanel, minimum of 12 new files are generated automatically, and 4 more files are updated. Potentially more, if you use some advanced features/modules.

For example, if you create CRUD called Transactions with a few simple columns like "amount" and "transaction_date", here's the minimum list of generated files.

[New Model]

  • app/Models/Transaction.php

<?php

namespace App\Models;

use \DateTimeInterface;
use App\Support\HasAdvancedFilter;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Transaction extends Model
{
    use HasFactory;
    use HasAdvancedFilter;
    use SoftDeletes;

    public $table = 'transactions';

    public $orderable = [
        'id',
        'amount',
        'transaction_date',
    ];

    public $filterable = [
        'id',
        'amount',
        'transaction_date',
    ];

    protected $fillable = [
        'amount',
        'transaction_date',
    ];

    protected $dates = [
        'transaction_date',
        'created_at',
        'updated_at',
        'deleted_at',
    ];

    public function getTransactionDateAttribute($value)
    {
        return $value ? Carbon::parse($value)->format(config('project.date_format')) : null;
    }

    public function setTransactionDateAttribute($value)
    {
        $this->attributes['transaction_date'] = $value ? Carbon::createFromFormat(config('project.date_format'), $value)->format('Y-m-d') : null;
    }

    protected function serializeDate(DateTimeInterface $date)
    {
        return $date->format('Y-m-d H:i:s');
    }
}

[New Controller]

  • app/Http/Controllers/Admin/TransactionController.php

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Transaction;
use Gate;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class TransactionController extends Controller
{
    public function index()
    {
        abort_if(Gate::denies('transaction_access'), Response::HTTP_FORBIDDEN, '403 Forbidden');

        return view('admin.transaction.index');
    }

    public function create()
    {
        abort_if(Gate::denies('transaction_create'), Response::HTTP_FORBIDDEN, '403 Forbidden');

        return view('admin.transaction.create');
    }

    public function edit(Transaction $transaction)
    {
        abort_if(Gate::denies('transaction_edit'), Response::HTTP_FORBIDDEN, '403 Forbidden');

        return view('admin.transaction.edit', compact('transaction'));
    }

    public function show(Transaction $transaction)
    {
        abort_if(Gate::denies('transaction_show'), Response::HTTP_FORBIDDEN, '403 Forbidden');

        return view('admin.transaction.show', compact('transaction'));
    }
}

[New database migration]

  • database/migrations/2021_04_18_000006_create_transactions_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTransactionsTable extends Migration
{
    public function up()
    {
        Schema::create('transactions', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->decimal('amount', 15, 2)->nullable();
            $table->date('transaction_date')->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }
}

Notice about migrations: after every new or changed CRUD, we regenerate all migration files to make sure they are in the right order, to avoid creating foreign keys on non-existing tables. Therefore, keep in mind that you need to double-check the migration files manually, so they still work after you merge changes.

[New Blade views]

  • resources/views/admin/transaction/create.blade.php

@extends('layouts.admin')
@section('content')

<div class="card bg-blueGray-100">
    <div class="card-header">
        <div class="card-header-container">
            <h6 class="card-title">
                {{ trans('global.create') }}
                {{ trans('cruds.transaction.title_singular') }}
            </h6>
        </div>
    </div>

    <div class="card-body">
        @livewire('transaction.create')
    </div>
</div>
@endsection
  • resources/views/admin/transaction/edit.blade.php

@extends('layouts.admin')
@section('content')

<div class="card bg-blueGray-100">
    <div class="card-header">
        <div class="card-header-container">
            <h6 class="card-title">
                {{ trans('global.edit') }}
                {{ trans('cruds.transaction.title_singular') }}:
                {{ trans('cruds.transaction.fields.id') }}
                {{ $transaction->id }}
            </h6>
        </div>
    </div>

    <div class="card-body">
        @livewire('transaction.edit', [$transaction])
    </div>
</div>
@endsection
  • resources/views/admin/transaction/index.blade.php

@extends('layouts.admin')
@section('content')
<div class="card bg-white">
    <div class="card-header border-b border-blueGray-200">
        <div class="card-header-container">
            <h6 class="card-title">
                {{ trans('cruds.transaction.title_singular') }}
                {{ trans('global.list') }}
            </h6>

            @can('transaction_create')
                <a class="btn btn-indigo" href="{{ route('admin.transactions.create') }}">
                    {{ trans('global.add') }} {{ trans('cruds.transaction.title_singular') }}
                </a>
            @endcan
        </div>
    </div>
    @livewire('transaction.index')

</div>
@endsection
  • resources/views/admin/transaction/show.blade.php

@extends('layouts.admin')
@section('content')

<div class="card bg-blueGray-100">
    <div class="card-header">
        <div class="card-header-container">
            <h6 class="card-title">
                {{ trans('global.view') }}
                {{ trans('cruds.transaction.title_singular') }}:
                {{ trans('cruds.transaction.fields.id') }}
                {{ $transaction->id }}
            </h6>
        </div>
    </div>

    <div class="card-body">
        <div class="pt-3">
            <table class="table table-view">
                <tbody class="bg-white">
                    <tr>
                        <th>
                            {{ trans('cruds.transaction.fields.id') }}
                        </th>
                        <td>
                            {{ $transaction->id }}
                        </td>
                    </tr>
                    <tr>
                        <th>
                            {{ trans('cruds.transaction.fields.amount') }}
                        </th>
                        <td>
                            {{ $transaction->amount }}
                        </td>
                    </tr>
                    <tr>
                        <th>
                            {{ trans('cruds.transaction.fields.transaction_date') }}
                        </th>
                        <td>
                            {{ $transaction->transaction_date }}
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
        <div class="form-group">
            <a href="{{ route('admin.transactions.index') }}" class="btn btn-secondary">
                {{ trans('global.back') }}
            </a>
        </div>
    </div>
</div>
@endsection

[New Livewire Components]

  • app/Http/Livewire/Transaction/Create.php

<?php

namespace App\Http\Livewire\Transaction;

use App\Models\Transaction;
use Livewire\Component;

class Create extends Component
{
    public Transaction $transaction;

    public function mount(Transaction $transaction)
    {
        $this->transaction = $transaction;
    }

    public function render()
    {
        return view('livewire.transaction.create');
    }

    public function submit()
    {
        $this->validate();

        $this->transaction->save();

        return redirect()->route('admin.transactions.index');
    }

    protected function rules(): array
    {
        return [
            'transaction.amount' => [
                'numeric',
                'nullable',
            ],
            'transaction.transaction_date' => [
                'nullable',
                'date_format:' . config('project.date_format'),
            ],
        ];
    }
}
  • app/Http/Livewire/Transaction/Edit.php

class Edit extends Component
{
    public Transaction $transaction;

    public function mount(Transaction $transaction)
    {
        $this->transaction = $transaction;
    }

    public function render()
    {
        return view('livewire.transaction.edit');
    }

    public function submit()
    {
        $this->validate();

        $this->transaction->save();

        return redirect()->route('admin.transactions.index');
    }

    protected function rules(): array
    {
        return [
            'transaction.amount' => [
                'numeric',
                'nullable',
            ],
            'transaction.transaction_date' => [
                'nullable',
                'date_format:' . config('project.date_format'),
            ],
        ];
    }
}
  • app/Http/Livewire/Transaction/Index.php

<?php

namespace App\Http\Livewire\Transaction;

use App\Http\Livewire\WithConfirmation;
use App\Http\Livewire\WithSorting;
use App\Models\Transaction;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Gate;
use Livewire\Component;
use Livewire\WithPagination;

class Index extends Component
{
    use WithPagination;
    use WithSorting;
    use WithConfirmation;

    public int $perPage;

    public array $orderable;

    public string $search = '';

    public array $selected = [];

    public array $paginationOptions;

    protected $queryString = [
        'search' => [
            'except' => '',
        ],
        'sortBy' => [
            'except' => 'id',
        ],
        'sortDirection' => [
            'except' => 'desc',
        ],
    ];

    public function getSelectedCountProperty()
    {
        return count($this->selected);
    }

    public function updatingSearch()
    {
        $this->resetPage();
    }

    public function updatingPerPage()
    {
        $this->resetPage();
    }

    public function resetSelected()
    {
        $this->selected = [];
    }

    public function mount()
    {
        $this->sortBy            = 'id';
        $this->sortDirection     = 'desc';
        $this->perPage           = 100;
        $this->paginationOptions = config('project.pagination.options');
        $this->orderable         = (new Transaction())->orderable;
    }

    public function render()
    {
        $query = Transaction::advancedFilter([
            's'               => $this->search ?: null,
            'order_column'    => $this->sortBy,
            'order_direction' => $this->sortDirection,
        ]);

        $transactions = $query->paginate($this->perPage);

        return view('livewire.transaction.index', compact('query', 'transactions', 'transactions'));
    }

    public function deleteSelected()
    {
        abort_if(Gate::denies('transaction_delete'), Response::HTTP_FORBIDDEN, '403 Forbidden');

        Transaction::whereIn('id', $this->selected)->delete();

        $this->resetSelected();
    }

    public function delete(Transaction $transaction)
    {
        abort_if(Gate::denies('transaction_delete'), Response::HTTP_FORBIDDEN, '403 Forbidden');

        $transaction->delete();
    }
}

[New Livewire Blade views]

  • resources/views/livewire/transaction/create.blade.php

<form wire:submit.prevent="submit" class="pt-3">

    <div class="form-group {{ $errors->has('transaction.amount') ? 'invalid' : '' }}">
        <label class="form-label" for="amount">{{ trans('cruds.transaction.fields.amount') }}</label>
        <input class="form-control" type="number" name="amount" id="amount" wire:model.defer="transaction.amount" step="0.01">
        <div class="validation-message">
            {{ $errors->first('transaction.amount') }}
        </div>
        <div class="help-block">
            {{ trans('cruds.transaction.fields.amount_helper') }}
        </div>
    </div>
    <div class="form-group {{ $errors->has('transaction.transaction_date') ? 'invalid' : '' }}">
        <label class="form-label" for="transaction_date">{{ trans('cruds.transaction.fields.transaction_date') }}</label>
        <x-date-picker class="form-control" wire:model="transaction.transaction_date" id="transaction_date" name="transaction_date" picker="date" />
        <div class="validation-message">
            {{ $errors->first('transaction.transaction_date') }}
        </div>
        <div class="help-block">
            {{ trans('cruds.transaction.fields.transaction_date_helper') }}
        </div>
    </div>

    <div class="form-group">
        <button class="btn btn-indigo mr-2" type="submit">
            {{ trans('global.save') }}
        </button>
        <a href="{{ route('admin.transactions.index') }}" class="btn btn-secondary">
            {{ trans('global.cancel') }}
        </a>
    </div>
</form>
  • resources/views/livewire/transaction/edit.blade.php

<form wire:submit.prevent="submit" class="pt-3">

    <div class="form-group {{ $errors->has('transaction.amount') ? 'invalid' : '' }}">
        <label class="form-label" for="amount">{{ trans('cruds.transaction.fields.amount') }}</label>
        <input class="form-control" type="number" name="amount" id="amount" wire:model.defer="transaction.amount" step="0.01">
        <div class="validation-message">
            {{ $errors->first('transaction.amount') }}
        </div>
        <div class="help-block">
            {{ trans('cruds.transaction.fields.amount_helper') }}
        </div>
    </div>
    <div class="form-group {{ $errors->has('transaction.transaction_date') ? 'invalid' : '' }}">
        <label class="form-label" for="transaction_date">{{ trans('cruds.transaction.fields.transaction_date') }}</label>
        <x-date-picker class="form-control" wire:model="transaction.transaction_date" id="transaction_date" name="transaction_date" picker="date" />
        <div class="validation-message">
            {{ $errors->first('transaction.transaction_date') }}
        </div>
        <div class="help-block">
            {{ trans('cruds.transaction.fields.transaction_date_helper') }}
        </div>
    </div>

    <div class="form-group">
        <button class="btn btn-indigo mr-2" type="submit">
            {{ trans('global.save') }}
        </button>
        <a href="{{ route('admin.transactions.index') }}" class="btn btn-secondary">
            {{ trans('global.cancel') }}
        </a>
    </div>
</form>
  • resources/views/livewire/transaction/index.blade.php

<div>
    <div class="card-controls sm:flex">
        <div class="w-full sm:w-1/2">
            Per page:
            <select wire:model="perPage" class="form-select w-full sm:w-1/6">
                @foreach($paginationOptions as $value)
                    <option value="{{ $value }}">{{ $value }}</option>
                @endforeach
            </select>

            <button class="btn btn-rose ml-3 disabled:opacity-50 disabled:cursor-not-allowed" type="button" wire:click="confirm('deleteSelected')" wire:loading.attr="disabled" {{ $this->selectedCount ? '' : 'disabled' }}>
                {{ __('Delete Selected') }}
            </button>

        </div>
        <div class="w-full sm:w-1/2 sm:text-right">
            Search:
            <input type="text" wire:model.debounce.300ms="search" class="w-full sm:w-1/3 inline-block" />
        </div>
    </div>
    <div wire:loading.delay class="col-12 alert alert-info">
        Loading...
    </div>
    <table class="table table-index w-full">
        <thead>
            <tr>
                <th class="w-9">
                </th>
                <th class="w-28">
                    {{ trans('cruds.transaction.fields.id') }}
                    @include('components.table.sort', ['field' => 'id'])
                </th>
                <th>
                    {{ trans('cruds.transaction.fields.amount') }}
                    @include('components.table.sort', ['field' => 'amount'])
                </th>
                <th>
                    {{ trans('cruds.transaction.fields.transaction_date') }}
                    @include('components.table.sort', ['field' => 'transaction_date'])
                </th>
                <th>
                </th>
            </tr>
        </thead>
        <tbody>
            @forelse($transactions as $transaction)
                <tr>
                    <td>
                        <input type="checkbox" value="{{ $transaction->id }}" wire:model="selected">
                    </td>
                    <td>
                        {{ $transaction->id }}
                    </td>
                    <td>
                        {{ $transaction->amount }}
                    </td>
                    <td>
                        {{ $transaction->transaction_date }}
                    </td>
                    <td>
                        <div class="flex justify-end">
                            @can('transaction_show')
                                <a class="btn btn-sm btn-info mr-2" href="{{ route('admin.transactions.show', $transaction) }}">
                                    {{ trans('global.view') }}
                                </a>
                            @endcan
                            @can('transaction_edit')
                                <a class="btn btn-sm btn-success mr-2" href="{{ route('admin.transactions.edit', $transaction) }}">
                                    {{ trans('global.edit') }}
                                </a>
                            @endcan
                            @can('transaction_delete')
                                <button class="btn btn-sm btn-rose mr-2" type="button" wire:click="confirm('delete', {{ $transaction->id }})" wire:loading.attr="disabled">
                                    {{ trans('global.delete') }}
                                </button>
                            @endcan
                        </div>
                    </td>
                </tr>
                @empty
                <tr>
                    <td colspan="10">No entries found.</td>
                </tr>
            @endforelse
        </tbody>
    </table>

    <div class="card-body">
        <div class="pt-3">
            @if($this->selectedCount)
                <p class="text-sm leading-5">
                    <span class="font-medium">
                        {{ $this->selectedCount }}
                    </span>
                    {{ __('Entries selected') }}
                </p>
            @endif
            {{ $transactions->links() }}
        </div>
    </div>
</div>

@push('scripts')
    <script>
        Livewire.on('confirm', e => {
    if (!confirm("{{ trans('global.areYouSure') }}")) {
        return
    }
@this[e.callback](...e.argv)
})
    </script>
@endpush

[Changed main menu Blade Component]

  • resources/views/components/sidebar.blade.php

<nav class="md:left-0 md:block md:fixed md:top-0 md:bottom-0 md:overflow-y-auto md:flex-row md:flex-nowrap md:overflow-hidden shadow-xl bg-white flex flex-wrap items-center justify-between relative md:w-64 z-10 py-4 px-6">
    <div class="md:flex-col md:items-stretch md:min-h-full md:flex-nowrap px-0 flex flex-wrap items-center justify-between w-full mx-auto">
        <button class="cursor-pointer text-black opacity-50 md:hidden px-3 py-1 text-xl leading-none bg-transparent rounded border border-solid border-transparent" type="button" onclick="toggleNavbar('example-collapse-sidebar')">
            <i class="fas fa-bars"></i>
        </button>
        <a class="md:block text-left md:pb-2 text-blueGray-700 mr-0 inline-block whitespace-nowrap text-sm uppercase font-bold p-4 px-0" href="{{ route('admin.home') }}">
            {{ trans('panel.site_title') }}
        </a>
        <div class="md:flex md:flex-col md:items-stretch md:opacity-100 md:relative md:mt-4 md:shadow-none shadow absolute top-0 left-0 right-0 z-40 overflow-y-auto overflow-x-hidden h-auto items-center flex-1 rounded hidden" id="example-collapse-sidebar">
            <div class="md:min-w-full md:hidden block pb-4 mb-4 border-b border-solid border-blueGray-300">
                <div class="flex flex-wrap">
                    <div class="w-6/12">
                        <a class="md:block text-left md:pb-2 text-blueGray-700 mr-0 inline-block whitespace-nowrap text-sm uppercase font-bold p-4 px-0" href="{{ route('admin.home') }}">
                            {{ trans('panel.site_title') }}
                        </a>
                    </div>
                    <div class="w-6/12 flex justify-end">
                        <button type="button" class="cursor-pointer text-black opacity-50 md:hidden px-3 py-1 text-xl leading-none bg-transparent rounded border border-solid border-transparent" onclick="toggleNavbar('example-collapse-sidebar')">
                            <i class="fas fa-times"></i>
                        </button>
                    </div>
                </div>
            </div>
            <!-- Divider -->
            <hr class="mb-6 md:min-w-full" />
            <!-- Heading -->

            <ul class="md:flex-col md:min-w-full flex flex-col list-none">
                <li class="items-center">
                    <a href="{{ route("admin.home") }}" class="{{ request()->is("admin") ? "sidebar-nav-active" : "sidebar-nav" }}">
                        <i class="fas fa-tv"></i>
                        {{ trans('global.dashboard') }}
                    </a>
                </li>
                
                @can('transaction_access')
                    <li class="items-center">
                        <a class="{{ request()->is("admin/transactions*") ? "sidebar-nav-active" : "sidebar-nav" }}" href="{{ route("admin.transactions.index") }}">
                            <i class="fa-fw c-sidebar-nav-icon fas fa-cogs">
                            </i>
                            {{ trans('cruds.transaction.title') }}
                        </a>
                    </li>
                @endcan

                @if(file_exists(app_path('Http/Controllers/Auth/ChangePasswordController.php')))
                    @can('profile_password_edit')
                        <li class="items-center">
                            <a href="{{ route("profile.password.edit") }}" class="{{ request()->is("profile/password") || request()->is("profile/password/*") ? "sidebar-nav-active" : "sidebar-nav" }}">
                                <i class="fas fa-cogs"></i>
                                {{ trans('global.change_password') }}
                            </a>
                        </li>
                    @endcan
                @endif

                <li class="items-center">
                    <a href="#" onclick="event.preventDefault(); document.getElementById('logoutform').submit();" class="sidebar-nav">
                        <i class="fa-fw fas fa-sign-out-alt"></i>
                        {{ trans('global.logout') }}
                    </a>
                </li>
            </ul>
        </div>
    </div>
</nav>

[Changed main routes]

  • routes/web.php

<?php

// ...

Route::group(['prefix' => 'admin', 'as' => 'admin.', 'middleware' => ['auth']], function () {
    // ... other routes

    // Transactions
    Route::resource('transactions', TransactionController::class, ['except' => ['store', 'update', 'destroy']]);
});

[Changed Seeds for Permissions]

  • database/seeds/PermissionsTableSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Permission;
use Illuminate\Database\Seeder;

class PermissionsTableSeeder extends Seeder
{
    public function run()
    {
        $permissions = [
            // ... other permissions
            
            [
                'id'    => 28,
                'title' => 'transaction_create',
            ],
            [
                'id'    => 29,
                'title' => 'transaction_edit',
            ],
            [
                'id'    => 30,
                'title' => 'transaction_show',
            ],
            [
                'id'    => 31,
                'title' => 'transaction_delete',
            ],
            [
                'id'    => 32,
                'title' => 'transaction_access',
            ],
        ];

        Permission::insert($permissions);
    }
}

[Changed Translation Files for new CRUD]

  • resources/lang/en/cruds.php

<?php

return [
    // ... other translations

    'transaction' => [
        'title'          => 'Transactions',
        'title_singular' => 'Transaction',
        'fields'         => [
            'id'                      => 'ID',
            'id_helper'               => ' ',
            'amount'                  => 'Amount',
            'amount_helper'           => ' ',
            'transaction_date'        => 'Transaction Date',
            'transaction_date_helper' => ' ',
            'created_at'              => 'Created at',
            'created_at_helper'       => ' ',
            'updated_at'              => 'Updated at',
            'updated_at_helper'       => ' ',
            'deleted_at'              => 'Deleted at',
            'deleted_at_helper'       => ' ',
        ],
    ],
];

Last updated