Filament Laravel

Filament Laravel
Filament

This filament is something to be recon, I got used to Voyager but this is the next level.

Filament Admin Panel
How to build a Laravel Admin Panel. Built with Laravel 9, Filament, Spatie’s Laravel-Permissions, Jetstream, Livewire, and Tailwind CSS. Project name will be...

This playlist is pretty straightforward, I 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

V2

I saw a few tricks to get this done but this is my way, it's simpler.

Users - Admin Panel - Filament
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;
}

V3

<?php
 
namespace App\Models;
 
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Foundation\Auth\User as Authenticatable;
 
class User extends Authenticatable implements FilamentUser
{
    // ...
 
    public function canAccessPanel(Panel $panel): bool
    {
        return str_ends_with($this->email, '@yourdomain.com') && $this->hasVerifiedEmail();
    }
}

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>

Export data using native Export from Filament 3

php artisan make:notifications-table // to enable notification panel
php artisan vendor:publish --tag=filament-actions-migrations
php artisan migrate

add ->databaseNotifications() to AdminPanelProvider

php artisan make:filament-exporter Order --generate // it will create a file

make sure php artisan queue:work is working

Custom Pages

In my case, I need to change the way how the form is saved.

php artisan make:filament-page FormResidenceClaim --type=custom

use Filament\Forms\Contracts\HasForms;
use InteractsWithForms;
class FormResidenceClaim extends Page implements HasForms
{
	use InteractsWithForms, InteractsWithRecord;

	public function mount(int | string $record): void
    {
        $this->record = $this->resolveRecord($record);
        //custom edits
        $this->form->fill($this->record->toArray());
    }
    
    public function form(Form $form): Form
    {

        return $form
            ->schema([
                //
            ])
            ->statePath('claim');
    }
    
    protected function getFormActions(): array
    {
        return [
            Action::make('save')
                ->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
                ->submit('save'),
        ];
    }
    
    public function save(): void
    {
        try {
            $data = $this->form->getState();
            !$data ? Model::create($residenceUser) : $this->record->residenceUser->update($residenceUser);
        } catch (Halt $exception) {
            return;
        }

        Notification::make()
            ->success()
            ->title(__('filament-panels::resources/pages/edit-record.notifications.saved.title'))
            ->send();
    }
}

Plugins

Settings

GitHub - reworck/filament-settings: Easy setting management for filament
Easy setting management for filament. Contribute to reworck/filament-settings development by creating an account on GitHub.
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');
}

Subscribe to You Live What You Learn

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe