The profile page's involving-contacts query now limits to the 100 most recent rows (ordered by qso_timestamp desc). A small note is shown under the heading when the cap is hit so users know there's more data they're not seeing.
201 lines
7.2 KiB
Elixir
201 lines
7.2 KiB
Elixir
defmodule MicrowavepropWeb.UserProfileLive do
|
|
@moduledoc false
|
|
use MicrowavepropWeb, :live_view
|
|
|
|
alias Microwaveprop.Accounts
|
|
alias Microwaveprop.Beacons
|
|
alias Microwaveprop.Beacons.Beacon
|
|
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 ->
|
|
contacts = Radio.list_contacts_for_user(user)
|
|
beacons = Beacons.list_beacons_for_user(user)
|
|
involving = Radio.list_contacts_involving_callsign(user.callsign)
|
|
|
|
{:ok,
|
|
assign(socket,
|
|
page_title: user.callsign,
|
|
profile: user,
|
|
contacts: contacts,
|
|
beacons: beacons,
|
|
involving: involving
|
|
)}
|
|
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} ·</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} ↔ {contact.station2}
|
|
</.link>
|
|
</td>
|
|
<td>{contact.band && "#{contact.band} MHz"}</td>
|
|
<td>{contact.mode}</td>
|
|
<td>{contact.distance_km && "#{contact.distance_km} 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 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} ↔ {contact.station2}
|
|
</td>
|
|
<td>{contact.band && "#{contact.band} MHz"}</td>
|
|
<td>{contact.mode}</td>
|
|
<td>{contact.distance_km && "#{contact.distance_km} km"}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</Layouts.app>
|
|
"""
|
|
end
|
|
end
|