Skip to content

Commit

Permalink
portal signup shows links to spreadsheet campaigns (#422)
Browse files Browse the repository at this point in the history
* wip

* Ensure unique id's for campaign or opportunity.

* Fix: signup button link.

* Spreadsheet specific signup text.

* Ensure reloading happens only when campaigns change.

* [wip]

* Fixed test; refactored

* Formatting

* Fix Formatting.

---------

Co-authored-by: Ty <[email protected]>
  • Loading branch information
mveytsman and teesloane authored Nov 12, 2024
1 parent 0aa9510 commit e2e672a
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 86 deletions.
160 changes: 100 additions & 60 deletions lib/bike_brigade_web/live/campaign_signup_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule BikeBrigadeWeb.CampaignSignupLive.Index do
alias BikeBrigade.Utils
alias BikeBrigade.LocalizedDateTime
alias BikeBrigade.Delivery
alias BikeBrigade.Delivery.{Opportunity, Campaign}

import BikeBrigadeWeb.CampaignHelpers

Expand All @@ -19,7 +20,7 @@ defmodule BikeBrigadeWeb.CampaignSignupLive.Index do

campaign_filter = {:current_week, current_week}

campaigns = fetch_campaigns(campaign_filter)
campaigns_and_opportunities = fetch_campaigns_and_opportunities(campaign_filter)
start_date = LocalizedDateTime.new!(current_week, ~T[00:00:00])
end_date = Date.add(current_week, 6) |> LocalizedDateTime.new!(~T[23:59:59])

Expand All @@ -34,20 +35,23 @@ defmodule BikeBrigadeWeb.CampaignSignupLive.Index do
Delivery.get_total_tasks_and_open_tasks(start_date, end_date)
)
|> assign(:showing_urgent_campaigns, false)
|> assign(:campaigns, campaigns)}
|> assign(:campaigns_and_opportunities, campaigns_and_opportunities)}
end

@impl true
def handle_params(%{"campaign_ids" => campaign_ids}, _url, socket) do
campaign_filter = {:campaign_ids, campaign_ids}
campaigns = fetch_campaigns(campaign_filter)
# We are joining campaigns and opportunities so that they can be displayed
# as a intermixed list of things that people can sign up for.
# This is why you will see a lot of long variables. Sorry.
campaigns_and_opportunities = fetch_campaigns_and_opportunities(campaign_filter)

start_date = LocalizedDateTime.now()
end_date = Date.add(start_date, 2) |> LocalizedDateTime.new!(~T[23:59:59])

