203 lines
7.4 KiB
Elixir
203 lines
7.4 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.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)
|
|
|
|
{: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>{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 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>{Format.distance_km(contact.distance_km)}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</Layouts.app>
|
|
"""
|
|
end
|
|
end
|