defmodule MicrowavepropWeb.ContactLive.Index do @moduledoc false use MicrowavepropWeb, :live_view alias Microwaveprop.Radio @sortable_fields ~w(station1 station2 band mode distance_km qso_timestamp inserted_at) @default_sort_by "inserted_at" @default_sort_order "desc" @impl true def mount(_params, _session, socket) do {:ok, socket} end @impl true def handle_params(params, _uri, socket) do page = case Integer.parse(Map.get(params, "page", "1")) do {n, _} -> max(n, 1) :error -> 1 end sort_by = validate_sort_field(Map.get(params, "sort_by", @default_sort_by)) sort_order = validate_sort_order(Map.get(params, "sort_order", @default_sort_order)) search = Map.get(params, "search", "") result = Radio.list_contacts( page: page, sort_by: String.to_existing_atom(sort_by), sort_order: String.to_existing_atom(sort_order), search: search ) {:noreply, assign(socket, page_title: "Contacts", page: result.page, total_pages: result.total_pages, total_entries: result.total_entries, contacts: result.entries, grouped_contacts: result.grouped_entries, sort_by: sort_by, sort_order: sort_order, search: search )} end @impl true def handle_event("search", %{"search" => search}, socket) do {:noreply, push_patch(socket, to: ~p"/contacts?search=#{search}")} end @impl true def handle_event("sort", %{"field" => field}, socket) do field = validate_sort_field(field) new_order = if socket.assigns.sort_by == field && socket.assigns.sort_order == "asc", do: "desc", else: "asc" params = %{sort_by: field, sort_order: new_order} params = if socket.assigns.search == "", do: params, else: Map.put(params, :search, socket.assigns.search) {:noreply, push_patch(socket, to: ~p"/contacts?#{params}")} end defp validate_sort_field(field) when field in @sortable_fields, do: field defp validate_sort_field(_), do: @default_sort_by defp validate_sort_order(order) when order in ~w(asc desc), do: order defp validate_sort_order(_), do: @default_sort_order @enrichment_fields [ {:hrrr_status, "HRRR"}, {:weather_status, "Weather"}, {:terrain_status, "Terrain"}, {:iemre_status, "IEMRE"} ] @done_statuses [:complete, :unavailable] defp submitted_cell(%{user: %{callsign: callsign}}) when is_binary(callsign), do: callsign defp submitted_cell(%{user_submitted: true}), do: "Yes" defp submitted_cell(_), do: "—" defp enrichment_badge(contact) do details = Enum.map(@enrichment_fields, fn {field, name} -> {name, Map.get(contact, field)} end) done = Enum.count(details, fn {_, s} -> s in @done_statuses end) failed = Enum.count(details, fn {_, s} -> s == :failed end) {label, class} = cond do done == 4 -> {"Complete", "badge-success"} failed > 0 -> {"Failed", "badge-error"} done > 0 -> {"Partial", "badge-warning"} true -> {"Pending", "badge-ghost"} end tooltip = Enum.map_join(details, ", ", fn {name, status} -> "#{name}: #{status}" end) assigns = %{label: label, class: class, tooltip: tooltip} ~H""" {@label} """ end @impl true def render(assigns) do ~H""" <.header> Contacts <:subtitle>{@total_entries} contacts <:actions> <.link navigate={~p"/submit"} class="btn btn-primary"> <.icon name="hero-plus" class="w-5 h-5" /> Submit Contact
<.link :if={@search != ""} patch={~p"/contacts"} class="btn btn-sm btn-ghost">Clear
<%= for {primary, reciprocals} <- @grouped_contacts do %> <%= for recip <- reciprocals do %> <% end %> <% end %>
Station 1 Grid 1 Station 2 Grid 2 Band Mode Distance (km) Submitted Enriched Timestamp (UTC) Added (UTC)
{length(reciprocals) + 1} {primary.station1} {primary.grid1 || "—"} {primary.station2} {primary.grid2 || "—"} {primary.band} {primary.mode} {primary.distance_km} {submitted_cell(primary)} {enrichment_badge(primary)} {if primary.flagged_invalid, do: "🚩"} {Calendar.strftime(primary.qso_timestamp, "%Y-%m-%d %H:%M")} {Calendar.strftime(primary.inserted_at, "%Y-%m-%d %H:%M")}
{recip.station1} {recip.grid1 || "—"} {recip.station2} {recip.grid2 || "—"} {recip.band} {recip.mode} {recip.distance_km} {submitted_cell(recip)} {enrichment_badge(recip)} {if recip.flagged_invalid, do: "🚩"} {Calendar.strftime(recip.qso_timestamp, "%Y-%m-%d %H:%M")} {Calendar.strftime(recip.inserted_at, "%Y-%m-%d %H:%M")}
<.link :if={@page > 1} patch={ ~p"/contacts?page=#{@page - 1}&sort_by=#{@sort_by}&sort_order=#{@sort_order}&search=#{@search}" } class="btn btn-sm btn-outline" > <.icon name="hero-chevron-left" class="w-4 h-4" /> Previous Page {@page} of {@total_pages} <.link :if={@page < @total_pages} patch={ ~p"/contacts?page=#{@page + 1}&sort_by=#{@sort_by}&sort_order=#{@sort_order}&search=#{@search}" } class="btn btn-sm btn-outline" > Next <.icon name="hero-chevron-right" class="w-4 h-4" /> = @total_pages} />
""" end end