From 52dc225b3d8e5992ab1bf10715b4e9fa5bf92e38 Mon Sep 17 00:00:00 2001 From: Ovidiu U Date: Sat, 4 Apr 2026 14:27:22 +0100 Subject: [PATCH] feat: add StationResource with poll action and view page Co-Authored-By: Claude Sonnet 4.6 --- app/Filament/Resources/StationResource.php | 118 ++++++++++++++++++ .../StationResource/Pages/ListStations.php | 35 ++++++ .../StationResource/Pages/ViewStation.php | 16 +++ tests/Feature/Admin/StationResourceTest.php | 37 ++++++ 4 files changed, 206 insertions(+) create mode 100644 app/Filament/Resources/StationResource.php create mode 100644 app/Filament/Resources/StationResource/Pages/ListStations.php create mode 100644 app/Filament/Resources/StationResource/Pages/ViewStation.php create mode 100644 tests/Feature/Admin/StationResourceTest.php diff --git a/app/Filament/Resources/StationResource.php b/app/Filament/Resources/StationResource.php new file mode 100644 index 0000000..0eb459e --- /dev/null +++ b/app/Filament/Resources/StationResource.php @@ -0,0 +1,118 @@ +columns([ + TextColumn::make('trading_name')->searchable()->sortable(), + TextColumn::make('brand_name')->searchable()->placeholder('—'), + TextColumn::make('postcode')->searchable(), + TextColumn::make('city'), + IconColumn::make('is_supermarket')->label('Supermarket')->boolean(), + IconColumn::make('is_motorway_service_station')->label('Motorway')->boolean(), + IconColumn::make('temporary_closure') + ->label('Temp closed') + ->boolean() + ->trueColor('warning') + ->falseColor('success'), + TextColumn::make('last_seen_at')->dateTime('d M Y H:i')->sortable(), + ]) + ->searchPlaceholder('Search name, brand, or postcode...') + ->defaultSort('last_seen_at', 'desc') + ->filters([ + TernaryFilter::make('is_supermarket')->label('Supermarket'), + TernaryFilter::make('is_motorway_service_station')->label('Motorway'), + TernaryFilter::make('temporary_closure')->label('Temporarily closed'), + TernaryFilter::make('permanent_closure')->label('Permanently closed'), + ]) + ->recordActions([ + ViewAction::make(), + ]); + } + + public static function infolist(Schema $schema): Schema + { + return $schema->components([ + Section::make('Location')->schema([ + TextEntry::make('trading_name'), + TextEntry::make('brand_name')->placeholder('—'), + TextEntry::make('address_line_1'), + TextEntry::make('address_line_2')->placeholder('—'), + TextEntry::make('city'), + TextEntry::make('county')->placeholder('—'), + TextEntry::make('postcode'), + TextEntry::make('country'), + ])->columns(3), + Section::make('Status')->schema([ + TextEntry::make('is_supermarket') + ->label('Supermarket') + ->badge() + ->formatStateUsing(fn (bool $state) => $state ? 'Yes' : 'No') + ->color(fn (bool $state) => $state ? 'success' : 'gray'), + TextEntry::make('is_motorway_service_station') + ->label('Motorway') + ->badge() + ->formatStateUsing(fn (bool $state) => $state ? 'Yes' : 'No') + ->color(fn (bool $state) => $state ? 'success' : 'gray'), + TextEntry::make('temporary_closure') + ->label('Temp Closed') + ->badge() + ->formatStateUsing(fn (bool $state) => $state ? 'Yes' : 'No') + ->color(fn (bool $state) => $state ? 'warning' : 'success'), + TextEntry::make('permanent_closure') + ->label('Permanently Closed') + ->badge() + ->formatStateUsing(fn (bool $state) => $state ? 'Yes' : 'No') + ->color(fn (bool $state) => $state ? 'danger' : 'success'), + TextEntry::make('permanent_closure_date')->date()->placeholder('—'), + TextEntry::make('last_seen_at')->dateTime('d M Y H:i'), + ])->columns(3), + Section::make('Fuel Types')->schema([ + TextEntry::make('fuel_types') + ->listWithLineBreaks() + ->columnSpanFull(), + ]), + Section::make('Amenities')->schema([ + TextEntry::make('amenities') + ->listWithLineBreaks() + ->placeholder('None recorded') + ->columnSpanFull(), + ]), + ]); + } + + public static function getPages(): array + { + return [ + 'index' => ListStations::route('/'), + 'view' => ViewStation::route('/{record}'), + ]; + } +} diff --git a/app/Filament/Resources/StationResource/Pages/ListStations.php b/app/Filament/Resources/StationResource/Pages/ListStations.php new file mode 100644 index 0000000..01d59e0 --- /dev/null +++ b/app/Filament/Resources/StationResource/Pages/ListStations.php @@ -0,0 +1,35 @@ +label('Trigger Full Poll') + ->icon('heroicon-o-arrow-path') + ->requiresConfirmation() + ->modalHeading('Trigger full station refresh?') + ->modalDescription('This dispatches a background job to refresh all ~14,500 stations from the Fuel Finder API. Results will appear in API Logs once complete.') + ->action(function () { + PollFuelPricesJob::dispatch(); + + Notification::make() + ->title('Poll dispatched to queue') + ->body('Check API Logs once the job completes.') + ->success() + ->send(); + }), + ]; + } +} diff --git a/app/Filament/Resources/StationResource/Pages/ViewStation.php b/app/Filament/Resources/StationResource/Pages/ViewStation.php new file mode 100644 index 0000000..18ac4d3 --- /dev/null +++ b/app/Filament/Resources/StationResource/Pages/ViewStation.php @@ -0,0 +1,16 @@ +admin = User::factory()->admin()->create(); + $this->actingAs($this->admin); +}); + +it('renders the station list', function () { + $stations = Station::factory()->count(3)->create(); + + Livewire::test(ListStations::class) + ->assertOk() + ->assertCanSeeTableRecords($stations); +}); + +it('filters to supermarkets only', function () { + $regular = Station::factory()->create(['is_supermarket' => false]); + $super = Station::factory()->supermarket()->create(); + + Livewire::test(ListStations::class) + ->filterTable('is_supermarket', true) + ->assertCanSeeTableRecords([$super]) + ->assertCanNotSeeTableRecords([$regular]); +}); + +it('has a trigger full poll header action', function () { + Livewire::test(ListStations::class) + ->assertActionExists('triggerFullPoll'); +});