"Contacts callsign is in" section on /u/:callsign
Add a new card on the profile page listing every contact where the callsign appears as either station1 or station2, case-insensitive, newest first. Backed by a new `Radio.list_contacts_involving_callsign/1` query with unit coverage for station1/station2 matching, case handling, ordering, and blank input.
This commit is contained in:
parent
3a1da13eab
commit
1013df6858
3 changed files with 113 additions and 2 deletions
|
|
@ -128,6 +128,22 @@ defmodule Microwaveprop.Radio do
|
|||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all contacts where the given callsign appears as either
|
||||
`station1` or `station2`, newest first. Matching is case-insensitive.
|
||||
"""
|
||||
@spec list_contacts_involving_callsign(String.t() | nil) :: [Contact.t()]
|
||||
def list_contacts_involving_callsign(callsign) when callsign in [nil, ""], do: []
|
||||
|
||||
def list_contacts_involving_callsign(callsign) when is_binary(callsign) do
|
||||
upcased = String.upcase(callsign)
|
||||
|
||||
Contact
|
||||
|> where([c], fragment("upper(?)", c.station1) == ^upcased or fragment("upper(?)", c.station2) == ^upcased)
|
||||
|> order_by([c], desc: c.qso_timestamp, desc: c.id)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec list_contacts(keyword()) :: contact_page()
|
||||
def list_contacts(opts \\ []) do
|
||||
page = max(Keyword.get(opts, :page, 1), 1)
|
||||
|
|
|
|||
|
|
@ -19,13 +19,15 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
|||
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
|
||||
beacons: beacons,
|
||||
involving: involving
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
|
@ -106,7 +108,7 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-200 shadow-sm">
|
||||
<div class="card bg-base-200 shadow-sm mb-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Beacons</h2>
|
||||
<%= if @beacons == [] do %>
|
||||
|
|
@ -150,6 +152,44 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
|||
<% 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 %>
|
||||
<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">
|
||||
<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>
|
||||
</Layouts.app>
|
||||
"""
|
||||
end
|
||||
|
|
|
|||
|
|
@ -498,4 +498,59 @@ defmodule Microwaveprop.RadioTest do
|
|||
assert Radio.list_contacts_for_user(user) == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_contacts_involving_callsign/1" do
|
||||
test "returns contacts where the callsign is station1" do
|
||||
match = create_contact(%{station1: "W5ISP", station2: "K5OTR"})
|
||||
_other = create_contact(%{station1: "W5OTH", station2: "K5OTR"})
|
||||
|
||||
assert [found] = Radio.list_contacts_involving_callsign("W5ISP")
|
||||
assert found.id == match.id
|
||||
end
|
||||
|
||||
test "returns contacts where the callsign is station2" do
|
||||
match = create_contact(%{station1: "W5OTH", station2: "W5ISP"})
|
||||
_other = create_contact(%{station1: "W5OTH", station2: "K5OTR"})
|
||||
|
||||
assert [found] = Radio.list_contacts_involving_callsign("W5ISP")
|
||||
assert found.id == match.id
|
||||
end
|
||||
|
||||
test "matching is case-insensitive" do
|
||||
match = create_contact(%{station1: "W5ISP", station2: "K5OTR"})
|
||||
|
||||
assert [found] = Radio.list_contacts_involving_callsign("w5isp")
|
||||
assert found.id == match.id
|
||||
end
|
||||
|
||||
test "orders newest first" do
|
||||
_old =
|
||||
create_contact(%{
|
||||
station1: "W5ISP",
|
||||
station2: "K5A",
|
||||
qso_timestamp: ~U[2020-01-01 00:00:00Z]
|
||||
})
|
||||
|
||||
new =
|
||||
create_contact(%{
|
||||
station1: "K5B",
|
||||
station2: "W5ISP",
|
||||
qso_timestamp: ~U[2026-06-15 12:00:00Z]
|
||||
})
|
||||
|
||||
assert [first, _] = Radio.list_contacts_involving_callsign("W5ISP")
|
||||
assert first.id == new.id
|
||||
end
|
||||
|
||||
test "returns empty list when no contacts match" do
|
||||
create_contact(%{station1: "W5OTH", station2: "K5OTR"})
|
||||
|
||||
assert Radio.list_contacts_involving_callsign("W5ISP") == []
|
||||
end
|
||||
|
||||
test "returns empty list for blank or nil callsign" do
|
||||
assert Radio.list_contacts_involving_callsign("") == []
|
||||
assert Radio.list_contacts_involving_callsign(nil) == []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue