Back to Course |
Filament Adminpanel for Booking.com API Project

Manage Properties and Apartments

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.

apartment tabs


Apartments Table

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(),
]);
}
// ...
}

apartments table


Showing Property for Apartments

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.

property relation added

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.

property table

And form with more fields.

property form


Image upload

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.

property 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.

rooms 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;
}
}

apartment tabs