Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions app/Api/Queries/WaterServicesQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class WaterServicesQuery

private ?string $siteStatus = null;

private ?string $siteOutput = null;

private function __construct()
{
}
Expand Down Expand Up @@ -191,6 +193,22 @@ public function siteType(string $siteType): self
return $this;
}

/**
* Control the amount of site metadata included in the response.
*
* Use 'expanded' to include `sourceInfo.siteProperty[]` — an array of key/value
* pairs containing state FIPS code, HUC, county FIPS code, and site type code.
* Defaults to 'default' (basic metadata only) when omitted.
*
* @param string $output Either 'default' or 'expanded'.
*/
public function siteOutput(string $output): self
{
$this->siteOutput = $output;

return $this;
}

/**
* Filter results to sites with the given operational status.
*
Expand Down Expand Up @@ -252,6 +270,7 @@ public function toArray(): array
'endDT' => $this->endDt?->toIso8601String(),
'siteType' => $this->siteType,
'siteStatus' => $this->siteStatus,
'siteOutput' => $this->siteOutput,
], fn ($value) => $value !== null);
}
}
131 changes: 131 additions & 0 deletions app/Livewire/Pages/StationDetail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

namespace App\Livewire\Pages;

use App\Services\StationDetailService;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
use Throwable;

/**
* Full-page component for the USGS station detail view.
*
* Mounted via /hydro/station/{siteNo}. On mount, the component calls
* StationDetailService which fetches 30 days of readings from USGS, upserts the
* station and readings into the DB, and returns chart-ready arrays.
*
* Public properties:
* $stationName — display name (used in the SEO title slot)
* $stationMeta — serialised station metadata for the view
* $streamflowChart — Chart.js-ready {labels, data} for streamflow
* $gageHeightChart — Chart.js-ready {labels, data} for gage height
* $error — user-facing error message on API failure
*/
#[Layout('components.layouts.app')]
#[Title('Station Detail — CronosPulse')]
class StationDetail extends Component
{
protected StationDetailService $stationDetailService;

/**
* Resolve the service on every Livewire lifecycle request.
*/
public function boot(StationDetailService $stationDetailService): void
{
$this->stationDetailService = $stationDetailService;
}

/**
* USGS site number from the route parameter.
*/
public string $siteNo = '';

/**
* Station display name — used in the SEO <title> slot.
*/
public string $stationName = 'Station Detail';

/**
* Station metadata for the view.
*
* @var array<string, mixed>|null
*/
public ?array $stationMeta = null;

/**
* Chart.js-ready streamflow series for the 30-day chart.
*
* @var array{labels: list<string|null>, data: list<float|null>}|null
*/
public ?array $streamflowChart = null;

/**
* Chart.js-ready gage height series for the 30-day chart.
*
* @var array{labels: list<string|null>, data: list<float|null>}|null
*/
public ?array $gageHeightChart = null;

/**
* User-facing error message shown when the API call fails.
*/
public ?string $error = null;

/**
* Load station data from the service and populate page properties.
*
* @param string $siteNo USGS site number from the route segment (e.g. '01646500').
*/
public function mount(string $siteNo): void
{
if (! preg_match('/^\d{1,15}$/', $siteNo)) {
abort(404);
}

$this->siteNo = $siteNo;

try {
$result = $this->stationDetailService->loadStation($siteNo);

$station = $result['station'];
$this->stationName = $station->name;

$this->stationMeta = [
'site_no' => $station->site_no,
'name' => $station->name,
'state' => $station->state,
'county' => $station->county,
'huc' => $station->huc,
'site_type' => $station->site_type,
'latitude' => $station->latitude,
'longitude' => $station->longitude,
'elevation_ft' => $station->elevation_ft,
'is_active' => $station->is_active,
];

$this->streamflowChart = $result['streamflow'];
$this->gageHeightChart = $result['gage_height'];
} catch (Throwable $e) {
Log::error('StationDetail: failed to load site', [
'site_no' => $siteNo,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);

$this->error = 'Unable to load data for station ' . $siteNo . '. The site may not exist or the USGS API is temporarily unavailable.';
}
}

/**
* Render the station detail page.
*/
public function render(): View
{
return view('livewire.pages.station-detail');
}
}
4 changes: 4 additions & 0 deletions app/Models/UsgsStation.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class UsgsStation extends Model
{
/** @use HasFactory<\Database\Factories\UsgsStationFactory> */
use HasFactory;

/**
* @var list<string>
*/
Expand Down
2 changes: 2 additions & 0 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use App\Api\USGSWaterServices;
use App\Services\EarthquakeService;
use App\Services\NWSAlertsService;
use App\Services\StationDetailService;
use App\Services\VolcanoService;
use App\Services\WaterServicesService;
use Illuminate\Support\Facades\URL;
Expand All @@ -30,6 +31,7 @@ public function register(): void
$this->app->singleton(VolcanoService::class);
$this->app->singleton(WaterServicesService::class);
$this->app->singleton(NWSAlertsService::class);
$this->app->singleton(StationDetailService::class);
}

/**
Expand Down
Loading
Loading