In this lesson, we will manage apartments. In the table, we will automatically calculate and show the average rating. And this resource will have two relation managers for properties and rooms.
First, we need a Resource for Apartment.
php artisan make:filament-resource ApartmentResource
Next, let's add a form and a table. For calculating the average rating, Filament has aggrregating relationships helpers.
app/Filament/Resources/ApartmentResource.php:
class ApartmentResource extends Resource{ protected static ?string $model = Apartment::class; protected static ?string $navigationIcon = 'heroicon-o-collection'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name') ->required() ->maxLength(255), Forms\Components\Select::make('apartment_type_id') ->preload() ->required() ->searchable() ->relationship('apartment_type', 'name'), Forms\Components\Select::make('property_id') ->preload() ->required() ->searchable() ->relationship('property', 'name'), Forms\Components\TextInput::make('capacity_adults') ->integer() ->required() ->minValue(0), Forms\Components\TextInput::make('capacity_children') ->integer() ->required() ->minValue(0), Forms\Components\TextInput::make('size') ->integer() ->required() ->minValue(0), Forms\Components\TextInput::make('bathrooms') ->integer() ->required() ->minValue(0), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('apartment_type.name'), Tables\Columns\TextColumn::make('property.name'), Tables\Columns\TextColumn::make('size'), Tables\Columns\TextColumn::make('bookings_avg_rating') ->label('Rating') ->placeholder(0) ->avg('bookings', 'rating') ->formatStateUsing(fn (?string $state): ?string => number_format($state, 2)), ]) ->filters([ // ]) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]); } // ...}
Every apartment belongs to a property. So now, let's add a Relation Manager for the Properties.
php artisan make:filament-relation-manager ApartmentResource property name
app/Filament/Resources/ApartmentResource.php:
class ApartmentResource extends Resource{ // ... public static function getRelations(): array { return [ RelationManagers\PropertyRelationManager::class, ]; } // ...}
For the apartment, now it shows properties, but only with the name
field.
So, let's add more fields to the table and the form.
app/Filament/Resources/ApartmentResource/RelationManagers/PropertyRelationManager.php:
use App\Rules\LatitudeRule;use App\Rules\LongitudeRule; class PropertyRelationManager extends RelationManager{ protected static string $relationship = 'property'; protected static ?string $recordTitleAttribute = 'name'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name') ->required(), Forms\Components\TextInput::make('address_street') ->required(), Forms\Components\TextInput::make('address_postcode') ->required(), Forms\Components\Select::make('city_id') ->relationship('city', 'name') ->preload() ->required() ->searchable(), Forms\Components\TextInput::make('lat') ->required() ->rules([new LatitudeRule()]), Forms\Components\TextInput::make('long') ->required() ->rules([new LongitudeRule()]), Forms\Components\Select::make('owner_id') ->relationship('owner', 'name') ->required() ->searchable(), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('owner.name'), Tables\Columns\TextColumn::make('address'), Tables\Columns\TextColumn::make('city.name'), ]) ->filters([ // ]) ->headerActions([ Tables\Actions\CreateAction::make(), ]) ->actions([ Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]); }}
To get the property owner, we need to add this relation to the Property
model.
app/Models/Property.php:
class Property extends Model implements HasMedia{ // ... public function owner(): BelongsTo { return $this->belongsTo(User::class, 'owner_id'); }}
Now we have table with more information.
And form with more fields.
Next, we need to add image upload for the properties. In the Re-creating Booking.com API with Laravel and PHPUnit course, we used spatie/laravel-medialibrary package to handle images.
Luckily, Filament has a plugin to upload files and use the same Media Library package. Let's install this plugin and use it.
composer require filament/spatie-laravel-media-library-plugin:"^2.0"
app/Filament/Resources/ApartmentResource/RelationManagers/PropertyRelationManager.php:
class PropertyRelationManager extends RelationManager{ protected static string $relationship = 'property'; protected static ?string $recordTitleAttribute = 'name'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name') ->required(), Forms\Components\TextInput::make('address_street') ->required(), Forms\Components\TextInput::make('address_postcode') ->required(), Forms\Components\Select::make('city_id') ->relationship('city', 'name') ->preload() ->required() ->searchable(), Forms\Components\TextInput::make('lat') ->required() ->rules([new LatitudeRule()]), Forms\Components\TextInput::make('long') ->required() ->rules([new LongitudeRule()]), Forms\Components\Select::make('owner_id') ->relationship('owner', 'name') ->required() ->searchable(), Forms\Components\SpatieMediaLibraryFileUpload::make('photo') ->image() ->maxSize(5000) ->multiple() ->columnSpanFull() ->collection('avatars') ->conversion('thumbnail'), ]); } // ...}
That's how easily we added file upload.
Next, let's add rooms relation manager.
php artisan make:filament-relation-manager ApartmentResource rooms name
app/Filament/Resources/ApartmentResource.php:
class ApartmentResource extends Resource{ // ... public static function getRelations(): array { return [ RelationManagers\PropertyRelationManager::class, RelationManagers\RoomsRelationManager::class, ]; } // ...}
This adds a second tab in the relation manager.
Next, add additional field to form and table.
app/Filament/Resources/ApartmentResource/RelationManagers/RoomsRelationManager.php:
class RoomsRelationManager extends RelationManager{ protected static string $relationship = 'rooms'; protected static ?string $recordTitleAttribute = 'name'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name') ->required() ->maxLength(255), Forms\Components\Select::make('room_type_id') ->preload() ->required() ->searchable() ->relationship('room_type', 'name'), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('room_type.name'), ]) ->filters([ // ]) ->headerActions([ Tables\Actions\CreateAction::make(), ]) ->actions([ Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]); }}
Also, we can move relation managers to tabs.
app/Filament/Resources/ApartmentResource/Pages/EditApartment.php:
class EditApartment extends EditRecord{ protected static string $resource = ApartmentResource::class; protected function getActions(): array { return [ Actions\DeleteAction::make(), ]; } public function hasCombinedRelationManagerTabsWithForm(): bool { return true; } }