feat(rover-locations): edit status + notes alongside marker drag
This commit is contained in:
parent
08ac76a4b8
commit
bd77d1a5b0
2 changed files with 174 additions and 13 deletions
|
|
@ -13,14 +13,14 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
|
||||||
case load_location(id) do
|
case load_location(id) do
|
||||||
%Location{} = loc ->
|
%Location{} = loc ->
|
||||||
{:ok,
|
{:ok,
|
||||||
assign(socket,
|
socket
|
||||||
|
|> assign(
|
||||||
page_title: "Rover Location",
|
page_title: "Rover Location",
|
||||||
location: loc,
|
location: loc,
|
||||||
editing: false,
|
editing: false,
|
||||||
working_lat: loc.lat,
|
status_options: status_options()
|
||||||
working_lon: loc.lon,
|
)
|
||||||
grid: Maidenhead.from_latlon(loc.lat, loc.lon, 10)
|
|> assign_working_fields(loc)}
|
||||||
)}
|
|
||||||
|
|
||||||
nil ->
|
nil ->
|
||||||
{:ok,
|
{:ok,
|
||||||
|
|
@ -43,11 +43,11 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("cancel_edit", _params, socket) do
|
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,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign_working_coords(lat, lon)
|
|> assign_working_fields(location)
|
||||||
|> assign(editing: false)
|
|> assign(editing: false)
|
||||||
|> push_event("reset_marker", %{lat: lat, lon: lon})
|
|> push_event("reset_marker", %{lat: lat, lon: lon})
|
||||||
|> push_event("set_marker_draggable", %{draggable: false})}
|
|> push_event("set_marker_draggable", %{draggable: false})}
|
||||||
|
|
@ -59,17 +59,33 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
|
||||||
{:noreply, assign_working_coords(socket, lat, lon)}
|
{:noreply, assign_working_coords(socket, lat, lon)}
|
||||||
end
|
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
|
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),
|
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)
|
updated = Repo.preload(updated, :user)
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(location: updated, editing: false)
|
|> assign(location: updated, editing: false)
|
||||||
|> assign_working_coords(updated.lat, updated.lon)
|
|> assign_working_fields(updated)
|
||||||
|> put_flash(:info, "Location updated.")
|
|> put_flash(:info, "Location updated.")
|
||||||
|> push_event("set_marker_draggable", %{draggable: false})}
|
|> push_event("set_marker_draggable", %{draggable: false})}
|
||||||
else
|
else
|
||||||
|
|
@ -100,6 +116,18 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
|
||||||
)
|
)
|
||||||
end
|
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
|
defp load_location(id) do
|
||||||
case Ecto.UUID.cast(id) do
|
case Ecto.UUID.cast(id) do
|
||||||
{:ok, uuid} -> Location |> Repo.get(uuid) |> Repo.preload(:user)
|
{:ok, uuid} -> Location |> Repo.get(uuid) |> Repo.preload(:user)
|
||||||
|
|
@ -195,8 +223,8 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-base-content/60">Status</dt>
|
<dt class="text-base-content/60">Status</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<span class={status_class(@location.status)}>
|
<span class={status_class(@working_status)}>
|
||||||
{status_label(@location.status)}
|
{status_label(@working_status)}
|
||||||
</span>
|
</span>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -217,10 +245,51 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</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>
|
<h3 class="text-sm text-base-content/60 mb-1">Notes</h3>
|
||||||
<p class="whitespace-pre-line text-sm">{@location.notes}</p>
|
<p class="whitespace-pre-line text-sm">{@location.notes}</p>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,98 @@ defmodule MicrowavepropWeb.RoverLocationsLive.ShowTest do
|
||||||
assert Repo.reload(loc).lat == 32.5
|
assert Repo.reload(loc).lat == 32.5
|
||||||
end
|
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
|
test "non-owner does not see the Edit button", %{conn: conn} do
|
||||||
owner = Microwaveprop.AccountsFixtures.user_fixture()
|
owner = Microwaveprop.AccountsFixtures.user_fixture()
|
||||||
visitor = Microwaveprop.AccountsFixtures.user_fixture()
|
visitor = Microwaveprop.AccountsFixtures.user_fixture()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue