From 8425edbdc40447937dbd6696eed13c42736dd318 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 11 Apr 2026 09:37:25 -0500 Subject: [PATCH] Add comma separators to MHz frequencies on beacon pages Formats frequencies like 10368.1 as "10,368.1" on both the beacon list table and detail page (subtitle, map label, stat field). --- lib/microwaveprop/beacons/beacon.ex | 26 +++++++++++++++++++ .../live/beacon_live/index.ex | 4 +-- .../live/beacon_live/show.ex | 6 ++--- .../live/beacon_live_test.exs | 2 +- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/microwaveprop/beacons/beacon.ex b/lib/microwaveprop/beacons/beacon.ex index 1de2056c..247ea003 100644 --- a/lib/microwaveprop/beacons/beacon.ex +++ b/lib/microwaveprop/beacons/beacon.ex @@ -62,6 +62,32 @@ defmodule Microwaveprop.Beacons.Beacon do def format_mw(mw), do: to_string(mw) + @doc "Formats a frequency in MHz with comma separators (e.g. 10368 → \"10,368\")." + def format_freq(nil), do: "" + + def format_freq(mhz) when is_number(mhz) do + int_part = trunc(mhz) + frac = mhz - int_part + + formatted_int = + int_part + |> Integer.to_string() + |> String.reverse() + |> String.replace(~r/.{3}/, "\\0,") + |> String.trim_trailing(",") + |> String.reverse() + + if frac == 0.0 do + formatted_int + else + decimal = mhz |> :erlang.float_to_binary(decimals: 3) |> trim_trailing_zeros() + [_int, frac_part] = String.split(decimal, ".") + "#{formatted_int}.#{frac_part}" + end + end + + def format_freq(mhz), do: to_string(mhz) + defp trim_trailing_zeros(str) do if String.contains?(str, ".") do str |> String.trim_trailing("0") |> String.trim_trailing(".") diff --git a/lib/microwaveprop_web/live/beacon_live/index.ex b/lib/microwaveprop_web/live/beacon_live/index.ex index 27b38939..2c9bea74 100644 --- a/lib/microwaveprop_web/live/beacon_live/index.ex +++ b/lib/microwaveprop_web/live/beacon_live/index.ex @@ -24,7 +24,7 @@ defmodule MicrowavepropWeb.BeaconLive.Index do rows={@streams.beacons} row_click={fn {_id, beacon} -> JS.navigate(~p"/beacons/#{beacon}") end} > - <:col :let={{_id, beacon}} label="Frequency (MHz)">{beacon.frequency_mhz} + <:col :let={{_id, beacon}} label="Frequency (MHz)">{Beacon.format_freq(beacon.frequency_mhz)} <:col :let={{_id, beacon}} label="Call">{beacon.callsign} <:col :let={{_id, beacon}} label="Grid">{beacon.grid} <:col :let={{_id, beacon}} label="Lat">{beacon.lat} @@ -65,7 +65,7 @@ defmodule MicrowavepropWeb.BeaconLive.Index do rows={@streams.pending} row_click={fn {_id, beacon} -> JS.navigate(~p"/beacons/#{beacon}") end} > - <:col :let={{_id, beacon}} label="Frequency (MHz)">{beacon.frequency_mhz} + <:col :let={{_id, beacon}} label="Frequency (MHz)">{Beacon.format_freq(beacon.frequency_mhz)} <:col :let={{_id, beacon}} label="Call">{beacon.callsign} <:col :let={{_id, beacon}} label="Grid">{beacon.grid} <:col :let={{_id, beacon}} label="EIRP (mW)">{Beacon.format_mw(beacon.power_mw)} diff --git a/lib/microwaveprop_web/live/beacon_live/show.ex b/lib/microwaveprop_web/live/beacon_live/show.ex index f5a4f214..e285524a 100644 --- a/lib/microwaveprop_web/live/beacon_live/show.ex +++ b/lib/microwaveprop_web/live/beacon_live/show.ex @@ -13,7 +13,7 @@ defmodule MicrowavepropWeb.BeaconLive.Show do <.header> {@beacon.callsign} <:subtitle> - {@beacon.frequency_mhz} MHz · {@beacon.grid} + {Beacon.format_freq(@beacon.frequency_mhz)} MHz · {@beacon.grid} Pending approval @@ -46,7 +46,7 @@ defmodule MicrowavepropWeb.BeaconLive.Show do class="h-96 w-full rounded-lg border border-base-300 mb-2" data-lat={@beacon.lat} data-lon={@beacon.lon} - data-label={"#{@beacon.callsign} · #{@beacon.frequency_mhz} MHz"} + data-label={"#{@beacon.callsign} · #{Beacon.format_freq(@beacon.frequency_mhz)} MHz"} data-on-the-air={to_string(@beacon.on_the_air)} data-grid-step={@estimate.grid_step} data-cells={Jason.encode!(@estimate.cells)} @@ -101,7 +101,7 @@ defmodule MicrowavepropWeb.BeaconLive.Show do
- <.stat_field label="Frequency">{@beacon.frequency_mhz} MHz + <.stat_field label="Frequency">{Beacon.format_freq(@beacon.frequency_mhz)} MHz <.stat_field label="Callsign">{@beacon.callsign} <.stat_field label="Keying">{Beacon.keying_label(@beacon.keying)} <.stat_field label="On the air"> diff --git a/test/microwaveprop_web/live/beacon_live_test.exs b/test/microwaveprop_web/live/beacon_live_test.exs index 474333ee..ba198d7d 100644 --- a/test/microwaveprop_web/live/beacon_live_test.exs +++ b/test/microwaveprop_web/live/beacon_live_test.exs @@ -84,7 +84,7 @@ defmodule MicrowavepropWeb.BeaconLiveTest do beacon = approved_beacon_fixture(user_fixture()) {:ok, _view, html} = live(conn, ~p"/beacons/#{beacon}") assert html =~ beacon.callsign - assert html =~ "#{beacon.frequency_mhz}" + assert html =~ "10,368.1" end test "shows pending badge on unapproved beacons", %{conn: conn} do