feat(rover-locations): edit status + notes alongside marker drag

This commit is contained in:
Graham McIntire 2026-05-03 13:10:47 -05:00
parent 08ac76a4b8
commit bd77d1a5b0
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 174 additions and 13 deletions

View file

@ -13,14 +13,14 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
case load_location(id) do
%Location{} = loc ->
{:ok,
assign(socket,
socket
|> assign(
page_title: "Rover Location",
location: loc,
editing: false,
working_lat: loc.lat,
working_lon: loc.lon,
grid: Maidenhead.from_latlon(loc.lat, loc.lon, 10)
)}
status_options: status_options()
)
|> assign_working_fields(loc)}
nil ->
{:ok,
@ -43,11 +43,11 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
end
def handle_event("cancel_edit", _params, socket) do
%Location{lat: lat, lon: lon} = socket.assigns.location
%Location{lat: lat, lon: lon} = location = socket.assigns.location
{:noreply,
socket
|> assign_working_coords(lat, lon)
|> assign_working_fields(location)
|> assign(editing: false)
|> push_event("reset_marker", %{lat: lat, lon: lon})
|> push_event("set_marker_draggable", %{draggable: false})}
@ -59,17 +59,33 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
{:noreply, assign_working_coords(socket, lat, lon)}
end
def handle_event("update_form", %{"location" => params}, socket) do
{:noreply,
assign(socket,
working_status: parse_status(Map.get(params, "status"), socket.assigns.working_status),
working_notes: Map.get(params, "notes", socket.assigns.working_notes)
)}
end
def handle_event("save_edit", _params, socket) do
%{working_lat: lat, working_lon: lon, location: %Location{id: id}} = socket.assigns
%{
working_lat: lat,
working_lon: lon,
working_status: status,
working_notes: notes,
location: %Location{id: id}
} = socket.assigns
attrs = %{lat: lat, lon: lon, status: status, notes: notes}
with {:ok, user} <- authenticated(socket),
{:ok, updated} <- Rover.update_location(user, id, %{lat: lat, lon: lon}) do
{:ok, updated} <- Rover.update_location(user, id, attrs) do
updated = Repo.preload(updated, :user)
{:noreply,
socket
|> assign(location: updated, editing: false)
|> assign_working_coords(updated.lat, updated.lon)
|> assign_working_fields(updated)
|> put_flash(:info, "Location updated.")
|> push_event("set_marker_draggable", %{draggable: false})}
else
@ -100,6 +116,18 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
)
end
defp assign_working_fields(socket, %Location{lat: lat, lon: lon, status: status, notes: notes}) do
socket
|> assign_working_coords(lat, lon)
|> assign(working_status: status, working_notes: notes || "")
end
defp status_options, do: [{"Good", "good"}, {"Bad", "bad"}]
defp parse_status("good", _fallback), do: :good
defp parse_status("bad", _fallback), do: :bad
defp parse_status(_, fallback), do: fallback
defp load_location(id) do
case Ecto.UUID.cast(id) do
{:ok, uuid} -> Location |> Repo.get(uuid) |> Repo.preload(:user)
@ -195,8 +223,8 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
<div>
<dt class="text-base-content/60">Status</dt>
<dd>
<span class={status_class(@location.status)}>
{status_label(@location.status)}
<span class={status_class(@working_status)}>
{status_label(@working_status)}
</span>
</dd>
</div>
@ -217,10 +245,51 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
</div>
</dl>
<div :if={@location.notes && @location.notes != ""} class="mt-4">
<div
:if={(not @editing and @location.notes) && @location.notes != ""}
class="mt-4"
>
<h3 class="text-sm text-base-content/60 mb-1">Notes</h3>
<p class="whitespace-pre-line text-sm">{@location.notes}</p>
</div>
<form
:if={@editing}
id="location-edit-form"
phx-change="update_form"
phx-submit="save_edit"
class="mt-4 space-y-3"
>
<div>
<label class="label" for="location-status">
<span class="label-text text-sm text-base-content/60">Status</span>
</label>
<select
id="location-status"
name="location[status]"
class="select select-bordered select-sm w-full max-w-xs"
>
<option
:for={{label, value} <- @status_options}
value={value}
selected={to_string(@working_status) == value}
>
{label}
</option>
</select>
</div>
<div>
<label class="label" for="location-notes">
<span class="label-text text-sm text-base-content/60">Notes</span>
</label>
<textarea
id="location-notes"
name="location[notes]"
rows="3"
class="textarea textarea-bordered w-full"
>{@working_notes}</textarea>
</div>
</form>
</div>
<div

View file

@ -152,6 +152,98 @@ defmodule MicrowavepropWeb.RoverLocationsLive.ShowTest do
assert Repo.reload(loc).lat == 32.5
end
test "edit mode exposes status + notes inputs populated with current values",
%{conn: conn} do
user = Microwaveprop.AccountsFixtures.user_fixture()
{:ok, loc} =
Rover.create_location(user, %{
lat: 32.5,
lon: -97.5,
status: :good,
notes: "first pass"
})
conn = log_in_user(conn, user)
{:ok, lv, html} = live(conn, ~p"/rover-locations/#{loc.id}")
# Inputs are not visible until edit mode is entered.
refute has_element?(lv, "select[name='location[status]']")
refute has_element?(lv, "textarea[name='location[notes]']")
assert html =~ "first pass"
_ = lv |> element("button", "Edit") |> render_click()
assert has_element?(lv, "select[name='location[status]']")
assert has_element?(
lv,
"textarea[name='location[notes]']",
"first pass"
)
end
test "save persists status + notes edits", %{conn: conn} do
user = Microwaveprop.AccountsFixtures.user_fixture()
{:ok, loc} =
Rover.create_location(user, %{
lat: 32.5,
lon: -97.5,
status: :good,
notes: "old"
})
conn = log_in_user(conn, user)
{:ok, lv, _html} = live(conn, ~p"/rover-locations/#{loc.id}")
_ = lv |> element("button", "Edit") |> render_click()
_ =
lv
|> form("#location-edit-form",
location: %{status: "bad", notes: "private property"}
)
|> render_change()
html = lv |> element("button[phx-click='save_edit']") |> render_click()
assert html =~ "Location updated"
reloaded = Repo.reload(loc)
assert reloaded.status == :bad
assert reloaded.notes == "private property"
end
test "cancel reverts pending status + notes changes", %{conn: conn} do
user = Microwaveprop.AccountsFixtures.user_fixture()
{:ok, loc} =
Rover.create_location(user, %{
lat: 32.5,
lon: -97.5,
status: :good,
notes: "keeper"
})
conn = log_in_user(conn, user)
{:ok, lv, _html} = live(conn, ~p"/rover-locations/#{loc.id}")
_ = lv |> element("button", "Edit") |> render_click()
_ =
lv
|> form("#location-edit-form",
location: %{status: "bad", notes: "scratch this"}
)
|> render_change()
_ = lv |> element("button[phx-click='cancel_edit']") |> render_click()
reloaded = Repo.reload(loc)
assert reloaded.status == :good
assert reloaded.notes == "keeper"
end
test "non-owner does not see the Edit button", %{conn: conn} do
owner = Microwaveprop.AccountsFixtures.user_fixture()
visitor = Microwaveprop.AccountsFixtures.user_fixture()