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