prop/lib/microwaveprop_web/live/beacon_live/index.ex
Graham McIntire 2c1c221398 Change beacon height_ft from float to integer
Height in feet doesn't need decimal precision. Migrates the DB
column, updates schema type, and strips trailing .0 from form
input so the integer cast succeeds.
2026-04-11 09:44:42 -05:00

165 lines
5.5 KiB
Elixir

defmodule MicrowavepropWeb.BeaconLive.Index 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} max_width="max-w-6xl">
<.header>
Beacons
<:subtitle>Microwave beacons tracked by NTMS.</:subtitle>
<:actions>
<.button variant="primary" navigate={~p"/beacons/new"}>
<.icon name="hero-plus" /> Submit Beacon
</.button>
</:actions>
</.header>
<.table
id="beacons"
rows={@streams.beacons}
row_click={fn {_id, beacon} -> JS.navigate(~p"/beacons/#{beacon}") end}
>
<:col :let={{_id, beacon}} label="Frequency (MHz)">{Beacon.format_freq(beacon.frequency_mhz)}</:col>
<:col :let={{_id, beacon}} label="Call">{beacon.callsign}</:col>
<:col :let={{_id, beacon}} label="Grid">{beacon.grid}</:col>
<:col :let={{_id, beacon}} label="Lat">{beacon.lat}</:col>
<:col :let={{_id, beacon}} label="Lon">{beacon.lon}</:col>
<:col :let={{_id, beacon}} label="EIRP (mW)">{Beacon.format_mw(beacon.power_mw)}</:col>
<:col :let={{_id, beacon}} label="Height AGL (ft)">{beacon.height_ft}</:col>
<:col :let={{_id, beacon}} label="Keying">{Beacon.keying_label(beacon.keying)}</:col>
<:col :let={{_id, beacon}} label="On air">
<span class={["badge badge-sm", (beacon.on_the_air && "badge-success") || "badge-ghost"]}>
{if beacon.on_the_air, do: "Yes", else: "No"}
</span>
</:col>
<:action :let={{_id, beacon}}>
<div class="sr-only">
<.link navigate={~p"/beacons/#{beacon}"}>Show</.link>
</div>
<.link :if={admin?(@current_scope)} navigate={~p"/beacons/#{beacon}/edit"}>Edit</.link>
</:action>
<:action :let={{id, beacon}}>
<.link
:if={admin?(@current_scope)}
phx-click={JS.push("delete", value: %{id: beacon.id}) |> hide("##{id}")}
data-confirm="Delete this beacon?"
>
Delete
</.link>
</:action>
</.table>
<div :if={admin?(@current_scope) and @pending != []} class="mt-10">
<.header>
Pending approval
<:subtitle>Submitted beacons waiting for admin review.</:subtitle>
</.header>
<.table
id="pending-beacons"
rows={@streams.pending}
row_click={fn {_id, beacon} -> JS.navigate(~p"/beacons/#{beacon}") end}
>
<:col :let={{_id, beacon}} label="Frequency (MHz)">{Beacon.format_freq(beacon.frequency_mhz)}</:col>
<:col :let={{_id, beacon}} label="Call">{beacon.callsign}</:col>
<:col :let={{_id, beacon}} label="Grid">{beacon.grid}</:col>
<:col :let={{_id, beacon}} label="EIRP (mW)">{Beacon.format_mw(beacon.power_mw)}</:col>
<:col :let={{_id, beacon}} label="Height AGL (ft)">{beacon.height_ft}</:col>
<:col :let={{_id, beacon}} label="Submitted">
{Calendar.strftime(beacon.inserted_at, "%Y-%m-%d %H:%M UTC")}
</:col>
<:action :let={{_id, beacon}}>
<.link
phx-click={JS.push("approve", value: %{id: beacon.id})}
data-confirm="Approve this beacon?"
>
Approve
</.link>
</:action>
<:action :let={{id, beacon}}>
<.link
phx-click={JS.push("delete", value: %{id: beacon.id}) |> hide("##{id}")}
data-confirm="Delete this beacon?"
>
Delete
</.link>
</:action>
</.table>
</div>
</Layouts.app>
"""
end
@impl true
def mount(_params, _session, socket) do
if connected?(socket), do: Beacons.subscribe_beacons()
pending =
if admin?(socket.assigns.current_scope) do
Beacons.list_pending_beacons()
else
[]
end
{:ok,
socket
|> assign(:page_title, "Beacons")
|> assign(:pending, pending)
|> stream(:beacons, Beacons.list_beacons())
|> stream(:pending, pending)}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
if admin?(socket.assigns.current_scope) do
beacon = Beacons.get_beacon!(id)
{:ok, _} = Beacons.delete_beacon(beacon)
{:noreply,
socket
|> stream_delete(:beacons, beacon)
|> stream_delete(:pending, beacon)}
else
{:noreply, put_flash(socket, :error, "Admins only.")}
end
end
def handle_event("approve", %{"id" => id}, socket) do
if admin?(socket.assigns.current_scope) do
beacon = Beacons.get_beacon!(id)
{:ok, approved} = Beacons.approve_beacon(beacon)
{:noreply,
socket
|> put_flash(:info, "Approved #{approved.callsign}.")
|> stream_delete(:pending, beacon)
|> stream_insert(:beacons, approved)}
else
{:noreply, put_flash(socket, :error, "Admins only.")}
end
end
@impl true
def handle_info({type, %Beacon{}}, socket) when type in [:created, :updated, :deleted] do
pending =
if admin?(socket.assigns.current_scope) do
Beacons.list_pending_beacons()
else
[]
end
{:noreply,
socket
|> assign(:pending, pending)
|> stream(:beacons, Beacons.list_beacons(), reset: true)
|> stream(:pending, pending, reset: true)}
end
defp admin?(%{user: %{is_admin: true}}), do: true
defp admin?(_), do: false
end