This guide assumes you have Docker Desktop installed (or something similar).
Most probably you'll also need Composer, which usually require PHP.
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"
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.
ddev composer require filament/filament:"^3.1" --update-with-all-dependencies
a filament:install --panels
a make:filament-user
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
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';
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',
]),
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
.
a make:filament-resource Reseller --view
a make:filament-relation-manager ResellerResource retailItems name --view