usersStat(), $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 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::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.'%' .' · '.strtoupper($prediction->source->value); 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); } }