Skip to content

Commit

Permalink
Merge branch 'master' into cm/expose-shuttle-shapes
Browse files Browse the repository at this point in the history
  • Loading branch information
cmaddox5 authored Feb 28, 2025
2 parents 1d9f5c0 + a8c0a32 commit da7c43d
Show file tree
Hide file tree
Showing 17 changed files with 584 additions and 31 deletions.
13 changes: 13 additions & 0 deletions assets/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,24 @@ const sortable = {
},
} as ViewHook

const LimitTime = {
mounted() {
this.el.addEventListener("input", () => {
const el = this.el as HTMLInputElement
const match = el.value.match(/^(\d{2})(\d{2})$/)
if (match) {
el.value = `${match[1]}:${match[2]}`
}
})
},
} as ViewHook

// https://github.com/fidr/phoenix_live_react
const hooks = {
LiveReact,
sortable,
...live_select,
LimitTime,
}

const csrfToken = document
Expand Down
21 changes: 21 additions & 0 deletions lib/arrow/disruptions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -396,4 +396,25 @@ defmodule Arrow.Disruptions do
&(&1 |> Integer.to_string() |> String.pad_leading(2, "0"))
)
end

def start_end_dates(%DisruptionV2{limits: [], replacement_services: []}) do
{nil, nil}
end

def start_end_dates(%DisruptionV2{
limits: limits,
replacement_services: replacement_services
}) do
min_date =
(limits ++ replacement_services)
|> Enum.map(& &1.start_date)
|> Enum.min(Date, fn -> ~D[9999-12-31] end)

max_date =
(limits ++ replacement_services)
|> Enum.map(& &1.end_date)
|> Enum.max(Date, fn -> ~D[0000-01-01] end)

{min_date, max_date}
end
end
23 changes: 22 additions & 1 deletion lib/arrow/shuttles/shuttle.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ defmodule Arrow.Shuttles.Shuttle do
@moduledoc "schema for a shuttle for the db"
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query

alias Arrow.Disruptions.ReplacementService
alias Arrow.Repo

