use strict context boundaries and remove all n+1 queries
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		| @@ -5,6 +5,7 @@ defmodule CanneryWeb.Components.AddShotGroupComponent do | ||||
|  | ||||
|   use CanneryWeb, :live_component | ||||
|   alias Cannery.{Accounts.User, ActivityLog, ActivityLog.ShotGroup, Ammo.AmmoGroup} | ||||
|   alias Ecto.Changeset | ||||
|   alias Phoenix.LiveView.{JS, Socket} | ||||
|  | ||||
|   @impl true | ||||
| @@ -18,7 +19,7 @@ defmodule CanneryWeb.Components.AddShotGroupComponent do | ||||
|         ) :: {:ok, Socket.t()} | ||||
|   def update(%{ammo_group: ammo_group, current_user: current_user} = assigns, socket) do | ||||
|     changeset = | ||||
|       %ShotGroup{date: NaiveDateTime.utc_now(), count: 1} | ||||
|       %ShotGroup{date: Date.utc_today()} | ||||
|       |> ShotGroup.create_changeset(current_user, ammo_group, %{}) | ||||
|  | ||||
|     {:ok, socket |> assign(assigns) |> assign(:changeset, changeset)} | ||||
| @@ -32,10 +33,13 @@ defmodule CanneryWeb.Components.AddShotGroupComponent do | ||||
|       ) do | ||||
|     params = shot_group_params |> process_params(ammo_group) | ||||
|  | ||||
|     changeset = %ShotGroup{} |> ShotGroup.create_changeset(current_user, ammo_group, params) | ||||
|  | ||||
|     changeset = | ||||
|       %ShotGroup{} | ||||
|       |> ShotGroup.create_changeset(current_user, ammo_group, params) | ||||
|       |> Map.put(:action, :validate) | ||||
|       case changeset |> Changeset.apply_action(:validate) do | ||||
|         {:ok, _data} -> changeset | ||||
|         {:error, changeset} -> changeset | ||||
|       end | ||||
|  | ||||
|     {:noreply, socket |> assign(:changeset, changeset)} | ||||
|   end | ||||
| @@ -56,7 +60,7 @@ defmodule CanneryWeb.Components.AddShotGroupComponent do | ||||
|           prompt = dgettext("prompts", "Shots recorded successfully") | ||||
|           socket |> put_flash(:info, prompt) |> push_navigate(to: return_to) | ||||
|  | ||||
|         {:error, %Ecto.Changeset{} = changeset} -> | ||||
|         {:error, %Changeset{} = changeset} -> | ||||
|           socket |> assign(changeset: changeset) | ||||
|       end | ||||
|  | ||||
| @@ -65,14 +69,14 @@ defmodule CanneryWeb.Components.AddShotGroupComponent do | ||||
|  | ||||
|   # calculate count from shots left | ||||
|   defp process_params(params, %AmmoGroup{count: count}) do | ||||
|     new_count = | ||||
|       if params |> Map.get("ammo_left", "0") == "" do | ||||
|         "0" | ||||
|     shot_group_count = | ||||
|       if params |> Map.get("ammo_left", "") == "" do | ||||
|         nil | ||||
|       else | ||||
|         params |> Map.get("ammo_left", "0") | ||||
|         new_count = params |> Map.get("ammo_left") |> String.to_integer() | ||||
|         count - new_count | ||||
|       end | ||||
|       |> String.to_integer() | ||||
|  | ||||
|     params |> Map.put("count", count - new_count) | ||||
|     params |> Map.put("count", shot_group_count) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -3,7 +3,7 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do | ||||
|   A component that displays a list of ammo groups | ||||
|   """ | ||||
|   use CanneryWeb, :live_component | ||||
|   alias Cannery.{Accounts.User, Ammo, Ammo.AmmoGroup, Repo} | ||||
|   alias Cannery.{Accounts.User, ActivityLog, Ammo, Ammo.AmmoGroup, Containers} | ||||
|   alias Ecto.UUID | ||||
|   alias Phoenix.LiveView.{Rendered, Socket} | ||||
|  | ||||
| @@ -54,8 +54,8 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do | ||||
|       end | ||||
|  | ||||
|     columns = [ | ||||
|       %{label: gettext("Purchased on"), key: :purchased_on}, | ||||
|       %{label: gettext("Last used on"), key: :used_up_on} | columns | ||||
|       %{label: gettext("Purchased on"), key: :purchased_on, type: Date}, | ||||
|       %{label: gettext("Last used on"), key: :used_up_on, type: Date} | columns | ||||
|     ] | ||||
|  | ||||
|     columns = | ||||
| @@ -94,13 +94,15 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do | ||||
|       ammo_type: ammo_type, | ||||
|       columns: columns, | ||||
|       container: container, | ||||
|       original_counts: Ammo.get_original_counts(ammo_groups, current_user), | ||||
|       cprs: Ammo.get_cprs(ammo_groups, current_user), | ||||
|       last_used_dates: ActivityLog.get_last_used_dates(ammo_groups, current_user), | ||||
|       actions: actions, | ||||
|       range: range | ||||
|     } | ||||
|  | ||||
|     rows = | ||||
|       ammo_groups | ||||
|       |> Repo.preload([:ammo_type, :container]) | ||||
|       |> Enum.map(fn ammo_group -> | ||||
|         ammo_group |> get_row_data_for_ammo_group(extra_data) | ||||
|       end) | ||||
| @@ -124,8 +126,6 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do | ||||
|  | ||||
|   @spec get_row_data_for_ammo_group(AmmoGroup.t(), additional_data :: map()) :: map() | ||||
|   defp get_row_data_for_ammo_group(ammo_group, %{columns: columns} = additional_data) do | ||||
|     ammo_group = ammo_group |> Repo.preload([:ammo_type, :container]) | ||||
|  | ||||
|     columns | ||||
|     |> Map.new(fn %{key: key} -> | ||||
|       {key, get_value_for_key(key, ammo_group, additional_data)} | ||||
| @@ -150,30 +150,23 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do | ||||
|   defp get_value_for_key(:price_paid, %{price_paid: nil}, _additional_data), do: {"", nil} | ||||
|  | ||||
|   defp get_value_for_key(:price_paid, %{price_paid: price_paid}, _additional_data), | ||||
|     do: gettext("$%{amount}", amount: price_paid |> :erlang.float_to_binary(decimals: 2)) | ||||
|  | ||||
|   defp get_value_for_key(:purchased_on, %{purchased_on: purchased_on}, _additional_data) do | ||||
|     assigns = %{purchased_on: purchased_on} | ||||
|     do: gettext("$%{amount}", amount: display_currency(price_paid)) | ||||
|  | ||||
|   defp get_value_for_key(:purchased_on, %{purchased_on: purchased_on} = assigns, _additional_data) do | ||||
|     {purchased_on, | ||||
|      ~H""" | ||||
|      <.date date={@purchased_on} /> | ||||
|      <.date id={"#{@id}-purchased-on"} date={@purchased_on} /> | ||||
|      """} | ||||
|   end | ||||
|  | ||||
|   defp get_value_for_key(:used_up_on, ammo_group, _additional_data) do | ||||
|     last_shot_group_date = | ||||
|       case ammo_group |> Ammo.get_last_used_shot_group() do | ||||
|         %{date: last_shot_group_date} -> last_shot_group_date | ||||
|         _no_shot_groups -> nil | ||||
|       end | ||||
|   defp get_value_for_key(:used_up_on, %{id: ammo_group_id}, %{last_used_dates: last_used_dates}) do | ||||
|     last_used_date = last_used_dates |> Map.get(ammo_group_id) | ||||
|     assigns = %{id: ammo_group_id, last_used_date: last_used_date} | ||||
|  | ||||
|     assigns = %{last_shot_group_date: last_shot_group_date} | ||||
|  | ||||
|     {last_shot_group_date, | ||||
|     {last_used_date, | ||||
|      ~H""" | ||||
|      <%= if @last_shot_group_date do %> | ||||
|        <.date date={@last_shot_group_date} /> | ||||
|      <%= if @last_used_date do %> | ||||
|        <.date id={"#{@id}-last-used-date"} date={@last_used_date} /> | ||||
|      <% else %> | ||||
|        <%= gettext("Never used") %> | ||||
|      <% end %> | ||||
| @@ -189,8 +182,11 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do | ||||
|      """} | ||||
|   end | ||||
|  | ||||
|   defp get_value_for_key(:remaining, ammo_group, _additional_data), | ||||
|     do: gettext("%{percentage}%", percentage: ammo_group |> Ammo.get_percentage_remaining()) | ||||
|   defp get_value_for_key(:remaining, ammo_group, %{current_user: current_user}), | ||||
|     do: | ||||
|       gettext("%{percentage}%", | ||||
|         percentage: ammo_group |> Ammo.get_percentage_remaining(current_user) | ||||
|       ) | ||||
|  | ||||
|   defp get_value_for_key(:actions, ammo_group, %{actions: actions}) do | ||||
|     assigns = %{actions: actions, ammo_group: ammo_group} | ||||
| @@ -204,31 +200,40 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do | ||||
|  | ||||
|   defp get_value_for_key( | ||||
|          :container, | ||||
|          %{container: %{name: container_name}} = ammo_group, | ||||
|          %{container: container} | ||||
|          %{container_id: container_id} = ammo_group, | ||||
|          %{container: container, current_user: current_user} | ||||
|        ) do | ||||
|     assigns = %{container: container, ammo_group: ammo_group} | ||||
|     assigns = %{ | ||||
|       container: | ||||
|         %{name: container_name} = container_id |> Containers.get_container!(current_user), | ||||
|       container_block: container, | ||||
|       ammo_group: ammo_group | ||||
|     } | ||||
|  | ||||
|     {container_name, | ||||
|      ~H""" | ||||
|      <%= render_slot(@container, @ammo_group) %> | ||||
|      <%= render_slot(@container_block, {@ammo_group, @container}) %> | ||||
|      """} | ||||
|   end | ||||
|  | ||||
|   defp get_value_for_key(:original_count, ammo_group, _additional_data), | ||||
|     do: ammo_group |> Ammo.get_original_count() | ||||
|   defp get_value_for_key(:original_count, %{id: ammo_group_id}, %{ | ||||
|          original_counts: original_counts | ||||
|        }) do | ||||
|     Map.fetch!(original_counts, ammo_group_id) | ||||
|   end | ||||
|  | ||||
|   defp get_value_for_key(:cpr, %{price_paid: nil}, _additional_data), | ||||
|     do: gettext("No cost information") | ||||
|  | ||||
|   defp get_value_for_key(:cpr, ammo_group, _additional_data) do | ||||
|     gettext("$%{amount}", | ||||
|       amount: ammo_group |> Ammo.get_cpr() |> :erlang.float_to_binary(decimals: 2) | ||||
|     ) | ||||
|   defp get_value_for_key(:cpr, %{id: ammo_group_id}, %{cprs: cprs}) do | ||||
|     gettext("$%{amount}", amount: display_currency(Map.fetch!(cprs, ammo_group_id))) | ||||
|   end | ||||
|  | ||||
|   defp get_value_for_key(:count, %{count: count}, _additional_data), | ||||
|     do: if(count == 0, do: gettext("Empty"), else: count) | ||||
|  | ||||
|   defp get_value_for_key(key, ammo_group, _additional_data), do: ammo_group |> Map.get(key) | ||||
|  | ||||
|   @spec display_currency(float()) :: String.t() | ||||
|   defp display_currency(float), do: :erlang.float_to_binary(float, decimals: 2) | ||||
| end | ||||
|   | ||||
| @@ -3,7 +3,7 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do | ||||
|   A component that displays a list of ammo type | ||||
|   """ | ||||
|   use CanneryWeb, :live_component | ||||
|   alias Cannery.{Accounts.User, Ammo, Ammo.AmmoType} | ||||
|   alias Cannery.{Accounts.User, ActivityLog, Ammo, Ammo.AmmoType} | ||||
|   alias Ecto.UUID | ||||
|   alias Phoenix.LiveView.{Rendered, Socket} | ||||
|  | ||||
| @@ -103,13 +103,13 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do | ||||
|           [ | ||||
|             %{ | ||||
|               label: gettext("Used packs"), | ||||
|               key: :used_ammo_count, | ||||
|               type: :used_ammo_count | ||||
|               key: :used_packs_count, | ||||
|               type: :used_packs_count | ||||
|             }, | ||||
|             %{ | ||||
|               label: gettext("Total ever packs"), | ||||
|               key: :historical_ammo_count, | ||||
|               type: :historical_ammo_count | ||||
|               key: :historical_packs_count, | ||||
|               type: :historical_packs_count | ||||
|             } | ||||
|           ] | ||||
|         else | ||||
| @@ -121,7 +121,35 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do | ||||
|         %{label: nil, key: "actions", type: :actions, sortable: false} | ||||
|       ]) | ||||
|  | ||||
|     extra_data = %{actions: actions, current_user: current_user} | ||||
|     round_counts = ammo_types |> Ammo.get_round_count_for_ammo_types(current_user) | ||||
|  | ||||
|     used_counts = | ||||
|       show_used && ammo_types |> ActivityLog.get_used_count_for_ammo_types(current_user) | ||||
|  | ||||
|     historical_round_counts = | ||||
|       show_used && ammo_types |> Ammo.get_historical_count_for_ammo_types(current_user) | ||||
|  | ||||
|     packs_count = ammo_types |> Ammo.get_ammo_groups_count_for_types(current_user) | ||||
|  | ||||
|     historical_packs_count = | ||||
|       show_used && ammo_types |> Ammo.get_ammo_groups_count_for_types(current_user, true) | ||||
|  | ||||
|     used_packs_count = | ||||
|       show_used && ammo_types |> Ammo.get_used_ammo_groups_count_for_types(current_user) | ||||
|  | ||||
|     average_costs = ammo_types |> Ammo.get_average_cost_for_ammo_types(current_user) | ||||
|  | ||||
|     extra_data = %{ | ||||
|       actions: actions, | ||||
|       current_user: current_user, | ||||
|       used_counts: used_counts, | ||||
|       round_counts: round_counts, | ||||
|       historical_round_counts: historical_round_counts, | ||||
|       packs_count: packs_count, | ||||
|       used_packs_count: used_packs_count, | ||||
|       historical_packs_count: historical_packs_count, | ||||
|       average_costs: average_costs | ||||
|     } | ||||
|  | ||||
|     rows = | ||||
|       ammo_types | ||||
| @@ -156,28 +184,44 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do | ||||
|   defp get_ammo_type_value(:boolean, key, ammo_type, _other_data), | ||||
|     do: ammo_type |> Map.get(key) |> humanize() | ||||
|  | ||||
|   defp get_ammo_type_value(:round_count, _key, ammo_type, %{current_user: current_user}), | ||||
|     do: ammo_type |> Ammo.get_round_count_for_ammo_type(current_user) | ||||
|   defp get_ammo_type_value(:round_count, _key, %{id: ammo_type_id}, %{round_counts: round_counts}), | ||||
|     do: Map.get(round_counts, ammo_type_id) | ||||
|  | ||||
|   defp get_ammo_type_value(:historical_round_count, _key, ammo_type, %{current_user: current_user}), | ||||
|        do: ammo_type |> Ammo.get_historical_count_for_ammo_type(current_user) | ||||
|   defp get_ammo_type_value( | ||||
|          :historical_round_count, | ||||
|          _key, | ||||
|          %{id: ammo_type_id}, | ||||
|          %{historical_round_counts: historical_round_counts} | ||||
|        ), | ||||
|        do: Map.get(historical_round_counts, ammo_type_id) | ||||
|  | ||||
|   defp get_ammo_type_value(:used_round_count, _key, ammo_type, %{current_user: current_user}), | ||||
|     do: ammo_type |> Ammo.get_used_count_for_ammo_type(current_user) | ||||
|   defp get_ammo_type_value(:used_round_count, _key, %{id: ammo_type_id}, %{ | ||||
|          used_counts: used_counts | ||||
|        }), | ||||
|        do: Map.get(used_counts, ammo_type_id) | ||||
|  | ||||
|   defp get_ammo_type_value(:historical_ammo_count, _key, ammo_type, %{current_user: current_user}), | ||||
|     do: ammo_type |> Ammo.get_ammo_groups_count_for_type(current_user, true) | ||||
|   defp get_ammo_type_value( | ||||
|          :historical_packs_count, | ||||
|          _key, | ||||
|          %{id: ammo_type_id}, | ||||
|          %{historical_packs_count: historical_packs_count} | ||||
|        ), | ||||
|        do: Map.get(historical_packs_count, ammo_type_id) | ||||
|  | ||||
|   defp get_ammo_type_value(:used_ammo_count, _key, ammo_type, %{current_user: current_user}), | ||||
|     do: ammo_type |> Ammo.get_used_ammo_groups_count_for_type(current_user) | ||||
|   defp get_ammo_type_value(:used_packs_count, _key, %{id: ammo_type_id}, %{ | ||||
|          used_packs_count: used_packs_count | ||||
|        }), | ||||
|        do: Map.get(used_packs_count, ammo_type_id) | ||||
|  | ||||
|   defp get_ammo_type_value(:ammo_count, _key, ammo_type, %{current_user: current_user}), | ||||
|     do: ammo_type |> Ammo.get_ammo_groups_count_for_type(current_user) | ||||
|   defp get_ammo_type_value(:ammo_count, _key, %{id: ammo_type_id}, %{packs_count: packs_count}), | ||||
|     do: Map.get(packs_count, ammo_type_id) | ||||
|  | ||||
|   defp get_ammo_type_value(:avg_price_paid, _key, ammo_type, %{current_user: current_user}) do | ||||
|     case ammo_type |> Ammo.get_average_cost_for_ammo_type!(current_user) do | ||||
|   defp get_ammo_type_value(:avg_price_paid, _key, %{id: ammo_type_id}, %{ | ||||
|          average_costs: average_costs | ||||
|        }) do | ||||
|     case Map.get(average_costs, ammo_type_id) do | ||||
|       nil -> gettext("No cost information") | ||||
|       count -> gettext("$%{amount}", amount: count |> :erlang.float_to_binary(decimals: 2)) | ||||
|       count -> gettext("$%{amount}", amount: display_currency(count)) | ||||
|     end | ||||
|   end | ||||
|  | ||||
| @@ -202,4 +246,7 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do | ||||
|   defp get_ammo_type_value(nil, _key, _ammo_type, _other_data), do: nil | ||||
|  | ||||
|   defp get_ammo_type_value(_other, key, ammo_type, _other_data), do: ammo_type |> Map.get(key) | ||||
|  | ||||
|   @spec display_currency(float()) :: String.t() | ||||
|   defp display_currency(float), do: :erlang.float_to_binary(float, decimals: 2) | ||||
| end | ||||
|   | ||||
| @@ -3,7 +3,7 @@ defmodule CanneryWeb.Components.ContainerTableComponent do | ||||
|   A component that displays a list of containers | ||||
|   """ | ||||
|   use CanneryWeb, :live_component | ||||
|   alias Cannery.{Accounts.User, Containers, Containers.Container, Repo} | ||||
|   alias Cannery.{Accounts.User, Ammo, Containers.Container} | ||||
|   alias Ecto.UUID | ||||
|   alias Phoenix.LiveView.{Rendered, Socket} | ||||
|  | ||||
| @@ -45,11 +45,7 @@ defmodule CanneryWeb.Components.ContainerTableComponent do | ||||
|         %{label: gettext("Name"), key: :name, type: :string}, | ||||
|         %{label: gettext("Description"), key: :desc, type: :string}, | ||||
|         %{label: gettext("Location"), key: :location, type: :string}, | ||||
|         %{label: gettext("Type"), key: :type, type: :string}, | ||||
|         %{label: gettext("Packs"), key: :packs, type: :integer}, | ||||
|         %{label: gettext("Rounds"), key: :rounds, type: :string}, | ||||
|         %{label: gettext("Tags"), key: :tags, type: :tags}, | ||||
|         %{label: nil, key: :actions, sortable: false, type: :actions} | ||||
|         %{label: gettext("Type"), key: :type, type: :string} | ||||
|       ] | ||||
|       |> Enum.filter(fn %{key: key, type: type} -> | ||||
|         # remove columns if all values match defaults | ||||
| @@ -64,11 +60,19 @@ defmodule CanneryWeb.Components.ContainerTableComponent do | ||||
|           type in [:tags, :actions] or not (container |> Map.get(key) == default_value) | ||||
|         end) | ||||
|       end) | ||||
|       |> Enum.concat([ | ||||
|         %{label: gettext("Packs"), key: :packs, type: :integer}, | ||||
|         %{label: gettext("Rounds"), key: :rounds, type: :integer}, | ||||
|         %{label: gettext("Tags"), key: :tags, type: :tags}, | ||||
|         %{label: nil, key: :actions, sortable: false, type: :actions} | ||||
|       ]) | ||||
|  | ||||
|     extra_data = %{ | ||||
|       current_user: current_user, | ||||
|       tag_actions: tag_actions, | ||||
|       actions: actions | ||||
|       actions: actions, | ||||
|       pack_count: Ammo.get_ammo_groups_count_for_containers(containers, current_user), | ||||
|       round_count: Ammo.get_round_count_for_containers(containers, current_user) | ||||
|     } | ||||
|  | ||||
|     rows = | ||||
| @@ -100,8 +104,6 @@ defmodule CanneryWeb.Components.ContainerTableComponent do | ||||
|  | ||||
|   @spec get_row_data_for_container(Container.t(), columns :: [map()], extra_data :: map) :: map() | ||||
|   defp get_row_data_for_container(container, columns, extra_data) do | ||||
|     container = container |> Repo.preload([:ammo_groups, :tags]) | ||||
|  | ||||
|     columns | ||||
|     |> Map.new(fn %{key: key} -> {key, get_value_for_key(key, container, extra_data)} end) | ||||
|   end | ||||
| @@ -120,18 +122,24 @@ defmodule CanneryWeb.Components.ContainerTableComponent do | ||||
|      """} | ||||
|   end | ||||
|  | ||||
|   defp get_value_for_key(:packs, container, _extra_data) do | ||||
|     container |> Containers.get_container_ammo_group_count!() | ||||
|   defp get_value_for_key(:packs, %{id: container_id}, %{pack_count: pack_count}) do | ||||
|     pack_count |> Map.get(container_id, 0) | ||||
|   end | ||||
|  | ||||
|   defp get_value_for_key(:rounds, container, _extra_data) do | ||||
|     container |> Containers.get_container_rounds!() | ||||
|   defp get_value_for_key(:rounds, %{id: container_id}, %{round_count: round_count}) do | ||||
|     round_count |> Map.get(container_id, 0) | ||||
|   end | ||||
|  | ||||
|   defp get_value_for_key(:tags, container, %{tag_actions: tag_actions}) do | ||||
|     assigns = %{tag_actions: tag_actions, container: container} | ||||
|  | ||||
|     {container.tags |> Enum.map(fn %{name: name} -> name end), | ||||
|     tag_names = | ||||
|       container.tags | ||||
|       |> Enum.map(fn %{name: name} -> name end) | ||||
|       |> Enum.sort() | ||||
|       |> Enum.join(" ") | ||||
|  | ||||
|     {tag_names, | ||||
|      ~H""" | ||||
|      <div class="flex flex-wrap justify-center items-center"> | ||||
|        <.simple_tag_card :for={tag <- @container.tags} :if={@container.tags} tag={tag} /> | ||||
|   | ||||
| @@ -4,9 +4,9 @@ defmodule CanneryWeb.CoreComponents do | ||||
|   """ | ||||
|   use Phoenix.Component | ||||
|   import CanneryWeb.{Gettext, ViewHelpers} | ||||
|   alias Cannery.{Accounts, Ammo, Ammo.AmmoGroup} | ||||
|   alias Cannery.Accounts.{Invite, Invites, User} | ||||
|   alias Cannery.{Containers, Containers.Container, Tags.Tag} | ||||
|   alias Cannery.{Accounts, Accounts.Invite, Accounts.User} | ||||
|   alias Cannery.{Ammo, Ammo.AmmoGroup} | ||||
|   alias Cannery.{Containers, Containers.Container, Containers.Tag} | ||||
|   alias CanneryWeb.{Endpoint, HomeLive} | ||||
|   alias CanneryWeb.Router.Helpers, as: Routes | ||||
|   alias Phoenix.LiveView.{JS, Rendered} | ||||
| @@ -70,6 +70,7 @@ defmodule CanneryWeb.CoreComponents do | ||||
|   def toggle_button(assigns) | ||||
|  | ||||
|   attr :container, Container, required: true | ||||
|   attr :current_user, User, required: true | ||||
|   slot(:tag_actions) | ||||
|   slot(:inner_block) | ||||
|  | ||||
| @@ -86,73 +87,30 @@ defmodule CanneryWeb.CoreComponents do | ||||
|   def simple_tag_card(assigns) | ||||
|  | ||||
|   attr :ammo_group, AmmoGroup, required: true | ||||
|   attr :current_user, User, required: true | ||||
|   attr :original_count, :integer, default: nil | ||||
|   attr :cpr, :integer, default: nil | ||||
|   attr :last_used_date, Date, default: nil | ||||
|   attr :show_container, :boolean, default: false | ||||
|   slot(:inner_block) | ||||
|  | ||||
|   def ammo_group_card(assigns) | ||||
|  | ||||
|   @spec display_currency(float()) :: String.t() | ||||
|   defp display_currency(float), do: :erlang.float_to_binary(float, decimals: 2) | ||||
|  | ||||
|   attr :user, User, required: true | ||||
|   slot(:inner_block, required: true) | ||||
|  | ||||
|   def user_card(assigns) | ||||
|  | ||||
|   attr :invite, Invite, required: true | ||||
|   attr :use_count, :integer, default: nil | ||||
|   attr :current_user, User, required: true | ||||
|   slot(:inner_block) | ||||
|   slot(:code_actions) | ||||
|  | ||||
|   def invite_card(%{invite: invite, current_user: current_user} = assigns) do | ||||
|     assigns = assigns |> assign(:use_count, Invites.get_use_count(invite, current_user)) | ||||
|  | ||||
|     ~H""" | ||||
|     <div class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center space-y-4 | ||||
|       border border-gray-400 rounded-lg shadow-lg hover:shadow-md | ||||
|       transition-all duration-300 ease-in-out"> | ||||
|       <h1 class="title text-xl"> | ||||
|         <%= @invite.name %> | ||||
|       </h1> | ||||
|  | ||||
|       <%= if @invite.disabled_at |> is_nil() do %> | ||||
|         <h2 class="title text-md"> | ||||
|           <%= if @invite.uses_left do %> | ||||
|             <%= gettext( | ||||
|               "Uses Left: %{uses_left_count}", | ||||
|               uses_left_count: @invite.uses_left | ||||
|             ) %> | ||||
|           <% else %> | ||||
|             <%= gettext("Uses Left: Unlimited") %> | ||||
|           <% end %> | ||||
|         </h2> | ||||
|       <% else %> | ||||
|         <h2 class="title text-md"> | ||||
|           <%= gettext("Invite Disabled") %> | ||||
|         </h2> | ||||
|       <% end %> | ||||
|  | ||||
|       <.qr_code | ||||
|         content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)} | ||||
|         filename={@invite.name} | ||||
|       /> | ||||
|  | ||||
|       <h2 :if={@use_count != 0} class="title text-md"> | ||||
|         <%= gettext("Uses: %{uses_count}", uses_count: @use_count) %> | ||||
|       </h2> | ||||
|  | ||||
|       <div class="flex flex-row flex-wrap justify-center items-center"> | ||||
|         <code | ||||
|           id={"code-#{@invite.id}"} | ||||
|           class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all text-gray-100 bg-primary-800" | ||||
|           phx-no-format | ||||
|         ><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %></code> | ||||
|         <%= if @code_actions, do: render_slot(@code_actions) %> | ||||
|       </div> | ||||
|  | ||||
|       <div :if={@inner_block} class="flex space-x-4 justify-center items-center"> | ||||
|         <%= render_slot(@inner_block) %> | ||||
|       </div> | ||||
|     </div> | ||||
|     """ | ||||
|   end | ||||
|   def invite_card(assigns) | ||||
|  | ||||
|   attr :content, :string, required: true | ||||
|   attr :filename, :string, default: "qrcode", doc: "filename without .png extension" | ||||
| @@ -164,6 +122,7 @@ defmodule CanneryWeb.CoreComponents do | ||||
|   """ | ||||
|   def qr_code(assigns) | ||||
|  | ||||
|   attr :id, :string, required: true | ||||
|   attr :date, :any, required: true, doc: "A `Date` struct or nil" | ||||
|  | ||||
|   @doc """ | ||||
| @@ -172,6 +131,7 @@ defmodule CanneryWeb.CoreComponents do | ||||
|   """ | ||||
|   def date(assigns) | ||||
|  | ||||
|   attr :id, :string, required: true | ||||
|   attr :datetime, :any, required: true, doc: "A `DateTime` struct or nil" | ||||
|  | ||||
|   @doc """ | ||||
|   | ||||
| @@ -17,12 +17,9 @@ | ||||
|       <%= if @ammo_group.count == 0, do: gettext("Empty"), else: @ammo_group.count %> | ||||
|     </span> | ||||
|  | ||||
|     <span | ||||
|       :if={@ammo_group |> Ammo.get_original_count() != @ammo_group.count} | ||||
|       class="rounded-lg title text-lg" | ||||
|     > | ||||
|     <span :if={@original_count != @ammo_group.count} class="rounded-lg title text-lg"> | ||||
|       <%= gettext("Original Count:") %> | ||||
|       <%= @ammo_group |> Ammo.get_original_count() %> | ||||
|       <%= @original_count %> | ||||
|     </span> | ||||
|  | ||||
|     <span :if={@ammo_group.notes} class="rounded-lg title text-lg"> | ||||
| @@ -32,38 +29,35 @@ | ||||
|  | ||||
|     <span class="rounded-lg title text-lg"> | ||||
|       <%= gettext("Purchased on:") %> | ||||
|       <.date date={@ammo_group.purchased_on} /> | ||||
|       <.date id={"#{@ammo_group.id}-purchased-on"} date={@ammo_group.purchased_on} /> | ||||
|     </span> | ||||
|  | ||||
|     <span :if={@ammo_group |> Ammo.get_last_used_shot_group()} class="rounded-lg title text-lg"> | ||||
|     <span :if={@last_used_date} class="rounded-lg title text-lg"> | ||||
|       <%= gettext("Last used on:") %> | ||||
|       <.date date={@ammo_group |> Ammo.get_last_used_shot_group() |> Map.get(:date)} /> | ||||
|       <.date id={"#{@ammo_group.id}-last-used-on"} date={@last_used_date} /> | ||||
|     </span> | ||||
|  | ||||
|     <%= if @ammo_group.price_paid do %> | ||||
|       <span class="rounded-lg title text-lg"> | ||||
|         <%= gettext("Price paid:") %> | ||||
|         <%= gettext("$%{amount}", | ||||
|           amount: @ammo_group.price_paid |> :erlang.float_to_binary(decimals: 2) | ||||
|         ) %> | ||||
|       </span> | ||||
|     <span :if={@ammo_group.price_paid} class="rounded-lg title text-lg"> | ||||
|       <%= gettext("Price paid:") %> | ||||
|       <%= gettext("$%{amount}", amount: display_currency(@ammo_group.price_paid)) %> | ||||
|     </span> | ||||
|  | ||||
|       <span class="rounded-lg title text-lg"> | ||||
|         <%= gettext("CPR:") %> | ||||
|         <%= gettext("$%{amount}", | ||||
|           amount: @ammo_group |> Ammo.get_cpr() |> :erlang.float_to_binary(decimals: 2) | ||||
|         ) %> | ||||
|       </span> | ||||
|     <% end %> | ||||
|     <span :if={@cpr} class="rounded-lg title text-lg"> | ||||
|       <%= gettext("CPR:") %> | ||||
|       <%= gettext("$%{amount}", amount: display_currency(@cpr)) %> | ||||
|     </span> | ||||
|  | ||||
|     <span :if={@show_container and @ammo_group.container} class="rounded-lg title text-lg"> | ||||
|     <span | ||||
|       :if={@show_container && Containers.get_container!(@ammo_group.container_id, @current_user)} | ||||
|       class="rounded-lg title text-lg" | ||||
|     > | ||||
|       <%= gettext("Container:") %> | ||||
|  | ||||
|       <.link | ||||
|         navigate={Routes.container_show_path(Endpoint, :show, @ammo_group.container)} | ||||
|         navigate={Routes.container_show_path(Endpoint, :show, @ammo_group.container_id)} | ||||
|         class="link" | ||||
|       > | ||||
|         <%= @ammo_group.container.name %> | ||||
|         <%= Containers.get_container!(@ammo_group.container_id, @current_user).name %> | ||||
|       </.link> | ||||
|     </span> | ||||
|   </div> | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| <div | ||||
|   id={"container-#{@container.id}"} | ||||
|   class="overflow-hidden max-w-full mx-4 mb-4 px-8 py-4 | ||||
|     flex flex-col justify-center items-center space-y-4 | ||||
|     flex flex-col justify-around items-center space-y-4 | ||||
|     border border-gray-400 rounded-lg shadow-lg hover:shadow-md | ||||
|     transition-all duration-300 ease-in-out" | ||||
| > | ||||
|   <div class="max-w-full mb-4 flex flex-col justify-center items-center space-y-2"> | ||||
|     <.link navigate={Routes.container_show_path(Endpoint, :show, @container)} class="link"> | ||||
|       <h1 class="px-4 py-2 rounded-lg title text-xl"> | ||||
|         <%= @container.name %> | ||||
|       </h1> | ||||
|     </.link> | ||||
|   <.link navigate={Routes.container_show_path(Endpoint, :show, @container)} class="link"> | ||||
|     <h1 class="px-4 py-2 rounded-lg title text-xl"> | ||||
|       <%= @container.name %> | ||||
|     </h1> | ||||
|   </.link> | ||||
|  | ||||
|   <div class="flex flex-col justify-center items-center space-y-2"> | ||||
|     <span :if={@container.desc} class="rounded-lg title text-lg"> | ||||
|       <%= gettext("Description:") %> | ||||
|       <%= @container.desc %> | ||||
| @@ -27,20 +27,23 @@ | ||||
|       <%= @container.location %> | ||||
|     </span> | ||||
|  | ||||
|     <%= unless @container.ammo_groups |> Enum.empty?() do %> | ||||
|     <%= if @container |> Ammo.get_ammo_groups_count_for_container!(@current_user) != 0 do %> | ||||
|       <span class="rounded-lg title text-lg"> | ||||
|         <%= gettext("Packs:") %> | ||||
|         <%= @container |> Containers.get_container_ammo_group_count!() %> | ||||
|         <%= @container |> Ammo.get_ammo_groups_count_for_container!(@current_user) %> | ||||
|       </span> | ||||
|  | ||||
|       <span class="rounded-lg title text-lg"> | ||||
|         <%= gettext("Rounds:") %> | ||||
|         <%= @container |> Containers.get_container_rounds!() %> | ||||
|         <%= @container |> Ammo.get_round_count_for_container!(@current_user) %> | ||||
|       </span> | ||||
|     <% end %> | ||||
|  | ||||
|     <div class="flex flex-wrap justify-center items-center"> | ||||
|       <.simple_tag_card :for={tag <- @container.tags} :if={@container.tags} tag={tag} /> | ||||
|     <div | ||||
|       :if={@tag_actions || @container.tags != []} | ||||
|       class="flex flex-wrap justify-center items-center" | ||||
|     > | ||||
|       <.simple_tag_card :for={tag <- @container.tags} tag={tag} /> | ||||
|  | ||||
|       <%= if @tag_actions, do: render_slot(@tag_actions) %> | ||||
|     </div> | ||||
|   | ||||
| @@ -1,12 +1,7 @@ | ||||
| <time | ||||
|   :if={@date} | ||||
|   datetime={@date |> Date.to_iso8601(:extended)} | ||||
|   x-data={~s<{ | ||||
| <time :if={@date} id={@id} datetime={@date |> Date.to_iso8601(:extended)} x-data={~s<{ | ||||
|     date: | ||||
|       Intl.DateTimeFormat([], {timeZone: 'Etc/UTC', dateStyle: 'short'}) | ||||
|         .format(new Date("#{Date.to_iso8601(@date, :extended)}")) | ||||
|   }>} | ||||
|   x-text="date" | ||||
| > | ||||
|   }>} x-text="date"> | ||||
|   <%= @date |> Date.to_iso8601(:extended) %> | ||||
| </time> | ||||
|   | ||||
| @@ -1,12 +1,7 @@ | ||||
| <time | ||||
|   :if={@datetime} | ||||
|   datetime={cast_datetime(@datetime)} | ||||
|   x-data={~s/{ | ||||
| <time :if={@datetime} id={@id} datetime={cast_datetime(@datetime)} x-data={~s/{ | ||||
|     datetime: | ||||
|       Intl.DateTimeFormat([], {dateStyle: 'short', timeStyle: 'long'}) | ||||
|         .format(new Date("#{cast_datetime(@datetime)}")) | ||||
|   }/} | ||||
|   x-text="datetime" | ||||
| > | ||||
|   }/} x-text="datetime"> | ||||
|   <%= cast_datetime(@datetime) %> | ||||
| </time> | ||||
|   | ||||
| @@ -0,0 +1,46 @@ | ||||
| <div class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center space-y-4 | ||||
|   border border-gray-400 rounded-lg shadow-lg hover:shadow-md | ||||
|   transition-all duration-300 ease-in-out"> | ||||
|   <h1 class="title text-xl"> | ||||
|     <%= @invite.name %> | ||||
|   </h1> | ||||
|  | ||||
|   <%= if @invite.disabled_at |> is_nil() do %> | ||||
|     <h2 class="title text-md"> | ||||
|       <%= if @invite.uses_left do %> | ||||
|         <%= gettext( | ||||
|           "Uses Left: %{uses_left_count}", | ||||
|           uses_left_count: @invite.uses_left | ||||
|         ) %> | ||||
|       <% else %> | ||||
|         <%= gettext("Uses Left: Unlimited") %> | ||||
|       <% end %> | ||||
|     </h2> | ||||
|   <% else %> | ||||
|     <h2 class="title text-md"> | ||||
|       <%= gettext("Invite Disabled") %> | ||||
|     </h2> | ||||
|   <% end %> | ||||
|  | ||||
|   <.qr_code | ||||
|     content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)} | ||||
|     filename={@invite.name} | ||||
|   /> | ||||
|  | ||||
|   <h2 :if={@use_count && @use_count != 0} class="title text-md"> | ||||
|     <%= gettext("Uses: %{uses_count}", uses_count: @use_count) %> | ||||
|   </h2> | ||||
|  | ||||
|   <div class="flex flex-row flex-wrap justify-center items-center"> | ||||
|     <code | ||||
|       id={"code-#{@invite.id}"} | ||||
|       class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all text-gray-100 bg-primary-800" | ||||
|       phx-no-format | ||||
|     ><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %></code> | ||||
|     <%= if @code_actions, do: render_slot(@code_actions) %> | ||||
|   </div> | ||||
|  | ||||
|   <div :if={@inner_block} class="flex space-x-4 justify-center items-center"> | ||||
|     <%= render_slot(@inner_block) %> | ||||
|   </div> | ||||
| </div> | ||||
| @@ -15,7 +15,7 @@ | ||||
|           "User was confirmed at%{confirmed_datetime}", | ||||
|           confirmed_datetime: "" | ||||
|         ) %> | ||||
|         <.datetime datetime={@user.confirmed_at} /> | ||||
|         <.datetime id={"#{@user.id}-confirmed-at"} datetime={@user.confirmed_at} /> | ||||
|       <% else %> | ||||
|         <%= gettext("Email unconfirmed") %> | ||||
|       <% end %> | ||||
| @@ -26,7 +26,7 @@ | ||||
|         "User registered on%{registered_datetime}", | ||||
|         registered_datetime: "" | ||||
|       ) %> | ||||
|       <.datetime datetime={@user.inserted_at} /> | ||||
|       <.datetime id={"#{@user.id}-inserted-at"} datetime={@user.inserted_at} /> | ||||
|     </p> | ||||
|   </h3> | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,7 @@ defmodule CanneryWeb.Components.MoveAmmoGroupComponent do | ||||
|   use CanneryWeb, :live_component | ||||
|   alias Cannery.{Accounts.User, Ammo, Ammo.AmmoGroup, Containers, Containers.Container} | ||||
|   alias CanneryWeb.Endpoint | ||||
|   alias Ecto.Changeset | ||||
|   alias Phoenix.LiveView.Socket | ||||
|  | ||||
|   @impl true | ||||
| @@ -51,10 +52,9 @@ defmodule CanneryWeb.Components.MoveAmmoGroupComponent do | ||||
|       |> case do | ||||
|         {:ok, _ammo_group} -> | ||||
|           prompt = dgettext("prompts", "Ammo moved to %{name} successfully", name: container_name) | ||||
|  | ||||
|           socket |> put_flash(:info, prompt) |> push_navigate(to: return_to) | ||||
|  | ||||
|         {:error, %Ecto.Changeset{} = changeset} -> | ||||
|         {:error, %Changeset{} = changeset} -> | ||||
|           socket |> assign(changeset: changeset) | ||||
|       end | ||||
|  | ||||
| @@ -64,10 +64,10 @@ defmodule CanneryWeb.Components.MoveAmmoGroupComponent do | ||||
|   @impl true | ||||
|   def render(%{containers: containers} = assigns) do | ||||
|     columns = [ | ||||
|       %{label: gettext("Container"), key: "name"}, | ||||
|       %{label: gettext("Type"), key: "type"}, | ||||
|       %{label: gettext("Location"), key: "location"}, | ||||
|       %{label: nil, key: "actions", sortable: false} | ||||
|       %{label: gettext("Container"), key: :name}, | ||||
|       %{label: gettext("Type"), key: :type}, | ||||
|       %{label: gettext("Location"), key: :location}, | ||||
|       %{label: nil, key: :actions, sortable: false} | ||||
|     ] | ||||
|  | ||||
|     rows = containers |> get_rows_for_containers(assigns, columns) | ||||
| @@ -110,8 +110,8 @@ defmodule CanneryWeb.Components.MoveAmmoGroupComponent do | ||||
|     end) | ||||
|   end | ||||
|  | ||||
|   @spec get_row_value_by_key(String.t(), Container.t(), map()) :: any() | ||||
|   defp get_row_value_by_key("actions", container, assigns) do | ||||
|   @spec get_row_value_by_key(atom(), Container.t(), map()) :: any() | ||||
|   defp get_row_value_by_key(:actions, container, assigns) do | ||||
|     assigns = assigns |> Map.put(:container, container) | ||||
|  | ||||
|     ~H""" | ||||
| @@ -129,6 +129,5 @@ defmodule CanneryWeb.Components.MoveAmmoGroupComponent do | ||||
|     """ | ||||
|   end | ||||
|  | ||||
|   defp get_row_value_by_key(key, container, _assigns), | ||||
|     do: container |> Map.get(key |> String.to_existing_atom()) | ||||
|   defp get_row_value_by_key(key, container, _assigns), do: container |> Map.get(key) | ||||
| end | ||||
|   | ||||
| @@ -3,7 +3,7 @@ defmodule CanneryWeb.Components.ShotGroupTableComponent do | ||||
|   A component that displays a list of shot groups | ||||
|   """ | ||||
|   use CanneryWeb, :live_component | ||||
|   alias Cannery.{Accounts.User, ActivityLog.ShotGroup, Repo} | ||||
|   alias Cannery.{Accounts.User, ActivityLog.ShotGroup, Ammo} | ||||
|   alias Ecto.UUID | ||||
|   alias Phoenix.LiveView.{Rendered, Socket} | ||||
|  | ||||
| @@ -41,11 +41,16 @@ defmodule CanneryWeb.Components.ShotGroupTableComponent do | ||||
|       %{label: gettext("Ammo"), key: :name}, | ||||
|       %{label: gettext("Rounds shot"), key: :count}, | ||||
|       %{label: gettext("Notes"), key: :notes}, | ||||
|       %{label: gettext("Date"), key: :date}, | ||||
|       %{label: gettext("Date"), key: :date, type: Date}, | ||||
|       %{label: nil, key: :actions, sortable: false} | ||||
|     ] | ||||
|  | ||||
|     extra_data = %{current_user: current_user, actions: actions} | ||||
|     ammo_groups = | ||||
|       shot_groups | ||||
|       |> Enum.map(fn %{ammo_group_id: ammo_group_id} -> ammo_group_id end) | ||||
|       |> Ammo.get_ammo_groups(current_user) | ||||
|  | ||||
|     extra_data = %{current_user: current_user, actions: actions, ammo_groups: ammo_groups} | ||||
|  | ||||
|     rows = | ||||
|       shot_groups | ||||
| @@ -79,34 +84,28 @@ defmodule CanneryWeb.Components.ShotGroupTableComponent do | ||||
|   @spec get_row_data_for_shot_group(ShotGroup.t(), columns :: [map()], extra_data :: map()) :: | ||||
|           map() | ||||
|   defp get_row_data_for_shot_group(shot_group, columns, extra_data) do | ||||
|     shot_group = shot_group |> Repo.preload(ammo_group: :ammo_type) | ||||
|  | ||||
|     columns | ||||
|     |> Map.new(fn %{key: key} -> | ||||
|       {key, get_row_value(key, shot_group, extra_data)} | ||||
|     end) | ||||
|   end | ||||
|  | ||||
|   defp get_row_value( | ||||
|          :name, | ||||
|          %{ammo_group: %{ammo_type: %{name: ammo_type_name} = ammo_group}}, | ||||
|          _extra_data | ||||
|        ) do | ||||
|     assigns = %{ammo_group: ammo_group, ammo_type_name: ammo_type_name} | ||||
|   defp get_row_value(:name, %{ammo_group_id: ammo_group_id}, %{ammo_groups: ammo_groups}) do | ||||
|     assigns = %{ammo_group: ammo_group = Map.fetch!(ammo_groups, ammo_group_id)} | ||||
|  | ||||
|     name_block = ~H""" | ||||
|     <.link navigate={Routes.ammo_group_show_path(Endpoint, :show, @ammo_group)} class="link"> | ||||
|       <%= @ammo_type_name %> | ||||
|     </.link> | ||||
|     """ | ||||
|  | ||||
|     {ammo_type_name, name_block} | ||||
|     {ammo_group.ammo_type.name, | ||||
|      ~H""" | ||||
|      <.link navigate={Routes.ammo_group_show_path(Endpoint, :show, @ammo_group)} class="link"> | ||||
|        <%= @ammo_group.ammo_type.name %> | ||||
|      </.link> | ||||
|      """} | ||||
|   end | ||||
|  | ||||
|   defp get_row_value(:date, %{date: _date} = assigns, _extra_data) do | ||||
|     ~H""" | ||||
|     <.date date={@date} /> | ||||
|     """ | ||||
|   defp get_row_value(:date, %{date: date} = assigns, _extra_data) do | ||||
|     {date, | ||||
|      ~H""" | ||||
|      <.date id={"#{@id}-date"} date={@date} /> | ||||
|      """} | ||||
|   end | ||||
|  | ||||
|   defp get_row_value(:actions, shot_group, %{actions: actions}) do | ||||
|   | ||||
| @@ -33,7 +33,8 @@ defmodule CanneryWeb.Components.TableComponent do | ||||
|                 optional(:class) => String.t(), | ||||
|                 optional(:row_class) => String.t(), | ||||
|                 optional(:alternate_row_class) => String.t(), | ||||
|                 optional(:sortable) => false | ||||
|                 optional(:sortable) => false, | ||||
|                 optional(:type) => module() | ||||
|               }), | ||||
|             required(:rows) => | ||||
|               list(%{ | ||||
| @@ -60,7 +61,8 @@ defmodule CanneryWeb.Components.TableComponent do | ||||
|         :asc | ||||
|       end | ||||
|  | ||||
|     rows = rows |> sort_by_custom_sort_value_or_value(initial_key, initial_sort_mode) | ||||
|     type = columns |> Enum.find(%{}, fn %{key: key} -> key == initial_key end) |> Map.get(:type) | ||||
|     rows = rows |> sort_by_custom_sort_value_or_value(initial_key, initial_sort_mode, type) | ||||
|  | ||||
|     socket = | ||||
|       socket | ||||
| @@ -68,6 +70,7 @@ defmodule CanneryWeb.Components.TableComponent do | ||||
|       |> assign( | ||||
|         columns: columns, | ||||
|         rows: rows, | ||||
|         key: initial_key, | ||||
|         last_sort_key: initial_key, | ||||
|         sort_mode: initial_sort_mode | ||||
|       ) | ||||
| @@ -81,7 +84,14 @@ defmodule CanneryWeb.Components.TableComponent do | ||||
|   def handle_event( | ||||
|         "sort_by", | ||||
|         %{"sort-key" => key}, | ||||
|         %{assigns: %{rows: rows, last_sort_key: last_sort_key, sort_mode: sort_mode}} = socket | ||||
|         %{ | ||||
|           assigns: %{ | ||||
|             columns: columns, | ||||
|             rows: rows, | ||||
|             last_sort_key: last_sort_key, | ||||
|             sort_mode: sort_mode | ||||
|           } | ||||
|         } = socket | ||||
|       ) do | ||||
|     key = key |> String.to_existing_atom() | ||||
|  | ||||
| @@ -92,11 +102,28 @@ defmodule CanneryWeb.Components.TableComponent do | ||||
|         {_new_sort_key, _last_sort_mode} -> :asc | ||||
|       end | ||||
|  | ||||
|     rows = rows |> sort_by_custom_sort_value_or_value(key, sort_mode) | ||||
|     type = | ||||
|       columns |> Enum.find(%{}, fn %{key: column_key} -> column_key == key end) |> Map.get(:type) | ||||
|  | ||||
|     rows = rows |> sort_by_custom_sort_value_or_value(key, sort_mode, type) | ||||
|     {:noreply, socket |> assign(last_sort_key: key, sort_mode: sort_mode, rows: rows)} | ||||
|   end | ||||
|  | ||||
|   defp sort_by_custom_sort_value_or_value(rows, key, sort_mode) do | ||||
|   defp sort_by_custom_sort_value_or_value(rows, key, sort_mode, type) | ||||
|        when type in [Date, DateTime] do | ||||
|     rows | ||||
|     |> Enum.sort_by( | ||||
|       fn row -> | ||||
|         case row |> Map.get(key) do | ||||
|           {custom_sort_key, _value} -> custom_sort_key | ||||
|           value -> value | ||||
|         end | ||||
|       end, | ||||
|       {sort_mode, type} | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   defp sort_by_custom_sort_value_or_value(rows, key, sort_mode, _type) do | ||||
|     rows | ||||
|     |> Enum.sort_by( | ||||
|       fn row -> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user