Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polished date picker implementation #2195

Merged
merged 12 commits into from
Oct 23, 2024
6 changes: 6 additions & 0 deletions assets/css/_trip-plan-form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@
}
}

#date-picker-calendar {
.form-control[readonly] {
background-color: $body-bg;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📓 Bootstrap was setting this to grey so we need this override.

}
}

#trip-planner-inputs {
margin: .75rem 0;
width: 100%;
Expand Down
18 changes: 10 additions & 8 deletions assets/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"leaflet": "^1.4.0",
"leaflet-rotatedmarker": "^0.2.0",
"lodash": "^4.17.21",
"mbta_metro": "^0.0.23",
"mbta_metro": "^0.0.49",
"mobile-detect": "^1.4.5",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
Expand Down
4 changes: 4 additions & 0 deletions assets/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ module.exports = {
},
plugins: [
...plugins(),
require("@tailwindcss/forms")({
// don't make global styles since they conflict with ours
strategy: "class"
}),
// Allows prefixing tailwind classes with LiveView classes to add rules
// only when LiveView classes are applied, for example:
//
Expand Down
10 changes: 3 additions & 7 deletions lib/dotcom/trip_plan/input_form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ defmodule Dotcom.TripPlan.InputForm do

alias OpenTripPlannerClient.PlanParams

@time_types ~W(now leave_at arrive_by)a

@error_messages %{
from: "Please specify an origin location.",
to: "Please add a destination.",
Expand All @@ -25,13 +23,11 @@ defmodule Dotcom.TripPlan.InputForm do
embeds_one(:from, __MODULE__.Location)
embeds_one(:to, __MODULE__.Location)
embeds_one(:modes, __MODULE__.Modes)
field(:datetime_type, Ecto.Enum, values: @time_types)
field(:datetime_type, :string)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📓 I changed this for now because we need to have a way to navigate between these atoms and the string we're given back by the form. We can figure this out later.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the atom/string juggling was definitely my least favorite part about trying to handle mode changes too...

field(:datetime, :naive_datetime)
field(:wheelchair, :boolean, default: true)
end

def time_types, do: @time_types

def initial_modes do
__MODULE__.Modes.fields()
|> Enum.map(&{Atom.to_string(&1), "true"})
Expand All @@ -49,7 +45,7 @@ defmodule Dotcom.TripPlan.InputForm do
%{
fromPlace: PlanParams.to_place_param(from),
toPlace: PlanParams.to_place_param(to),
arriveBy: datetime_type == :arrive_by,
arriveBy: datetime_type == "arrive_by",
date: PlanParams.to_date_param(datetime),
time: PlanParams.to_time_param(datetime),
transportModes: __MODULE__.Modes.selected_mode_keys(modes) |> PlanParams.to_modes_param(),
Expand Down Expand Up @@ -114,7 +110,7 @@ defmodule Dotcom.TripPlan.InputForm do

defp validate_chosen_datetime(changeset) do
case get_field(changeset, :datetime_type) do
:now ->
"now" ->
force_change(changeset, :datetime, Util.now())

_ ->
Expand Down
152 changes: 82 additions & 70 deletions lib/dotcom_web/components/live_components/trip_planner_form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,28 @@ defmodule DotcomWeb.Components.LiveComponents.TripPlannerForm do
use DotcomWeb, :live_component

import DotcomWeb.ViewHelpers, only: [svg: 1]
import MbtaMetro.Components.Feedback
import MbtaMetro.Components.InputGroup
import Phoenix.HTML.Form, only: [input_name: 2, input_value: 2, input_id: 2]
import MbtaMetro.Components.{Feedback, InputGroup}
import Phoenix.HTML.Form, only: [input_value: 2]

alias Dotcom.TripPlan.{InputForm, InputForm.Modes}

@form_defaults %{
"datetime_type" => :now,
"datetime" => NaiveDateTime.local_now(),
"modes" => InputForm.initial_modes(),
"wheelchair" => true
}
alias MbtaMetro.Live.DatePicker

@impl true
def mount(socket) do
form =
%InputForm{}
|> InputForm.changeset(@form_defaults)
|> to_form()

{:ok,
assign(socket, %{
form: form,
location_keys: InputForm.Location.fields(),
show_datepicker: input_value(form, :datetime_type) != :now
})}
form_defaults = %{
"datetime_type" => "now",
"datetime" => Timex.now("America/New_York"),
"modes" => InputForm.initial_modes(),
"wheelchair" => true
}

defaults = %{
form: %InputForm{} |> InputForm.changeset(form_defaults) |> to_form(),
location_keys: InputForm.Location.fields(),
show_datepicker: false
}

{:ok, assign(socket, defaults)}
end

@impl true
Expand All @@ -43,11 +39,11 @@ defmodule DotcomWeb.Components.LiveComponents.TripPlannerForm do
id={@id}
for={@form}
method="get"
phx-change="validate"
anthonyshull marked this conversation as resolved.
Show resolved Hide resolved
phx-submit="save_form"
phx-change="validate"
phx-target={@myself}
>
<div :for={field <- [:from, :to]} class="mb-1">
<div :for={field <- [:from, :to]} class="mb-1" id="trip-planner-locations" phx-update="ignore">
<.algolia_autocomplete
config_type="trip-planner"
placeholder="Enter a location"
Expand All @@ -68,65 +64,43 @@ defmodule DotcomWeb.Components.LiveComponents.TripPlannerForm do
</.feedback>
</.algolia_autocomplete>
</div>
<.fieldset legend="When">
<ul class="m-0 p-0 flex flex-col sm:flex-row list-none">
<li
:for={type <- Ecto.Enum.values(InputForm, :datetime_type)}
class={[
"py-0 px-4",
"border border-solid border-slate-300 bg-white",
"has-[:checked]:bg-blue-50 has-[:checked]:border-blue-600",
"first:max-sm:rounded-t-lg last:max-sm:rounded-b-lg",
"sm:first:rounded-l-lg sm:last:rounded-r-lg"
]}
>
<.input
id={input_id(@form, :datetime_type) <> "_#{type}"}
type="radio"
field={f[:datetime_type]}
value={type}
checked={input_value(@form, :datetime_type) == type}
phx-click="toggle_datepicker"
phx-target={@myself}
/>
</li>
</ul>

<.feedback
:for={{msg, _} <- f[:datetime_type].errors}
:if={used_input?(f[:datetime_type])}
kind={:error}
>
<%= msg %>
</.feedback>
<.label :if={@show_datepicker} for="timepick">
<input
id="timepick"
type="datetime-local"
step="any"
name={input_name(@form, :datetime)}
value={input_value(@form, :datetime)}
/>
<span class="sr-only">Date and time to leave at or arrive by</span>
</.label>
<div>
<.input_group
legend="When"
form={f}
field={:datetime_type}
id="datetime_type"
options={[{"Now", "now"}, {"Leave at", "leave_at"}, {"Arrive by", "arrive_by"}]}
type="radio-button"
class="mb-0"
phx-change="toggle_datepicker"
phx-update="ignore"
/>
<.feedback
:for={{msg, _} <- f[:datetime_type].errors}
:if={used_input?(f[:datetime_type])}
kind={:error}
>
<%= msg %>
</.feedback>
<.live_component
:if={@show_datepicker}
module={DatePicker}
config={datepicker_config()}
field={f[:datetime]}
id={:datepicker}
/>
<.feedback
:for={{msg, _} <- f[:datetime].errors}
:if={used_input?(f[:datetime])}
kind={:error}
>
<%= msg %>
</.feedback>
</.fieldset>
</div>
<div>
<.fieldset legend="Modes">
<.accordion id="input_modes">
<.fieldset id="modes" legend="Modes">
<.accordion id="accordion">
<:heading>
<%= Modes.selected_modes(input_value(f, :modes)) %>
</:heading>
Expand Down Expand Up @@ -157,7 +131,7 @@ defmodule DotcomWeb.Components.LiveComponents.TripPlannerForm do
</div>
</div>
<div class="col-start-2 justify-self-end">
<.button color="green" type="submit" phx-disable-with="Planning your trip...">
<.button type="submit" phx-disable-with="Planning your trip...">
Get trip suggestions
</.button>
</div>
Expand All @@ -167,8 +141,28 @@ defmodule DotcomWeb.Components.LiveComponents.TripPlannerForm do
end

@impl true
def handle_event("toggle_datepicker", %{"value" => datetime_value}, socket) do
{:noreply, assign(socket, :show_datepicker, datetime_value !== "now")}
@doc """
If the user selects "now" for the date and time, hide the datepicker.
This will destroy the flatpickr instance.

If the user selects arrive by or leave at, then we show the datepicker and set the time to the nearest 5 minutes.
"""
def handle_event("toggle_datepicker", %{"input_form" => %{"datetime_type" => "now"}}, socket) do
new_socket =
socket
|> assign(show_datepicker: false)
|> push_event("set-datetime", %{datetime: nearest_5_minutes()})

{:noreply, new_socket}
end

def handle_event("toggle_datepicker", _, socket) do
new_socket =
socket
|> assign(show_datepicker: true)
|> push_event("set-datetime", %{datetime: nearest_5_minutes()})

{:noreply, new_socket}
end

def handle_event("validate", %{"input_form" => params}, socket) do
Expand Down Expand Up @@ -197,4 +191,22 @@ defmodule DotcomWeb.Components.LiveComponents.TripPlannerForm do
{:noreply, assign(socket, %{form: form})}
end
end

defp datepicker_config do
%{
default_date: Timex.now("America/New_York"),
enable_time: true,
max_date: Schedules.Repo.end_of_rating(),
min_date: Timex.today("America/New_York")
}
end

defp nearest_5_minutes do
datetime = Timex.now("America/New_York")
minutes = datetime.minute
rounded_minutes = Float.ceil(minutes / 5) * 5
added_minutes = Kernel.trunc(rounded_minutes - minutes)

Timex.shift(datetime, minutes: added_minutes)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule DotcomWeb.Components.TripPlanner.ItineraryGroup do
<div class="mb-3 p-2 border border-2 border-slate-200">
<% [first | rest] = @group %>
<div class="text-slate-800 font-bold">Group with <%= Enum.count(@group) %> options</div>
<.accordion id="itinerary_group">
<.accordion id="itinerary-group">
<:heading>
<%= format_datetime_full(first.departure) %> — <%= format_datetime_full(first.arrival) %>
</:heading>
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ defmodule DotCom.Mixfile do
{:jason, "1.4.4", override: true},
{:logster, "1.1.1"},
{:mail, "0.3.1"},
{:mbta_metro, "0.0.16"},
{:mbta_metro, "0.0.49"},
{:mock, "0.3.8", [only: :test]},
{:mox, "1.1.0", [only: :test]},
{:nebulex, "2.6.3"},
Expand Down
Loading
Loading