diff --git a/lib/microwaveprop_web/live/rover_live.ex b/lib/microwaveprop_web/live/rover_live.ex index 2c0fdf99..11775859 100644 --- a/lib/microwaveprop_web/live/rover_live.ex +++ b/lib/microwaveprop_web/live/rover_live.ex @@ -199,20 +199,51 @@ defmodule MicrowavepropWeb.RoverLive do def handle_event("add_station", params, socket) do callsign = params |> Map.get("callsign", "") |> String.trim() grid = params |> Map.get("grid", "") |> String.trim() - attrs = build_station_attrs(callsign, grid) - case create_station(socket, attrs) do - {:ok, stations} -> - {:noreply, - socket - |> assign(fixed_stations: stations, station_form_error: nil) - |> sync_url()} + case resolve_station_grid(callsign, grid) do + {:ok, resolved_grid} -> + attrs = build_station_attrs(callsign, resolved_grid) + + case create_station(socket, attrs) do + {:ok, stations} -> + {:noreply, + socket + |> assign(fixed_stations: stations, station_form_error: nil) + |> sync_url()} + + {:error, msg} -> + {:noreply, assign(socket, station_form_error: msg)} + end {:error, msg} -> {:noreply, assign(socket, station_form_error: msg)} end end + def handle_event("update_station_grid", %{"station_id" => id, "grid" => new_grid}, socket) do + new_grid = String.trim(new_grid) + + cond do + new_grid == "" -> + {:noreply, socket} + + not Maidenhead.valid?(new_grid) -> + {:noreply, assign(socket, station_form_error: "invalid grid: #{new_grid}")} + + true -> + case mutate_station_grid(socket, id, new_grid) do + {:ok, stations} -> + {:noreply, + socket + |> assign(fixed_stations: stations, station_form_error: nil) + |> sync_url()} + + {:error, msg} -> + {:noreply, assign(socket, station_form_error: msg)} + end + end + end + def handle_event("set_home_qth", params, socket) do input = params |> Map.get("home_grid", "") |> String.trim() @@ -283,6 +314,30 @@ defmodule MicrowavepropWeb.RoverLive do put_flash(socket, :info, Enum.join(warnings, " ")) end + # When the user supplies just a callsign, look it up via QRZ (with the + # Google Maps geocoder fallback already wired into CallsignClient) and + # use the returned 10-char grid. Explicit user-entered grids win over + # the lookup so an operator can override an old QRZ entry. + defp resolve_station_grid(_call, grid) when is_binary(grid) and grid != "", do: {:ok, grid} + + defp resolve_station_grid("", _grid), do: {:error, "callsign required"} + + defp resolve_station_grid(call, _grid) do + case CallsignClient.locate(call) do + {:ok, %{gridsquare: g}} when is_binary(g) and byte_size(g) >= 4 -> + {:ok, g} + + {:ok, %{lat: lat, lon: lon}} when is_number(lat) and is_number(lon) -> + {:ok, Maidenhead.from_latlon(lat, lon, 10)} + + {:ok, _} -> + {:error, "QRZ lookup for #{call} returned no grid or coords"} + + {:error, reason} -> + {:error, "couldn't locate #{call}: #{inspect(reason)}"} + end + end + # ── Mutation helpers ──────────────────────────────────────────────── defp handle_station_mutation(socket, id, fun) do @@ -350,6 +405,32 @@ defmodule MicrowavepropWeb.RoverLive do {:ok, stations} end + defp mutate_station_grid(%{assigns: %{persisted?: true}} = socket, id, new_grid) do + user = current_user(socket) + + case Rover.update_station(user, id, %{"grid" => new_grid, "lat" => nil, "lon" => nil}) do + {:ok, _} -> {:ok, Rover.list_stations(user)} + {:error, :not_found} -> {:error, "station not found"} + {:error, %Ecto.Changeset{} = cs} -> {:error, changeset_first_error(cs)} + end + end + + defp mutate_station_grid(socket, id, new_grid) do + stations = + Enum.map(socket.assigns.fixed_stations, fn s -> + if station_id(s) == id, do: replace_anonymous_station_grid(s, new_grid), else: s + end) + + {:ok, stations} + end + + defp replace_anonymous_station_grid(station, new_grid) do + case Maidenhead.to_latlon(new_grid) do + {:ok, {lat, lon}} -> %{station | grid: new_grid, lat: lat, lon: lon} + :error -> station + end + end + defp create_station(%{assigns: %{persisted?: true}} = socket, attrs) do user = current_user(socket) @@ -620,6 +701,12 @@ defmodule MicrowavepropWeb.RoverLive do defp station_id(%{callsign: call}) when is_binary(call), do: "call:" <> call defp station_id(_), do: "" + defp km_to_mi(km) when is_number(km), do: round(km * 0.621371) + defp km_to_mi(_), do: 0 + + defp elev_ft(nil), do: "—" + defp elev_ft(m) when is_number(m), do: "#{round(m * 3.28084)} ft" + # ── Render ─────────────────────────────────────────────────────────── @impl true @@ -683,10 +770,10 @@ defmodule MicrowavepropWeb.RoverLive do