Filament Laravel
 
                        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/excelCreate 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 workingCustom 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');
} 
                    

