feat(rover-planning): clickable rows + callsign·grid labels + 10-char heading grid
- Rows in each rover-location group are now phx-click handlers that navigate to /path with source (rover-loc as 10-char Maidenhead grid), destination (callsign > grid > coords), band, and rover/station heights pre-filled — one click takes you to a ready-to-compute Path Calculator screen. - Station label now shows "AA5C · EM12kp" when both callsign and grid resolved, instead of falling back to lat/lon. Bare-grid and bare-coord stations keep their existing labels. - Group heading uses Maidenhead.from_latlon(lat, lon, 10) so the 10-char grid is shown in full instead of being truncated to 6 characters.
This commit is contained in:
parent
5ac625915d
commit
dbd4a78502
2 changed files with 96 additions and 2 deletions
|
|
@ -10,6 +10,7 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
|||
alias Microwaveprop.Radio.Maidenhead
|
||||
alias Microwaveprop.RoverPlanning
|
||||
alias Microwaveprop.RoverPlanning.Mission
|
||||
alias Phoenix.LiveView.JS
|
||||
|
||||
@impl true
|
||||
def mount(%{"id" => id}, _session, socket) do
|
||||
|
|
@ -152,6 +153,9 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
|||
defp format_meters(nil), do: "—"
|
||||
defp format_meters(value) when is_number(value), do: "#{Float.round(value * 1.0, 1)} m"
|
||||
|
||||
defp station_label(%{station: %{callsign: c, grid: g}}) when is_binary(c) and c != "" and is_binary(g) and g != "",
|
||||
do: "#{c} · #{g}"
|
||||
|
||||
defp station_label(%{station: %{callsign: c}}) when is_binary(c) and c != "", do: c
|
||||
defp station_label(%{station: %{grid: g}}) when is_binary(g) and g != "", do: g
|
||||
|
||||
|
|
@ -160,6 +164,41 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
|||
|
||||
defp station_label(_), do: "—"
|
||||
|
||||
# Endpoint string used for /path?destination=…. Prefers callsign, then
|
||||
# grid (so PathLive's LocationResolver re-resolves to the same point),
|
||||
# falling back to bare coordinates.
|
||||
defp station_endpoint(%{callsign: c}) when is_binary(c) and c != "", do: c
|
||||
defp station_endpoint(%{grid: g}) when is_binary(g) and g != "", do: g
|
||||
|
||||
defp station_endpoint(%{lat: lat, lon: lon}) when is_number(lat) and is_number(lon), do: "#{lat},#{lon}"
|
||||
|
||||
defp station_endpoint(_), do: ""
|
||||
|
||||
# Builds the /path URL pre-filling source (rover-location, max-precision
|
||||
# grid), destination (station endpoint), band, and rover/station heights
|
||||
# so the user lands on a ready-to-compute Path Calculator screen.
|
||||
defp path_url(_mission, %{rover_location: nil}), do: nil
|
||||
defp path_url(_mission, %{station: nil}), do: nil
|
||||
|
||||
defp path_url(%Mission{} = mission, %{rover_location: %{lat: lat, lon: lon}, station: station})
|
||||
when is_number(lat) and is_number(lon) do
|
||||
src = Maidenhead.from_latlon(lat, lon, 10)
|
||||
dst = station_endpoint(station)
|
||||
|
||||
query =
|
||||
URI.encode_query(%{
|
||||
"source" => src,
|
||||
"destination" => dst,
|
||||
"band" => Integer.to_string(mission.band_mhz),
|
||||
"src_height_ft" => Float.to_string(mission.rover_height_ft * 1.0),
|
||||
"dst_height_ft" => Float.to_string(mission.station_height_ft * 1.0)
|
||||
})
|
||||
|
||||
"/path?" <> query
|
||||
end
|
||||
|
||||
defp path_url(_, _), do: nil
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
summary = progress_summary(assigns.paths)
|
||||
|
|
@ -270,7 +309,14 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr :for={path <- group_paths}>
|
||||
<tr
|
||||
:for={path <- group_paths}
|
||||
phx-click={path_url(@mission, path) && JS.navigate(path_url(@mission, path))}
|
||||
class={[
|
||||
path_url(@mission, path) && "cursor-pointer hover:bg-base-200"
|
||||
]}
|
||||
title={path_url(@mission, path) && "Open in Path Calculator"}
|
||||
>
|
||||
<td class="font-mono text-xs">{station_label(path)}</td>
|
||||
<td>{status_badge(path.status)}</td>
|
||||
<td class="text-xs">
|
||||
|
|
@ -297,7 +343,10 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
|||
end
|
||||
|
||||
defp rover_location_heading(%{lat: lat, lon: lon}) when is_number(lat) and is_number(lon) do
|
||||
grid = Maidenhead.from_latlon(lat, lon, 6)
|
||||
# 10-char grid is the maximum useful Maidenhead precision; anything
|
||||
# finer is below the float lat/lon's resolution. The user wants the
|
||||
# full precision, not a 6-char truncation.
|
||||
grid = Maidenhead.from_latlon(lat, lon, 10)
|
||||
"#{grid} (#{Float.round(lat, 4)}, #{Float.round(lon, 4)})"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ defmodule MicrowavepropWeb.RoverPlanningLiveTest do
|
|||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Microwaveprop.AccountsFixtures
|
||||
alias Microwaveprop.Radio.Maidenhead
|
||||
alias Microwaveprop.Repo
|
||||
alias Microwaveprop.Rover
|
||||
alias Microwaveprop.RoverPlanning
|
||||
|
|
@ -74,6 +75,50 @@ defmodule MicrowavepropWeb.RoverPlanningLiveTest do
|
|||
assert html =~ "Path profiles"
|
||||
end
|
||||
|
||||
test "group heading uses max-precision Maidenhead grid (10 chars)", %{conn: conn} do
|
||||
user = AccountsFixtures.user_fixture()
|
||||
mission = create_mission(user, "Precision mission")
|
||||
|
||||
{:ok, _lv, html} = live(conn, ~p"/rover-planning/#{mission.id}")
|
||||
|
||||
# Maidenhead.from_latlon(32.0, -97.0, 10) → "EM12VX00AA" (or similar
|
||||
# 10-char grid). The group heading must NOT truncate at 6 chars.
|
||||
grid_10 = Maidenhead.from_latlon(32.0, -97.0, 10)
|
||||
assert String.length(grid_10) == 10
|
||||
assert html =~ grid_10
|
||||
end
|
||||
|
||||
test "station label shows callsign + grid together when both are known", %{conn: conn} do
|
||||
user = AccountsFixtures.user_fixture()
|
||||
mission = create_mission(user, "Callsign-grid mission")
|
||||
|
||||
[path | _] = Path |> Repo.all() |> Repo.preload(:station)
|
||||
{:ok, _} = path.station |> Ecto.Changeset.change(%{callsign: "AA5C", grid: "EM13se"}) |> Repo.update()
|
||||
|
||||
{:ok, _lv, html} = live(conn, ~p"/rover-planning/#{mission.id}")
|
||||
|
||||
# The path table should display "AA5C · EM13se" so the operator can
|
||||
# see both at a glance — not just one or the other and not lat/lon.
|
||||
assert html =~ "AA5C · EM13se"
|
||||
end
|
||||
|
||||
test "path rows link to /path with mission params prefilled", %{conn: conn} do
|
||||
user = AccountsFixtures.user_fixture()
|
||||
mission = create_mission(user, "Click-through mission")
|
||||
|
||||
{:ok, lv, _html} = live(conn, ~p"/rover-planning/#{mission.id}")
|
||||
|
||||
# Each path row carries a phx-click that navigates to /path with
|
||||
# the mission's band + heights and the rover/station endpoints
|
||||
# filled in. Assert the click handler is present on the <tr>.
|
||||
rendered = render(lv)
|
||||
assert rendered =~ "phx-click"
|
||||
assert rendered =~ "/path?"
|
||||
assert rendered =~ "band=10000"
|
||||
assert rendered =~ "src_height_ft=8.0"
|
||||
assert rendered =~ "dst_height_ft=30.0"
|
||||
end
|
||||
|
||||
test "groups paths by rover location", %{conn: conn} do
|
||||
user = AccountsFixtures.user_fixture()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue