defmodule MicrowavepropWeb.RoverPlanningLive.Form do @moduledoc "New / edit form for `/rover-planning` missions." use MicrowavepropWeb, :live_view alias Microwaveprop.Accounts.User alias Microwaveprop.RoverPlanning alias Microwaveprop.RoverPlanning.Mission alias Microwaveprop.RoverPlanning.Station @band_options [ {"902 MHz", 902}, {"1.3 GHz", 1296}, {"2.3 GHz", 2304}, {"3.4 GHz", 3400}, {"5.7 GHz", 5760}, {"10 GHz", 10_000}, {"24 GHz", 24_000}, {"47 GHz", 47_000} ] @impl true def mount(params, _session, socket) do case current_user(socket) do nil -> {:ok, socket |> put_flash(:error, "Sign in to plan a mission.") |> push_navigate(to: ~p"/rover-planning")} %User{} = user -> {:ok, socket |> assign( page_title: "Rover Mission", band_options: @band_options, current_user: user ) |> apply_action(socket.assigns.live_action, params)} end end defp apply_action(socket, :new, _params) do mission = %Mission{stations: [%Station{position: 0}]} cs = Mission.changeset(mission, %{}) assign(socket, mission: mission, form: to_form(cs), mode: :new ) end defp apply_action(socket, :edit, %{"id" => id}) do user = socket.assigns.current_user case RoverPlanning.get_mission(id) do nil -> socket |> put_flash(:error, "Mission not found.") |> push_navigate(to: ~p"/rover-planning") mission -> if can_modify?(user, mission) do cs = Mission.changeset(mission, %{}) assign(socket, mission: mission, form: to_form(cs), mode: :edit ) else socket |> put_flash(:error, "You can only edit your own missions.") |> push_navigate(to: ~p"/rover-planning/#{mission.id}") end end end @impl true def handle_event("validate", %{"mission" => params}, socket) do cs = socket.assigns.mission |> Mission.changeset(params) |> Map.put(:action, :validate) {:noreply, assign(socket, form: to_form(cs))} end def handle_event("add_station", _params, socket) do params = current_params(socket) stations = Map.get(params, "stations", %{}) next_index = next_station_index(stations) stations = Map.put(stations, Integer.to_string(next_index), %{ "input" => "", "position" => Integer.to_string(next_index) }) cs = socket.assigns.mission |> Mission.changeset(Map.put(params, "stations", stations)) |> Map.put(:action, :validate) {:noreply, assign(socket, form: to_form(cs))} end def handle_event("remove_station", %{"index" => index}, socket) do params = current_params(socket) stations = Map.get(params, "stations", %{}) stations = Map.delete(stations, index) cs = socket.assigns.mission |> Mission.changeset(Map.put(params, "stations", stations)) |> Map.put(:action, :validate) {:noreply, assign(socket, form: to_form(cs))} end def handle_event("save", %{"mission" => params}, socket) do user = socket.assigns.current_user result = case socket.assigns.mode do :new -> RoverPlanning.create_mission(user, params) :edit -> RoverPlanning.update_mission(user, socket.assigns.mission.id, params) end case result do {:ok, mission} -> {:noreply, socket |> put_flash(:info, "Mission saved — computing path profiles.") |> push_navigate(to: ~p"/rover-planning/#{mission.id}")} {:error, :not_found} -> {:noreply, put_flash(socket, :error, "Mission not found.")} {:error, %Ecto.Changeset{} = cs} -> {:noreply, assign(socket, form: to_form(cs))} end end defp current_params(socket) do case socket.assigns.form.source do %Ecto.Changeset{params: params} when is_map(params) -> params _ -> %{} end end defp next_station_index(stations) when map_size(stations) == 0, do: 0 defp next_station_index(stations) do stations |> Map.keys() |> Enum.map(fn k -> case Integer.parse(to_string(k)) do {n, _} -> n :error -> -1 end end) |> Enum.max() |> Kernel.+(1) end defp current_user(%Phoenix.LiveView.Socket{assigns: assigns}) do case assigns[:current_scope] do %{user: %User{} = user} -> user _ -> nil end end defp can_modify?(%User{is_admin: true}, _), do: true defp can_modify?(%User{id: id}, %Mission{user_id: id}) when not is_nil(id), do: true defp can_modify?(_, _), do: false @impl true def render(assigns) do ~H""" <.header> {if @mode == :edit, do: "Edit mission", else: "New rover mission"} <:subtitle> Stationary stations can be callsigns, Maidenhead grids, or lat, lon pairs. <.form for={@form} id="mission-form" phx-change="validate" phx-submit="save" class="space-y-4" > <.input field={@form[:name]} type="text" label="Mission name" placeholder="DFW August rove" />
<.input field={@form[:band_mhz]} type="select" label="Band" options={@band_options} /> <.input field={@form[:rover_height_ft]} type="number" step="any" label="Rover antenna (ft)" /> <.input field={@form[:station_height_ft]} type="number" step="any" label="Station antenna (ft)" />

Stationary stations

<.inputs_for :let={s} field={@form[:stations]}>
<.input field={s[:input]} type="text" label="Callsign / grid / lat,lon" placeholder="W5LUA or EM13qc or 32.91, -97.06" />
<.input field={s[:lat]} type="number" step="any" label="Lat" />
<.input field={s[:lon]} type="number" step="any" label="Lon" />

Resolved callsign: {s[:callsign].value} · grid {s[:grid].value}

{Phoenix.HTML.html_escape(Enum.map_join(@form[:stations].errors, ", ", &elem(&1, 0)))}

<.input field={@form[:notes]} type="textarea" label="Notes" rows="3" />
<.link navigate={~p"/rover-planning"} class="btn btn-ghost">Cancel
""" end end