defmodule ToweropsWeb.CoverageLive.Show do @moduledoc false use ToweropsWeb, :live_view alias Towerops.Coverages alias Towerops.Coverages.Antenna alias Towerops.Coverages.Coverage alias ToweropsWeb.CoverageLive.Map, as: CoverageMapView @impl true def mount(%{"id" => id}, _session, socket) do organization = socket.assigns.current_scope.organization coverage = Coverages.get_coverage_with_device!(organization.id, id) antenna = Antenna.get(coverage.antenna_slug) eirp = Coverage.eirp_dbm(coverage, (antenna && antenna.gain_dbi) || 0.0) _ = if connected?(socket), do: Coverages.subscribe(coverage.id) {:ok, socket |> assign(:page_title, coverage.name) |> assign(:organization, organization) |> assign(:coverage, coverage) |> assign(:antenna, antenna) |> assign(:eirp_dbm, eirp) |> assign(:probe, nil) |> assign(:probe_units, "ft") |> assign(:show_params, true)} end @impl true def handle_params(_params, _url, socket), do: {:noreply, socket} @impl true def handle_event("recompute", _params, socket) do case Coverages.queue_compute(socket.assigns.coverage) do {:ok, coverage} -> {:noreply, socket |> assign(:coverage, coverage) |> put_flash(:info, t("Coverage queued for recompute"))} {:error, :compute_in_progress} -> {:noreply, put_flash(socket, :error, t("Coverage is already being computed"))} {:error, _} -> {:noreply, put_flash(socket, :error, t("Failed to queue coverage"))} end end @impl true def handle_event("delete", _params, socket) do case Coverages.delete_coverage(socket.assigns.coverage) do {:ok, _} -> {:noreply, socket |> put_flash(:info, t("Coverage deleted")) |> push_navigate(to: ~p"/coverage")} {:error, _} -> {:noreply, put_flash(socket, :error, t("Unable to delete coverage"))} end end # Probe (Distance Tool) — clicking the map fires this. @impl true def handle_event("probe_point", %{"lat" => lat, "lon" => lon}, socket) do lat = to_float(lat) lon = to_float(lon) # Reuse the org-wide query but only keep this coverage's row. rows = socket.assigns.organization.id |> Coverages.query_point(lat, lon) |> Enum.filter(&(&1.coverage.id == socket.assigns.coverage.id)) {:noreply, assign(socket, :probe, %{lat: lat, lon: lon, rows: rows})} end @impl true def handle_event("close_probe", _params, socket) do {:noreply, assign(socket, :probe, nil)} end @impl true def handle_event("set_probe_units", %{"units" => units}, socket) when units in ["ft", "m"] do {:noreply, assign(socket, :probe_units, units)} end @impl true def handle_event("toggle_params", _params, socket) do {:noreply, update(socket, :show_params, &(!&1))} end @impl true def handle_info({:coverage_status, _status, _extra}, socket) do coverage = Coverages.get_coverage_with_device!(socket.assigns.organization.id, socket.assigns.coverage.id) eirp = Coverage.eirp_dbm( coverage, (socket.assigns.antenna && socket.assigns.antenna.gain_dbi) || 0.0 ) {:noreply, socket |> assign(:coverage, coverage) |> assign(:eirp_dbm, eirp) |> push_event("coverage:list-changed", %{ coverages: CoverageMapView.coverages_payload([coverage]), enabled: [coverage.id] })} end # -- helpers (template) -- defdelegate coverages_payload(coverages), to: CoverageMapView defdelegate fmt_distance(m, units), to: CoverageMapView defdelegate fmt_rssi(v), to: CoverageMapView @doc false def status_badge_class("draft"), do: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300" def status_badge_class("queued"), do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300" def status_badge_class("computing"), do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300" def status_badge_class("ready"), do: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300" def status_badge_class("failed"), do: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300" def status_badge_class(_), do: "bg-gray-100 text-gray-700" @doc false def coverage_lat(coverage) do case Coverage.location(coverage) do {lat, _lon} -> lat nil -> nil end end @doc false def coverage_lon(coverage) do case Coverage.location(coverage) do {_lat, lon} -> lon nil -> nil end end @doc false def format_ft(nil), do: "—" def format_ft(m) when is_number(m), do: "#{round(m / 0.3048)} ft" @doc false def format_mi(nil), do: "—" def format_mi(m) when is_number(m), do: "#{Float.round(m / 1609.344, 2)} mi" @doc false def format_ghz(nil), do: "—" def format_ghz(mhz) when is_number(mhz), do: "#{Float.round(mhz / 1000.0, 3)} GHz" attr :label, :string, required: true attr :value, :string, required: true def param_row(assigns) do ~H"""
{@label}
{@value}
""" end defp to_float(v) when is_number(v), do: v * 1.0 defp to_float(v) when is_binary(v) do case Float.parse(v) do {n, _} -> n :error -> 0.0 end end end