Adds complete API reference (api-reference.md) covering all endpoints: /api/stations (nearby search with postcode/lat+lng), /api/stats/searches (aggregated search stats), /api/prediction (7-day price forecast with multi-signal breakdown), and auth routes (register/login/logout/me). Includes request/response examples, error shapes, fuel type aliases, and signal structure details. Also removes unused AccountWidget from admin panel, disables BrentPriceChartWidget discovery, and adds searches stat to StatsOverviewWidget. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
93 lines
2.6 KiB
PHP
93 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Widgets;
|
|
|
|
use App\Models\ApiLog;
|
|
use App\Models\PricePrediction;
|
|
use App\Models\Search;
|
|
use App\Models\Station;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
|
use Filament\Widgets\StatsOverviewWidget\Stat;
|
|
|
|
class StatsOverviewWidget extends BaseWidget
|
|
{
|
|
protected ?string $pollingInterval = '30s';
|
|
|
|
protected function getStats(): array
|
|
{
|
|
return [
|
|
$this->usersStat(),
|
|
$this->searchesStat(),
|
|
$this->stationsStat(),
|
|
$this->oilPredictionStat(),
|
|
$this->apiErrorsStat(),
|
|
];
|
|
}
|
|
|
|
private function usersStat(): Stat
|
|
{
|
|
return Stat::make('Total users', User::count())
|
|
->icon('heroicon-o-users')
|
|
->color('primary');
|
|
}
|
|
|
|
private function searchesStat(): Stat
|
|
{
|
|
return Stat::make('Total searches', Search::count())
|
|
->icon('heroicon-o-magnifying-glass')
|
|
->color('primary');
|
|
}
|
|
|
|
private function stationsStat(): Stat
|
|
{
|
|
$count = Station::count();
|
|
$lastSeen = Station::max('last_seen_at');
|
|
$description = $lastSeen
|
|
? 'Last seen '.Carbon::parse($lastSeen)->diffForHumans()
|
|
: 'No stations yet';
|
|
|
|
return Stat::make('Stations in DB', number_format($count))
|
|
->description($description)
|
|
->icon('heroicon-o-map-pin')
|
|
->color('success');
|
|
}
|
|
|
|
private function oilPredictionStat(): Stat
|
|
{
|
|
$prediction = PricePrediction::bestFirst()->latest('generated_at')->first();
|
|
|
|
if ($prediction === null) {
|
|
return Stat::make('Latest oil prediction', 'None')
|
|
->icon('heroicon-o-beaker')
|
|
->color('gray');
|
|
}
|
|
|
|
$ageHours = $prediction->generated_at->diffInHours(now());
|
|
$color = $ageHours > 24 ? 'warning' : 'success';
|
|
$value = ucfirst($prediction->direction->value)
|
|
.' · '.$prediction->confidence.'%';
|
|
|
|
return Stat::make('Latest oil prediction', $value)
|
|
->description('Generated '.$prediction->generated_at->diffForHumans())
|
|
->icon('heroicon-o-beaker')
|
|
->color($color);
|
|
}
|
|
|
|
private function apiErrorsStat(): Stat
|
|
{
|
|
$errors = ApiLog::where('created_at', '>=', now()->subDay())
|
|
->where(fn ($q) => $q->where('status_code', '>=', 400)
|
|
->orWhereNull('status_code')
|
|
->orWhereNotNull('error'))
|
|
->count();
|
|
|
|
$color = $errors > 0 ? 'danger' : 'success';
|
|
|
|
return Stat::make('API errors (24h)', $errors)
|
|
->icon('heroicon-o-exclamation-triangle')
|
|
->color($color);
|
|
}
|
|
}
|