Skip to content

Commit

Permalink
Add: Campaign Signup Index view
Browse files Browse the repository at this point in the history
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
teesloane committed Dec 11, 2023
1 parent cdf2d95 commit 83815e9
Show file tree
Hide file tree
Showing 3 changed files with 350 additions and 0 deletions.
130 changes: 130 additions & 0 deletions lib/bike_brigade_web/live/campaign_signup_live/index.ex
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 lib/bike_brigade_web/live/campaign_signup_live/index.html.heex
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 test/bike_brigade_web/live/campaign_signup_live_test.exs
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

0 comments on commit 83815e9

Please sign in to comment.