{:noreply,
socket
|> assign(:campaigns, campaigns)
|> assign(:campaigns_and_opportunities, campaigns_and_opportunities)
|> assign(:campaign_filter, campaign_filter)
|> assign(
:campaign_task_counts,
Expand Down Expand Up @@ -98,7 +102,7 @@ defmodule BikeBrigadeWeb.CampaignSignupLive.Index do
assign(socket,
current_week: week,
campaign_filter: campaign_filter,
campaigns: fetch_campaigns(campaign_filter),
campaigns_and_opportunities: fetch_campaigns_and_opportunities(campaign_filter),
campaign_task_counts: Delivery.get_total_tasks_and_open_tasks(week)
)

Expand All @@ -110,10 +114,9 @@ defmodule BikeBrigadeWeb.CampaignSignupLive.Index do
|> assign(:campaign, nil)
end

defp fetch_campaigns({:current_week, current_week}) do
defp fetch_campaigns_and_opportunities({:campaign_ids, campaign_ids}) do
Delivery.list_campaigns(
start_date: current_week,
end_date: Date.add(current_week, 6),
campaign_ids: campaign_ids,
preload: [:program, :stats, :latest_message, :scheduled_message],
public: true
)
Expand All @@ -122,15 +125,26 @@ defmodule BikeBrigadeWeb.CampaignSignupLive.Index do
|> Enum.reverse()
end

defp fetch_campaigns({:campaign_ids, campaign_ids}) do
Delivery.list_campaigns(
campaign_ids: campaign_ids,
preload: [:program, :stats, :latest_message, :scheduled_message],
public: true
)
|> Enum.reverse()
defp fetch_campaigns_and_opportunities({:current_week, current_week}) do
opportunities =
Delivery.list_opportunities(
start_date: current_week,
end_date: Date.add(current_week, 6),
published: true,
preload: [location: [:neighborhood], program: [:items]]
)

campaigns =
Delivery.list_campaigns(
start_date: current_week,
end_date: Date.add(current_week, 6),
public: true,
preload: [:program, :stats, :latest_message, :scheduled_message]
)

(opportunities ++ campaigns)
|> Enum.sort_by(& &1.delivery_start, Date)
|> Utils.ordered_group_by(&LocalizedDateTime.to_date(&1.delivery_start))
|> Enum.reverse()
end

# TODO HACK: right now everytime something about a task, or campaign rider
Expand All @@ -142,28 +156,31 @@ defmodule BikeBrigadeWeb.CampaignSignupLive.Index do

socket
|> assign(:campaign_task_counts, Delivery.get_total_tasks_and_open_tasks(week))
|> assign(:campaigns, fetch_campaigns(campaign_filter))
|> assign(:campaigns_and_opportunities, fetch_campaigns_and_opportunities(campaign_filter))
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.any?(fn c -> c.id == entity_campaign_id end)
socket.assigns.campaigns_and_opportunities
|> Enum.flat_map(fn {_date, campaigns_and_opportunities} -> campaigns_and_opportunities end)
|> Enum.any?(fn c_or_o -> match?(%Campaign{}, c_or_o) and c_or_o.id == entity_campaign_id end)
end

attr :filled_tasks, :integer, required: true
attr :total_tasks, :integer, required: true
attr :campaign, :any, required: true
attr :campaign_or_opportunity, :any, required: true

defp tasks_filled_text(assigns) do
{class, copy} =
cond do
match?(%Opportunity{}, assigns.campaign_or_opportunity) ->
{"text-gray-600", ""}

assigns.filled_tasks == nil ->
{"text-gray-600", "N/A"}

campaign_in_past(assigns.campaign) ->
campaign_in_past(assigns.campaign_or_opportunity) ->
{"text-gray-600", "Campaign over"}

assigns.total_tasks - assigns.filled_tasks == 0 ->
Expand All @@ -188,58 +205,81 @@ defmodule BikeBrigadeWeb.CampaignSignupLive.Index do
"""
end

attr :campaign, :any, required: true
defp campaign_or_opportunity_element_id(%Opportunity{id: id}) do
"opportunity-#{id}"
end

defp campaign_or_opportunity_element_id(%Campaign{id: id}) do
"campaign-#{id}"
end

attr :campaign_or_opportunity, :any, required: true
attr :rider_id, :integer, required: true
attr :campaign_task_counts, :any, required: true

defp signup_button(assigns) do
c = assigns.campaign
filled_tasks = assigns.campaign_task_counts[c.id][:filled_tasks]
total_tasks = assigns.campaign_task_counts[c.id][:total_tasks]
campaign_tasks_fully_assigned? = filled_tasks == total_tasks
campaign_not_ready_for_signup? = is_nil(total_tasks)

current_rider_task_count =
if is_nil(total_tasks) do
0
else
assigns.campaign_task_counts[c.id].rider_ids_counts[assigns.rider_id] || 0
end

campaign_in_past = campaign_in_past(assigns.campaign)

# Define map for button properties
buttonType =
cond do
campaign_in_past ->
%{color: :disabled, text: "Completed"}

current_rider_task_count > 0 ->
%{color: :secondary, text: "Signed up for #{current_rider_task_count} deliveries"}

campaign_not_ready_for_signup? ->
%{color: :disabled, text: "Campaign not ready for signup"}

campaign_tasks_fully_assigned? ->
%{color: :secondary, text: "Campaign Filled"}

true ->
%{color: :secondary, text: "Sign up"}
c_or_o = assigns.campaign_or_opportunity

{button_type, signup_link} =
case c_or_o do
%Opportunity{signup_link: signup_link} ->
button_type =
if campaign_in_past(c_or_o) do
%{color: :disabled, text: "Completed"}
else
%{color: :secondary, text: "Sign up"}
end

{button_type, signup_link}

%Campaign{} ->
filled_tasks = assigns.campaign_task_counts[c_or_o.id][:filled_tasks]
total_tasks = assigns.campaign_task_counts[c_or_o.id][:total_tasks]
campaign_tasks_fully_assigned? = filled_tasks == total_tasks
campaign_not_ready_for_signup? = match?(%Campaign{}, c_or_o) and is_nil(total_tasks)

current_rider_task_count =
if is_nil(total_tasks) do
0
else
assigns.campaign_task_counts[c_or_o.id].rider_ids_counts[assigns.rider_id] || 0
end

# Define map for button properties
button_type =
cond do
campaign_in_past(c_or_o) ->
%{color: :disabled, text: "Completed"}

current_rider_task_count > 0 ->
%{color: :secondary, text: "Signed up for #{current_rider_task_count} deliveries"}

campaign_not_ready_for_signup? ->
%{color: :disabled, text: "Campaign not ready for signup"}

campaign_tasks_fully_assigned? ->
%{color: :secondary, text: "Campaign Filled"}

true ->
%{color: :secondary, text: "Sign up"}
end

{button_type, ~p"/campaigns/signup/#{c_or_o}/"}
end

assigns =
assigns
|> assign(:signup_text, Map.get(buttonType, :text))
|> assign(:button_color, Map.get(buttonType, :color))
|> assign(:button_type, button_type)
|> assign(:signup_link, signup_link)

~H"""
<.button
size={:small}
class="w-full rounded-none md:rounded-sm"
color={@button_color}
navigate={~p"/campaigns/signup/#{@campaign}/"}
color={@button_type.color}
navigate={@signup_link}
>
<%= @signup_text %>
<%= @button_type.text %>
</.button>
"""
end
Expand Down
55 changes: 29 additions & 26 deletions lib/bike_brigade_web/live/campaign_signup_live/index.html.heex
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<nav
:if={@showing_urgent_campaigns == false}
class="flex flex-col md:flex-row md:items-center justify-between md:px-4 md:py-3 md:mb-4 md:border-b-2 md:border-gray-200 md:justify-end"
class="flex flex-col justify-between md:flex-row md:items-center md:px-4 md:py-3 md:mb-4 md:border-b-2 md:border-gray-200 md:justify-end"
aria-label="Pagination"
>
<div class="flex justify-between md:my-4 align-end">
<span class="relative z-0 w-full inline-flex rounded-md shadow-sm">
<span class="relative z-0 inline-flex w-full 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"
Expand All @@ -15,7 +15,7 @@
patch={
~p"/campaigns/signup?current_week=#{Date.beginning_of_week(LocalizedDateTime.today())}"
}
class="w-full text-center relative items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
class="relative items-center w-full px-4 py-2 text-sm font-medium text-center text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
>
Week of <%= Calendar.strftime(@current_week, "%B %-d") %>
</.link>
Expand All @@ -34,74 +34,77 @@
<nav :if={@showing_urgent_campaigns}>
<.button
size={:small}
class="w-full rounded-none md:rounded-sm mb-2"
class="w-full mb-2 rounded-none md:rounded-sm"
color={:secondary}
navigate={~p"/campaigns/signup"}
>
View all available deliveries ⏎
</.button>
</nav>

<%= if @campaigns != [] do %>
<div :if={@showing_urgent_campaigns} class="bg-red-300 p-2 lg:mb-4 rounded bg-opacity-40">
<%= if @campaigns_and_opportunities != [] do %>
<div :if={@showing_urgent_campaigns} class="p-2 bg-red-300 rounded lg:mb-4 bg-opacity-40">
These deliveries need riders in the next 48 hours:
</div>

<%= for {date, campaigns} <- @campaigns do %>
<div class="flex flex-col lg:flex-row w-full sm:my-1 sm:rounded-md mb-4 md:mb-0 lg:mb-8">
<div class="flex w-full lg:w-40 justify-center py-4 my-4 lg:py-0 lg:my-0">
<div class="flex w-full items-center lg:items-start">
<div class="md:hidden flex-1 border-t-2 border-gray-300"></div>
<%= for {date, campaigns_and_opportunities} <- @campaigns_and_opportunities do %>
<div class="flex flex-col w-full mb-4 lg:flex-row sm:my-1 sm:rounded-md md:mb-0 lg:mb-8">
<div class="flex justify-center w-full py-4 my-4 lg:w-40 lg:py-0 lg:my-0">
<div class="flex items-center w-full lg:items-start">
<div class="flex-1 border-t-2 border-gray-300 md:hidden"></div>
<span class="px-3">
<.date date={date} />
</span>
<div class="md:hidden flex-1 border-t-2 border-gray-300"></div>
<div class="flex-1 border-t-2 border-gray-300 md:hidden"></div>
</div>
</div>

<ul id="campaign-list" role="list" class="w-full divide-y divide-gray-200">
<%= for c <- campaigns do %>
<li id={"campaign-#{c.id}"} class="campaign-item bg-white shadow mb-8 border last:mb-0">
<div class="flex items-center space-x-1 px-4 py-2 bg-slate-100">
<%= for c_or_o <- campaigns_and_opportunities do %>
<li
id={campaign_or_opportunity_element_id(c_or_o)}
class="mb-8 bg-white border shadow campaign-item last:mb-0"
>
<div class="flex items-center px-4 py-2 space-x-1 bg-slate-100">
<div
class="flex items-center justify-between w-full text-sm"
data-test-group="campaign-name"
>
<span class="font-medium truncate"><%= name(c) %></span>
<span class="font-medium truncate"><%= name(c_or_o) %></span>
<.date class="border-none opacity-80" date={date} />
</div>
</div>

<div class="flex flex-col md:flex-row md:px-4 ">
<div class="order-2 items-center justify-between md:flex md:flex-3">
<div class="items-center justify-between order-2 md:flex md:flex-3">
<div class="flex-shrink-0 md:space-x-2 md:flex">
<div class="md:mt-0">
<.signup_button
rider_id={@current_user.rider_id}
campaign_task_counts={@campaign_task_counts}
campaign={c}
campaign_or_opportunity={c_or_o}
/>
</div>
</div>
</div>
<div class="order-1 flex-1 sm:flex sm:justify-between">
<div class="flex flex-row divide-x-2 md:divide-x-0 justify-around py-4 w-full">
<div class="flex-1 order-1 sm:flex sm:justify-between">
<div class="flex flex-row justify-around w-full py-4 divide-x-2 md:divide-x-0">
<div class="flex-1">
<.tasks_filled_text
filled_tasks={@campaign_task_counts[c.id][:filled_tasks]}
total_tasks={@campaign_task_counts[c.id][:total_tasks]}
campaign={c}
filled_tasks={@campaign_task_counts[c_or_o.id][:filled_tasks]}
total_tasks={@campaign_task_counts[c_or_o.id][:total_tasks]}
campaign_or_opportunity={c_or_o}
/>
</div>

<div class="flex-1">
<p class="flex flex-col md:flex-row items-center mt-0 text-sm text-gray-700">
<p class="flex flex-col items-center mt-0 text-sm text-gray-700 md:flex-row">
<Heroicons.clock
aria-label="pickup time"
class="flex-shrink-0 mb-2 mr-1.5 h-8 w-8 md:h-5 md:w-5 md:mb-0 text-gray-500"
/>
<span class="flex space-x-2 font-bold md:font-normal">
<%= pickup_window(c) %>
<%= pickup_window(c_or_o) %>
</span>
</p>
</div>
Expand All @@ -114,7 +117,7 @@
</div>
<% end %>
<% else %>
<div class="flex items-center justify-center h-96 w-4/5 mx-auto text-center">
<div class="flex items-center justify-center w-4/5 mx-auto text-center h-96">
No campaigns set up yet for the week of <%= Calendar.strftime(@current_week, "%B %-d") %>.
</div>
<% end %>
Loading

0 comments on commit e2e672a

Please sign in to comment.