Gotcha, I was hesitant to post the full components bcs they’re pretty huge but here they are, thanks so much for the help!!
This is kanban_card_component.ex
defmodule TSSWeb.DispatchHomeLive.Kanban.Card.Component do
@moduledoc """
Card component for display truck loads on the dispatch kanban
"""
use Surface.Component
alias TSSWeb.Live.Components.{
Grid,
CardHeader,
CardInfo,
CardStat,
CardFooterLink,
CardStatTripTime
}
alias TSS.{
Helpers.Formatters,
Conversions.Timezone,
Conversions.Weight,
TruckLoads.Calls
}
alias TSSWeb.Router.Helpers, as: Routes
alias TSSWeb.SharedHelpers, as: Shared
prop truck_load, :any
prop well, :any
prop current_leg_eta, :map
prop total_eta, :map
prop trip_time, :any
prop show_reroute_button?, :boolean
prop show_change_pp_button?, :boolean
prop show_start_over_button?, :boolean
prop show_drop_trailer_button?, :boolean
prop show_call_to_well_button?, :boolean
prop show_unload_button?, :boolean
prop show_trip_times?, :boolean
prop show_driver_avail_info?, :boolean
def mount(socket) do
{:ok, Surface.init(socket)}
end
def update(%{truck_load: %{id: truck_load_id}} = assigns, socket) do
trip_time = TSS.TripTimes.TruckLoads.get_trip_time_message(truck_load_id)
assigns = Map.put(assigns, :trip_time, trip_time)
{:ok, assign(socket, assigns)}
end
def render(assigns) do
~H"""
<div class={{ dispatch_card_class(@truck_load) }}>
<Grid type="row">
<CardHeader
:if={{ not is_nil(@truck_load.sand_type) }}
background_color={{ @truck_load.sand_type_background_color }}
text_color={{ @truck_load.sand_type_text_color }}
title={{ @truck_load.sand_type }}
info={{ render_measured_weight_info(@truck_load.measured_weight) }}
/>
<Grid type="column" opts={{ [large: 7] }} class="DispatchCard-info">
<CardInfo
label="Truck Number"
title={{ "#{@truck_load.driver_first_name} #{@truck_load.driver_last_name}" }}
info={{ "##{@truck_load.truck_number}" }}
/>
<CardInfo label="Carrier" title={{ @truck_load.carrier_name }} small />
<CardInfo
label="Phone"
title={{ Formatters.format_phone_number(@truck_load.driver_phone_number) }}
small
/>
<CardInfo label="Trailer" title={{ Shared.get_string(@truck_load.trailer_type, :name) }} small />
<CardInfo label="Pull Point" title={{ @truck_load.pull_point_name }} small />
<button type="button" class="rev-Button rev-Button--secondary rev-Button--tiny" click="show_driver_availability_info" phx-target={{ @show_driver_avail_info? }}>See Driver Availability Info</button>
<CardInfo :if={{ @show_driver_avail_info? }} label="Time Since Available" title={{ get_time_since_available(@truck_load) }} small />
<CardInfo :if={{ @show_driver_avail_info? }} label="Current Status" title={{ get_driver_current_status(@truck_load) }} small />
<CardInfo :if={{ @show_driver_avail_info? }} label="Time Until Cycle Reset" title={{ get_time_until_cycle_reset(@truck_load) }} small />
<CardStatTripTime
id={{ "trip_time_card_#{@truck_load.id}" }}
trip_time={{ @trip_time }}
has_active_fulfillment={{ active_fulfillment?(assigns) }}
/>
</Grid>
<Grid type="column" opts={{ [large: 5] }} class="DispatchCard-info">
<CardStat
title={{ TSSWeb.TruckLoadView.event_badge(@truck_load) }}
label="Time Elapsed"
value={{ get_time_elapsed(@truck_load) }}
icon="clock"
/>
<CardStat
:if={{ show_old_eta?(assigns, :current_leg) }}
label="Current Leg"
icon="watch"
value={{ eta_value(@current_leg_eta) }}
alert={{ eta_alert(@current_leg_eta) }}
tooltip={{ eta_tooltip(@current_leg_eta) }}
/>
<CardStat
:if={{ show_old_eta?(assigns, :total_eta) }}
label="Total"
icon="watch"
value={{ eta_value(@total_eta) }}
alert={{ eta_alert(@total_eta) }}
tooltip={{ eta_tooltip(@total_eta) }}
/>
<CardStat
:if={{ @truck_load.bol_status }}
icon="file-text"
label="BOL"
value="BOL"
class={{ bol_class(@truck_load) }}
/>
<CardStat
icon="user"
label="Driver Duty Status"
value={{ Recase.to_title(@truck_load.driver_duty_status) }}
/>
<CardStat
:if={{ show_trip_break?(@truck_load.current_trip_break) }}
icon={{ trip_break_icon(@truck_load.current_trip_break) }}
label={{ trip_break_label(@truck_load.current_trip_break) }}
value={{ trip_break_value(@truck_load.current_trip_break) }}
/>
</Grid>
</Grid>
<div class="rev-Card-footer">
<Grid type="row" class="DispatchCard-footer">
<Grid type="column" opts={{ [small: 9] }} class="u-flexAlignStart">
<CardFooterLink
:if={{ @show_start_over_button? }}
to={{Routes.live_path(TSSWeb.Endpoint, TSSWeb.DispatchHomeLive.AddTruckLoad, @well.id,
truck_load_id: @truck_load.id
)}}
title="Start Over"
icon="arrow-left-circle"
/>
<CardFooterLink
:if={{ @show_change_pp_button? }}
title="Change Pull Point"
click="change_procurement_modal:on_open"
truck_load_id={{ @truck_load.id }}
icon="arrow-up-circle"
class="ProcurementModal"
/>
<CardFooterLink
:if={{ @show_reroute_button? && active_fulfillment?(assigns) }}
title="Reroute"
click="reroute_truck_load_modal:on_open"
truck_load_id={{ @truck_load.id }}
icon="refresh-cw"
/>
<CardFooterLink
:if={{ @truck_load.can_cancel? }}
click="cancel_truck_load"
truck_load_id={{ @truck_load.id }}
title="Cancel"
confirm="You cannot undo this action. Are you sure you want to CANCEL AND PERMANENTLY DELETE this truckload? Your name will be tied to the deleted truckload for tracking and user management purposes."
icon="x-circle"
/>
<CardFooterLink
:if={{ get_in(@truck_load, [:current_trip_break, :type]) == "break" }}
title="Resume"
click="end_trip_break"
truck_load_id={{ @truck_load.id }}
icon="play-circle"
/>
<CardFooterLink
:if={{ get_in(@truck_load, [:current_trip_break, :type]) == "breakdown" }}
title="Report Truck Back Up"
click="end_trip_break"
truck_load_id={{ @truck_load.id }}
icon="play-circle"
/>
<CardFooterLink
:if={{ is_nil(get_in(@truck_load, [:current_trip_break, :type])) }}
title="Pause"
click="trip_break_modal:on_open"
truck_load_id={{ @truck_load.id }}
icon="pause-circle"
/>
<CardFooterLink
:if={{ is_nil(get_in(@truck_load, [:current_trip_break, :type])) }}
title="Report Truck Down"
click="report_truck_down"
truck_load_id={{ @truck_load.id }}
icon="alert-circle"
/>
<CardFooterLink
:if={{ @show_drop_trailer_button? && @truck_load.status == "trailer_dropped" }}
to={{ Routes.live_path(TSSWeb.Endpoint, TSSWeb.DispatchHomeLive.RequestTrailerPickup, @truck_load.id) }}
title="Request Trailer Pickup"
truck_load_id={{ @truck_load.id }}
icon="upload"
/>
<CardFooterLink
:if={{ @show_drop_trailer_button? && @truck_load.status == "pickup_requested" }}
title="Trailer Picked Up"
icon="upload"
click="dropped_trailer:trailer_picked_up"
confirm="Are you sure you want to mark this trailer as picked up?"
truck_load_id={{ @truck_load.id }}
/>
<CardFooterLink
:if={{ @show_drop_trailer_button? && @truck_load.status not in ["pickup_requested", "trailer_dropped"] }}
title="Drop Trailer"
icon="download"
click="dropped_trailer:drop_trailer"
confirm="Are you sure you want to mark this trailer as dropped?"
truck_load_id={{ @truck_load.id }}
/>
<CardFooterLink
:if={{ @show_call_to_well_button? and Calls.can_call?(@truck_load) }}
title="Call To Well"
confirm="Are you sure you want to call this driver to the well?"
click="call_to_well:call"
truck_load_id={{ @truck_load.id }}
disabled={{ !show_call_to_well?(@truck_load) }}
icon="phone"
/>
<CardFooterLink
to={{ Routes.live_path(TSSWeb.Endpoint, TSSWeb.DispatchHomeLive.SlipSeatModal, @truck_load.id) }}
title="Slip Seat"
icon="shuffle"
/>
<CardFooterLink
:if={{ @show_unload_button? }}
to={{ Routes.live_path(TSSWeb.Endpoint, TSSWeb.DispatchHomeLive.UnloadModal, @truck_load.id) }}
title="Unload"
icon="check-square"
/>
</Grid>
<Grid type="column" opts={{ [small: 3] }}>
<a
href={{Routes.live_path(TSSWeb.Endpoint, TSSWeb.DispatchHomeLive.ViewTruckLoad, @well.id, @truck_load.id,
referrer: Routes.live_path(TSSWeb.Endpoint, TSSWeb.DispatchHomeLive, @well.id, landing: :kanban)
)}}
title="View Details"
class="DispatchCard-footerLink"
>#{{ @truck_load.id }}</a>
</Grid>
</Grid>
</div>
</div>
"""
end
defp render_measured_weight_info(nil), do: ""
defp render_measured_weight_info(weight) do
measured_weight_lbs = Formatters.format_as_pounds(weight)
measured_weight_tons = Weight.convert_pounds_to_tons_and_round(weight, 2)
"#{measured_weight_lbs} lbs (#{measured_weight_tons} tons) "
end
defp active_fulfillment?(%{truck_load: %{active_fulfillment: nil}}), do: false
defp active_fulfillment?(_), do: true
defp show_call_to_well?(truck_load) do
latest_call_status = Calls.latest_call_status(truck_load)
is_nil(latest_call_status) || latest_call_status == "unskipped"
end
defp show_trip_break?(nil), do: false
defp show_trip_break?(%{expected_duration: nil, type: "break"}), do: false
defp show_trip_break?(_), do: true
defp trip_break_label(%{type: "breakdown"}), do: "Time down"
defp trip_break_label(_), do: "Time on break"
defp trip_break_icon(%{type: "breakdown"}), do: "alert-circle"
defp trip_break_icon(_), do: "pause-circle"
defp trip_break_value(nil), do: nil
defp trip_break_value(%{expected_duration: nil, type: "break"}), do: nil
defp trip_break_value(%{type: "breakdown", started_at: started_at}) do
started_at
|> Timezone.diff_current_time_in_minutes()
|> abs()
|> format_minutes()
end
defp trip_break_value(%{expected_duration: expected_duration, started_at: started_at}) do
mins_since_start = started_at |> Timezone.diff_current_time_in_minutes() |> abs()
remaining_time = expected_duration - mins_since_start
if remaining_time < 0 do
format_minutes(0)
else
format_minutes(remaining_time)
end
end
defp format_minutes(minutes) do
Formatters.humanize_time(minutes, :minutes, :minutes) <> " (hh:mm)"
end
defp bol_class(%{bol_status: bol_status}) do
"DispatchCard-iconStat--#{bol_status}"
end
defp show_old_eta?(%{current_leg_eta: %{eta: eta}} = assigns, :current_leg)
when not is_nil(eta) do
active_fulfillment?(assigns)
end
defp show_old_eta?(%{total_eta: %{eta: eta}} = assigns, :total_eta)
when not is_nil(eta) do
active_fulfillment?(assigns)
end
defp show_old_eta?(_, _), do: false
defp eta_value(%{text: text}), do: "#{text} (hh:mm)"
defp eta_value(_), do: nil
defp eta_alert(%{driver_idle?: driver_idle?}), do: driver_idle?
defp eta_alert(_), do: nil
defp eta_tooltip(%{tooltip: tooltip}), do: tooltip
defp eta_tooltip(_), do: nil
defp get_time_elapsed(truck_load) do
time_elapsed = TSSWeb.DispatchHome.View.get_time_elapsed_since_entered_column(truck_load)
"#{time_elapsed} (hh:mm)"
end
defp get_time_since_available(truck_load) do
driver = TSS.DriverStatuses.get_driver_by_driver_id(truck_load.driver_id)
if !is_nil(driver) do
time_elapsed = Formatters.decorate_time_since_available(driver)
"#{time_elapsed}"
else
"driver not available"
end
end
defp get_driver_current_status(truck_load) do
driver = TSS.DriverStatuses.get_driver_status_by_driver_id(truck_load.driver_id)
if !is_nil(driver) do
status = Formatters.decorate_driver_current_status(driver)
"#{status}"
else
"driver not available"
end
end
defp get_time_until_cycle_reset(truck_load) do
driver = TSS.DriverStatuses.get_driver_status_by_driver_id(truck_load.driver_id)
remaining_cycle_time = Formatters.decorate_remaining_cycle_time(driver)
"#{remaining_cycle_time}"
end
defp dispatch_card_class(truck_load) do
default_class = "DispatchCard rev-Card"
if is_nil(truck_load.current_trip_break) do
"#{default_class} DispatchCard-status--#{truck_load.status}"
else
"#{default_class} DispatchCard-status--#{truck_load.current_trip_break.type}"
end
end
end
And this is DispatchHomeLive
where the handle_event
is (all the other handle_event
's for components in the kanban_card_component.ex
file are in here and working fine, which is why I believe this handle_event
belongs here…I tried moving it to the actual DispatchHomeLive
component and still got the same [error] GenServer #PID<0.1218.0> terminating ** (UndefinedFunctionError) function TSSWeb.DispatchHomeLive.handle_event/3 is undefined or private
error) :
defmodule TSSWeb.DispatchHomeLive.Kanban.Component do
@moduledoc """
Renders the kanban board / "Active truck loads" tab on the Dispatch Board of a
given well.
"Active" means any truckload that has had a job request sent and is not yet
unloaded.
"""
use TSSWeb, :live_component
alias TSSWeb.{
DispatchHomeLive.ActionValidators,
DispatchHomeLive.RerouteTruckLoadModal,
DispatchHomeLive.ChangeProcurementModal,
DispatchHomeLive.DroppedTrailer,
DispatchHomeLive.CallToWell,
DispatchHomeLive.TripBreakModal,
DispatchHome
}
alias TSS.{
DispatchOrders,
TruckLoad,
TruckLoads,
TruckLoads.Calls,
TripBreak,
TripBreaks,
Orders,
Order
}
@init_assigns %{
truck_loads: [],
truck_load: nil,
modal_assigns: nil,
dispatched_loads: [],
loaded_loads: [],
on_site_loads: [],
scheduled: [],
# a hidden/unused field to allow for re-rendering manually:
last_updated_timestamp: nil,
# A map of truck_load ids => eta timestamps
load_etas: %{},
reroute_truck_load_modal_open?: false,
well_options: [],
procurement_modal_open?: false,
trip_break_modal_open?: false,
procurement_options: [],
show_driver_avail_info?: false
}
def render(assigns) do
Phoenix.View.render(DispatchHome.View, "kanban.html", assigns)
end
def update(
%{
data: data,
well: well,
show?: show?,
current_user: current_user
},
socket
) do
socket =
socket
|> init_assigns(@init_assigns)
|> assign_data(data)
|> assign(:well, well)
|> assign(:show?, show?)
|> assign(:current_user, current_user)
{:ok, socket}
end
defp assign_data(socket, nil), do: socket
defp assign_data(socket, data), do: assign(socket, data)
def handle_event("reroute_truck_load_modal:" <> action, attrs, socket) do
RerouteTruckLoadModal.Actions.handle_event(action, attrs, socket)
end
def handle_event("dropped_trailer:" <> action, attrs, socket) do
DroppedTrailer.Actions.handle_event(action, attrs, socket)
end
def handle_event("call_to_well:" <> action, attrs, socket) do
CallToWell.Actions.handle_event(action, attrs, socket)
end
def handle_event("change_procurement_modal:" <> action, attrs, socket) do
ChangeProcurementModal.Actions.handle_event(action, attrs, socket)
end
def handle_event("trip_break_modal:" <> action, attrs, socket) do
TripBreakModal.Actions.handle_event(action, attrs, socket)
end
def handle_event(
"report_truck_down",
%{"truck-load-id" => truck_load_id},
%{assigns: %{current_user: user}} = socket
) do
truck_load = TruckLoads.get_truck_load!(truck_load_id)
ActionValidators.validate_user_current_location_for_trip_break(
truck_load,
"breakdown",
socket,
fn user_current_location ->
attrs =
TripBreaks.append_coords_to_trip_break_attrs(
%{
truck_load_id: truck_load.id,
driver_id: truck_load.driver_id,
creator_id: user.id,
type: "breakdown",
started_at: DateTime.utc_now()
},
user_current_location
)
case TripBreaks.create_trip_break(attrs) do
{:error, _changeset} ->
{:noreply,
put_flash(socket, :error, "Error Reporting Truckload ##{truck_load_id} down!")}
{:ok, %TripBreak{} = _trip_break} ->
{:noreply, put_flash(socket, :success, "Truckload ##{truck_load_id} reported down!")}
end
end
)
end
def handle_event(
"end_trip_break",
%{"truck-load-id" => truck_load_id},
socket
) do
trip_break = TripBreaks.get_current_trip_break(truck_load_id)
case TripBreaks.end_trip_break(trip_break) do
{:error, _changeset} ->
{:noreply,
put_flash(socket, :error, "Error Reporting Truckload ##{truck_load_id} back up!")}
{:ok, %TripBreak{} = _trip_break} ->
{:noreply, put_flash(socket, :success, "Truckload ##{truck_load_id} reported back up!")}
end
end
def handle_event(
"cancel_truck_load",
%{"truck-load-id" => truck_load_id},
%{assigns: %{current_user: user}} = socket
) do
with %TruckLoad{} = truck_load <- TruckLoads.get_truck_load(truck_load_id),
{:ok, truck_load} <- TruckLoads.cancel_truck_load(truck_load, user) do
Calls.create_cancelled_call(truck_load, user)
{:noreply, put_flash(socket, :success, "Truckload ##{truck_load_id} Cancelled!")}
else
_ ->
{:noreply, put_flash(socket, :error, "Error Cancelling Truckload ##{truck_load_id}!")}
end
end
def handle_event(
"cancel_order",
%{"order-id" => order_id},
%{assigns: %{current_user: user}} = socket
) do
with %Order{} = order <- Orders.get_order(order_id, [:fulfillments]),
{:ok, %Order{}} <- DispatchOrders.cancel_order(order, user) do
{:noreply, put_flash(socket, :success, "Truckload Cancelled!")}
else
_ ->
{:noreply, put_flash(socket, :error, "Error Cancelling Truckload!")}
end
end
def handle_event("show_driver_availability_info", _, socket) do
IO.puts("show_driver_availability_info being called")
IO.inspect(socket.assigns.show_driver_avail_info?)
socket = assign(socket, :show_driver_avail_info?, true)
{:noreply, socket}
end
end
Also, this is the kanban.html.leex
template file:
<%= if @show? do %>
<div id="kanban" class="<%= Shared.suit_class("rev-Row", flex: true, stretch: true, class: "Kanban-wrapper") %>">
<%= if render_kanban?(@scheduled, @dispatched, @loaded, @on_site) do %>
<div class="<%= Shared.suit_class("rev-Col", medium: 3, class: "Kanban-col") %>">
<%= render "_kanban_column_header.html",
[
name: "Scheduled",
column_name: "scheduled",
card_count: Enum.count(@scheduled),
card_count_changed?: @count_changes.scheduled,
error_count: 0
]
%>
<%= live_component @socket, TSSWeb.DispatchHomeLive.Kanban.Column.Component,
show_reroute_button?: false,
show_drop_trailer_button?: false,
show_start_over_button?: true,
show_change_pp_button?: false,
show_call_to_well_button?: false,
show_unload_button?: false,
truck_load_etas: @load_etas,
truck_loads: [],
orders: @scheduled,
well: @well,
show_driver_avail_info?: @show_driver_avail_info?
%>
</div>
<div class="<%= Shared.suit_class("rev-Col", medium: 3, class: "Kanban-col") %>">
<%= render "_kanban_column_header.html",
[
name: "Dispatched",
column_name: "dispatched",
card_count: Enum.count(@dispatched),
card_count_changed?: @count_changes.dispatched,
error_count: get_kanban_column_error_count(@dispatched)
]
%>
<%= live_component @socket, TSSWeb.DispatchHomeLive.Kanban.Column.Component,
show_reroute_button?: false,
show_drop_trailer_button?: false,
show_start_over_button?: false,
show_change_pp_button?: true,
show_call_to_well_button?: false,
show_unload_button?: false,
truck_load_etas: @load_etas,
truck_loads: @dispatched,
orders: [],
well: @well,
show_driver_avail_info?: @show_driver_avail_info?
%>
</div>
<div class="<%= Shared.suit_class("rev-Col", medium: 3, class: "Kanban-col") %>">
<%= render "_kanban_column_header.html",
[
name: "loaded",
column_name: "loaded",
card_count: Enum.count(@loaded),
card_count_changed?: @count_changes.loaded,
error_count: get_kanban_column_error_count(@loaded)
]
%>
<%= live_component @socket, TSSWeb.DispatchHomeLive.Kanban.Column.Component,
show_reroute_button?: true,
show_drop_trailer_button?: true,
show_start_over_button?: false,
show_change_pp_button?: false,
show_call_to_well_button?: false,
show_unload_button?: false,
truck_load_etas: @load_etas,
truck_loads: @loaded,
orders: [],
well: @well,
show_driver_avail_info?: @show_driver_avail_info?
%>
</div>
<div class="<%= Shared.suit_class("rev-Col", medium: 3, class: "Kanban-col") %>">
<%= render "_kanban_column_header.html",
[
name: "On Site",
column_name: "on_site",
card_count: Enum.count(@on_site),
card_count_changed?: @count_changes.on_site,
error_count: get_kanban_column_error_count(@on_site)
]
%>
<%= live_component @socket, TSSWeb.DispatchHomeLive.Kanban.Column.Component,
show_reroute_button?: true,
show_drop_trailer_button?: true,
show_start_over_button?: false,
show_change_pp_button?: false,
show_call_to_well_button?: true,
show_unload_button?: true,
truck_load_etas: @load_etas,
truck_loads: @on_site,
orders: [],
well: @well,
show_driver_avail_info?: @show_driver_avail_info?
%>
</div>
<% else %>
<div class="rev-Col">
<div class="EmptyState EmptyState--borderless u-flexVerticalAlignCenter">
<div><%= Shared.icon "alert-circle", class: "EmptyState-icon" %></div>
There are no trucks currently dispatched to this well. Click 'Add Load' at the top of the page to dispatch a truck.
</div>
</div>
<% end %>
<%= if @reroute_truck_load_modal_open? do %>
<%= render "_reroute_truck_load_modal.html", assigns %>
<% end %>
<%= if @procurement_modal_open? do %>
<%= render "_change_procurement_modal.html", assigns %>
<% end %>
<%= if @trip_break_modal_open? do %>
<%= render "_trip_break_modal.html", assigns %>
<% end %>
</div>
<% else %>
<div id="kanban"></div>
<% end %>