Filament Laravel
This filament is something to be recon, got used to Voyager but this is the next level.
This playlist is pretty straightforward, understood every bit of it.
laravel new project --jet
# update env for database
composer require filament/filament
php artisan migrate
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\\Permission\\PermissionServiceProvider"
php artisan make:migration add_is_admin_users
Schema::table('users', function(Blueprint $table) {
$table->boolean('is_admin')->after('name')->default(0);
});
use Spatie\\Permission\\Traits\\HasRoles;
class User extends Authenticatable {
use HasRoles;
protected $fillable = [
'is_admin',
];
}
php artisan make:seeder RolesAndPermissionSeeder
# get this to rolesAndPermissionSeeder
<https://gist.github.com/madindo/95e9c7bacd744d27a31760dd0cc31683>
php artisan make:filament-resource Permission --simple
---
use Spatie\Permission\Models\Permission;
protected static ?string $navigationGroup = 'Admin Management';
public static function form(Form $form): Form
{
return $form
->schema([
Card::make()
->schema([
TextInput::make('name')
->unique(ignoreRecord: true)
->required()
])
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('id')->sortable(),
TextColumn::make('name')->sortable()->searchable(),
TextColumn::make('created_at')
->dateTime('d-M-Y')
->sortable()
->searchable()
])
->filters([
//
])
->actions([
Tables\\Actions\\EditAction::make(),
Tables\\Actions\\DeleteAction::make(),
])
->bulkActions([
Tables\\Actions\\DeleteBulkAction::make(),
]);
}
protected function getRedirectUrl(): string
{
return $this->getResource()::getUrl('index');
}
php artisan make:filament-relation-manager RoleResource permissions name
User Resource w/ soft delete
composer require doctrine/dbal --dev
php artisan make:filament-resource User --generate
// to make list of checkbox relationship
CheckboxList::make('roles')
->relationship('roles', 'name')
->columns(2)
->helperText('Only Choose One!')
->required()
// password changing
Forms\\Components\\TextInput::make('password')
->password()
->maxLength(255)
->dehydrateStateUsing(
static fn(null|string $state):
null|string => filled($state) ? Hash::make($state) : null,
)->required(
static fn(Page $livewire):
string => $livewire instanceOf CreateUser,
)->dehydrated(
static fn(null|string $state):
bool => filled($state),
)->label(
static fn(Page $livewire):
string => $livewire instanceOf EditUser ? 'New Password' : "Password"
),
Customizing user menu
php artisan make:provider FilamentServiceProvider
#add to config app.php
App\\Providers\\FilamentServiceProvider::class,
#boot()
Filament::serving(function() {
if (!empty(auth()->user()) && auth()->user()->is_admin == 1 && auth()->user()->hasAnyRole('super-admin', 'admin', 'moderator')) {
Filament::registerUserMenuItems([
UserMenuItem::make()
->label('Manage Users')
->url(UserResource::getUrl())
->icon('heroicon-s-users'),
UserMenuItem::make()
->label('Manage Roles')
->url(RoleResource::getUrl())
->icon('heroicon-s-cog'),
UserMenuItem::make()
->label('Manage Permission')
->url(PermissionResource::getUrl())
->icon('heroicon-s-key')
]);
}
});
// to hide menu sidebar UserResource.php
protected static bool $shouldRegisterNavigation = false;
Authorization with policies
# User.php add
implements FilamentUser
public function canAccessFilament(): bool
{
return $this->is_admin;
}
php artisan make:policy PermissionPolicy --model=Permission
public function viewAny(User $user)
{
return $user->hasAnyRole(['super-admin', 'admin', 'moderator']);
}
# app/Providers/AuthServiceProvider.php
use App\\Policies\\PermissionPolicy;
protected $policies = [
\\Spatie\\Permission\\Models\\Permission::class => PermissionPolicy::class,
];
Dashboard
php artisan make:filament-widget StatsOverview --stats-overview
protected function getCards(): array
{
return [
Card::make('Unique views', '192.1k'),
Card::make('Bounce rate', '21%'),
Card::make('Average time on page', '3:12'),
];
}
php artisan make:filament-widget BlogPostsChart --chart
protected function getData(): array
{
$users = User::select('created_at')->get()->groupBy(function($users) {
return Carbon::parse($users->created_at)->format('F');
});
$quantities = [];
foreach ($users as $user => $value) {
array_push($quantities, $value->count());
}
return [
'datasets' => [
[
'label' => 'Users Joined',
'data' => $quantities,
'backgroundColor' => [
'rgba(255,99,132,0.2',
'rgba(255,99,132,0.2',
'rgba(255,99,132,0.2',
'rgba(255,99,132,0.2',
]
],
],
'labels' => $users->keys(),
];
}
Custom theme
npm install tippy.js --save-dev
#tailwind.config.js
const colors = require('tailwindcss/colors')
colors: {
danger: colors.rose,
primary: colors.blue,
success: colors.green,
warning: colors.yellow,
},
#vite.config.js
'resources/css/filament.css',
#create new file in resources/css/filament.css
@import '../../vendor/filament/filament/resources/css/app.css';
# open filamentServiceProvider in serving function
Filament::registerViteTheme('resources/css/filament.css');
Make only admin accessible
I saw a few tricks to get this done but this is my way, it's simpler.
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_admin')->default(0);
});
// then in user model
use Filament\Models\Contracts\FilamentUser;
... implements FilamentUser
public function canAccessFilament(): bool
{
return $this->is_admin;
}
Import data
I have tried with some plugins but with a new project, the package doesn't work, here's another approach.
First get the package first
composer require maatwebsite/excel
Create the import
php artisan make:import ImportCampaign --model=Campaign
<?php
namespace App\Imports;
use App\Models\Campaign;
use Maatwebsite\Excel\Concerns\ToModel;
class ImportCampaign implements ToModel
{
/**
* @param array $row
*
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
return new Campaign([
'client_id' => $row[0] ?? '',
'date' => $row[1] ?? '',
'name' => $row[2] ?? '',
]);
}
}
Add this to Page/List{model} if User ListUser
// Pages/List{model}
public function getHeader(): ?View {
$data = Actions\CreateAction::make();
return view('filament.custom.upload-file', compact('data'));
}
public function save() {
if ($this->file != '') {
Excel::import(new ImportCampaign, $this->file);
}
}
Look at the get header it return a view, make like this below where wire:submit="save" (on top save())
<div>
<x-filament::breadcrumbs :breadcrumbs="[
'/admin/campaigns' => 'Campaign',
'' => 'List',
]" />
<div class="flex justify-between mt-1">
<h1 class="font-bold text-3xl">Campaign</h1>
<div>{{ $data }}</div>
</div>
</div>
<div>
<form wire:submit="save" class="w-full max-w-sm flex mt-2">
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2" for="fileInput">
Upload File
</label>
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="fileInput" type="file" wire:model='file'>
</div>
<div class="item-center justify-between mt-3 pl-5">
<br>
<button class="text-black font-bold border rounded px-6 py-2" type="submit">
Upload
</button>
</div>
</form>
</div>
Plugins
Settings
composer require reworck/filament-settings
# appServiceProvider - add more in values in here
\Reworck\FilamentSettings\FilamentSettings::setFormFields([
\Filament\Forms\Components\TextInput::make('title'),
\Filament\Forms\Components\Textarea::make('description'),
]);
# in User.php add function below, create permission and add to user if have any
public function canManageSettings(): bool
{
return $this->can('manage.settings');
}