feat(rover-planning): inline per-group delete, drop standalone site list

The 'Rover sites' card no longer renders the long list of every site —
each rover-location now appears once as a path-profile group heading,
which carries its own delete button next to the n/m progress text. The
add-site form stays in place.
This commit is contained in:
Graham McIntire 2026-05-03 13:54:46 -05:00
parent c2a23d1840
commit 327b4fc561
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 42 additions and 43 deletions

View file

@ -374,55 +374,17 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
<div class="card bg-base-100 border border-base-300 p-4 mb-4"> <div class="card bg-base-100 border border-base-300 p-4 mb-4">
<div class="flex items-center justify-between mb-2 gap-2 flex-wrap"> <div class="flex items-center justify-between mb-2 gap-2 flex-wrap">
<h3 class="font-semibold">Rover sites</h3> <h3 class="font-semibold">Add rover site</h3>
<span class="text-xs text-base-content/60"> <span class="text-xs text-base-content/60">
{length(@rover_sites)} site(s) paths recompute on add/remove {length(@rover_sites)} site(s) paths recompute on add/remove
</span> </span>
</div> </div>
<ul :if={@rover_sites != []} class="text-sm space-y-1 mb-3">
<%!--
`flex` (no `flex-wrap`) + `min-w-0` on the row, plus `shrink-0`
on the identifier columns and `truncate flex-1 min-w-0` on the
notes cell, keeps the row to a single line: long notes get
ellipsised instead of pushing the trash button onto a new line.
`title={site.notes}` preserves the full text on hover.
--%>
<li :for={site <- @rover_sites} class="flex items-center gap-2 min-w-0">
<span class="font-mono shrink-0">
{Maidenhead.from_latlon(site.lat, site.lon, 10)}
</span>
<span class="text-base-content/60 font-mono text-xs shrink-0">
({Float.round(site.lat, 4)}, {Float.round(site.lon, 4)})
</span>
<span
:if={site.notes && site.notes != ""}
class="text-base-content/60 text-xs truncate min-w-0 flex-1"
title={site.notes}
>
· {site.notes}
</span>
<button
:if={can_delete_site?(assigns, site)}
type="button"
phx-click="delete_rover_site"
phx-value-id={site.id}
data-confirm="Remove this rover site? This affects every mission that uses it."
class="btn btn-ghost btn-xs text-error ml-auto shrink-0"
title="Remove rover site"
>
<.icon name="hero-trash" class="w-4 h-4" />
</button>
</li>
</ul>
<p :if={@rover_sites == []} class="text-sm text-base-content/60 mb-3">
No rover sites match this mission's scope yet — add one below.
</p>
<%!-- <%!--
The add form takes the same flexible input the station inputs use: The add form takes the same flexible input the station inputs use:
callsign, Maidenhead grid, or `lat, lon`. Sign-in required. callsign, Maidenhead grid, or `lat, lon`. Sign-in required.
The site list itself is rendered inline as path-profile group
headings below each heading carries its own delete button.
--%> --%>
<form <form
:if={@current_scope && @current_scope.user} :if={@current_scope && @current_scope.user}
@ -489,8 +451,21 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
· {location.notes} · {location.notes}
</span> </span>
</div> </div>
<div class="text-xs text-base-content/70"> <div class="flex items-center gap-2">
{group_progress(group_paths)} <span class="text-xs text-base-content/70">
{group_progress(group_paths)}
</span>
<button
:if={location && location.id && can_delete_site?(assigns, location)}
type="button"
phx-click="delete_rover_site"
phx-value-id={location.id}
data-confirm="Remove this rover site? This affects every mission that uses it."
class="btn btn-ghost btn-xs text-error"
title="Remove rover site"
>
<.icon name="hero-trash" class="w-4 h-4" />
</button>
</div> </div>
</div> </div>

View file

@ -398,6 +398,30 @@ defmodule MicrowavepropWeb.RoverPlanningLiveTest do
assert Repo.aggregate(Location, :count) == 0 assert Repo.aggregate(Location, :count) == 0
end end
test "delete button lives inside the path-profile group heading, not above it",
%{conn: conn} do
user = AccountsFixtures.user_fixture()
mission = create_mission(user, "Per-group delete mission")
conn = log_in_user(conn, user)
[site] = Repo.all(Location)
{:ok, lv, html} = live(conn, ~p"/rover-planning/#{mission.id}")
# The standalone "Rover sites" list is gone — the heading now reads
# "Add rover site" (still the form). Each path-profile group
# heading carries its own per-location delete button. Asserting on
# the structural ancestor (`[data-rover-group]`) ensures the button
# is INSIDE the grouping, not in some legacy list above it.
refute html =~ "Rover sites</h3>"
assert html =~ "Add rover site</h3>"
assert has_element?(
lv,
"[data-rover-group] button[phx-click='delete_rover_site'][phx-value-id='#{site.id}']"
)
end
test "add_rover_site rejects whitespace-only input with a flash error", test "add_rover_site rejects whitespace-only input with a flash error",
%{conn: conn} do %{conn: conn} do
user = AccountsFixtures.user_fixture() user = AccountsFixtures.user_fixture()