prop/lib/microwaveprop_web/live/beacon_live/form.ex
Graham McIntire fbae931d7e Add beacon bearing, beamwidth, and weak-signal keyings
Beacons can now record a directional bearing ("omni" or 0-360 degrees)
and a beamwidth in degrees. The keying list grows from {on_off, fsk}
to include FM Voice, WSPR, and the full Q65A/B/C/D/E × 15/30/60/120
family, grouped via optgroups in the submission form.
2026-04-09 08:59:16 -05:00

154 lines
4.6 KiB
Elixir

defmodule MicrowavepropWeb.BeaconLive.Form do
@moduledoc false
use MicrowavepropWeb, :live_view
alias Microwaveprop.Beacons
alias Microwaveprop.Beacons.Beacon
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<.header>
{@page_title}
<:subtitle>
Enter either a grid or lat/lon — whichever you leave blank is filled in
from the other.
</:subtitle>
</.header>
<.form for={@form} id="beacon-form" phx-change="validate" phx-submit="save">
<.input
field={@form[:frequency_mhz]}
type="number"
label="Frequency (MHz)"
step="any"
required
/>
<.input field={@form[:callsign]} type="text" label="Call" required />
<.input field={@form[:grid]} type="text" label="Grid" />
<.input field={@form[:lat]} type="number" label="Latitude" step="any" />
<.input field={@form[:lon]} type="number" label="Longitude" step="any" />
<.input
field={@form[:power_mw]}
type="number"
label="TX power (EIRP) (mW)"
step="any"
required
/>
<.input
field={@form[:height_ft]}
type="number"
label="Height above ground (ft)"
step="any"
required
/>
<.input
field={@form[:bearing]}
type="text"
label="Bearing"
placeholder="omni or 0-360 degrees"
/>
<.input
field={@form[:beamwidth_deg]}
type="number"
label="Beamwidth (degrees)"
step="any"
placeholder="Leave blank for omni"
/>
<.input
field={@form[:keying]}
type="select"
label="Keying"
options={Beacon.keying_options()}
required
/>
<.input field={@form[:on_the_air]} type="checkbox" label="On the air" />
<.input field={@form[:notes]} type="textarea" label="Notes" />
<footer>
<.button phx-disable-with="Saving..." variant="primary">Save Beacon</.button>
<.button navigate={return_path(@return_to, @beacon)}>Cancel</.button>
</footer>
</.form>
</Layouts.app>
"""
end
@impl true
def mount(params, _session, socket) do
{:ok,
socket
|> assign(:return_to, return_to(params["return_to"]))
|> apply_action(socket.assigns.live_action, params)}
end
defp return_to("show"), do: "show"
defp return_to(_), do: "index"
defp apply_action(socket, :edit, %{"id" => id}) do
beacon = Beacons.get_beacon!(id)
socket
|> assign(:page_title, "Edit beacon")
|> assign(:beacon, beacon)
|> assign(:form, to_form(Beacons.change_beacon(beacon)))
end
defp apply_action(socket, :new, _params) do
beacon = %Beacon{}
socket
|> assign(:page_title, "New beacon")
|> assign(:beacon, beacon)
|> assign(:form, to_form(Beacons.change_beacon(beacon)))
end
@impl true
def handle_event("validate", %{"beacon" => beacon_params}, socket) do
changeset = Beacons.change_beacon(socket.assigns.beacon, beacon_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
def handle_event("save", %{"beacon" => beacon_params}, socket) do
save_beacon(socket, socket.assigns.live_action, beacon_params)
end
defp save_beacon(socket, :edit, beacon_params) do
case Beacons.update_beacon(socket.assigns.beacon, beacon_params) do
{:ok, beacon} ->
{:noreply,
socket
|> put_flash(:info, "Beacon updated")
|> push_navigate(to: return_path(socket.assigns.return_to, beacon))}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp save_beacon(socket, :new, beacon_params) do
user = current_user(socket.assigns.current_scope)
case Beacons.create_beacon(user, beacon_params) do
{:ok, beacon} ->
flash =
if user,
do: "Beacon submitted — pending admin approval.",
else: "Thanks! Your beacon has been submitted and is awaiting admin approval."
{:noreply,
socket
|> put_flash(:info, flash)
|> push_navigate(to: return_path(socket.assigns.return_to, beacon))}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp current_user(%{user: user}), do: user
defp current_user(_), do: nil
defp return_path("index", _beacon), do: ~p"/beacons"
defp return_path("show", beacon), do: ~p"/beacons/#{beacon}"
end