defmodule MicrowavepropWeb.BeaconLive.Index do @moduledoc "`/beacons` list + admin controls for the beacon catalog." use MicrowavepropWeb, :live_view use MicrowavepropWeb.LiveTableResource, schema: Microwaveprop.Beacons.Beacon alias Microwaveprop.Beacons alias Microwaveprop.Beacons.Beacon def table_options do %{exports: %{formats: [:csv]}} end def fields do [ id: %{label: "ID", hidden: true}, frequency_mhz: %{ label: "Frequency (MHz)", sortable: true, searchable: false, renderer: &freq_cell/1 }, callsign: %{label: "Call", sortable: true, searchable: true}, grid: %{label: "Grid", sortable: true, searchable: true}, power_mw: %{label: "EIRP (mW)", sortable: true, renderer: &mw_cell/1}, height_ft: %{label: "Height AGL (ft)", sortable: true}, keying: %{label: "Keying", sortable: true, renderer: &keying_cell/1}, on_the_air: %{label: "On air", sortable: true, renderer: &on_air_cell/1} ] end def filters, do: [] def actions do [ show: fn %{record: beacon} -> assigns = %{beacon: beacon} ~H""" <.link navigate={~p"/beacons/#{@beacon.id}"} class="btn btn-xs btn-ghost">Details """ end ] end @impl true def mount(_params, _session, socket) do _ = if connected?(socket), do: Beacons.subscribe_beacons() beacons = Beacons.list_beacons() pending = if admin?(socket.assigns.current_scope) do Beacons.list_pending_beacons() else [] end {:ok, socket |> assign(:page_title, "Beacons") |> assign(:pending, pending) |> assign(:beacons_json, encode_beacons(beacons)) |> assign(:data_provider, {Beacons, :approved_beacons_query, []}) |> 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(:pending, beacon) |> push_patch(to: path_with_prefix(socket.assigns.current_path))} 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) |> push_patch(to: path_with_prefix(socket.assigns.current_path))} 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 beacons = Beacons.list_beacons() pending = if admin?(socket.assigns.current_scope) do Beacons.list_pending_beacons() else [] end {:noreply, socket |> assign(:pending, pending) |> assign(:beacons_json, encode_beacons(beacons)) |> stream(:pending, pending, reset: true) |> push_patch(to: path_with_prefix(socket.assigns.current_path))} end defp admin?(%{user: %{is_admin: true}}), do: true defp admin?(_), do: false defp path_with_prefix(path) when is_binary(path), do: "/" <> path defp path_with_prefix(_), do: "/beacons" defp encode_beacons(beacons) do beacons |> Enum.filter(&(is_number(&1.lat) and is_number(&1.lon))) |> Enum.map(fn b -> %{ id: b.id, callsign: b.callsign, lat: b.lat, lon: b.lon, frequency_mhz: b.frequency_mhz, grid: b.grid, keying: Beacon.keying_label(b.keying), on_the_air: b.on_the_air } end) |> Jason.encode!() end defp freq_cell(freq), do: Beacon.format_freq(freq) defp mw_cell(power), do: Beacon.format_mw(power) defp keying_cell(keying), do: Beacon.keying_label(keying) defp on_air_cell(on_the_air) do assigns = %{on_the_air: on_the_air} ~H""" {if @on_the_air, do: "Yes", else: "No"} """ end @impl true def render(assigns) do ~H""" <.header> Beacons <:subtitle>Microwave beacons tracked by NTMS. <:actions> <.button variant="primary" navigate={~p"/beacons/new"}> <.icon name="hero-plus" /> Submit Beacon
<.live_table fields={fields()} filters={filters()} options={@options} streams={@streams} actions={actions()} />
<.header> Pending approval <:subtitle>Submitted beacons waiting for admin review. <.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 :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)} <:col :let={{_id, beacon}} label="Height AGL (ft)">{beacon.height_ft} <:col :let={{_id, beacon}} label="Submitted"> {Calendar.strftime(beacon.inserted_at, "%Y-%m-%d %H:%M UTC")} <:action :let={{_id, beacon}}> <.link phx-click={JS.push("approve", value: %{id: beacon.id})} data-confirm="Approve this beacon?" > Approve <:action :let={{id, beacon}}> <.link phx-click={JS.push("delete", value: %{id: beacon.id}) |> hide("##{id}")} data-confirm="Delete this beacon?" > Delete
""" end end