Skip to content

Instantly share code, notes, and snippets.

@avioli
Created December 16, 2023 00:04
Show Gist options
  • Save avioli/59cb490b71a92fce90370c648af6dbe0 to your computer and use it in GitHub Desktop.
Save avioli/59cb490b71a92fce90370c648af6dbe0 to your computer and use it in GitHub Desktop.

Getting started with Filament

Assumptions

This guide assumes you have Docker Desktop installed (or something similar).

Most probably you'll also need Composer, which usually require PHP.

Install Laravel

Make a place for the new project - open a Terminal and make a new folder:

mkdir fildemo
cd fildemo

Install Laravel (using DDEV):

ddev-laravel # see details below

NOTE: If you don't want to use DDEV, then you can follow the official Installation guide. If you do not use DDEV, then replace all use of ddev ... below with most probably sail ... (read more about Sail).

`ddev-laravel` expanded

The ddev-laravel tool is an executable script in my PATH that will do the following commands:

# Use DDEV to setup the stage for Laravel
ddev config --project-type=laravel --docroot=public --upload-dirs='../storage/app/uploads' --create-docroot --php-version=8.1

# Use composer from within the container to install Laravel
ddev composer create --prefer-dist --no-install --no-scripts laravel/laravel -y

# Use composer from within the container to install any other dependencies
ddev composer install

# Generate an APP_KEY in your .env file (used for unique session, cookie and encryption randomness)
ddev php artisan key:generate

# (optional) Open the new Laravel project in your default browser
ddev launch

At the time of this gist Laravel 10 required a minimum of PHP 8.1, so we explicitly specify it above using --php-version=8.1.

Let Laravel Artisan initialise its database:

a migrate # see details below
`a` expanded

The a tool is an alias within my shell defined in my shell config:

alias a='ddev php artisan'

Edit timezone in config/app.php:

-    'timezone' => 'UTC',
+    'timezone' => env('APP_TIMEZONE', 'UTC'),

Add timezone in .env:

APP_TIMEZONE="Australia/Hobart"

GIT

Although optional - this is a good time to init our GIT repository.

git init .
git add . --all
git commit -m "init"

It is a good idea to commit often after every new set of changes that combined make one feature, fix, or refactor.

Filament

Install

ddev composer require filament/filament:"^3.1" --update-with-all-dependencies

Setup

a filament:install --panels

Make an admin user

a make:filament-user

Make models

a make:model Reseller --migration
a make:model RetailItem --migration
a make:model RetailStock --migration

Edit models to add DB fields:

**Reseller**
            $table->string('name');
            $table->string('phone')->nullable();
            $table->string('email')->nullable();
**Retail Item**
            $table->foreignId('reseller_id')->constrained('resellers')->cascadeOnDelete();
            $table->string('name');
            $table->unsignedInteger('selling_price')->nullable();
            $table->unsignedInteger('retail_price')->nullable();
**Retail Stock**
            $table->foreignId('retail_item_id')->constrained('retail_items')->cascadeOnDelete();
            $table->unsignedInteger('quantity');
            $table->string('type');

Edit models to add relationships:

**Reseller**
    public function retailItems(): HasMany
    {
        return $this->hasMany(RetailItem::class);
    }
**Retail Item**
    public function reseller(): BelongsTo
    {
        return $this->belongsTo(Reseller::class);
    }

    public function retailStocks(): HasMany
    {
        return $this->hasMany(RetailStock::class);
    }
**Retail Stock**
    public function retailItem(): BelongsTo
    {
        return $this->belongsTo(RetailItem::class);
    }
**Unguard all models in `AppServiceProvider.php`**
    public function boot(): void
    {
        Model::unguard();
    }

Migrate database:

a migrate

Make a resource - Retail Item

