prop/lib/microwaveprop_web/live/user_profile_live.ex

278 lines
10 KiB
Elixir

defmodule MicrowavepropWeb.UserProfileLive do
@moduledoc "Signed-in user's own profile + callsign linkage at `/account`."
use MicrowavepropWeb, :live_view
alias Microwaveprop.Accounts
alias Microwaveprop.BeaconMonitors
alias Microwaveprop.Beacons
alias Microwaveprop.Beacons.Beacon
alias Microwaveprop.Format
alias Microwaveprop.Radio
@impl true
def mount(%{"callsign" => raw_callsign}, _session, socket) do
case Accounts.get_user_by_callsign(raw_callsign) do
nil ->
{:ok,
socket
|> put_flash(:error, "No profile for #{raw_callsign}.")
|> push_navigate(to: ~p"/")}
user ->
viewer = socket.assigns[:current_scope] && socket.assigns.current_scope.user
contacts = Radio.list_contacts_for_user(user, viewer)
beacons = Beacons.list_beacons_for_user(user, viewer)
involving = Radio.list_contacts_involving_callsign(user.callsign, viewer)
monitors =
if viewer && viewer.id == user.id do
BeaconMonitors.list_monitors_for_user(user)
else
[]
end
{:ok,
assign(socket,
page_title: user.callsign,
profile: user,
contacts: contacts,
beacons: beacons,
involving: involving,
beacon_monitors: monitors,
is_own_profile: viewer && viewer.id == user.id
)}
end
end
defp initial(%{name: name}) when is_binary(name) and name != "" do
name |> String.trim() |> String.first() |> String.upcase()
end
defp initial(%{callsign: callsign}) when is_binary(callsign), do: callsign |> String.first() |> String.upcase()
defp initial(_), do: "?"
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope} max_width="max-w-5xl">
<div class="card bg-base-200 shadow-sm mb-6">
<div class="card-body flex-row items-center gap-4 py-5">
<div class="flex h-16 w-16 shrink-0 items-center justify-center rounded-full bg-primary text-primary-content">
<span class="font-mono text-2xl font-semibold leading-none">{initial(@profile)}</span>
</div>
<div class="flex-1 min-w-0">
<h1 class="card-title font-mono text-2xl">{@profile.callsign}</h1>
<p class="text-sm opacity-70">
<span :if={@profile.name}>{@profile.name} &middot;</span>
Member since {Calendar.strftime(@profile.inserted_at, "%B %Y")}
</p>
</div>
</div>
</div>
<div class="stats bg-base-200 shadow-sm mb-6 w-full">
<div class="stat">
<div class="stat-title">Contacts submitted</div>
<div class="stat-value text-primary">{length(@contacts)}</div>
</div>
<div class="stat">
<div class="stat-title">Beacons submitted</div>
<div class="stat-value text-secondary">{length(@beacons)}</div>
</div>
</div>
<div class="card bg-base-200 shadow-sm mb-6">
<div class="card-body">
<h2 class="card-title">Contacts</h2>
<%= if @contacts == [] do %>
<p class="text-sm opacity-70">No contacts submitted yet.</p>
<% else %>
<div class="overflow-x-auto">
<table class="table table-zebra table-sm">
<thead>
<tr>
<th>When</th>
<th>Stations</th>
<th>Band</th>
<th>Mode</th>
<th>Distance</th>
</tr>
</thead>
<tbody>
<tr :for={contact <- @contacts} class="hover">
<td class="whitespace-nowrap">
{Calendar.strftime(contact.qso_timestamp, "%Y-%m-%d %H:%M UTC")}
</td>
<td>
<.link navigate={~p"/contacts/#{contact.id}"} class="link link-hover font-mono">
{contact.station1} &harr; {contact.station2}
</.link>
</td>
<td>{contact.band && "#{contact.band} MHz"}</td>
<td>{contact.mode}</td>
<td>{Format.distance_km(contact.distance_km)}</td>
</tr>
</tbody>
</table>
</div>
<% end %>
</div>
</div>
<div class="card bg-base-200 shadow-sm mb-6">
<div class="card-body">
<h2 class="card-title">Beacons</h2>
<%= if @beacons == [] do %>
<p class="text-sm opacity-70">No beacons submitted yet.</p>
<% else %>
<div class="overflow-x-auto">
<table class="table table-zebra table-sm">
<thead>
<tr>
<th>Callsign</th>
<th>Frequency</th>
<th>Grid</th>
<th>Keying</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr :for={beacon <- @beacons} class="hover">
<td>
<.link navigate={~p"/beacons/#{beacon.id}"} class="link link-hover font-mono">
{beacon.callsign}
</.link>
</td>
<td class="whitespace-nowrap">
{Beacon.format_freq(beacon.frequency_mhz)} MHz
</td>
<td>{beacon.grid}</td>
<td>{Beacon.keying_label(beacon.keying)}</td>
<td>
<div class={[
"badge badge-sm",
if(beacon.approved, do: "badge-success", else: "badge-warning")
]}>
{if beacon.approved, do: "Approved", else: "Pending"}
</div>
</td>
</tr>
</tbody>
</table>
</div>
<% end %>
</div>
</div>
<div :if={@is_own_profile} class="card bg-base-200 shadow-sm mb-6">
<div class="card-body">
<h2 class="card-title">Beacon monitors</h2>
<p class="text-sm opacity-70">
Physical monitor hardware assigned to you.
</p>
<div class="overflow-x-auto">
<table class="table table-zebra table-sm">
<thead>
<tr>
<th>Name</th>
<th>Hardware</th>
<th>Monitoring</th>
<th>Last seen</th>
</tr>
</thead>
<tbody>
<tr :for={monitor <- @beacon_monitors}>
<td>
<.link
navigate={~p"/beacon-monitors/#{monitor.id}"}
class="link link-hover font-semibold"
>
{monitor.name}
</.link>
</td>
<td class="text-sm">
<%= if monitor.hardware_type do %>
{monitor.hardware_type}<br />
<span class="opacity-60 text-xs">{monitor.hardware_id}</span>
<% else %>
<span class="opacity-50">&mdash;</span>
<% end %>
</td>
<td class="text-sm">
<%= if monitor.beacon do %>
<.link
navigate={~p"/beacons/#{monitor.beacon.id}"}
class="link link-hover font-mono"
>
{monitor.beacon.callsign}
</.link>
<span class="opacity-60"> ({monitor.beacon.frequency_mhz} MHz)</span>
<% else %>
<span class="opacity-50">Not configured</span>
<% end %>
</td>
<td class="text-sm opacity-70">
<%= if monitor.last_seen_at do %>
{Calendar.strftime(monitor.last_seen_at, "%Y-%m-%d %H:%M UTC")}
<% else %>
never
<% end %>
</td>
</tr>
</tbody>
</table>
</div>
<%= if @beacon_monitors == [] do %>
<p class="text-sm opacity-70">No monitors assigned to you yet.</p>
<% end %>
</div>
</div>
<div class="card bg-base-200 shadow-sm">
<div class="card-body">
<h2 class="card-title">Contacts {@profile.callsign} is in</h2>
<%= if @involving == [] do %>
<p class="text-sm opacity-70">No contacts on file with this callsign yet.</p>
<% else %>
<p :if={length(@involving) == 100} class="text-xs opacity-60">
Showing the 100 most recent contacts.
</p>
<div class="overflow-x-auto">
<table class="table table-zebra table-sm">
<thead>
<tr>
<th>When</th>
<th>Stations</th>
<th>Band</th>
<th>Mode</th>
<th>Distance</th>
</tr>
</thead>
<tbody>
<tr
:for={contact <- @involving}
class="hover cursor-pointer"
phx-click={JS.navigate(~p"/contacts/#{contact.id}")}
>
<td class="whitespace-nowrap">
{Calendar.strftime(contact.qso_timestamp, "%Y-%m-%d %H:%M UTC")}
</td>
<td class="font-mono">
{contact.station1} &harr; {contact.station2}
</td>
<td>{contact.band && "#{contact.band} MHz"}</td>
<td>{contact.mode}</td>
<td>{Format.distance_km(contact.distance_km)}</td>
</tr>
</tbody>
</table>
</div>
<% end %>
</div>
</div>
</Layouts.app>
"""
end
end