defmodule MicrowavepropWeb.RoverLocationsLive.Show do @moduledoc "Detail page for a single rover location, with a map + marker." use MicrowavepropWeb, :live_view alias Microwaveprop.Accounts.User alias Microwaveprop.Radio.Maidenhead alias Microwaveprop.Repo alias Microwaveprop.Rover alias Microwaveprop.Rover.Location @impl true def mount(%{"id" => id}, _session, socket) do case load_location(id) do %Location{} = loc -> {:ok, socket |> assign( page_title: "Rover Location", location: loc, editing: false, status_options: status_options() ) |> assign_working_fields(loc)} nil -> {:ok, socket |> put_flash(:error, "Location not found.") |> push_navigate(to: ~p"/rover-locations")} end end @impl true def handle_event("toggle_edit", _params, socket) do if can_modify?(socket.assigns[:current_scope], socket.assigns.location) do {:noreply, socket |> assign(editing: true) |> push_event("set_marker_draggable", %{draggable: true})} else {:noreply, put_flash(socket, :error, "You can only edit your own locations.")} end end def handle_event("cancel_edit", _params, socket) do %Location{lat: lat, lon: lon} = location = socket.assigns.location {:noreply, socket |> assign_working_fields(location) |> assign(editing: false) |> push_event("reset_marker", %{lat: lat, lon: lon}) |> push_event("set_marker_draggable", %{draggable: false})} end # Hook fires this on `dragend` with the marker's new coordinates. # We only update the working preview — persistence waits for save. def handle_event("location_dragged", %{"lat" => lat, "lon" => lon}, socket) when is_number(lat) and is_number(lon) 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, 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, attrs) do updated = Repo.preload(updated, :user) {:noreply, socket |> assign(location: updated, editing: false) |> assign_working_fields(updated) |> put_flash(:info, "Location updated.") |> push_event("set_marker_draggable", %{draggable: false})} else {:error, :unauthenticated} -> {:noreply, put_flash(socket, :error, "Sign in required.")} {:error, :not_found} -> {:noreply, put_flash(socket, :error, "You can only edit your own locations.")} {:error, %Ecto.Changeset{}} -> {:noreply, put_flash(socket, :error, "Could not save those coordinates.")} end end def handle_event("delete", _params, socket) do with {:ok, user} <- authenticated(socket), {:ok, _} <- Rover.delete_location(user, socket.assigns.location.id) do {:noreply, socket |> put_flash(:info, "Location removed.") |> push_navigate(to: ~p"/rover-locations")} else {:error, :unauthenticated} -> {:noreply, put_flash(socket, :error, "Sign in required.")} {:error, :not_found} -> {:noreply, put_flash(socket, :error, "You can only delete your own locations.")} end end defp assign_working_coords(socket, lat, lon) do assign(socket, working_lat: lat, working_lon: lon, grid: Maidenhead.from_latlon(lat, lon, 10) ) 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) :error -> nil end end defp authenticated(%Phoenix.LiveView.Socket{assigns: %{current_scope: %{user: %User{} = user}}}), do: {:ok, user} defp authenticated(_), do: {:error, :unauthenticated} defp can_modify?(%{user: %User{is_admin: true}}, _), do: true defp can_modify?(%{user: %User{id: id}}, %Location{user_id: id}) when not is_nil(id), do: true defp can_modify?(_, _), do: false defp status_label(:good), do: "Good" defp status_label(:bad), do: "Bad" defp status_label(_), do: "" defp status_class(:good), do: "badge badge-success" defp status_class(:bad), do: "badge badge-error" defp status_class(_), do: "badge" @impl true def render(assigns) do ~H""" <.header> Rover Location <:subtitle> {status_label(@location.status)} {@grid} Editing — drag the marker <:actions> <.link navigate={~p"/rover-locations"} class="btn btn-ghost btn-sm"> <.icon name="hero-arrow-left" class="w-4 h-4" /> Back
Grid
{@grid}
Coordinates
{Float.round(@working_lat, 6)}, {Float.round(@working_lon, 6)}
Status
{status_label(@working_status)}
Added
{Calendar.strftime(@location.inserted_at, "%Y-%m-%d %H:%M UTC")}
Submitted by
<.link navigate={~p"/u/#{@location.user.callsign}"} class="link link-primary font-mono" > {@location.user.callsign}

Notes

{@location.notes}

""" end end