-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This view is responsible for showing riders a list of campaigns which will have varying CTA's depending on how full they are.
- Loading branch information
Showing
3 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
130 changes: 130 additions & 0 deletions
130
lib/bike_brigade_web/live/campaign_signup_live/index.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
defmodule BikeBrigadeWeb.CampaignSignupLive.Index do | ||
use BikeBrigadeWeb, :live_view | ||
|
||
alias BikeBrigade.Utils | ||
alias BikeBrigade.LocalizedDateTime | ||
alias BikeBrigade.Delivery | ||
|
||
import BikeBrigadeWeb.CampaignHelpers | ||
|
||
@impl true | ||
def mount(_params, _session, socket) do | ||
if connected?(socket) do | ||
Delivery.subscribe() | ||
end | ||
|
||
current_week = | ||
LocalizedDateTime.today() | ||
|> Date.beginning_of_week() | ||
|
||
campaigns = fetch_campaigns(current_week) | ||
|
||
{:ok, | ||
socket | ||
|> assign(:page, :campaigns) | ||
|> assign(:page_title, "Campaign Signup List") | ||
|> assign(:current_week, current_week) | ||
# REVIEW: rename this to `campaign_meta` ? | ||
|> assign(:campaign_task_counts, Delivery.get_total_tasks_and_open_tasks(current_week)) | ||
|> assign(:campaigns, campaigns)} | ||
end | ||
|
||
@impl true | ||
def handle_params(params, _url, socket) do | ||
{:noreply, apply_action(socket, socket.assigns.live_action, params)} | ||
end | ||
|
||
# -- Delivery callbacks | ||
|
||
@broadcasted_infos [ | ||
:task_created, | ||
:task_deleted, | ||
:task_updated, | ||
:campaign_rider_created, | ||
:campaign_rider_deleted | ||
] | ||
|
||
@impl Phoenix.LiveView | ||
def handle_info({event, entity}, socket) when event in @broadcasted_infos do | ||
if entity_in_campaigns?(socket, entity.campaign_id) do | ||
{:noreply, refetch_and_assign_data(socket)} | ||
else | ||
{:noreply, socket} | ||
end | ||
end | ||
|
||
## -- End Delivery callbacks | ||
|
||
defp apply_action(socket, :index, params) do | ||
socket = | ||
case params do | ||
%{"current_week" => week} -> | ||
week = Date.from_iso8601!(week) | ||
|
||
assign(socket, | ||
current_week: week, | ||
campaigns: fetch_campaigns(week), | ||
campaign_task_counts: Delivery.get_total_tasks_and_open_tasks(week) | ||
) | ||
|
||
_ -> | ||
socket | ||
end | ||
|
||
socket | ||
|> assign(:campaign, nil) | ||
end | ||
|
||
defp fetch_campaigns(current_week) do | ||
Delivery.list_campaigns(current_week, | ||
preload: [:program, :stats, :latest_message, :scheduled_message] | ||
) | ||
|> Enum.reverse() | ||
|> Utils.ordered_group_by(&LocalizedDateTime.to_date(&1.delivery_start)) | ||
|> Enum.reverse() | ||
end | ||
|
||
defp refetch_and_assign_data(socket) do | ||
week = socket.assigns.current_week | ||
|
||
socket | ||
|> assign(:campaign_task_counts, Delivery.get_total_tasks_and_open_tasks(week)) | ||
|> assign(:campaigns, fetch_campaigns(week)) | ||
end | ||
|
||
defp campaign_is_in_past(campaign) do | ||
date_now = DateTime.utc_now() | ||
|
||
case DateTime.compare(campaign.delivery_end, date_now) do | ||
:gt -> false | ||
:eq -> false | ||
:lt -> true | ||
end | ||
end | ||
|
||
defp get_signup_text(campaign_id, rider_id, campaign_task_counts) do | ||
count_tasks_for_current_rider = | ||
campaign_task_counts[campaign_id].rider_ids | ||
|> Enum.count(fn i -> i == Integer.to_string(rider_id) end) | ||
|
||
cond do | ||
count_tasks_for_current_rider > 0 -> | ||
"Signed up for #{count_tasks_for_current_rider} deliveries" | ||
|
||
true -> | ||
"Sign up" | ||
end | ||
end | ||
|
||
defp campaign_tasks_fully_assigned?(c_id, campaign_task_count) do | ||
campaign_task_count[c_id][:filled_tasks] == campaign_task_count[c_id][:total_tasks] | ||
end | ||
|
||
# Use this to determine if we need to refetch data to update the liveview. | ||
# ex: dispatcher changes riders/tasks, or another rider signs up -> refetch. | ||
defp entity_in_campaigns?(socket, entity_campaign_id) do | ||
socket.assigns.campaigns | ||
|> Enum.flat_map(fn {_date, campaigns} -> campaigns end) | ||
|> Enum.find(false, fn c -> c.id == entity_campaign_id end) | ||
end | ||
end |
113 changes: 113 additions & 0 deletions
113
lib/bike_brigade_web/live/campaign_signup_live/index.html.heex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<nav | ||
class="flex flex-col md:flex-row md:items-center justify-between px-4 py-3 mb-4 border-b-2 border-gray-200" | ||
aria-label="Pagination" | ||
> | ||
<div class="flex-1"> | ||
<p class="font-medium xl:flex"> | ||
<span class="mr-2">Showing week of</span> | ||
<time datetime={@current_week} class="font-medium"> | ||
<%= Calendar.strftime(@current_week, "%B %-d, %Y") %> | ||
</time> | ||
</p> | ||
</div> | ||
<div class="flex justify-between mt-4 md:mt-0 align-end"> | ||
<span class="relative z-0 inline-flex rounded-md shadow-sm"> | ||
<.link | ||
patch={~p"/campaigns/signup?current_week=#{Date.add(@current_week, -7)}"} | ||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500" | ||
> | ||
<Heroicons.chevron_left solid class="w-5 h-5" /> | ||
</.link> | ||
<.link | ||
patch={~p"/campaigns/signup?current_week=#{Date.beginning_of_week(LocalizedDateTime.today())}"} | ||
class="relative items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 sm:inline-flex hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500" | ||
> | ||
Today | ||
</.link> | ||
|
||
<.link | ||
patch={~p"/campaigns/signup?current_week=#{Date.add(@current_week, 7)}"} | ||
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-r-md hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500" | ||
> | ||
<Heroicons.chevron_right solid class="w-5 h-5" /> | ||
</.link> | ||
</span> | ||
</div> | ||
</nav> | ||
<%= if @campaigns != [] do %> | ||
<%= for {date, campaigns} <- @campaigns do %> | ||
<div class="flex flex-col md:flex-row w-full bg-white shadow sm:my-1 sm:rounded-md"> | ||
<div class="w-32 py-4 pl-4"> | ||
<.date date={date} /> | ||
</div> | ||
<ul role="list" class="w-full divide-y divide-gray-200"> | ||
<%= for c <- campaigns do %> | ||
<li id={"campaign-#{c.id}"}> | ||
<div class="px-4 py-4"> | ||
<div class="items-center justify-between md:flex"> | ||
<div class="flex items-center mb-2 space-x-1"> | ||
<p | ||
class="text-sm font-medium" | ||
data-test-group="campaign-name" | ||
> | ||
<%= name(c) %> | ||
</p> | ||
</div> | ||
<div class="flex-shrink-0 space-y-1 md:space-y-0 md:space-x-2 md:flex"> | ||
<%= cond do %> | ||
<% campaign_is_in_past(c) -> %> | ||
<.button | ||
size={:xsmall} | ||
color={:disabled}> | ||
Completed | ||
</.button> | ||
|
||
<% campaign_tasks_fully_assigned?(c.id, @campaign_task_counts) -> %> | ||
<.button | ||
size={:xsmall} | ||
color={:primary} | ||
|
||
navigate={~p"/campaigns/signup/#{c}/"}> | ||
Campaign Filled | ||
</.button> | ||
|
||
<% true -> %> | ||
<.button | ||
size={:xsmall} | ||
color={:primary} | ||
navigate={~p"/campaigns/signup/#{c}/"}> | ||
<%= get_signup_text(c.id, @current_user.rider_id, @campaign_task_counts) %> | ||
</.button> | ||
<% end %> | ||
</div> | ||
</div> | ||
<div class="mt-2 sm:flex sm:justify-between"> | ||
<div class="flex flex-col md:flex-row justify-between w-full"> | ||
|
||
<p class="flex items-center mt-0 text-sm text-gray-700"> | ||
<Icons.maki_bicycle_share class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-500" /> | ||
<span> | ||
<%= @campaign_task_counts[c.id][:filled_tasks] %> / | ||
<%= @campaign_task_counts[c.id][:total_tasks] %> Tasks filled | ||
</span> | ||
</p> | ||
|
||
<p class="flex items-center text-sm text-gray-700"> | ||
<Heroicons.clock | ||
mini | ||
aria-label="Pickup Time" | ||
class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-500" | ||
/> | ||
Pickup time: <%= pickup_window(c) %> | ||
</p> | ||
</div> | ||
</div> | ||
</div> | ||
</li> | ||
<% end %> | ||
</ul> | ||
</div> | ||
<% end %> | ||
<% else %> | ||
<div class="py-4 pl-4">No campaigns found</div> | ||
<% end %> |
107 changes: 107 additions & 0 deletions
107
test/bike_brigade_web/live/campaign_signup_live_test.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
defmodule BikeBrigadeWeb.CampaignSignupLiveTest do | ||
use BikeBrigadeWeb.ConnCase, only: [] | ||
|
||
import Phoenix.LiveViewTest | ||
|
||
@week_in_sec 604_900 | ||
|
||
describe "Index - General" do | ||
setup ctx do | ||
program = fixture(:program, %{name: "ACME Delivery"}) | ||
res = login_as_rider(ctx) | ||
Map.merge(res, %{program: program}) | ||
end | ||
|
||
test "It displays the expected number of campaigns for this week", ctx do | ||
campaigns = | ||
for _n <- 1..3 do | ||
fixture(:campaign, %{program_id: ctx.program.id}) | ||
end | ||
|
||
{:ok, live, _html} = live(ctx.conn, ~p"/campaigns/signup") | ||
|
||
for c <- campaigns do | ||
assert has_element?(live, "#campaign-#{c.id}") | ||
end | ||
end | ||
|
||
test "It displays a campaign in a future week", ctx do | ||
campaign = | ||
fixture(:campaign, %{ | ||
program_id: ctx.program.id, | ||
delivery_start: DateTime.utc_now() |> DateTime.add(@week_in_sec), | ||
delivery_end: | ||
DateTime.utc_now() |> DateTime.add(@week_in_sec) |> DateTime.add(60, :second) | ||
}) | ||
|
||
{:ok, live, _html} = live(ctx.conn, ~p"/campaigns/signup") | ||
refute has_element?(live, "#campaign-#{campaign.id}") | ||
|
||
week_ahead = Date.utc_today() |> Date.add(7) | ||
{:ok, live, _html} = live(ctx.conn, ~p"/campaigns/signup?current_week=#{week_ahead}") | ||
assert has_element?(live, "#campaign-#{campaign.id}") | ||
end | ||
|
||
test "It displays a campaign in a previous week; button says 'Completed'", ctx do | ||
campaign = | ||
fixture(:campaign, %{ | ||
program_id: ctx.program.id, | ||
delivery_start: DateTime.utc_now() |> DateTime.add(-@week_in_sec), | ||
delivery_end: | ||
DateTime.utc_now() |> DateTime.add(-@week_in_sec) |> DateTime.add(60, :second) | ||
}) | ||
|
||
{:ok, live, _html} = live(ctx.conn, ~p"/campaigns/signup") | ||
refute has_element?(live, "#campaign-#{campaign.id}") | ||
|
||
week_ago = Date.utc_today() |> Date.add(-7) | ||
{:ok, live, html} = live(ctx.conn, ~p"/campaigns/signup?current_week=#{week_ago}") | ||
assert has_element?(live, "#campaign-#{campaign.id}") | ||
assert html =~ "Completed" | ||
end | ||
end | ||
|
||
describe "Index - Campaign shows correct signup button" do | ||
setup ctx do | ||
program = fixture(:program, %{name: "ACME Delivery"}) | ||
res = login_as_rider(ctx) | ||
Map.merge(res, %{program: program}) | ||
end | ||
|
||
test "A campaign shows the correct filled to total tasks", ctx do | ||
campaign = fixture(:campaign, %{program_id: ctx.program.id}) | ||
rider_1 = fixture(:rider, %{name: "Hannah Bannana"}) | ||
_rider_2 = fixture(:rider, %{name: "Kiwi Stevie"}) | ||
fixture(:task, %{campaign: campaign, rider: rider_1}) | ||
fixture(:task, %{campaign: campaign, rider: nil}) | ||
|
||
{:ok, _live, html} = live(ctx.conn, ~p"/campaigns/signup") | ||
|
||
# HACK to cleanup html with tons of whitespace. | ||
# Could also just use Floki to find the element and test it's there. | ||
normalized_html = html |> String.split() |> Enum.join(" ") | ||
assert normalized_html =~ "1 / 2 Tasks filled" | ||
end | ||
|
||
test "'signup' when rider hasn't signed up and there are open tasks", ctx do | ||
campaign = fixture(:campaign, %{program_id: ctx.program.id}) | ||
rider_1 = fixture(:rider, %{name: "Hannah Bannana"}) | ||
fixture(:rider, %{name: "Kiwi Stevie"}) | ||
fixture(:task, %{campaign: campaign, rider: rider_1}) | ||
fixture(:task, %{campaign: campaign, rider: nil}) | ||
|
||
{:ok, _live, html} = live(ctx.conn, ~p"/campaigns/signup") | ||
assert html =~ "Sign up" | ||
end | ||
|
||
test "'signed up for N deliveries' if open deliveries and rider has at least one.", ctx do | ||
campaign = fixture(:campaign, %{program_id: ctx.program.id}) | ||
fixture(:task, %{campaign: campaign, rider: ctx.rider}) | ||
fixture(:task, %{campaign: campaign, rider: ctx.rider}) | ||
fixture(:task, %{campaign: campaign, rider: nil}) | ||
|
||
{:ok, _live, html} = live(ctx.conn, ~p"/campaigns/signup") | ||
assert html =~ "Signed up for 2 deliveries" | ||
end | ||
end | ||
end |