a make:filament-resource RetailItem
**Add form schema**
                Forms\Components\TextInput::make('name')
                    ->maxLength(255)
                    ->required(),
                Forms\Components\Select::make('reseller_id')
                    ->relationship('reseller', 'name')
                    // ->createOptionForm([
                    //     Forms\Components\TextInput::make('name')
                    //         ->columnSpanFull()
                    //         ->maxLength(255)
                    //         ->required(),
                    //     Forms\Components\TextInput::make('email')
                    //         ->label('Email address')
                    //         ->email()
                    //         ->maxLength(255),
                    //     Forms\Components\TextInput::make('phone')
                    //         ->label('Phone number')
                    //         ->tel(),
                    // ])
                    // ->searchable()
                    // ->preload()
                    ->required(),
                Forms\Components\TextInput::make('selling_price')
                    ->numeric()
                    ->prefix('$')
                    ->maxValue(42949672.95),
                Forms\Components\TextInput::make('retail_price')
                    ->numeric()
                    ->prefix('$')
                    ->maxValue(42949672.95),
**Add table columns**
                Tables\Columns\TextColumn::make('name')
                    ->searchable()
                    ->sortable(),
                Tables\Columns\TextColumn::make('reseller.name')
                    ->searchable()
                    ->sortable(),
                // Tables\Columns\TextColumn::make('stock'),
**Add record title**
    protected static ?string $recordTitleAttribute = 'name';

Add a relation manager - Retail Stocks

a make:filament-relation-manager RetailItemResource retailStocks quantity
**Add it to the resource's relations**
    public static function getRelations(): array
    {
        return [
            RelationManagers\RetailStocksRelationManager::class,
        ];
    }
**Add form schema**
                Forms\Components\TextInput::make('quantity')
                    ->columnSpanFull()
                    ->numeric()
                    ->minValue(0)
                    ->maxValue(100)
                    ->required()
                    ->helperText('The number of items either restocked or paid for.'),
                Forms\Components\Radio::make('type')
                    ->options([
                        'restock' => 'Restock',
                        'paid' => 'Paid',
                    ])
                    ->descriptions([
                        'restock' => 'Restocking the reseller.',
                        'paid' => 'Reseller paid for given quantity.',
                    ])
                    ->required(),
**Add table columns**
                Tables\Columns\TextColumn::make('quantity'),
                Tables\Columns\TextColumn::make('type')
                    ->badge()
                    ->color(fn (string $state): string => match ($state) {
                        'restock' => 'gray',
                        'paid' => 'success',
                    }),
                Tables\Columns\TextColumn::make('created_at')
                    ->dateTime('d M Y h:i a'),
**Add table filters**
                Tables\Filters\SelectFilter::make('type')
                    ->options([
                        'restock' => 'Restock',
                        'paid' => 'Paid',
                    ]),

Add a stats widget - Stock

a make:filament-widget RetailItemStockOverview --resource=RetailItemResource --stats-overview
**Add the widget to the Edit page - `EditRetailItem.php`**
    protected function getHeaderWidgets(): array
    {
        return [
            RetailItemResource\Widgets\RetailItemStockOverview::class,
        ];
    }
**Add Stats block**
    public ?Model $record = null;

    protected function getStats(): array
    {
        return [
            // Stat::make('Stock', $this->record->stock),
            Stat::make('Stock', 'computed value goes here'),
        ];
    }
**Implement `RetailItem->stock` computed attribute**

Laravel uses getXXXAttribute convention to give access to "computed" attributes:

    public function getStockAttribute()
    {
        return $this->retailStocks->reduce(function ($carry, $item) {
            $quantity = abs($item->quantity);
            $mul = match ($item->type) {
                'restock' => 1,
                'paid' => -1,
                default => 1,
            };
            return $carry + ($quantity * $mul);
        }, 0);
    }

Above will be available using $item->stock;.

NOTE: camelCase will be converted to snake_case.

Make a new resource - Reseller

a make:filament-resource Reseller --view

Add a relation manager - Retail Items

a make:filament-relation-manager ResellerResource retailItems name --view
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment