Files
fuel-price/app/Http/Controllers/Api/StatsController.php
Ovidiu U 831637380c
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
feat: expand station cards with detailed information and add live statistics endpoint
- Add `/stats/live` endpoint returning station count and latest price timestamp with 5-minute cache
- Transform StationCard into expandable component with click/keyboard interaction showing full details
- Display brand label, badges (24h/Supermarket/Motorway), fuel types, amenities, opening hours, and price delta vs average
- Add brand filter dropdown to StationList with dynamic brand extraction from results
- Calculate and display price comparison against filtered stations average
- Redesign map markers to simpler price display; move directions link to popup alongside station details
- Add "locate-me" button to SearchBar for geolocation trigger
- Show "Live" indicator with station count and last-update time on homepage hero
- Remove standalone directions link from marker HTML; consolidate in popup with click propagation handling
- Persist `avgPence` calculation across StationList and pass to cards for delta display
- Add `@iconify-json/lucide` dev dependency and register collection on app mount
- Stop click propagation on card action buttons (directions, remove)
2026-04-20 18:58:13 +01:00

72 lines
2.6 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Search;
use App\Models\Station;
use App\Models\StationPrice;
use Carbon\CarbonImmutable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class StatsController extends Controller
{
public function live(): JsonResponse
{
$payload = Cache::remember('api:stats:live', now()->addMinutes(5), function (): array {
$stationCount = Station::query()
->where('permanent_closure', false)
->count();
$latestPriceAt = StationPrice::query()->max('recorded_at');
return [
'station_count' => $stationCount,
'latest_price_at' => $latestPriceAt ? CarbonImmutable::parse($latestPriceAt)->toIso8601String() : null,
];
});
return response()->json($payload);
}
public function searches(Request $request): JsonResponse
{
$period = $request->input('period', 'week');
$days = $period === 'month' ? 30 : 7;
$stats = Search::query()
->where('searched_at', '>=', now()->subDays($days))
->selectRaw('
COUNT(*) as total_searches,
COUNT(DISTINCT ip_hash) as unique_searchers,
AVG(results_count) as avg_results,
AVG(lowest_pence) as avg_lowest_pence,
AVG(highest_pence) as avg_highest_pence,
AVG(avg_pence) as avg_avg_pence
')
->first();
$totalSearches = (int) $stats->total_searches;
$uniqueSearchers = (int) $stats->unique_searchers;
$avgResults = $stats->avg_results !== null ? round((float) $stats->avg_results, 1) : 0.0;
$avgLowestPrice = $stats->avg_lowest_pence !== null ? round((float) $stats->avg_lowest_pence / 100, 1) : 0.0;
$avgHighestPrice = $stats->avg_highest_pence !== null ? round((float) $stats->avg_highest_pence / 100, 1) : 0.0;
$avgPrice = $stats->avg_avg_pence !== null ? round((float) $stats->avg_avg_pence / 100, 1) : 0.0;
$periodLabel = $period === 'month' ? 'month' : 'week';
return response()->json([
'total_searches' => $totalSearches,
'unique_searchers' => $uniqueSearchers,
'avg_results' => $avgResults,
'avg_lowest_price' => $avgLowestPrice,
'avg_highest_price' => $avgHighestPrice,
'avg_price' => $avgPrice,
'period' => $periodLabel,
'message' => "Helped {$uniqueSearchers} drivers find cheaper fuel this {$periodLabel} so far!",
]);
}
}