"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()
|
|> Repo.all()
|
||||||
end
|
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()
|
@spec list_contacts(keyword()) :: contact_page()
|
||||||
def list_contacts(opts \\ []) do
|
def list_contacts(opts \\ []) do
|
||||||
page = max(Keyword.get(opts, :page, 1), 1)
|
page = max(Keyword.get(opts, :page, 1), 1)
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,15 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
||||||
user ->
|
user ->
|
||||||
contacts = Radio.list_contacts_for_user(user)
|
contacts = Radio.list_contacts_for_user(user)
|
||||||
beacons = Beacons.list_beacons_for_user(user)
|
beacons = Beacons.list_beacons_for_user(user)
|
||||||
|
involving = Radio.list_contacts_involving_callsign(user.callsign)
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
assign(socket,
|
assign(socket,
|
||||||
page_title: user.callsign,
|
page_title: user.callsign,
|
||||||
profile: user,
|
profile: user,
|
||||||
contacts: contacts,
|
contacts: contacts,
|
||||||
beacons: beacons
|
beacons: beacons,
|
||||||
|
involving: involving
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -106,7 +108,7 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card bg-base-200 shadow-sm">
|
<div class="card bg-base-200 shadow-sm mb-6">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title">Beacons</h2>
|
<h2 class="card-title">Beacons</h2>
|
||||||
<%= if @beacons == [] do %>
|
<%= if @beacons == [] do %>
|
||||||
|
|
@ -150,6 +152,44 @@ defmodule MicrowavepropWeb.UserProfileLive do
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -498,4 +498,59 @@ defmodule Microwaveprop.RadioTest do
|
||||||
assert Radio.list_contacts_for_user(user) == []
|
assert Radio.list_contacts_for_user(user) == []
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue