From bd77d1a5b05b2aa242ef1d5f84e083bad0347df0 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sun, 3 May 2026 13:10:47 -0500 Subject: [PATCH] feat(rover-locations): edit status + notes alongside marker drag --- .../live/rover_locations_live/show.ex | 95 ++++++++++++++++--- .../live/rover_locations_live/show_test.exs | 92 ++++++++++++++++++ 2 files changed, 174 insertions(+), 13 deletions(-) diff --git a/lib/microwaveprop_web/live/rover_locations_live/show.ex b/lib/microwaveprop_web/live/rover_locations_live/show.ex index d57b29d7..0efa8918 100644 --- a/lib/microwaveprop_web/live/rover_locations_live/show.ex +++ b/lib/microwaveprop_web/live/rover_locations_live/show.ex @@ -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
Status
- - {status_label(@location.status)} + + {status_label(@working_status)}
@@ -217,10 +245,51 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do -
+

Notes

{@location.notes}

+ +
+
+ + +
+
+ + +
+
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()