diff --git a/lib/microwaveprop_web/live/rover_planning_live/show.ex b/lib/microwaveprop_web/live/rover_planning_live/show.ex index bc643e84..f047073e 100644 --- a/lib/microwaveprop_web/live/rover_planning_live/show.ex +++ b/lib/microwaveprop_web/live/rover_planning_live/show.ex @@ -153,24 +153,36 @@ 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 - - defp station_label(%{station: %{lat: lat, lon: lon}}) when is_number(lat) and is_number(lon), - do: "#{Float.round(lat, 3)}, #{Float.round(lon, 3)}" - + # Grid+callsign-first station label. Stations entered as raw lat/lon + # have no callsign or grid stored, so derive a 6-char grid from the + # coords — that's what the user wants to see, not bare decimals. + defp station_label(%{station: %{} = station}), do: station_label_for(station) defp station_label(_), do: "—" + defp station_label_for(%{callsign: c, grid: g}) when is_binary(c) and c != "" and is_binary(g) and g != "", + do: "#{c} · #{g}" + + defp station_label_for(%{callsign: c, lat: lat, lon: lon}) + when is_binary(c) and c != "" and is_number(lat) and is_number(lon), + do: "#{c} · #{Maidenhead.from_latlon(lat, lon, 6)}" + + defp station_label_for(%{callsign: c}) when is_binary(c) and c != "", do: c + defp station_label_for(%{grid: g}) when is_binary(g) and g != "", do: g + + defp station_label_for(%{lat: lat, lon: lon}) when is_number(lat) and is_number(lon), + do: Maidenhead.from_latlon(lat, lon, 6) + + defp station_label_for(_), 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. + # any stored or derived grid (PathLive's LocationResolver re-resolves + # both back to the same point), falling back to bare coordinates only + # when nothing else is available. 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(%{lat: lat, lon: lon}) when is_number(lat) and is_number(lon), + do: Maidenhead.from_latlon(lat, lon, 8) defp station_endpoint(_), do: "" diff --git a/test/microwaveprop_web/live/rover_planning_live_test.exs b/test/microwaveprop_web/live/rover_planning_live_test.exs index c7623346..6df715f4 100644 --- a/test/microwaveprop_web/live/rover_planning_live_test.exs +++ b/test/microwaveprop_web/live/rover_planning_live_test.exs @@ -88,6 +88,33 @@ defmodule MicrowavepropWeb.RoverPlanningLiveTest do assert html =~ grid_10 end + test "station label uses derived grid when only lat/lon are stored", %{conn: conn} do + user = AccountsFixtures.user_fixture() + {:ok, _} = Rover.create_location(user, %{lat: 32.0, lon: -97.0, status: :good}) + + # User enters raw coords (no callsign or grid lookup) → station ends + # up with lat/lon set but callsign/grid blank. + {:ok, mission} = + RoverPlanning.create_mission(user, %{ + "name" => "Coord-only station mission", + "band_mhz" => 10_000, + "only_known_good" => true, + "rover_height_ft" => 8.0, + "station_height_ft" => 30.0, + "stations" => %{"0" => %{"input" => "33.189,-96.452", "position" => 0}} + }) + + {:ok, _lv, html} = live(conn, ~p"/rover-planning/#{mission.id}") + + derived = Maidenhead.from_latlon(33.189, -96.452, 6) + assert html =~ derived + # The path-profiles "Station" cell must render the derived grid, + # not the bare lat/lon. Match the exact