@type id :: integer
@type t :: %__MODULE__{
Expand Down Expand Up @@ -59,7 +63,24 @@ defmodule Arrow.Shuttles.Shuttle do
end

_ ->
changeset
id = get_field(changeset, :id)

replacement_services =
if is_nil(id) do
[]
else
Repo.all(from r in ReplacementService, where: r.shuttle_id == ^id)
end

if length(replacement_services) > 0 do
add_error(
changeset,
:status,
"cannot set to a non-active status while in use as a replacement service"
)
else
changeset
end
end
end

Expand Down
2 changes: 2 additions & 0 deletions lib/arrow_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ defmodule ArrowWeb.CoreComponents do
attr :field, :any, required: true, doc: "Field for `shuttle_id` value"

attr :shuttle, :any, required: true, doc: "Currently selected shuttle, if any"
attr :only_approved?, :boolean, default: false

attr :label, :string, default: "Stop ID"
attr :class, :string, default: nil
Expand All @@ -527,6 +528,7 @@ defmodule ArrowWeb.CoreComponents do
id={@id}
field={@field}
shuttle={@shuttle}
only_approved?={@only_approved?}
class={@class}
/>
"""
Expand Down
2 changes: 2 additions & 0 deletions lib/arrow_web/components/limit_section.ex
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,15 @@ defmodule ArrowWeb.LimitSection do
:if={normalize_value("checkbox", input_value(f_day_of_week, :active?))}
field={f_day_of_week[:start_time]}
disabled={normalize_value("checkbox", input_value(f_day_of_week, :all_day?))}
phx-hook="LimitTime"
/>
</div>
<div class="col col-lg-3">
<.input
:if={normalize_value("checkbox", input_value(f_day_of_week, :active?))}
field={f_day_of_week[:end_time]}
disabled={normalize_value("checkbox", input_value(f_day_of_week, :all_day?))}
phx-hook="LimitTime"
/>
</div>
<div class="col">
Expand Down
6 changes: 5 additions & 1 deletion lib/arrow_web/components/replacement_service_section.ex
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,11 @@ defmodule ArrowWeb.ReplacementServiceSection do
do: "add new replacement service component",
else: "edit disruption replacement service component"}
</h4>
<.shuttle_input field={@form[:shuttle_id]} shuttle={input_value(@form, :shuttle)} />
<.shuttle_input
field={@form[:shuttle_id]}
shuttle={input_value(@form, :shuttle)}
only_approved?={true}
/>
<div :if={not empty_input_value?(@form[:shuttle_id].value)} class="row relative z-0">
<div class="col p-0">
{live_react_component(
Expand Down
21 changes: 18 additions & 3 deletions lib/arrow_web/components/shuttle_input.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule ArrowWeb.ShuttleInput do
attr :id, :string, required: true
attr :field, :any, required: true
attr :shuttle, :any, required: true
attr :only_approved?, :boolean, default: false
attr :label, :string, default: "select shuttle route"
attr :class, :string, default: nil

Expand All @@ -22,7 +23,9 @@ defmodule ArrowWeb.ShuttleInput do
assigns,
:options,
if is_nil(assigns.shuttle) || !Ecto.assoc_loaded?(assigns.shuttle) do
Shuttles.list_shuttles() |> Enum.map(&option_for_shuttle/1)
Shuttles.list_shuttles()
|> filter_only_approved(assigns.only_approved?)
|> Enum.map(&option_for_shuttle/1)
else
[option_for_shuttle(assigns.shuttle)]
end
Expand All @@ -47,9 +50,14 @@ defmodule ArrowWeb.ShuttleInput do
def handle_event("live_select_change", %{"id" => live_select_id, "text" => text}, socket) do
new_opts =
if String.length(text) == 0 do
Shuttles.list_shuttles() |> Enum.map(&option_for_shuttle/1)
Shuttles.list_shuttles()
|> filter_only_approved(socket.assigns.only_approved?)
|> Enum.map(&option_for_shuttle/1)
else
text |> Shuttles.shuttles_by_search_string() |> Enum.map(&option_for_shuttle/1)
text
|> Shuttles.shuttles_by_search_string()
|> filter_only_approved(socket.assigns.only_approved?)
|> Enum.map(&option_for_shuttle/1)
end

send_update(LiveSelect.Component, id: live_select_id, options: new_opts)
Expand Down Expand Up @@ -81,4 +89,11 @@ defmodule ArrowWeb.ShuttleInput do
defp option_for_shuttle(%Shuttle{id: id, shuttle_name: shuttle_name}) do
{shuttle_name, id}
end

@spec filter_only_approved([Shuttle.t()], boolean()) :: [Shuttle.t()]
defp filter_only_approved(shuttles, false), do: shuttles

defp filter_only_approved(shuttles, true) do
Enum.filter(shuttles, fn shuttle -> shuttle.status == :active end)
end
end
7 changes: 4 additions & 3 deletions lib/arrow_web/controllers/disruption_v2_controller.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
defmodule ArrowWeb.DisruptionV2Controller do
use ArrowWeb, :controller

alias Arrow.Disruptions
alias ArrowWeb.DisruptionV2Controller.Filters
alias ArrowWeb.DisruptionV2Controller.{Filters, Index}
alias ArrowWeb.Plug.Authorize
alias Plug.Conn

Expand All @@ -12,9 +11,11 @@ defmodule ArrowWeb.DisruptionV2Controller do
def index(conn, params) do
filters = Filters.from_params(params)

disruptions = Index.all(filters)

render(conn, "index.html",
user: conn.assigns.current_user,
disruptions: Disruptions.list_disruptionsv2(),
disruptions: disruptions,
filters: filters
)
end
Expand Down
32 changes: 30 additions & 2 deletions lib/arrow_web/controllers/disruption_v2_controller/filters.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ defmodule ArrowWeb.DisruptionV2Controller.Filters do

@behaviour Behaviour

@type t :: %__MODULE__{view: Calendar.t() | Table.t()}
@type t :: %__MODULE__{
kinds: MapSet.t(atom()),
only_approved?: boolean(),
view: Calendar.t() | Table.t()
}

defstruct kinds: @empty_set, only_approved?: false, search: nil, view: %Table{}

Expand All @@ -37,7 +41,12 @@ defmodule ArrowWeb.DisruptionV2Controller.Filters do
def from_params(params) when is_map(params) do
view_mod = if(params["view"] == "calendar", do: Calendar, else: Table)

%__MODULE__{view: view_mod.from_params(params)}
%__MODULE__{
kinds:
params |> Map.get("kinds", []) |> Enum.map(&String.to_existing_atom/1) |> MapSet.new(),
only_approved?: not is_nil(params["only_approved"]),
view: view_mod.from_params(params)
}
end

@impl true
Expand All @@ -50,16 +59,35 @@ defmodule ArrowWeb.DisruptionV2Controller.Filters do
%__MODULE__{view: view_mod.reset(view)}
end

@spec toggle_kind(%__MODULE__{}, atom()) :: %__MODULE__{}
def toggle_kind(%__MODULE__{kinds: kinds} = filters, kind) do
new_kinds = if(kind in kinds, do: MapSet.delete(kinds, kind), else: MapSet.put(kinds, kind))
struct!(filters, kinds: new_kinds)
end

@spec toggle_only_approved(t()) :: t()
def toggle_only_approved(%__MODULE__{only_approved?: only_approved} = filters) do
%__MODULE__{filters | only_approved?: !only_approved}
end

@spec toggle_view(%__MODULE__{}) :: %__MODULE__{}
def toggle_view(%__MODULE__{view: %Calendar{}} = filters), do: %{filters | view: %Table{}}
def toggle_view(%__MODULE__{view: %Table{}} = filters), do: %{filters | view: %Calendar{}}

@impl true
def to_params(%__MODULE__{
kinds: kinds,
only_approved?: only_approved?,
view: %{__struct__: view_mod} = view
}) do
%{}
|> put_if(view_mod == Calendar, "view", "calendar")
|> put_if(
kinds != @empty_set,
"kinds",
kinds |> MapSet.to_list() |> Enum.map(&to_string/1) |> Enum.sort()
)
|> put_if(only_approved?, "only_approved", "true")
|> Map.merge(view_mod.to_params(view))
end

Expand Down
62 changes: 62 additions & 0 deletions lib/arrow_web/controllers/disruption_v2_controller/index.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
defmodule ArrowWeb.DisruptionV2Controller.Index do
@moduledoc """
Applies filters on disruptions for index view
"""

alias Arrow.Disruptions
alias Arrow.Disruptions.DisruptionV2
alias ArrowWeb.DisruptionV2Controller.Filters
alias ArrowWeb.DisruptionV2Controller.Filters.Table

@disruption_kind_routes %{
blue_line: ["Blue"],
orange_line: ["Orange"],
red_line: ["Red"],
mattapan_line: ["Mattapan"],
green_line: ["Green-B", "Green-C", "Green-D", "Green-E"],
green_line_b: ["Green-B"],
green_line_c: ["Green-C"],
green_line_d: ["Green-D"],
green_line_e: ["Green-E"]
}

@empty_set MapSet.new()

@spec all(Filters.t() | nil) :: [DisruptionV2.t()]
def all(filters),
do: apply_to_disruptions(Disruptions.list_disruptionsv2(), filters)

@spec apply_to_disruptions([DisruptionV2.t()], Filters.t()) :: [DisruptionV2.t()]
def apply_to_disruptions(disruptions, filters) do
Enum.filter(
disruptions,
&(apply_kinds_filter(&1, filters) and apply_only_approved_filter(&1, filters) and
apply_past_filter(&1, filters))
)
end

defp apply_kinds_filter(_disruption, %Filters{kinds: kinds}) when kinds == @empty_set,
do: true

defp apply_kinds_filter(disruption, %Filters{kinds: kinds}) do
kind_routes = kinds |> Enum.map(&@disruption_kind_routes[&1]) |> List.flatten()

Enum.any?(disruption.limits, fn limit -> limit.route.id in kind_routes end)
end

defp apply_only_approved_filter(disruption, %Filters{only_approved?: true}),
do: disruption.is_active

defp apply_only_approved_filter(_disruption, %Filters{only_approved?: false}),
do: true

defp apply_past_filter(disruption, %Filters{view: %Table{include_past?: false}}) do
cutoff = Date.utc_today() |> Date.add(-7)

{_start_date, end_date} = Disruptions.start_end_dates(disruption)

is_nil(end_date) or Date.after?(end_date, cutoff)
end

defp apply_past_filter(_disruption, _filter), do: true
end
Loading

0 comments on commit da7c43d

Please sign in to comment.