Skip to content

Commit

Permalink
feat(disruptions): Create new disruption page/form component for Arro…
Browse files Browse the repository at this point in the history
…w V2 (#1073)

Adds a new disruption form for the Arrow V2 epic based on the mockup here: https://miro.com/app/board/uXjVKVLTOI0=/?moveToWidget=3458764585920181114&cot=14
  • Loading branch information
rudiejd authored Jan 8, 2025
1 parent 1a0a1db commit 9711886
Show file tree
Hide file tree
Showing 10 changed files with 617 additions and 0 deletions.
104 changes: 104 additions & 0 deletions lib/arrow/disruptions.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
defmodule Arrow.Disruptions do
@moduledoc """
The Disruptions context.
"""

import Ecto.Query, warn: false
alias Arrow.Repo

alias Arrow.Disruptions.DisruptionV2

@doc """
Returns the list of disruptionsv2.
## Examples
iex> list_disruptionsv2()
[%DisruptionV2{}, ...]
"""
def list_disruptionsv2 do
Repo.all(DisruptionV2)
end

@doc """
Gets a single disruption_v2.
Raises `Ecto.NoResultsError` if the Disruption v2 does not exist.
## Examples
iex> get_disruption_v2!(123)
%DisruptionV2{}
iex> get_disruption_v2!(456)
** (Ecto.NoResultsError)
"""
def get_disruption_v2!(id), do: Repo.get!(DisruptionV2, id)

@doc """
Creates a disruption_v2.
## Examples
iex> create_disruption_v2(%{field: value})
{:ok, %DisruptionV2{}}
iex> create_disruption_v2(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_disruption_v2(attrs \\ %{}) do
%DisruptionV2{}
|> DisruptionV2.changeset(attrs)
|> Repo.insert()
end

@doc """
Updates a disruption_v2.
## Examples
iex> update_disruption_v2(disruption_v2, %{field: new_value})
{:ok, %DisruptionV2{}}
iex> update_disruption_v2(disruption_v2, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_disruption_v2(%DisruptionV2{} = disruption_v2, attrs) do
disruption_v2
|> DisruptionV2.changeset(attrs)
|> Repo.update()
end

@doc """
Deletes a disruption_v2.
## Examples
iex> delete_disruption_v2(disruption_v2)
{:ok, %DisruptionV2{}}
iex> delete_disruption_v2(disruption_v2)
{:error, %Ecto.Changeset{}}
"""
def delete_disruption_v2(%DisruptionV2{} = disruption_v2) do
Repo.delete(disruption_v2)
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking disruption_v2 changes.
## Examples
iex> change_disruption_v2(disruption_v2)
%Ecto.Changeset{data: %DisruptionV2{}}
"""
def change_disruption_v2(%DisruptionV2{} = disruption_v2, attrs \\ %{}) do
DisruptionV2.changeset(disruption_v2, attrs)
end
end
26 changes: 26 additions & 0 deletions lib/arrow/disruptions/disruption_v2.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Arrow.Disruptions.DisruptionV2 do
@moduledoc """
Represents a change to scheduled service for one of our transportions modes.
See: https://github.com/mbta/gtfs_creator/blob/ab5aac52561027aa13888e4c4067a8de177659f6/gtfs_creator2/disruptions/disruption.py
"""
use Ecto.Schema
import Ecto.Changeset

schema "disruptionsv2" do
field :title, :string
field :mode, Ecto.Enum, values: [:subway, :commuter_rail, :silver_line, :bus]
field :is_active, :boolean
field :description, :string

timestamps(type: :utc_datetime)
end

@doc false
def changeset(disruption_v2, attrs) do
disruption_v2
|> cast(attrs, [:title, :is_active, :description])
|> cast(attrs, [:mode], force_changes: true)
|> validate_required([:title, :mode, :is_active])
end
end
231 changes: 231 additions & 0 deletions lib/arrow_web/live/disruption_v2_live/disruption_v2_view_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
defmodule ArrowWeb.DisruptionV2ViewLive do
use ArrowWeb, :live_view

alias Arrow.Adjustment
alias Arrow.Disruptions
alias Arrow.Disruptions.DisruptionV2

@spec disruption_status_labels :: map()
def disruption_status_labels, do: %{Approved: true, Pending: false}

@spec mode_labels :: map()
def mode_labels,
do: %{
Subway: :subway,
"Commuter Rail": :commuter_rail,
Bus: :bus,
"Silver Line": :silver_line
}

def disruption_form(assigns) do
~H"""
<div class="w-75">
<.simple_form for={@form} id="disruption_v2-form" phx-submit="save" phx-change="validate">
<div class="flex flex-row">
<fieldset class="w-50">
<legend>Title</legend>
<.input field={@form[:title]} type="text" placeholder="Add text" />
</fieldset>
<fieldset class="w-50 ml-20">
<legend>Approval Status</legend>
<div :for={{{status, value}, idx} <- Enum.with_index(disruption_status_labels())} %>
<label class="form-check form-check-label">
<input
name={@form[:is_active].name}
id={"#{@form[:is_active].id}-#{idx}"}
class="form-check-input"
type="radio"
checked={to_string(@form[:is_active].value) == to_string(value)}
value={to_string(value)}
/>
{status}
</label>
</div>
</fieldset>
</div>
<fieldset>
<legend>Mode</legend>
<div :for={{{mode, value}, idx} <- Enum.with_index(mode_labels())}>
<label class="form-check form-check-label">
<input
name={@form[:mode].name}
id={"#{@form[:mode].id}-#{idx}"}
class="form-check-input"
type="radio"
checked={to_string(@form[:mode].value) == to_string(value) |> IO.inspect()}
value={to_string(value)}
/>
<span
class="m-icon m-icon-sm mr-1"
style={"background-image: url('#{Map.get(@icon_paths, value)}');"}
}
>
</span>
{mode}
</label>
</div>
</fieldset>
<fieldset>
<legend>Description</legend>
<.input
type="textarea"
class="form-control"
cols={30}
field={@form[:description]}
aria-describedby="descriptionHelp"
aria-label="description"
/>
<small id="descriptionHelp" class="form-text">
please include: types of disruption, place, and reason
</small>
</fieldset>
<:actions>
<div class="w-25 mr-2">
<.button disabled={not Enum.empty?(@form.source.errors)} class="btn btn-primary w-100">
Save Disruption
</.button>
</div>
<div class="w-25 mr-2">
<.link_button
href={~p"/"}
class="btn-outline-primary w-100"
data-confirm="Are you sure you want to cancel? All changes will be lost!"
>
Cancel
</.link_button>
</div>
</:actions>
</.simple_form>
</div>
"""
end

@impl true
def mount(%{"id" => disruption_id}, _session, socket) do
disruption = Disruptions.get_disruption_v2!(disruption_id)

socket =
socket
|> assign(:form_action, :edit)
|> assign(:title, "edit disruption")
|> assign(:form, Disruptions.change_disruption_v2(disruption) |> to_form)
|> assign(:errors, %{})
|> assign(:icon_paths, icon_paths(socket))
|> assign(:disruption_v2, disruption)

{:ok, socket}
end

@impl true
def mount(%{} = _params, _session, socket) do
socket =
socket
|> assign(:form_action, :create)
|> assign(:http_action, ~p"/disruptionsv2/new")
|> assign(:title, "create new disruption")
|> assign(:form, Disruptions.change_disruption_v2(%DisruptionV2{}) |> to_form)
|> assign(:errors, %{})
|> assign(:icon_paths, icon_paths(socket))
|> assign(:disruption_v2, %DisruptionV2{})

{:ok, socket}
end

@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end

@impl true
def handle_event(
"validate",
%{"disruption_v2" => disruption_v2_params},
%Phoenix.LiveView.Socket{} = socket
) do
form =
socket.assigns.disruption_v2
|> Disruptions.change_disruption_v2(disruption_v2_params)
|> to_form(action: :validate)

{:noreply, assign(socket, form: form)}
end

@impl true
def handle_event("save", %{"disruption_v2" => disruption_v2_params}, socket) do
save_disruption_v2(socket, socket.assigns.form_action, disruption_v2_params)
end

defp save_disruption_v2(socket, action, disruption_v2_params) do
save_result =
case action do
:create ->
Disruptions.create_disruption_v2(disruption_v2_params)

:edit ->
Disruptions.update_disruption_v2(socket.assigns.disruption_v2, disruption_v2_params)

_ ->
raise "Unknown action for disruption form: #{action}"
end

case save_result do
{:ok, _} ->
{:noreply,
socket
|> put_flash(:info, "Disruption saved successfully")}

{:error, %Ecto.Changeset{} = changeset} ->
{:noreply,
socket
|> assign(form: to_form(changeset))
|> put_flash(:error, "Error when saving disruption!")}
end
end

@adjustment_kind_icon_names %{
blue_line: "blue-line",
bus: "mode-bus",
commuter_rail: "mode-commuter-rail",
green_line: "green-line",
green_line_b: "green-line-b",
green_line_c: "green-line-c",
green_line_d: "green-line-d",
green_line_e: "green-line-e",
mattapan_line: "mattapan-line",
orange_line: "orange-line",
red_line: "red-line",
silver_line: "silver-line"
}

defp adjustment_kind_icon_path(socket, kind) do
Phoenix.VerifiedRoutes.static_path(
socket,
"/images/icon-#{@adjustment_kind_icon_names[kind]}-small.svg"
)
end

defp icon_paths(socket) do
Adjustment.kinds()
|> Enum.map(&{&1, adjustment_kind_icon_path(socket, &1)})
|> Enum.into(%{})
|> Map.put(
:subway,
Phoenix.VerifiedRoutes.static_path(socket, "/images/icon-mode-subway-small.svg")
)
end

defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Disruption v2")
|> assign(:disruption_v2, Disruptions.get_disruption_v2!(id))
end

defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Disruption v2")
|> assign(:disruption_v2, %DisruptionV2{})
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<hr />

<.header>
{@title}
</.header>

<hr />

<h2>Disruption</h2>

<.disruption_form
id="disruption_v2_form"
form={@form}
disruption_v2={@disruption_v2}
icon_paths={@icon_paths}
/>
4 changes: 4 additions & 0 deletions lib/arrow_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ defmodule ArrowWeb.Router do
resources("/disruptions", DisruptionController, except: [:index])
put("/disruptions/:id/row_status", DisruptionController, :update_row_status)
post("/disruptions/:id/notes", NoteController, :create)

live("/disruptionsv2/new", DisruptionV2ViewLive, :new)
live("/disruptionsv2/:id/edit", DisruptionV2ViewLive, :edit)

live("/stops/new", StopViewLive, :new)
live("/stops/:id/edit", StopViewLive, :edit)
get("/stops", StopController, :index)
Expand Down
Loading

0 comments on commit 9711886

Please sign in to comment.