forked from shibao/cannery
		
	add selectable ammo types
This commit is contained in:
		@@ -5,6 +5,7 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do
 | 
			
		||||
  use CanneryWeb, :live_component
 | 
			
		||||
  alias Cannery.{Accounts.User, Ammo.AmmoGroup, ComparableDate}
 | 
			
		||||
  alias Cannery.{ActivityLog, Ammo, Containers}
 | 
			
		||||
  alias CanneryWeb.Components.TableComponent
 | 
			
		||||
  alias Ecto.UUID
 | 
			
		||||
  alias Phoenix.LiveView.{Rendered, Socket}
 | 
			
		||||
 | 
			
		||||
@@ -54,59 +55,47 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do
 | 
			
		||||
         } = socket
 | 
			
		||||
       ) do
 | 
			
		||||
    columns =
 | 
			
		||||
      if actions == [] do
 | 
			
		||||
        []
 | 
			
		||||
      else
 | 
			
		||||
        [%{label: gettext("Actions"), key: :actions, sortable: false}]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    columns = [
 | 
			
		||||
      %{label: gettext("Purchased on"), key: :purchased_on, type: ComparableDate},
 | 
			
		||||
      %{label: gettext("Last used on"), key: :used_up_on, type: ComparableDate} | columns
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    columns =
 | 
			
		||||
      if container == [] do
 | 
			
		||||
        columns
 | 
			
		||||
      else
 | 
			
		||||
        [%{label: gettext("Container"), key: :container} | columns]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    columns =
 | 
			
		||||
      if range == [] do
 | 
			
		||||
        columns
 | 
			
		||||
      else
 | 
			
		||||
        [%{label: gettext("Range"), key: :range} | columns]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    columns = [
 | 
			
		||||
      %{label: gettext("Price paid"), key: :price_paid},
 | 
			
		||||
      %{label: gettext("CPR"), key: :cpr}
 | 
			
		||||
      | columns
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    columns =
 | 
			
		||||
      if show_used do
 | 
			
		||||
        [
 | 
			
		||||
          %{label: gettext("Original Count"), key: :original_count},
 | 
			
		||||
          %{label: gettext("% left"), key: :remaining}
 | 
			
		||||
          | columns
 | 
			
		||||
        ]
 | 
			
		||||
      else
 | 
			
		||||
        columns
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    columns = [
 | 
			
		||||
      %{label: if(show_used, do: gettext("Current Count"), else: gettext("Count")), key: :count}
 | 
			
		||||
      | columns
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    columns =
 | 
			
		||||
      if ammo_type == [] do
 | 
			
		||||
        columns
 | 
			
		||||
      else
 | 
			
		||||
        [%{label: gettext("Ammo type"), key: :ammo_type} | columns]
 | 
			
		||||
      end
 | 
			
		||||
      []
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{label: gettext("Actions"), key: :actions, sortable: false},
 | 
			
		||||
        actions != []
 | 
			
		||||
      )
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(%{
 | 
			
		||||
        label: gettext("Last used on"),
 | 
			
		||||
        key: :used_up_on,
 | 
			
		||||
        type: ComparableDate
 | 
			
		||||
      })
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(%{
 | 
			
		||||
        label: gettext("Purchased on"),
 | 
			
		||||
        key: :purchased_on,
 | 
			
		||||
        type: ComparableDate
 | 
			
		||||
      })
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{label: gettext("Container"), key: :container},
 | 
			
		||||
        container != []
 | 
			
		||||
      )
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{label: gettext("Range"), key: :range},
 | 
			
		||||
        range != []
 | 
			
		||||
      )
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(%{label: gettext("CPR"), key: :cpr})
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(%{label: gettext("Price paid"), key: :price_paid})
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{label: gettext("% left"), key: :remaining},
 | 
			
		||||
        show_used
 | 
			
		||||
      )
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{label: gettext("Original Count"), key: :original_count},
 | 
			
		||||
        show_used
 | 
			
		||||
      )
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(%{
 | 
			
		||||
        label: if(show_used, do: gettext("Current Count"), else: gettext("Count")),
 | 
			
		||||
        key: :count
 | 
			
		||||
      })
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{label: gettext("Ammo type"), key: :ammo_type},
 | 
			
		||||
        ammo_type != []
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
    containers =
 | 
			
		||||
      ammo_groups
 | 
			
		||||
@@ -140,12 +129,7 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do
 | 
			
		||||
  def render(assigns) do
 | 
			
		||||
    ~H"""
 | 
			
		||||
    <div id={@id} class="w-full">
 | 
			
		||||
      <.live_component
 | 
			
		||||
        module={CanneryWeb.Components.TableComponent}
 | 
			
		||||
        id={"table-#{@id}"}
 | 
			
		||||
        columns={@columns}
 | 
			
		||||
        rows={@rows}
 | 
			
		||||
      />
 | 
			
		||||
      <.live_component module={TableComponent} id={"table-#{@id}"} columns={@columns} rows={@rows} />
 | 
			
		||||
    </div>
 | 
			
		||||
    """
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
 | 
			
		||||
  """
 | 
			
		||||
  use CanneryWeb, :live_component
 | 
			
		||||
  alias Cannery.{Accounts.User, ActivityLog, Ammo, Ammo.AmmoType}
 | 
			
		||||
  alias CanneryWeb.Components.TableComponent
 | 
			
		||||
  alias Ecto.UUID
 | 
			
		||||
  alias Phoenix.LiveView.{Rendered, Socket}
 | 
			
		||||
 | 
			
		||||
@@ -12,6 +13,7 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
 | 
			
		||||
          %{
 | 
			
		||||
            required(:id) => UUID.t(),
 | 
			
		||||
            required(:current_user) => User.t(),
 | 
			
		||||
            optional(:type) => AmmoType.type() | nil,
 | 
			
		||||
            optional(:show_used) => boolean(),
 | 
			
		||||
            optional(:ammo_types) => [AmmoType.t()],
 | 
			
		||||
            optional(:actions) => Rendered.t(),
 | 
			
		||||
@@ -24,6 +26,7 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
 | 
			
		||||
      socket
 | 
			
		||||
      |> assign(assigns)
 | 
			
		||||
      |> assign_new(:show_used, fn -> false end)
 | 
			
		||||
      |> assign_new(:type, fn -> :all end)
 | 
			
		||||
      |> assign_new(:actions, fn -> [] end)
 | 
			
		||||
      |> display_ammo_types()
 | 
			
		||||
 | 
			
		||||
@@ -36,90 +39,118 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
 | 
			
		||||
             ammo_types: ammo_types,
 | 
			
		||||
             current_user: current_user,
 | 
			
		||||
             show_used: show_used,
 | 
			
		||||
             type: type,
 | 
			
		||||
             actions: actions
 | 
			
		||||
           }
 | 
			
		||||
         } = socket
 | 
			
		||||
       ) do
 | 
			
		||||
    columns =
 | 
			
		||||
    filtered_columns =
 | 
			
		||||
      [
 | 
			
		||||
        %{label: gettext("Name"), key: :name, type: :name},
 | 
			
		||||
        %{label: gettext("Bullet type"), key: :bullet_type, type: :string},
 | 
			
		||||
        %{label: gettext("Bullet core"), key: :bullet_core, type: :string},
 | 
			
		||||
        %{label: gettext("Cartridge"), key: :cartridge, type: :string},
 | 
			
		||||
        %{label: gettext("Caliber"), key: :caliber, type: :string},
 | 
			
		||||
        %{label: gettext("Case material"), key: :case_material, type: :string},
 | 
			
		||||
        %{
 | 
			
		||||
          label: if(type == :shotgun, do: gettext("Gauge"), else: gettext("Caliber")),
 | 
			
		||||
          key: :caliber,
 | 
			
		||||
          type: :string
 | 
			
		||||
        },
 | 
			
		||||
        %{label: gettext("Unfired shell length"), key: :unfired_length, type: :string},
 | 
			
		||||
        %{label: gettext("Brass height"), key: :brass_height, type: :string},
 | 
			
		||||
        %{label: gettext("Chamber size"), key: :chamber_size, type: :string},
 | 
			
		||||
        %{label: gettext("Chamber size"), key: :chamber_size, type: :string},
 | 
			
		||||
        %{label: gettext("Grains"), key: :grains, type: :string},
 | 
			
		||||
        %{label: gettext("Bullet type"), key: :bullet_type, type: :string},
 | 
			
		||||
        %{
 | 
			
		||||
          label: if(type == :shotgun, do: gettext("Slug core"), else: gettext("Bullet core")),
 | 
			
		||||
          key: :bullet_core,
 | 
			
		||||
          type: :string
 | 
			
		||||
        },
 | 
			
		||||
        %{label: gettext("Jacket type"), key: :jacket_type, type: :string},
 | 
			
		||||
        %{label: gettext("Muzzle velocity"), key: :muzzle_velocity, type: :string},
 | 
			
		||||
        %{label: gettext("Case material"), key: :case_material, type: :string},
 | 
			
		||||
        %{label: gettext("Wadding"), key: :wadding, type: :string},
 | 
			
		||||
        %{label: gettext("Shot type"), key: :shot_type, type: :string},
 | 
			
		||||
        %{label: gettext("Shot material"), key: :shot_material, type: :string},
 | 
			
		||||
        %{label: gettext("Shot size"), key: :shot_size, type: :string},
 | 
			
		||||
        %{label: gettext("Load grains"), key: :load_grains, type: :string},
 | 
			
		||||
        %{label: gettext("Shot charge weight"), key: :shot_charge_weight, type: :string},
 | 
			
		||||
        %{label: gettext("Powder type"), key: :powder_type, type: :string},
 | 
			
		||||
        %{
 | 
			
		||||
          label: gettext("Powder grains per charge"),
 | 
			
		||||
          key: :powder_grains_per_charge,
 | 
			
		||||
          type: :string
 | 
			
		||||
        },
 | 
			
		||||
        %{label: gettext("Grains"), key: :grains, type: :string},
 | 
			
		||||
        %{label: gettext("Pressure"), key: :pressure, type: :string},
 | 
			
		||||
        %{label: gettext("Dram equivalent"), key: :dram_equivalent, type: :string},
 | 
			
		||||
        %{label: gettext("Muzzle velocity"), key: :muzzle_velocity, type: :string},
 | 
			
		||||
        %{label: gettext("Primer type"), key: :primer_type, type: :string},
 | 
			
		||||
        %{label: gettext("Firing type"), key: :firing_type, type: :string},
 | 
			
		||||
        %{label: gettext("Tracer"), key: :tracer, type: :boolean},
 | 
			
		||||
        %{label: gettext("Incendiary"), key: :incendiary, type: :boolean},
 | 
			
		||||
        %{label: gettext("Blank"), key: :blank, type: :boolean},
 | 
			
		||||
        %{label: gettext("Corrosive"), key: :corrosive, type: :boolean},
 | 
			
		||||
        %{label: gettext("Manufacturer"), key: :manufacturer, type: :string},
 | 
			
		||||
        %{label: gettext("UPC"), key: "upc", type: :string}
 | 
			
		||||
        %{label: gettext("Tracer"), key: :tracer, type: :atom},
 | 
			
		||||
        %{label: gettext("Incendiary"), key: :incendiary, type: :atom},
 | 
			
		||||
        %{label: gettext("Blank"), key: :blank, type: :atom},
 | 
			
		||||
        %{label: gettext("Corrosive"), key: :corrosive, type: :atom},
 | 
			
		||||
        %{label: gettext("Manufacturer"), key: :manufacturer, type: :string}
 | 
			
		||||
      ]
 | 
			
		||||
      |> Enum.filter(fn %{key: key, type: type} ->
 | 
			
		||||
        # remove columns if all values match defaults
 | 
			
		||||
        default_value = if type == :boolean, do: false, else: nil
 | 
			
		||||
        default_value = if type == :atom, do: false, else: nil
 | 
			
		||||
 | 
			
		||||
        ammo_types
 | 
			
		||||
        |> Enum.any?(fn ammo_type ->
 | 
			
		||||
          not (ammo_type |> Map.get(key) == default_value)
 | 
			
		||||
        end)
 | 
			
		||||
        |> Enum.any?(fn ammo_type -> Map.get(ammo_type, key, default_value) != default_value end)
 | 
			
		||||
      end)
 | 
			
		||||
      |> Kernel.++([
 | 
			
		||||
        %{label: gettext("Rounds"), key: :round_count, type: :round_count}
 | 
			
		||||
      ])
 | 
			
		||||
      |> Kernel.++(
 | 
			
		||||
        if show_used do
 | 
			
		||||
          [
 | 
			
		||||
            %{
 | 
			
		||||
              label: gettext("Used rounds"),
 | 
			
		||||
              key: :used_round_count,
 | 
			
		||||
              type: :used_round_count
 | 
			
		||||
            },
 | 
			
		||||
            %{
 | 
			
		||||
              label: gettext("Total ever rounds"),
 | 
			
		||||
              key: :historical_round_count,
 | 
			
		||||
              type: :historical_round_count
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        else
 | 
			
		||||
          []
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
    columns =
 | 
			
		||||
      [%{label: gettext("Actions"), key: "actions", type: :actions, sortable: false}]
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(%{
 | 
			
		||||
        label: gettext("Average CPR"),
 | 
			
		||||
        key: :avg_price_paid,
 | 
			
		||||
        type: :avg_price_paid
 | 
			
		||||
      })
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{
 | 
			
		||||
          label: gettext("Total ever packs"),
 | 
			
		||||
          key: :historical_pack_count,
 | 
			
		||||
          type: :historical_pack_count
 | 
			
		||||
        },
 | 
			
		||||
        show_used
 | 
			
		||||
      )
 | 
			
		||||
      |> Kernel.++([%{label: gettext("Packs"), key: :ammo_count, type: :ammo_count}])
 | 
			
		||||
      |> Kernel.++(
 | 
			
		||||
        if show_used do
 | 
			
		||||
          [
 | 
			
		||||
            %{
 | 
			
		||||
              label: gettext("Used packs"),
 | 
			
		||||
              key: :used_pack_count,
 | 
			
		||||
              type: :used_pack_count
 | 
			
		||||
            },
 | 
			
		||||
            %{
 | 
			
		||||
              label: gettext("Total ever packs"),
 | 
			
		||||
              key: :historical_pack_count,
 | 
			
		||||
              type: :historical_pack_count
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
        else
 | 
			
		||||
          []
 | 
			
		||||
        end
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{
 | 
			
		||||
          label: gettext("Used packs"),
 | 
			
		||||
          key: :used_pack_count,
 | 
			
		||||
          type: :used_pack_count
 | 
			
		||||
        },
 | 
			
		||||
        show_used
 | 
			
		||||
      )
 | 
			
		||||
      |> Kernel.++([
 | 
			
		||||
        %{label: gettext("Average CPR"), key: :avg_price_paid, type: :avg_price_paid},
 | 
			
		||||
        %{label: gettext("Actions"), key: "actions", type: :actions, sortable: false}
 | 
			
		||||
      ])
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(%{
 | 
			
		||||
        label: gettext("Packs"),
 | 
			
		||||
        key: :ammo_count,
 | 
			
		||||
        type: :ammo_count
 | 
			
		||||
      })
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{
 | 
			
		||||
          label: gettext("Total ever rounds"),
 | 
			
		||||
          key: :historical_round_count,
 | 
			
		||||
          type: :historical_round_count
 | 
			
		||||
        },
 | 
			
		||||
        show_used
 | 
			
		||||
      )
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{
 | 
			
		||||
          label: gettext("Used rounds"),
 | 
			
		||||
          key: :used_round_count,
 | 
			
		||||
          type: :used_round_count
 | 
			
		||||
        },
 | 
			
		||||
        show_used
 | 
			
		||||
      )
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(%{
 | 
			
		||||
        label: gettext("Rounds"),
 | 
			
		||||
        key: :round_count,
 | 
			
		||||
        type: :round_count
 | 
			
		||||
      })
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(filtered_columns)
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(
 | 
			
		||||
        %{label: gettext("Type"), key: :type, type: :atom},
 | 
			
		||||
        type in [:all, nil]
 | 
			
		||||
      )
 | 
			
		||||
      |> TableComponent.maybe_compose_columns(%{label: gettext("Name"), key: :name, type: :name})
 | 
			
		||||
 | 
			
		||||
    round_counts = ammo_types |> Ammo.get_round_count_for_ammo_types(current_user)
 | 
			
		||||
    packs_count = ammo_types |> Ammo.get_ammo_groups_count_for_types(current_user)
 | 
			
		||||
@@ -162,12 +193,7 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
 | 
			
		||||
  def render(assigns) do
 | 
			
		||||
    ~H"""
 | 
			
		||||
    <div id={@id} class="w-full">
 | 
			
		||||
      <.live_component
 | 
			
		||||
        module={CanneryWeb.Components.TableComponent}
 | 
			
		||||
        id={"table-#{@id}"}
 | 
			
		||||
        columns={@columns}
 | 
			
		||||
        rows={@rows}
 | 
			
		||||
      />
 | 
			
		||||
      <.live_component module={TableComponent} id={"table-#{@id}"} columns={@columns} rows={@rows} />
 | 
			
		||||
    </div>
 | 
			
		||||
    """
 | 
			
		||||
  end
 | 
			
		||||
@@ -179,7 +205,7 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
 | 
			
		||||
    end)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp get_ammo_type_value(:boolean, key, ammo_type, _other_data),
 | 
			
		||||
  defp get_ammo_type_value(:atom, key, ammo_type, _other_data),
 | 
			
		||||
    do: ammo_type |> Map.get(key) |> humanize()
 | 
			
		||||
 | 
			
		||||
  defp get_ammo_type_value(:round_count, _key, %{id: ammo_type_id}, %{round_counts: round_counts}),
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ defmodule CanneryWeb.ExportController do
 | 
			
		||||
  alias Cannery.{ActivityLog, Ammo, Containers}
 | 
			
		||||
 | 
			
		||||
  def export(%{assigns: %{current_user: current_user}} = conn, %{"mode" => "json"}) do
 | 
			
		||||
    ammo_types = Ammo.list_ammo_types(current_user)
 | 
			
		||||
    ammo_types = Ammo.list_ammo_types(current_user, :all)
 | 
			
		||||
    used_counts = ammo_types |> ActivityLog.get_used_count_for_ammo_types(current_user)
 | 
			
		||||
    round_counts = ammo_types |> Ammo.get_round_count_for_ammo_types(current_user)
 | 
			
		||||
    ammo_group_counts = ammo_types |> Ammo.get_ammo_groups_count_for_types(current_user)
 | 
			
		||||
@@ -28,7 +28,7 @@ defmodule CanneryWeb.ExportController do
 | 
			
		||||
        })
 | 
			
		||||
      end)
 | 
			
		||||
 | 
			
		||||
    ammo_groups = Ammo.list_ammo_groups(nil, true, current_user)
 | 
			
		||||
    ammo_groups = Ammo.list_ammo_groups(nil, :all, current_user, true)
 | 
			
		||||
    used_counts = ammo_groups |> ActivityLog.get_used_counts(current_user)
 | 
			
		||||
    original_counts = ammo_groups |> Ammo.get_original_counts(current_user)
 | 
			
		||||
    cprs = ammo_groups |> Ammo.get_cprs(current_user)
 | 
			
		||||
@@ -48,7 +48,7 @@ defmodule CanneryWeb.ExportController do
 | 
			
		||||
        })
 | 
			
		||||
      end)
 | 
			
		||||
 | 
			
		||||
    shot_groups = ActivityLog.list_shot_groups(current_user)
 | 
			
		||||
    shot_groups = ActivityLog.list_shot_groups(:all, current_user)
 | 
			
		||||
 | 
			
		||||
    containers =
 | 
			
		||||
      Containers.list_containers(current_user)
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ defmodule CanneryWeb.AmmoGroupLive.FormComponent do
 | 
			
		||||
      socket =
 | 
			
		||||
      socket
 | 
			
		||||
      |> assign(:ammo_group_create_limit, @ammo_group_create_limit)
 | 
			
		||||
      |> assign(:ammo_types, Ammo.list_ammo_types(current_user))
 | 
			
		||||
      |> assign(:ammo_types, Ammo.list_ammo_types(current_user, :all))
 | 
			
		||||
      |> assign_new(:containers, fn -> Containers.list_containers(current_user) end)
 | 
			
		||||
 | 
			
		||||
    params =
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,11 @@ defmodule CanneryWeb.AmmoGroupLive.Index do
 | 
			
		||||
 | 
			
		||||
  @impl true
 | 
			
		||||
  def mount(%{"search" => search}, _session, socket) do
 | 
			
		||||
    {:ok, socket |> assign(show_used: false, search: search) |> display_ammo_groups()}
 | 
			
		||||
    {:ok, socket |> assign(type: :all, show_used: false, search: search) |> display_ammo_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def mount(_params, _session, socket) do
 | 
			
		||||
    {:ok, socket |> assign(show_used: false, search: nil) |> display_ammo_groups()}
 | 
			
		||||
    {:ok, socket |> assign(type: :all, show_used: false, search: nil) |> display_ammo_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @impl true
 | 
			
		||||
@@ -119,10 +119,36 @@ defmodule CanneryWeb.AmmoGroupLive.Index do
 | 
			
		||||
    {:noreply, socket}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "rifle"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :rifle) |> display_ammo_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "shotgun"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :shotgun) |> display_ammo_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "pistol"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :pistol) |> display_ammo_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => _all}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :all) |> display_ammo_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp display_ammo_groups(
 | 
			
		||||
         %{assigns: %{search: search, current_user: current_user, show_used: show_used}} = socket
 | 
			
		||||
         %{
 | 
			
		||||
           assigns: %{
 | 
			
		||||
             type: type,
 | 
			
		||||
             search: search,
 | 
			
		||||
             current_user: current_user,
 | 
			
		||||
             show_used: show_used
 | 
			
		||||
           }
 | 
			
		||||
         } = socket
 | 
			
		||||
       ) do
 | 
			
		||||
    ammo_groups = Ammo.list_ammo_groups(search, show_used, current_user)
 | 
			
		||||
    # get total number of ammo groups to determine whether to display onboarding
 | 
			
		||||
    # prompts
 | 
			
		||||
    ammo_groups_count = Ammo.get_ammo_groups_count!(current_user, true)
 | 
			
		||||
    ammo_groups = Ammo.list_ammo_groups(search, type, current_user, show_used)
 | 
			
		||||
    ammo_types_count = Ammo.get_ammo_types_count!(current_user)
 | 
			
		||||
    containers_count = Containers.get_containers_count!(current_user)
 | 
			
		||||
 | 
			
		||||
@@ -130,7 +156,8 @@ defmodule CanneryWeb.AmmoGroupLive.Index do
 | 
			
		||||
    |> assign(
 | 
			
		||||
      ammo_groups: ammo_groups,
 | 
			
		||||
      ammo_types_count: ammo_types_count,
 | 
			
		||||
      containers_count: containers_count
 | 
			
		||||
      containers_count: containers_count,
 | 
			
		||||
      ammo_groups_count: ammo_groups_count
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,6 @@
 | 
			
		||||
    <%= gettext("Ammo") %>
 | 
			
		||||
  </h1>
 | 
			
		||||
 | 
			
		||||
  <h2
 | 
			
		||||
    :if={@ammo_groups |> Enum.empty?() and @search |> is_nil()}
 | 
			
		||||
    class="title text-xl text-primary-600"
 | 
			
		||||
  >
 | 
			
		||||
    <%= gettext("No Ammo") %>
 | 
			
		||||
    <%= display_emoji("😔") %>
 | 
			
		||||
  </h2>
 | 
			
		||||
 | 
			
		||||
  <%= cond do %>
 | 
			
		||||
    <% @containers_count == 0 -> %>
 | 
			
		||||
      <div class="flex justify-center items-center">
 | 
			
		||||
@@ -32,7 +24,12 @@
 | 
			
		||||
          <%= dgettext("actions", "add an ammo type first") %>
 | 
			
		||||
        </.link>
 | 
			
		||||
      </div>
 | 
			
		||||
    <% @ammo_groups |> Enum.empty?() and @search |> is_nil() -> %>
 | 
			
		||||
    <% @ammo_groups_count == 0 -> %>
 | 
			
		||||
      <h2 class="title text-xl text-primary-600">
 | 
			
		||||
        <%= gettext("No ammo") %>
 | 
			
		||||
        <%= display_emoji("😔") %>
 | 
			
		||||
      </h2>
 | 
			
		||||
 | 
			
		||||
      <.link patch={Routes.ammo_group_index_path(Endpoint, :new)} class="btn btn-primary">
 | 
			
		||||
        <%= dgettext("actions", "Add your first box!") %>
 | 
			
		||||
      </.link>
 | 
			
		||||
@@ -40,144 +37,168 @@
 | 
			
		||||
      <.link patch={Routes.ammo_group_index_path(Endpoint, :new)} class="btn btn-primary">
 | 
			
		||||
        <%= dgettext("actions", "Add Ammo") %>
 | 
			
		||||
      </.link>
 | 
			
		||||
  <% end %>
 | 
			
		||||
 | 
			
		||||
  <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
 | 
			
		||||
    <.form
 | 
			
		||||
      :let={f}
 | 
			
		||||
      for={%{}}
 | 
			
		||||
      as={:search}
 | 
			
		||||
      phx-change="search"
 | 
			
		||||
      phx-submit="search"
 | 
			
		||||
      class="grow self-stretch flex flex-col items-stretch"
 | 
			
		||||
    >
 | 
			
		||||
      <%= text_input(f, :search_term,
 | 
			
		||||
        class: "input input-primary",
 | 
			
		||||
        value: @search,
 | 
			
		||||
        role: "search",
 | 
			
		||||
        phx_debounce: 300,
 | 
			
		||||
        placeholder: gettext("Search ammo")
 | 
			
		||||
      ) %>
 | 
			
		||||
    </.form>
 | 
			
		||||
      <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-2xl">
 | 
			
		||||
        <.form
 | 
			
		||||
          :let={f}
 | 
			
		||||
          for={%{}}
 | 
			
		||||
          as={:ammo_type}
 | 
			
		||||
          phx-change="change_type"
 | 
			
		||||
          phx-submit="change_type"
 | 
			
		||||
          class="flex items-center"
 | 
			
		||||
        >
 | 
			
		||||
          <%= label(f, :type, gettext("Type"), class: "title text-primary-600 text-lg text-center") %>
 | 
			
		||||
 | 
			
		||||
    <.toggle_button action="toggle_show_used" value={@show_used}>
 | 
			
		||||
      <span class="title text-lg text-primary-600">
 | 
			
		||||
        <%= gettext("Show used") %>
 | 
			
		||||
      </span>
 | 
			
		||||
    </.toggle_button>
 | 
			
		||||
  </div>
 | 
			
		||||
          <%= select(
 | 
			
		||||
            f,
 | 
			
		||||
            :type,
 | 
			
		||||
            [
 | 
			
		||||
              {gettext("All"), :all},
 | 
			
		||||
              {gettext("Rifle"), :rifle},
 | 
			
		||||
              {gettext("Shotgun"), :shotgun},
 | 
			
		||||
              {gettext("Pistol"), :pistol}
 | 
			
		||||
            ],
 | 
			
		||||
            class: "mx-2 my-1 min-w-md input input-primary",
 | 
			
		||||
            value: @type
 | 
			
		||||
          ) %>
 | 
			
		||||
        </.form>
 | 
			
		||||
 | 
			
		||||
  <%= if @ammo_groups |> Enum.empty?() do %>
 | 
			
		||||
    <h2 class="title text-xl text-primary-600">
 | 
			
		||||
      <%= gettext("No Ammo") %>
 | 
			
		||||
      <%= display_emoji("😔") %>
 | 
			
		||||
    </h2>
 | 
			
		||||
  <% else %>
 | 
			
		||||
    <.live_component
 | 
			
		||||
      module={CanneryWeb.Components.AmmoGroupTableComponent}
 | 
			
		||||
      id="ammo-group-index-table"
 | 
			
		||||
      ammo_groups={@ammo_groups}
 | 
			
		||||
      current_user={@current_user}
 | 
			
		||||
      show_used={@show_used}
 | 
			
		||||
    >
 | 
			
		||||
      <:ammo_type :let={%{name: ammo_type_name} = ammo_type}>
 | 
			
		||||
        <.link navigate={Routes.ammo_type_show_path(Endpoint, :show, ammo_type)} class="link">
 | 
			
		||||
          <%= ammo_type_name %>
 | 
			
		||||
        </.link>
 | 
			
		||||
      </:ammo_type>
 | 
			
		||||
      <:range :let={ammo_group}>
 | 
			
		||||
        <div class="min-w-20 py-2 px-4 h-full flex flew-wrap justify-center items-center">
 | 
			
		||||
          <button
 | 
			
		||||
            type="button"
 | 
			
		||||
            class="mx-2 my-1 text-sm btn btn-primary"
 | 
			
		||||
            phx-click="toggle_staged"
 | 
			
		||||
            phx-value-ammo_group_id={ammo_group.id}
 | 
			
		||||
          >
 | 
			
		||||
            <%= if ammo_group.staged,
 | 
			
		||||
              do: dgettext("actions", "Unstage"),
 | 
			
		||||
              else: dgettext("actions", "Stage") %>
 | 
			
		||||
          </button>
 | 
			
		||||
        <.form
 | 
			
		||||
          :let={f}
 | 
			
		||||
          for={%{}}
 | 
			
		||||
          as={:search}
 | 
			
		||||
          phx-change="search"
 | 
			
		||||
          phx-submit="search"
 | 
			
		||||
          class="grow flex items-center"
 | 
			
		||||
        >
 | 
			
		||||
          <%= text_input(f, :search_term,
 | 
			
		||||
            class: "grow input input-primary",
 | 
			
		||||
            value: @search,
 | 
			
		||||
            role: "search",
 | 
			
		||||
            phx_debounce: 300,
 | 
			
		||||
            placeholder: gettext("Search ammo")
 | 
			
		||||
          ) %>
 | 
			
		||||
        </.form>
 | 
			
		||||
 | 
			
		||||
          <.link
 | 
			
		||||
            patch={Routes.ammo_group_index_path(Endpoint, :add_shot_group, ammo_group)}
 | 
			
		||||
            class="mx-2 my-1 text-sm btn btn-primary"
 | 
			
		||||
          >
 | 
			
		||||
            <%= dgettext("actions", "Record shots") %>
 | 
			
		||||
          </.link>
 | 
			
		||||
        </div>
 | 
			
		||||
      </:range>
 | 
			
		||||
      <:container :let={{ammo_group, %{name: container_name} = container}}>
 | 
			
		||||
        <div class="min-w-20 py-2 px-4 h-full flex flew-wrap justify-center items-center">
 | 
			
		||||
          <.link
 | 
			
		||||
            navigate={Routes.container_show_path(Endpoint, :show, container)}
 | 
			
		||||
            class="mx-2 my-1 link"
 | 
			
		||||
          >
 | 
			
		||||
            <%= container_name %>
 | 
			
		||||
          </.link>
 | 
			
		||||
        <.toggle_button action="toggle_show_used" value={@show_used}>
 | 
			
		||||
          <span class="title text-lg text-primary-600">
 | 
			
		||||
            <%= gettext("Show used") %>
 | 
			
		||||
          </span>
 | 
			
		||||
        </.toggle_button>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
          <.link
 | 
			
		||||
            patch={Routes.ammo_group_index_path(Endpoint, :move, ammo_group)}
 | 
			
		||||
            class="mx-2 my-1 text-sm btn btn-primary"
 | 
			
		||||
          >
 | 
			
		||||
            <%= dgettext("actions", "Move ammo") %>
 | 
			
		||||
          </.link>
 | 
			
		||||
        </div>
 | 
			
		||||
      </:container>
 | 
			
		||||
      <:actions :let={%{count: ammo_group_count} = ammo_group}>
 | 
			
		||||
        <div class="py-2 px-4 h-full space-x-4 flex justify-center items-center">
 | 
			
		||||
          <.link
 | 
			
		||||
            navigate={Routes.ammo_group_show_path(Endpoint, :show, ammo_group)}
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            aria-label={
 | 
			
		||||
              dgettext("actions", "View ammo group of %{ammo_group_count} bullets",
 | 
			
		||||
                ammo_group_count: ammo_group_count
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-eye"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
      <%= if @ammo_groups |> Enum.empty?() do %>
 | 
			
		||||
        <h2 class="title text-xl text-primary-600">
 | 
			
		||||
          <%= gettext("No Ammo") %>
 | 
			
		||||
          <%= display_emoji("😔") %>
 | 
			
		||||
        </h2>
 | 
			
		||||
      <% else %>
 | 
			
		||||
        <.live_component
 | 
			
		||||
          module={CanneryWeb.Components.AmmoGroupTableComponent}
 | 
			
		||||
          id="ammo-group-index-table"
 | 
			
		||||
          ammo_groups={@ammo_groups}
 | 
			
		||||
          current_user={@current_user}
 | 
			
		||||
          show_used={@show_used}
 | 
			
		||||
        >
 | 
			
		||||
          <:ammo_type :let={%{name: ammo_type_name} = ammo_type}>
 | 
			
		||||
            <.link navigate={Routes.ammo_type_show_path(Endpoint, :show, ammo_type)} class="link">
 | 
			
		||||
              <%= ammo_type_name %>
 | 
			
		||||
            </.link>
 | 
			
		||||
          </:ammo_type>
 | 
			
		||||
          <:range :let={ammo_group}>
 | 
			
		||||
            <div class="min-w-20 py-2 px-4 h-full flex flew-wrap justify-center items-center">
 | 
			
		||||
              <button
 | 
			
		||||
                type="button"
 | 
			
		||||
                class="mx-2 my-1 text-sm btn btn-primary"
 | 
			
		||||
                phx-click="toggle_staged"
 | 
			
		||||
                phx-value-ammo_group_id={ammo_group.id}
 | 
			
		||||
              >
 | 
			
		||||
                <%= if ammo_group.staged,
 | 
			
		||||
                  do: dgettext("actions", "Unstage"),
 | 
			
		||||
                  else: dgettext("actions", "Stage") %>
 | 
			
		||||
              </button>
 | 
			
		||||
 | 
			
		||||
          <.link
 | 
			
		||||
            patch={Routes.ammo_group_index_path(Endpoint, :edit, ammo_group)}
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            aria-label={
 | 
			
		||||
              dgettext("actions", "Edit ammo group of %{ammo_group_count} bullets",
 | 
			
		||||
                ammo_group_count: ammo_group_count
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-edit"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
              <.link
 | 
			
		||||
                patch={Routes.ammo_group_index_path(Endpoint, :add_shot_group, ammo_group)}
 | 
			
		||||
                class="mx-2 my-1 text-sm btn btn-primary"
 | 
			
		||||
              >
 | 
			
		||||
                <%= dgettext("actions", "Record shots") %>
 | 
			
		||||
              </.link>
 | 
			
		||||
            </div>
 | 
			
		||||
          </:range>
 | 
			
		||||
          <:container :let={{ammo_group, %{name: container_name} = container}}>
 | 
			
		||||
            <div class="min-w-20 py-2 px-4 h-full flex flew-wrap justify-center items-center">
 | 
			
		||||
              <.link
 | 
			
		||||
                navigate={Routes.container_show_path(Endpoint, :show, container)}
 | 
			
		||||
                class="mx-2 my-1 link"
 | 
			
		||||
              >
 | 
			
		||||
                <%= container_name %>
 | 
			
		||||
              </.link>
 | 
			
		||||
 | 
			
		||||
          <.link
 | 
			
		||||
            patch={Routes.ammo_group_index_path(Endpoint, :clone, ammo_group)}
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            aria-label={
 | 
			
		||||
              dgettext("actions", "Clone ammo group of %{ammo_group_count} bullets",
 | 
			
		||||
                ammo_group_count: ammo_group_count
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-copy"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
              <.link
 | 
			
		||||
                patch={Routes.ammo_group_index_path(Endpoint, :move, ammo_group)}
 | 
			
		||||
                class="mx-2 my-1 text-sm btn btn-primary"
 | 
			
		||||
              >
 | 
			
		||||
                <%= dgettext("actions", "Move ammo") %>
 | 
			
		||||
              </.link>
 | 
			
		||||
            </div>
 | 
			
		||||
          </:container>
 | 
			
		||||
          <:actions :let={%{count: ammo_group_count} = ammo_group}>
 | 
			
		||||
            <div class="py-2 px-4 h-full space-x-4 flex justify-center items-center">
 | 
			
		||||
              <.link
 | 
			
		||||
                navigate={Routes.ammo_group_show_path(Endpoint, :show, ammo_group)}
 | 
			
		||||
                class="text-primary-600 link"
 | 
			
		||||
                aria-label={
 | 
			
		||||
                  dgettext("actions", "View ammo group of %{ammo_group_count} bullets",
 | 
			
		||||
                    ammo_group_count: ammo_group_count
 | 
			
		||||
                  )
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <i class="fa-fw fa-lg fas fa-eye"></i>
 | 
			
		||||
              </.link>
 | 
			
		||||
 | 
			
		||||
          <.link
 | 
			
		||||
            href="#"
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            phx-click="delete"
 | 
			
		||||
            phx-value-id={ammo_group.id}
 | 
			
		||||
            data-confirm={dgettext("prompts", "Are you sure you want to delete this ammo?")}
 | 
			
		||||
            aria-label={
 | 
			
		||||
              dgettext("actions", "Delete ammo group of %{ammo_group_count} bullets",
 | 
			
		||||
                ammo_group_count: ammo_group_count
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-trash"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
        </div>
 | 
			
		||||
      </:actions>
 | 
			
		||||
    </.live_component>
 | 
			
		||||
              <.link
 | 
			
		||||
                patch={Routes.ammo_group_index_path(Endpoint, :edit, ammo_group)}
 | 
			
		||||
                class="text-primary-600 link"
 | 
			
		||||
                aria-label={
 | 
			
		||||
                  dgettext("actions", "Edit ammo group of %{ammo_group_count} bullets",
 | 
			
		||||
                    ammo_group_count: ammo_group_count
 | 
			
		||||
                  )
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <i class="fa-fw fa-lg fas fa-edit"></i>
 | 
			
		||||
              </.link>
 | 
			
		||||
 | 
			
		||||
              <.link
 | 
			
		||||
                patch={Routes.ammo_group_index_path(Endpoint, :clone, ammo_group)}
 | 
			
		||||
                class="text-primary-600 link"
 | 
			
		||||
                aria-label={
 | 
			
		||||
                  dgettext("actions", "Clone ammo group of %{ammo_group_count} bullets",
 | 
			
		||||
                    ammo_group_count: ammo_group_count
 | 
			
		||||
                  )
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <i class="fa-fw fa-lg fas fa-copy"></i>
 | 
			
		||||
              </.link>
 | 
			
		||||
 | 
			
		||||
              <.link
 | 
			
		||||
                href="#"
 | 
			
		||||
                class="text-primary-600 link"
 | 
			
		||||
                phx-click="delete"
 | 
			
		||||
                phx-value-id={ammo_group.id}
 | 
			
		||||
                data-confirm={dgettext("prompts", "Are you sure you want to delete this ammo?")}
 | 
			
		||||
                aria-label={
 | 
			
		||||
                  dgettext("actions", "Delete ammo group of %{ammo_group_count} bullets",
 | 
			
		||||
                    ammo_group_count: ammo_group_count
 | 
			
		||||
                  )
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <i class="fa-fw fa-lg fas fa-trash"></i>
 | 
			
		||||
              </.link>
 | 
			
		||||
            </div>
 | 
			
		||||
          </:actions>
 | 
			
		||||
        </.live_component>
 | 
			
		||||
      <% end %>
 | 
			
		||||
  <% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,9 +15,19 @@
 | 
			
		||||
      :if={@changeset.action && not @changeset.valid?()}
 | 
			
		||||
      class="invalid-feedback col-span-3 text-center"
 | 
			
		||||
    >
 | 
			
		||||
      <%= changeset_errors(@changeset) %>
 | 
			
		||||
      <%= dgettext("errors", "Oops, something went wrong! Please check the errors below.") %>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :type, gettext("Type"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= select(
 | 
			
		||||
      f,
 | 
			
		||||
      :type,
 | 
			
		||||
      [{gettext("Rifle"), :rifle}, {gettext("Shotgun"), :shotgun}, {gettext("Pistol"), :pistol}],
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :type, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :name, gettext("Name"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :name,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
@@ -34,37 +44,31 @@
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :desc, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <.link
 | 
			
		||||
      href="https://shootersreference.com/reloadingdata/bullet_abbreviations/"
 | 
			
		||||
      class="col-span-3 text-center link title text-md text-primary-600"
 | 
			
		||||
    >
 | 
			
		||||
      <%= gettext("Example bullet type abbreviations") %>
 | 
			
		||||
    </.link>
 | 
			
		||||
    <%= label(f, :bullet_type, gettext("Bullet type"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :bullet_type,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255,
 | 
			
		||||
      placeholder: gettext("FMJ")
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :bullet_type, "col-span-3 text-center") %>
 | 
			
		||||
    <h2 class="text-center title text-lg text-primary-600 col-span-3">
 | 
			
		||||
      <%= gettext("Dimensions") %>
 | 
			
		||||
    </h2>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :bullet_core, gettext("Bullet core"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :bullet_core,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255,
 | 
			
		||||
      placeholder: gettext("Steel")
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :bullet_core, "col-span-3 text-center") %>
 | 
			
		||||
    <%= if Changeset.get_field(@changeset, :type) in [:rifle, :pistol] do %>
 | 
			
		||||
      <%= label(f, :cartridge, gettext("Cartridge"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
      <%= text_input(f, :cartridge,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255,
 | 
			
		||||
        placeholder: gettext("5.56x46mm NATO")
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :cartridge, "col-span-3 text-center") %>
 | 
			
		||||
    <% else %>
 | 
			
		||||
      <%= hidden_input(f, :cartridge, value: nil) %>
 | 
			
		||||
    <% end %>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :cartridge, gettext("Cartridge"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :cartridge,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255,
 | 
			
		||||
      placeholder: gettext("5.56x46mm NATO")
 | 
			
		||||
    <%= label(
 | 
			
		||||
      f,
 | 
			
		||||
      :caliber,
 | 
			
		||||
      if(Changeset.get_field(@changeset, :type) == :shotgun,
 | 
			
		||||
        do: gettext("Gauge"),
 | 
			
		||||
        else: gettext("Caliber")
 | 
			
		||||
      ),
 | 
			
		||||
      class: "title text-lg text-primary-600"
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :cartridge, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :caliber, gettext("Caliber"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :caliber,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255,
 | 
			
		||||
@@ -72,48 +76,38 @@
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :caliber, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :case_material, gettext("Case material"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :case_material,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255,
 | 
			
		||||
      placeholder: gettext("Brass")
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :case_material, "col-span-3 text-center") %>
 | 
			
		||||
    <%= if Changeset.get_field(@changeset, :type) == :shotgun do %>
 | 
			
		||||
      <%= label(f, :unfired_length, gettext("Unfired shell length"),
 | 
			
		||||
        class: "title text-lg text-primary-600"
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= text_input(f, :unfired_length,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :unfired_length, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :jacket_type, gettext("Jacket type"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :jacket_type,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255,
 | 
			
		||||
      placeholder: gettext("Bimetal")
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :case_material, "col-span-3 text-center") %>
 | 
			
		||||
      <%= label(f, :brass_height, gettext("Brass height"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
      <%= text_input(f, :brass_height,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :brass_height, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :muzzle_velocity, gettext("Muzzle velocity"),
 | 
			
		||||
      class: "title text-lg text-primary-600"
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= number_input(f, :muzzle_velocity,
 | 
			
		||||
      step: "1",
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      min: 1
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :muzzle_velocity, "col-span-3 text-center") %>
 | 
			
		||||
      <%= label(f, :chamber_size, gettext("Chamber size"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
      <%= text_input(f, :chamber_size,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :chamber_size, "col-span-3 text-center") %>
 | 
			
		||||
    <% else %>
 | 
			
		||||
      <%= hidden_input(f, :unfired_length, value: nil) %>
 | 
			
		||||
      <%= hidden_input(f, :brass_height, value: nil) %>
 | 
			
		||||
      <%= hidden_input(f, :chamber_size, value: nil) %>
 | 
			
		||||
    <% end %>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :powder_type, gettext("Powder type"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :powder_type,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :powder_type, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :powder_grains_per_charge, gettext("Powder grains per charge"),
 | 
			
		||||
      class: "title text-lg text-primary-600"
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= number_input(f, :powder_grains_per_charge,
 | 
			
		||||
      step: "1",
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      min: 1
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :powder_grains_per_charge, "col-span-3 text-center") %>
 | 
			
		||||
    <h2 class="text-center title text-lg text-primary-600 col-span-3">
 | 
			
		||||
      <%= gettext("Projectile") %>
 | 
			
		||||
    </h2>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :grains, gettext("Grains"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= number_input(f, :grains,
 | 
			
		||||
@@ -123,6 +117,143 @@
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :grains, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= label f, :bullet_type, class: "flex title text-lg text-primary-600 space-x-2" do %>
 | 
			
		||||
      <p><%= gettext("Bullet type") %></p>
 | 
			
		||||
 | 
			
		||||
      <.link
 | 
			
		||||
        href="https://shootersreference.com/reloadingdata/bullet_abbreviations/"
 | 
			
		||||
        class="link"
 | 
			
		||||
        target="_blank"
 | 
			
		||||
        rel="noopener noreferrer"
 | 
			
		||||
      >
 | 
			
		||||
        <i class="fas fa-md fa-external-link-alt"></i>
 | 
			
		||||
      </.link>
 | 
			
		||||
    <% end %>
 | 
			
		||||
    <%= text_input(f, :bullet_type,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255,
 | 
			
		||||
      placeholder: gettext("FMJ")
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :bullet_type, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= label(
 | 
			
		||||
      f,
 | 
			
		||||
      :bullet_core,
 | 
			
		||||
      if(Changeset.get_field(@changeset, :type) == :shotgun,
 | 
			
		||||
        do: gettext("Slug core"),
 | 
			
		||||
        else: gettext("Bullet core")
 | 
			
		||||
      ),
 | 
			
		||||
      class: "title text-lg text-primary-600"
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= text_input(f, :bullet_core,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255,
 | 
			
		||||
      placeholder: gettext("Steel")
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :bullet_core, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= if Changeset.get_field(@changeset, :type) in [:rifle, :pistol] do %>
 | 
			
		||||
      <%= label(f, :jacket_type, gettext("Jacket type"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
      <%= text_input(f, :jacket_type,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255,
 | 
			
		||||
        placeholder: gettext("Bimetal")
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :jacket_type, "col-span-3 text-center") %>
 | 
			
		||||
    <% else %>
 | 
			
		||||
      <%= hidden_input(f, :jacket_type, value: nil) %>
 | 
			
		||||
    <% end %>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :case_material, gettext("Case material"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :case_material,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255,
 | 
			
		||||
      placeholder: gettext("Brass")
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :case_material, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= if Changeset.get_field(@changeset, :type) == :shotgun do %>
 | 
			
		||||
      <%= label(f, :wadding, gettext("Wadding"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
      <%= text_input(f, :wadding,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :wadding, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
      <%= label(f, :shot_type, gettext("Shot type"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
      <%= text_input(f, :shot_type,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255,
 | 
			
		||||
        placeholder: gettext("Target, bird, buck, etc")
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :shot_type, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
      <%= label(f, :shot_material, gettext("Shot material"),
 | 
			
		||||
        class: "title text-lg text-primary-600"
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= text_input(f, :shot_material,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :shot_material, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
      <%= label(f, :shot_size, gettext("Shot size"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
      <%= text_input(f, :shot_size,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :shot_size, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
      <%= label(f, :load_grains, gettext("Load grains"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
      <%= number_input(f, :load_grains,
 | 
			
		||||
        step: "1",
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        min: 1
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :load_grains, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
      <%= label(f, :shot_charge_weight, gettext("Shot charge weight"),
 | 
			
		||||
        class: "title text-lg text-primary-600"
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= text_input(f, :shot_charge_weight,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :shot_charge_weight, "col-span-3 text-center") %>
 | 
			
		||||
    <% else %>
 | 
			
		||||
      <%= hidden_input(f, :wadding, value: nil) %>
 | 
			
		||||
      <%= hidden_input(f, :shot_type, value: nil) %>
 | 
			
		||||
      <%= hidden_input(f, :shot_material, value: nil) %>
 | 
			
		||||
      <%= hidden_input(f, :shot_size, value: nil) %>
 | 
			
		||||
      <%= hidden_input(f, :load_grains, value: nil) %>
 | 
			
		||||
      <%= hidden_input(f, :shot_charge_weight, value: nil) %>
 | 
			
		||||
    <% end %>
 | 
			
		||||
 | 
			
		||||
    <h2 class="text-center title text-lg text-primary-600 col-span-3">
 | 
			
		||||
      <%= gettext("Powder") %>
 | 
			
		||||
    </h2>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :powder_type, gettext("Powder type"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :powder_type,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
      maxlength: 255
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :powder_type, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= if Changeset.get_field(@changeset, :type) in [:rifle, :pistol] do %>
 | 
			
		||||
      <%= label(f, :powder_grains_per_charge, gettext("Powder grains per charge"),
 | 
			
		||||
        class: "title text-lg text-primary-600"
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= number_input(f, :powder_grains_per_charge,
 | 
			
		||||
        step: "1",
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        min: 1
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :powder_grains_per_charge, "col-span-3 text-center") %>
 | 
			
		||||
    <% else %>
 | 
			
		||||
      <%= hidden_input(f, :powder_grains_per_charge, value: nil) %>
 | 
			
		||||
    <% end %>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :pressure, gettext("Pressure"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :pressure,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
@@ -131,6 +262,37 @@
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :pressure, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <%= if Changeset.get_field(@changeset, :type) == :shotgun do %>
 | 
			
		||||
      <%= label(f, :dram_equivalent, gettext("Dram equivalent"),
 | 
			
		||||
        class: "title text-lg text-primary-600"
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= text_input(f, :dram_equivalent,
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        maxlength: 255
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :dram_equivalent, "col-span-3 text-center") %>
 | 
			
		||||
    <% else %>
 | 
			
		||||
      <%= hidden_input(f, :dram_equivalent, value: nil) %>
 | 
			
		||||
    <% end %>
 | 
			
		||||
 | 
			
		||||
    <%= if Changeset.get_field(@changeset, :type) in [:rifle, :pistol] do %>
 | 
			
		||||
      <%= label(f, :muzzle_velocity, gettext("Muzzle velocity"),
 | 
			
		||||
        class: "title text-lg text-primary-600"
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= number_input(f, :muzzle_velocity,
 | 
			
		||||
        step: "1",
 | 
			
		||||
        class: "text-center col-span-2 input input-primary",
 | 
			
		||||
        min: 1
 | 
			
		||||
      ) %>
 | 
			
		||||
      <%= error_tag(f, :muzzle_velocity, "col-span-3 text-center") %>
 | 
			
		||||
    <% else %>
 | 
			
		||||
      <%= hidden_input(f, :muzzle_velocity, value: nil) %>
 | 
			
		||||
    <% end %>
 | 
			
		||||
 | 
			
		||||
    <h2 class="text-center title text-lg text-primary-600 col-span-3">
 | 
			
		||||
      <%= gettext("Primer") %>
 | 
			
		||||
    </h2>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :primer_type, gettext("Primer type"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :primer_type,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
@@ -147,6 +309,10 @@
 | 
			
		||||
    ) %>
 | 
			
		||||
    <%= error_tag(f, :firing_type, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <h2 class="text-center title text-lg text-primary-600 col-span-3">
 | 
			
		||||
      <%= gettext("Attributes") %>
 | 
			
		||||
    </h2>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :tracer, gettext("Tracer"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= checkbox(f, :tracer, class: "text-center col-span-2 checkbox") %>
 | 
			
		||||
    <%= error_tag(f, :tracer, "col-span-3 text-center") %>
 | 
			
		||||
@@ -163,6 +329,10 @@
 | 
			
		||||
    <%= checkbox(f, :corrosive, class: "text-center col-span-2 checkbox") %>
 | 
			
		||||
    <%= error_tag(f, :corrosive, "col-span-3 text-center") %>
 | 
			
		||||
 | 
			
		||||
    <h2 class="text-center title text-lg text-primary-600 col-span-3">
 | 
			
		||||
      <%= gettext("Manufacturer") %>
 | 
			
		||||
    </h2>
 | 
			
		||||
 | 
			
		||||
    <%= label(f, :manufacturer, gettext("Manufacturer"), class: "title text-lg text-primary-600") %>
 | 
			
		||||
    <%= text_input(f, :manufacturer,
 | 
			
		||||
      class: "text-center col-span-2 input input-primary",
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,11 @@ defmodule CanneryWeb.AmmoTypeLive.Index do
 | 
			
		||||
 | 
			
		||||
  @impl true
 | 
			
		||||
  def mount(%{"search" => search}, _session, socket) do
 | 
			
		||||
    {:ok, socket |> assign(show_used: false, search: search) |> list_ammo_types()}
 | 
			
		||||
    {:ok, socket |> assign(type: :all, show_used: false, search: search) |> list_ammo_types()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def mount(_params, _session, socket) do
 | 
			
		||||
    {:ok, socket |> assign(show_used: false, search: nil) |> list_ammo_types()}
 | 
			
		||||
    {:ok, socket |> assign(type: :all, show_used: false, search: nil) |> list_ammo_types()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @impl true
 | 
			
		||||
@@ -86,7 +86,29 @@ defmodule CanneryWeb.AmmoTypeLive.Index do
 | 
			
		||||
    {:noreply, socket |> push_patch(to: search_path)}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp list_ammo_types(%{assigns: %{search: search, current_user: current_user}} = socket) do
 | 
			
		||||
    socket |> assign(ammo_types: Ammo.list_ammo_types(search, current_user))
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "rifle"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :rifle) |> list_ammo_types()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "shotgun"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :shotgun) |> list_ammo_types()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "pistol"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :pistol) |> list_ammo_types()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => _all}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :all) |> list_ammo_types()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp list_ammo_types(
 | 
			
		||||
         %{assigns: %{type: type, search: search, current_user: current_user}} = socket
 | 
			
		||||
       ) do
 | 
			
		||||
    socket
 | 
			
		||||
    |> assign(
 | 
			
		||||
      ammo_types: Ammo.list_ammo_types(search, current_user, type),
 | 
			
		||||
      ammo_types_count: Ammo.get_ammo_types_count!(current_user)
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
    <%= gettext("Catalog") %>
 | 
			
		||||
  </h1>
 | 
			
		||||
 | 
			
		||||
  <%= if @ammo_types |> Enum.empty?() and @search |> is_nil() do %>
 | 
			
		||||
  <%= if @ammo_types_count == 0 do %>
 | 
			
		||||
    <h2 class="title text-xl text-primary-600">
 | 
			
		||||
      <%= gettext("No Ammo types") %>
 | 
			
		||||
      <%= display_emoji("😔") %>
 | 
			
		||||
@@ -17,17 +17,41 @@
 | 
			
		||||
      <%= dgettext("actions", "New Ammo type") %>
 | 
			
		||||
    </.link>
 | 
			
		||||
 | 
			
		||||
    <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
 | 
			
		||||
    <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-2xl">
 | 
			
		||||
      <.form
 | 
			
		||||
        :let={f}
 | 
			
		||||
        for={%{}}
 | 
			
		||||
        as={:ammo_type}
 | 
			
		||||
        phx-change="change_type"
 | 
			
		||||
        phx-submit="change_type"
 | 
			
		||||
        class="flex items-center"
 | 
			
		||||
      >
 | 
			
		||||
        <%= label(f, :type, gettext("Type"), class: "title text-primary-600 text-lg text-center") %>
 | 
			
		||||
 | 
			
		||||
        <%= select(
 | 
			
		||||
          f,
 | 
			
		||||
          :type,
 | 
			
		||||
          [
 | 
			
		||||
            {gettext("All"), :all},
 | 
			
		||||
            {gettext("Rifle"), :rifle},
 | 
			
		||||
            {gettext("Shotgun"), :shotgun},
 | 
			
		||||
            {gettext("Pistol"), :pistol}
 | 
			
		||||
          ],
 | 
			
		||||
          class: "mx-2 my-1 min-w-md input input-primary",
 | 
			
		||||
          value: @type
 | 
			
		||||
        ) %>
 | 
			
		||||
      </.form>
 | 
			
		||||
 | 
			
		||||
      <.form
 | 
			
		||||
        :let={f}
 | 
			
		||||
        for={%{}}
 | 
			
		||||
        as={:search}
 | 
			
		||||
        phx-change="search"
 | 
			
		||||
        phx-submit="search"
 | 
			
		||||
        class="grow self-stretch flex flex-col items-stretch"
 | 
			
		||||
        class="grow flex items-center"
 | 
			
		||||
      >
 | 
			
		||||
        <%= text_input(f, :search_term,
 | 
			
		||||
          class: "input input-primary",
 | 
			
		||||
          class: "grow input input-primary",
 | 
			
		||||
          value: @search,
 | 
			
		||||
          role: "search",
 | 
			
		||||
          phx_debounce: 300,
 | 
			
		||||
@@ -55,6 +79,7 @@
 | 
			
		||||
        ammo_types={@ammo_types}
 | 
			
		||||
        current_user={@current_user}
 | 
			
		||||
        show_used={@show_used}
 | 
			
		||||
        type={@type}
 | 
			
		||||
      >
 | 
			
		||||
        <:actions :let={ammo_type}>
 | 
			
		||||
          <div class="px-4 py-2 space-x-4 flex justify-center items-center">
 | 
			
		||||
 
 | 
			
		||||
@@ -7,28 +7,6 @@ defmodule CanneryWeb.AmmoTypeLive.Show do
 | 
			
		||||
  alias Cannery.{ActivityLog, Ammo, Ammo.AmmoType, Containers}
 | 
			
		||||
  alias CanneryWeb.Endpoint
 | 
			
		||||
 | 
			
		||||
  @fields_list [
 | 
			
		||||
    %{label: gettext("Bullet type:"), key: :bullet_type, type: :string},
 | 
			
		||||
    %{label: gettext("Bullet core:"), key: :bullet_core, type: :string},
 | 
			
		||||
    %{label: gettext("Cartridge:"), key: :cartridge, type: :string},
 | 
			
		||||
    %{label: gettext("Caliber:"), key: :caliber, type: :string},
 | 
			
		||||
    %{label: gettext("Case material:"), key: :case_material, type: :string},
 | 
			
		||||
    %{label: gettext("Jacket type:"), key: :jacket_type, type: :string},
 | 
			
		||||
    %{label: gettext("Muzzle velocity:"), key: :muzzle_velocity, type: :string},
 | 
			
		||||
    %{label: gettext("Powder type:"), key: :powder_type, type: :string},
 | 
			
		||||
    %{label: gettext("Powder grains per charge:"), key: :powder_grains_per_charge, type: :string},
 | 
			
		||||
    %{label: gettext("Grains:"), key: :grains, type: :string},
 | 
			
		||||
    %{label: gettext("Pressure:"), key: :pressure, type: :string},
 | 
			
		||||
    %{label: gettext("Primer type:"), key: :primer_type, type: :string},
 | 
			
		||||
    %{label: gettext("Firing type:"), key: :firing_type, type: :string},
 | 
			
		||||
    %{label: gettext("Tracer:"), key: :tracer, type: :boolean},
 | 
			
		||||
    %{label: gettext("Incendiary:"), key: :incendiary, type: :boolean},
 | 
			
		||||
    %{label: gettext("Blank:"), key: :blank, type: :boolean},
 | 
			
		||||
    %{label: gettext("Corrosive:"), key: :corrosive, type: :boolean},
 | 
			
		||||
    %{label: gettext("Manufacturer:"), key: :manufacturer, type: :string},
 | 
			
		||||
    %{label: gettext("UPC:"), key: :upc, type: :string}
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  @impl true
 | 
			
		||||
  def mount(_params, _session, socket),
 | 
			
		||||
    do: {:ok, socket |> assign(show_used: false, view_table: true)}
 | 
			
		||||
@@ -65,8 +43,8 @@ defmodule CanneryWeb.AmmoTypeLive.Show do
 | 
			
		||||
           socket,
 | 
			
		||||
         %AmmoType{name: ammo_type_name} = ammo_type
 | 
			
		||||
       ) do
 | 
			
		||||
    fields_to_display =
 | 
			
		||||
      @fields_list
 | 
			
		||||
    custom_fields? =
 | 
			
		||||
      fields_to_display(ammo_type)
 | 
			
		||||
      |> Enum.any?(fn %{key: field, type: type} ->
 | 
			
		||||
        default_value =
 | 
			
		||||
          case type do
 | 
			
		||||
@@ -125,8 +103,8 @@ defmodule CanneryWeb.AmmoTypeLive.Show do
 | 
			
		||||
      packs_count: ammo_type |> Ammo.get_ammo_groups_count_for_type(current_user),
 | 
			
		||||
      used_packs_count: used_packs_count,
 | 
			
		||||
      historical_packs_count: historical_packs_count,
 | 
			
		||||
      fields_list: @fields_list,
 | 
			
		||||
      fields_to_display: fields_to_display
 | 
			
		||||
      fields_to_display: fields_to_display(ammo_type),
 | 
			
		||||
      custom_fields?: custom_fields?
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
@@ -138,6 +116,48 @@ defmodule CanneryWeb.AmmoTypeLive.Show do
 | 
			
		||||
    socket |> display_ammo_type(ammo_type)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp fields_to_display(%AmmoType{type: type}) do
 | 
			
		||||
    [
 | 
			
		||||
      %{label: gettext("Cartridge:"), key: :cartridge, type: :string},
 | 
			
		||||
      %{
 | 
			
		||||
        label: if(type == :shotgun, do: gettext("Gauge:"), else: gettext("Caliber:")),
 | 
			
		||||
        key: :caliber,
 | 
			
		||||
        type: :string
 | 
			
		||||
      },
 | 
			
		||||
      %{label: gettext("Unfired length:"), key: :unfired_length, type: :string},
 | 
			
		||||
      %{label: gettext("Brass height:"), key: :brass_height, type: :string},
 | 
			
		||||
      %{label: gettext("Chamber size:"), key: :chamber_size, type: :string},
 | 
			
		||||
      %{label: gettext("Grains:"), key: :grains, type: :string},
 | 
			
		||||
      %{label: gettext("Bullet type:"), key: :bullet_type, type: :string},
 | 
			
		||||
      %{label: gettext("Bullet core:"), key: :bullet_core, type: :string},
 | 
			
		||||
      %{label: gettext("Jacket type:"), key: :jacket_type, type: :string},
 | 
			
		||||
      %{label: gettext("Case material:"), key: :case_material, type: :string},
 | 
			
		||||
      %{label: gettext("Wadding:"), key: :wadding, type: :string},
 | 
			
		||||
      %{label: gettext("Shot type:"), key: :shot_type, type: :string},
 | 
			
		||||
      %{label: gettext("Shot material:"), key: :shot_material, type: :string},
 | 
			
		||||
      %{label: gettext("Shot size:"), key: :shot_size, type: :string},
 | 
			
		||||
      %{label: gettext("Load grains:"), key: :load_grains, type: :string},
 | 
			
		||||
      %{label: gettext("Shot charge weight:"), key: :shot_charge_weight, type: :string},
 | 
			
		||||
      %{label: gettext("Powder type:"), key: :powder_type, type: :string},
 | 
			
		||||
      %{
 | 
			
		||||
        label: gettext("Powder grains per charge:"),
 | 
			
		||||
        key: :powder_grains_per_charge,
 | 
			
		||||
        type: :string
 | 
			
		||||
      },
 | 
			
		||||
      %{label: gettext("Pressure:"), key: :pressure, type: :string},
 | 
			
		||||
      %{label: gettext("Dram equivalent:"), key: :dram_equivalent, type: :string},
 | 
			
		||||
      %{label: gettext("Muzzle velocity:"), key: :muzzle_velocity, type: :string},
 | 
			
		||||
      %{label: gettext("Primer type:"), key: :primer_type, type: :string},
 | 
			
		||||
      %{label: gettext("Firing type:"), key: :firing_type, type: :string},
 | 
			
		||||
      %{label: gettext("Tracer:"), key: :tracer, type: :boolean},
 | 
			
		||||
      %{label: gettext("Incendiary:"), key: :incendiary, type: :boolean},
 | 
			
		||||
      %{label: gettext("Blank:"), key: :blank, type: :boolean},
 | 
			
		||||
      %{label: gettext("Corrosive:"), key: :corrosive, type: :boolean},
 | 
			
		||||
      %{label: gettext("Manufacturer:"), key: :manufacturer, type: :string},
 | 
			
		||||
      %{label: gettext("UPC:"), key: :upc, type: :string}
 | 
			
		||||
    ]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @spec display_currency(float()) :: String.t()
 | 
			
		||||
  defp display_currency(float), do: :erlang.float_to_binary(float, decimals: 2)
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -42,9 +42,26 @@
 | 
			
		||||
 | 
			
		||||
  <hr class="hr" />
 | 
			
		||||
 | 
			
		||||
  <%= if @fields_to_display do %>
 | 
			
		||||
  <%= if @ammo_type.type || @custom_fields? do %>
 | 
			
		||||
    <div class="grid sm:grid-cols-2 gap-4 text-center justify-center items-center">
 | 
			
		||||
      <%= for %{label: label, key: key, type: type} <- @fields_list do %>
 | 
			
		||||
      <h3 class="title text-lg">
 | 
			
		||||
        <%= gettext("Type") %>
 | 
			
		||||
      </h3>
 | 
			
		||||
 | 
			
		||||
      <span class="text-primary-600">
 | 
			
		||||
        <%= case @ammo_type.type do %>
 | 
			
		||||
          <% :shotgun -> %>
 | 
			
		||||
            <%= gettext("Shotgun") %>
 | 
			
		||||
          <% :rifle -> %>
 | 
			
		||||
            <%= gettext("Rifle") %>
 | 
			
		||||
          <% :pistol -> %>
 | 
			
		||||
            <%= gettext("Pistol") %>
 | 
			
		||||
          <% _ -> %>
 | 
			
		||||
            <%= gettext("None specified") %>
 | 
			
		||||
        <% end %>
 | 
			
		||||
      </span>
 | 
			
		||||
 | 
			
		||||
      <%= for %{label: label, key: key, type: type} <- @fields_to_display do %>
 | 
			
		||||
        <%= if @ammo_type |> Map.get(key) do %>
 | 
			
		||||
          <h3 class="title text-lg">
 | 
			
		||||
            <%= label %>
 | 
			
		||||
 
 | 
			
		||||
@@ -17,17 +17,17 @@
 | 
			
		||||
      <%= dgettext("actions", "New Container") %>
 | 
			
		||||
    </.link>
 | 
			
		||||
 | 
			
		||||
    <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
 | 
			
		||||
    <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-2xl">
 | 
			
		||||
      <.form
 | 
			
		||||
        :let={f}
 | 
			
		||||
        for={%{}}
 | 
			
		||||
        as={:search}
 | 
			
		||||
        phx-change="search"
 | 
			
		||||
        phx-submit="search"
 | 
			
		||||
        class="grow self-stretch flex flex-col items-stretch"
 | 
			
		||||
        class="grow flex items-center"
 | 
			
		||||
      >
 | 
			
		||||
        <%= text_input(f, :search_term,
 | 
			
		||||
          class: "input input-primary",
 | 
			
		||||
          class: "grow input input-primary",
 | 
			
		||||
          value: @search,
 | 
			
		||||
          role: "search",
 | 
			
		||||
          phx_debounce: 300,
 | 
			
		||||
@@ -41,80 +41,22 @@
 | 
			
		||||
        </span>
 | 
			
		||||
      </.toggle_button>
 | 
			
		||||
    </div>
 | 
			
		||||
  <% end %>
 | 
			
		||||
 | 
			
		||||
  <%= if @containers |> Enum.empty?() do %>
 | 
			
		||||
    <h2 class="title text-xl text-primary-600">
 | 
			
		||||
      <%= gettext("No containers") %>
 | 
			
		||||
      <%= display_emoji("😔") %>
 | 
			
		||||
    </h2>
 | 
			
		||||
  <% else %>
 | 
			
		||||
    <%= if @view_table do %>
 | 
			
		||||
      <.live_component
 | 
			
		||||
        module={CanneryWeb.Components.ContainerTableComponent}
 | 
			
		||||
        id="containers_index_table"
 | 
			
		||||
        action={@live_action}
 | 
			
		||||
        containers={@containers}
 | 
			
		||||
        current_user={@current_user}
 | 
			
		||||
      >
 | 
			
		||||
        <:tag_actions :let={container}>
 | 
			
		||||
          <div class="mx-4 my-2">
 | 
			
		||||
            <.link
 | 
			
		||||
              patch={Routes.container_index_path(Endpoint, :edit_tags, container)}
 | 
			
		||||
              class="text-primary-600 link"
 | 
			
		||||
              aria-label={
 | 
			
		||||
                dgettext("actions", "Tag %{container_name}", container_name: container.name)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <i class="fa-fw fa-lg fas fa-tags"></i>
 | 
			
		||||
            </.link>
 | 
			
		||||
          </div>
 | 
			
		||||
        </:tag_actions>
 | 
			
		||||
        <:actions :let={container}>
 | 
			
		||||
          <.link
 | 
			
		||||
            patch={Routes.container_index_path(Endpoint, :edit, container)}
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            aria-label={
 | 
			
		||||
              dgettext("actions", "Edit %{container_name}", container_name: container.name)
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-edit"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
 | 
			
		||||
          <.link
 | 
			
		||||
            patch={Routes.container_index_path(Endpoint, :clone, container)}
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            aria-label={
 | 
			
		||||
              dgettext("actions", "Clone %{container_name}", container_name: container.name)
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-copy"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
 | 
			
		||||
          <.link
 | 
			
		||||
            href="#"
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            phx-click="delete"
 | 
			
		||||
            phx-value-id={container.id}
 | 
			
		||||
            data-confirm={
 | 
			
		||||
              dgettext("prompts", "Are you sure you want to delete %{name}?", name: container.name)
 | 
			
		||||
            }
 | 
			
		||||
            aria-label={
 | 
			
		||||
              dgettext("actions", "Delete %{container_name}", container_name: container.name)
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-trash"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
        </:actions>
 | 
			
		||||
      </.live_component>
 | 
			
		||||
    <%= if @containers |> Enum.empty?() do %>
 | 
			
		||||
      <h2 class="title text-xl text-primary-600">
 | 
			
		||||
        <%= gettext("No containers") %>
 | 
			
		||||
        <%= display_emoji("😔") %>
 | 
			
		||||
      </h2>
 | 
			
		||||
    <% else %>
 | 
			
		||||
      <div class="w-full flex flex-row flex-wrap justify-center items-stretch">
 | 
			
		||||
        <.container_card
 | 
			
		||||
          :for={container <- @containers}
 | 
			
		||||
          container={container}
 | 
			
		||||
      <%= if @view_table do %>
 | 
			
		||||
        <.live_component
 | 
			
		||||
          module={CanneryWeb.Components.ContainerTableComponent}
 | 
			
		||||
          id="containers_index_table"
 | 
			
		||||
          action={@live_action}
 | 
			
		||||
          containers={@containers}
 | 
			
		||||
          current_user={@current_user}
 | 
			
		||||
        >
 | 
			
		||||
          <:tag_actions>
 | 
			
		||||
          <:tag_actions :let={container}>
 | 
			
		||||
            <div class="mx-4 my-2">
 | 
			
		||||
              <.link
 | 
			
		||||
                patch={Routes.container_index_path(Endpoint, :edit_tags, container)}
 | 
			
		||||
@@ -127,42 +69,104 @@
 | 
			
		||||
              </.link>
 | 
			
		||||
            </div>
 | 
			
		||||
          </:tag_actions>
 | 
			
		||||
          <.link
 | 
			
		||||
            patch={Routes.container_index_path(Endpoint, :edit, container)}
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            aria-label={
 | 
			
		||||
              dgettext("actions", "Edit %{container_name}", container_name: container.name)
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-edit"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
          <:actions :let={container}>
 | 
			
		||||
            <.link
 | 
			
		||||
              patch={Routes.container_index_path(Endpoint, :edit, container)}
 | 
			
		||||
              class="text-primary-600 link"
 | 
			
		||||
              aria-label={
 | 
			
		||||
                dgettext("actions", "Edit %{container_name}", container_name: container.name)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <i class="fa-fw fa-lg fas fa-edit"></i>
 | 
			
		||||
            </.link>
 | 
			
		||||
 | 
			
		||||
          <.link
 | 
			
		||||
            patch={Routes.container_index_path(Endpoint, :clone, container)}
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            aria-label={
 | 
			
		||||
              dgettext("actions", "Clone %{container_name}", container_name: container.name)
 | 
			
		||||
            }
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-copy"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
            <.link
 | 
			
		||||
              patch={Routes.container_index_path(Endpoint, :clone, container)}
 | 
			
		||||
              class="text-primary-600 link"
 | 
			
		||||
              aria-label={
 | 
			
		||||
                dgettext("actions", "Clone %{container_name}", container_name: container.name)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <i class="fa-fw fa-lg fas fa-copy"></i>
 | 
			
		||||
            </.link>
 | 
			
		||||
 | 
			
		||||
          <.link
 | 
			
		||||
            href="#"
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            phx-click="delete"
 | 
			
		||||
            phx-value-id={container.id}
 | 
			
		||||
            data-confirm={
 | 
			
		||||
              dgettext("prompts", "Are you sure you want to delete %{name}?", name: container.name)
 | 
			
		||||
            }
 | 
			
		||||
            aria-label={
 | 
			
		||||
              dgettext("actions", "Delete %{container_name}", container_name: container.name)
 | 
			
		||||
            }
 | 
			
		||||
            <.link
 | 
			
		||||
              href="#"
 | 
			
		||||
              class="text-primary-600 link"
 | 
			
		||||
              phx-click="delete"
 | 
			
		||||
              phx-value-id={container.id}
 | 
			
		||||
              data-confirm={
 | 
			
		||||
                dgettext("prompts", "Are you sure you want to delete %{name}?",
 | 
			
		||||
                  name: container.name
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
              aria-label={
 | 
			
		||||
                dgettext("actions", "Delete %{container_name}", container_name: container.name)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <i class="fa-fw fa-lg fas fa-trash"></i>
 | 
			
		||||
            </.link>
 | 
			
		||||
          </:actions>
 | 
			
		||||
        </.live_component>
 | 
			
		||||
      <% else %>
 | 
			
		||||
        <div class="w-full flex flex-row flex-wrap justify-center items-stretch">
 | 
			
		||||
          <.container_card
 | 
			
		||||
            :for={container <- @containers}
 | 
			
		||||
            container={container}
 | 
			
		||||
            current_user={@current_user}
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-trash"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
        </.container_card>
 | 
			
		||||
      </div>
 | 
			
		||||
            <:tag_actions>
 | 
			
		||||
              <div class="mx-4 my-2">
 | 
			
		||||
                <.link
 | 
			
		||||
                  patch={Routes.container_index_path(Endpoint, :edit_tags, container)}
 | 
			
		||||
                  class="text-primary-600 link"
 | 
			
		||||
                  aria-label={
 | 
			
		||||
                    dgettext("actions", "Tag %{container_name}", container_name: container.name)
 | 
			
		||||
                  }
 | 
			
		||||
                >
 | 
			
		||||
                  <i class="fa-fw fa-lg fas fa-tags"></i>
 | 
			
		||||
                </.link>
 | 
			
		||||
              </div>
 | 
			
		||||
            </:tag_actions>
 | 
			
		||||
            <.link
 | 
			
		||||
              patch={Routes.container_index_path(Endpoint, :edit, container)}
 | 
			
		||||
              class="text-primary-600 link"
 | 
			
		||||
              aria-label={
 | 
			
		||||
                dgettext("actions", "Edit %{container_name}", container_name: container.name)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <i class="fa-fw fa-lg fas fa-edit"></i>
 | 
			
		||||
            </.link>
 | 
			
		||||
 | 
			
		||||
            <.link
 | 
			
		||||
              patch={Routes.container_index_path(Endpoint, :clone, container)}
 | 
			
		||||
              class="text-primary-600 link"
 | 
			
		||||
              aria-label={
 | 
			
		||||
                dgettext("actions", "Clone %{container_name}", container_name: container.name)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <i class="fa-fw fa-lg fas fa-copy"></i>
 | 
			
		||||
            </.link>
 | 
			
		||||
 | 
			
		||||
            <.link
 | 
			
		||||
              href="#"
 | 
			
		||||
              class="text-primary-600 link"
 | 
			
		||||
              phx-click="delete"
 | 
			
		||||
              phx-value-id={container.id}
 | 
			
		||||
              data-confirm={
 | 
			
		||||
                dgettext("prompts", "Are you sure you want to delete %{name}?",
 | 
			
		||||
                  name: container.name
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
              aria-label={
 | 
			
		||||
                dgettext("actions", "Delete %{container_name}", container_name: container.name)
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <i class="fa-fw fa-lg fas fa-trash"></i>
 | 
			
		||||
            </.link>
 | 
			
		||||
          </.container_card>
 | 
			
		||||
        </div>
 | 
			
		||||
      <% end %>
 | 
			
		||||
    <% end %>
 | 
			
		||||
  <% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ defmodule CanneryWeb.ContainerLive.Show do
 | 
			
		||||
 | 
			
		||||
  @impl true
 | 
			
		||||
  def mount(_params, _session, socket),
 | 
			
		||||
    do: {:ok, socket |> assign(show_used: false, view_table: true)}
 | 
			
		||||
    do: {:ok, socket |> assign(type: :all, view_table: true)}
 | 
			
		||||
 | 
			
		||||
  @impl true
 | 
			
		||||
  def handle_params(%{"id" => id}, _session, %{assigns: %{current_user: current_user}} = socket) do
 | 
			
		||||
@@ -82,22 +82,34 @@ defmodule CanneryWeb.ContainerLive.Show do
 | 
			
		||||
    {:noreply, socket}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("toggle_show_used", _params, %{assigns: %{show_used: show_used}} = socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:show_used, !show_used) |> render_container()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("toggle_table", _params, %{assigns: %{view_table: view_table}} = socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:view_table, !view_table) |> render_container()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "rifle"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :rifle) |> render_container()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "shotgun"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :shotgun) |> render_container()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "pistol"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :pistol) |> render_container()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => _all}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :all) |> render_container()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @spec render_container(Socket.t(), Container.id(), User.t()) :: Socket.t()
 | 
			
		||||
  defp render_container(
 | 
			
		||||
         %{assigns: %{live_action: live_action, show_used: show_used}} = socket,
 | 
			
		||||
         %{assigns: %{type: type, live_action: live_action}} = socket,
 | 
			
		||||
         id,
 | 
			
		||||
         current_user
 | 
			
		||||
       ) do
 | 
			
		||||
    %{name: container_name} = container = Containers.get_container!(id, current_user)
 | 
			
		||||
    ammo_groups = Ammo.list_ammo_groups_for_container(container, current_user, show_used)
 | 
			
		||||
    ammo_groups = Ammo.list_ammo_groups_for_container(container, type, current_user)
 | 
			
		||||
    original_counts = ammo_groups |> Ammo.get_original_counts(current_user)
 | 
			
		||||
    cprs = ammo_groups |> Ammo.get_cprs(current_user)
 | 
			
		||||
    last_used_dates = ammo_groups |> ActivityLog.get_last_used_dates(current_user)
 | 
			
		||||
@@ -113,6 +125,7 @@ defmodule CanneryWeb.ContainerLive.Show do
 | 
			
		||||
    |> assign(
 | 
			
		||||
      container: container,
 | 
			
		||||
      round_count: Ammo.get_round_count_for_container!(container, current_user),
 | 
			
		||||
      ammo_groups_count: Ammo.get_ammo_groups_count_for_container!(container, current_user),
 | 
			
		||||
      ammo_groups: ammo_groups,
 | 
			
		||||
      original_counts: original_counts,
 | 
			
		||||
      cprs: cprs,
 | 
			
		||||
 
 | 
			
		||||
@@ -18,22 +18,15 @@
 | 
			
		||||
    <%= @container.location %>
 | 
			
		||||
  </span>
 | 
			
		||||
 | 
			
		||||
  <%= unless @ammo_groups |> Enum.empty?() do %>
 | 
			
		||||
    <span class="rounded-lg title text-lg">
 | 
			
		||||
      <%= gettext("Packs:") %>
 | 
			
		||||
      <%= @ammo_groups |> Enum.reject(fn %{count: count} -> count in [0, nil] end) |> Enum.count() %>
 | 
			
		||||
    </span>
 | 
			
		||||
  <span class="rounded-lg title text-lg">
 | 
			
		||||
    <%= gettext("Packs:") %>
 | 
			
		||||
    <%= @ammo_groups_count %>
 | 
			
		||||
  </span>
 | 
			
		||||
 | 
			
		||||
    <span :if={@show_used} class="rounded-lg title text-lg">
 | 
			
		||||
      <%= gettext("Total packs:") %>
 | 
			
		||||
      <%= Enum.count(@ammo_groups) %>
 | 
			
		||||
    </span>
 | 
			
		||||
 | 
			
		||||
    <span class="rounded-lg title text-lg">
 | 
			
		||||
      <%= gettext("Rounds:") %>
 | 
			
		||||
      <%= @round_count %>
 | 
			
		||||
    </span>
 | 
			
		||||
  <% end %>
 | 
			
		||||
  <span class="rounded-lg title text-lg">
 | 
			
		||||
    <%= gettext("Rounds:") %>
 | 
			
		||||
    <%= @round_count %>
 | 
			
		||||
  </span>
 | 
			
		||||
 | 
			
		||||
  <div class="flex space-x-4 justify-center items-center text-primary-600">
 | 
			
		||||
    <.link
 | 
			
		||||
@@ -93,11 +86,29 @@
 | 
			
		||||
  <hr class="mb-4 hr" />
 | 
			
		||||
 | 
			
		||||
  <div class="flex justify-center items-center space-x-4">
 | 
			
		||||
    <.toggle_button action="toggle_show_used" value={@show_used}>
 | 
			
		||||
      <span class="title text-lg text-primary-600">
 | 
			
		||||
        <%= gettext("Show used") %>
 | 
			
		||||
      </span>
 | 
			
		||||
    </.toggle_button>
 | 
			
		||||
    <.form
 | 
			
		||||
      :let={f}
 | 
			
		||||
      for={%{}}
 | 
			
		||||
      as={:ammo_type}
 | 
			
		||||
      phx-change="change_type"
 | 
			
		||||
      phx-submit="change_type"
 | 
			
		||||
      class="flex items-center"
 | 
			
		||||
    >
 | 
			
		||||
      <%= label(f, :type, gettext("Type"), class: "title text-primary-600 text-lg text-center") %>
 | 
			
		||||
 | 
			
		||||
      <%= select(
 | 
			
		||||
        f,
 | 
			
		||||
        :type,
 | 
			
		||||
        [
 | 
			
		||||
          {gettext("All"), :all},
 | 
			
		||||
          {gettext("Rifle"), :rifle},
 | 
			
		||||
          {gettext("Shotgun"), :shotgun},
 | 
			
		||||
          {gettext("Pistol"), :pistol}
 | 
			
		||||
        ],
 | 
			
		||||
        class: "mx-2 my-1 min-w-md input input-primary",
 | 
			
		||||
        value: @type
 | 
			
		||||
      ) %>
 | 
			
		||||
    </.form>
 | 
			
		||||
 | 
			
		||||
    <.toggle_button action="toggle_table" value={@view_table}>
 | 
			
		||||
      <span class="title text-lg text-primary-600">
 | 
			
		||||
@@ -118,7 +129,7 @@
 | 
			
		||||
          id="ammo-type-show-table"
 | 
			
		||||
          ammo_groups={@ammo_groups}
 | 
			
		||||
          current_user={@current_user}
 | 
			
		||||
          show_used={@show_used}
 | 
			
		||||
          show_used={false}
 | 
			
		||||
        >
 | 
			
		||||
          <:ammo_type :let={%{name: ammo_type_name} = ammo_type}>
 | 
			
		||||
            <.link navigate={Routes.ammo_type_show_path(Endpoint, :show, ammo_type)} class="link">
 | 
			
		||||
 
 | 
			
		||||
@@ -10,11 +10,11 @@ defmodule CanneryWeb.RangeLive.Index do
 | 
			
		||||
 | 
			
		||||
  @impl true
 | 
			
		||||
  def mount(%{"search" => search}, _session, socket) do
 | 
			
		||||
    {:ok, socket |> assign(search: search) |> display_shot_groups()}
 | 
			
		||||
    {:ok, socket |> assign(type: :all, search: search) |> display_shot_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def mount(_params, _session, socket) do
 | 
			
		||||
    {:ok, socket |> assign(search: nil) |> display_shot_groups()}
 | 
			
		||||
    {:ok, socket |> assign(type: :all, search: nil) |> display_shot_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @impl true
 | 
			
		||||
@@ -102,9 +102,27 @@ defmodule CanneryWeb.RangeLive.Index do
 | 
			
		||||
    {:noreply, socket |> push_patch(to: Routes.range_index_path(Endpoint, :search, search_term))}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "rifle"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :rifle) |> display_shot_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "shotgun"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :shotgun) |> display_shot_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => "pistol"}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :pistol) |> display_shot_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def handle_event("change_type", %{"ammo_type" => %{"type" => _all}}, socket) do
 | 
			
		||||
    {:noreply, socket |> assign(:type, :all) |> display_shot_groups()}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @spec display_shot_groups(Socket.t()) :: Socket.t()
 | 
			
		||||
  defp display_shot_groups(%{assigns: %{search: search, current_user: current_user}} = socket) do
 | 
			
		||||
    shot_groups = ActivityLog.list_shot_groups(search, current_user)
 | 
			
		||||
  defp display_shot_groups(
 | 
			
		||||
         %{assigns: %{type: type, search: search, current_user: current_user}} = socket
 | 
			
		||||
       ) do
 | 
			
		||||
    shot_groups = ActivityLog.list_shot_groups(search, type, current_user)
 | 
			
		||||
    ammo_groups = Ammo.list_staged_ammo_groups(current_user)
 | 
			
		||||
    chart_data = shot_groups |> get_chart_data_for_shot_group()
 | 
			
		||||
    original_counts = ammo_groups |> Ammo.get_original_counts(current_user)
 | 
			
		||||
 
 | 
			
		||||
@@ -74,17 +74,41 @@
 | 
			
		||||
      <%= dgettext("errors", "Your browser does not support the canvas element.") %>
 | 
			
		||||
    </canvas>
 | 
			
		||||
 | 
			
		||||
    <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
 | 
			
		||||
    <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-2xl">
 | 
			
		||||
      <.form
 | 
			
		||||
        :let={f}
 | 
			
		||||
        for={%{}}
 | 
			
		||||
        as={:ammo_type}
 | 
			
		||||
        phx-change="change_type"
 | 
			
		||||
        phx-submit="change_type"
 | 
			
		||||
        class="flex items-center"
 | 
			
		||||
      >
 | 
			
		||||
        <%= label(f, :type, gettext("Type"), class: "title text-primary-600 text-lg text-center") %>
 | 
			
		||||
 | 
			
		||||
        <%= select(
 | 
			
		||||
          f,
 | 
			
		||||
          :type,
 | 
			
		||||
          [
 | 
			
		||||
            {gettext("All"), :all},
 | 
			
		||||
            {gettext("Rifle"), :rifle},
 | 
			
		||||
            {gettext("Shotgun"), :shotgun},
 | 
			
		||||
            {gettext("Pistol"), :pistol}
 | 
			
		||||
          ],
 | 
			
		||||
          class: "mx-2 my-1 min-w-md input input-primary",
 | 
			
		||||
          value: @type
 | 
			
		||||
        ) %>
 | 
			
		||||
      </.form>
 | 
			
		||||
 | 
			
		||||
      <.form
 | 
			
		||||
        :let={f}
 | 
			
		||||
        for={%{}}
 | 
			
		||||
        as={:search}
 | 
			
		||||
        phx-change="search"
 | 
			
		||||
        phx-submit="search"
 | 
			
		||||
        class="grow self-stretch flex flex-col items-stretch"
 | 
			
		||||
        class="grow flex items-center"
 | 
			
		||||
      >
 | 
			
		||||
        <%= text_input(f, :search_term,
 | 
			
		||||
          class: "input input-primary",
 | 
			
		||||
          class: "grow input input-primary",
 | 
			
		||||
          value: @search,
 | 
			
		||||
          role: "search",
 | 
			
		||||
          phx_debounce: 300,
 | 
			
		||||
 
 | 
			
		||||
@@ -18,57 +18,57 @@
 | 
			
		||||
    <.link patch={Routes.tag_index_path(Endpoint, :new)} class="btn btn-primary">
 | 
			
		||||
      <%= dgettext("actions", "New Tag") %>
 | 
			
		||||
    </.link>
 | 
			
		||||
  <% end %>
 | 
			
		||||
 | 
			
		||||
  <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
 | 
			
		||||
    <.form
 | 
			
		||||
      :let={f}
 | 
			
		||||
      for={%{}}
 | 
			
		||||
      as={:search}
 | 
			
		||||
      phx-change="search"
 | 
			
		||||
      phx-submit="search"
 | 
			
		||||
      class="grow self-stretch flex flex-col items-stretch"
 | 
			
		||||
    >
 | 
			
		||||
      <%= text_input(f, :search_term,
 | 
			
		||||
        class: "input input-primary",
 | 
			
		||||
        value: @search,
 | 
			
		||||
        role: "search",
 | 
			
		||||
        phx_debounce: 300,
 | 
			
		||||
        placeholder: gettext("Search tags")
 | 
			
		||||
      ) %>
 | 
			
		||||
    </.form>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <%= if @tags |> Enum.empty?() do %>
 | 
			
		||||
    <h2 class="title text-xl text-primary-600">
 | 
			
		||||
      <%= gettext("No tags") %>
 | 
			
		||||
      <%= display_emoji("😔") %>
 | 
			
		||||
    </h2>
 | 
			
		||||
  <% else %>
 | 
			
		||||
    <div class="flex flex-row flex-wrap justify-center items-stretch">
 | 
			
		||||
      <.tag_card :for={tag <- @tags} tag={tag}>
 | 
			
		||||
        <.link
 | 
			
		||||
          patch={Routes.tag_index_path(Endpoint, :edit, tag)}
 | 
			
		||||
          class="text-primary-600 link"
 | 
			
		||||
          aria-label={dgettext("actions", "Edit %{tag_name}", tag_name: tag.name)}
 | 
			
		||||
        >
 | 
			
		||||
          <i class="fa-fw fa-lg fas fa-edit"></i>
 | 
			
		||||
        </.link>
 | 
			
		||||
 | 
			
		||||
        <.link
 | 
			
		||||
          href="#"
 | 
			
		||||
          class="text-primary-600 link"
 | 
			
		||||
          phx-click="delete"
 | 
			
		||||
          phx-value-id={tag.id}
 | 
			
		||||
          data-confirm={
 | 
			
		||||
            dgettext("prompts", "Are you sure you want to delete %{name}?", name: tag.name)
 | 
			
		||||
          }
 | 
			
		||||
          aria-label={dgettext("actions", "Delete %{tag_name}", tag_name: tag.name)}
 | 
			
		||||
        >
 | 
			
		||||
          <i class="fa-fw fa-lg fas fa-trash"></i>
 | 
			
		||||
        </.link>
 | 
			
		||||
      </.tag_card>
 | 
			
		||||
    <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-2xl">
 | 
			
		||||
      <.form
 | 
			
		||||
        :let={f}
 | 
			
		||||
        for={%{}}
 | 
			
		||||
        as={:search}
 | 
			
		||||
        phx-change="search"
 | 
			
		||||
        phx-submit="search"
 | 
			
		||||
        class="grow flex items-center"
 | 
			
		||||
      >
 | 
			
		||||
        <%= text_input(f, :search_term,
 | 
			
		||||
          class: "grow input input-primary",
 | 
			
		||||
          value: @search,
 | 
			
		||||
          role: "search",
 | 
			
		||||
          phx_debounce: 300,
 | 
			
		||||
          placeholder: gettext("Search tags")
 | 
			
		||||
        ) %>
 | 
			
		||||
      </.form>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <%= if @tags |> Enum.empty?() do %>
 | 
			
		||||
      <h2 class="title text-xl text-primary-600">
 | 
			
		||||
        <%= gettext("No tags") %>
 | 
			
		||||
        <%= display_emoji("😔") %>
 | 
			
		||||
      </h2>
 | 
			
		||||
    <% else %>
 | 
			
		||||
      <div class="flex flex-row flex-wrap justify-center items-stretch">
 | 
			
		||||
        <.tag_card :for={tag <- @tags} tag={tag}>
 | 
			
		||||
          <.link
 | 
			
		||||
            patch={Routes.tag_index_path(Endpoint, :edit, tag)}
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            aria-label={dgettext("actions", "Edit %{tag_name}", tag_name: tag.name)}
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-edit"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
 | 
			
		||||
          <.link
 | 
			
		||||
            href="#"
 | 
			
		||||
            class="text-primary-600 link"
 | 
			
		||||
            phx-click="delete"
 | 
			
		||||
            phx-value-id={tag.id}
 | 
			
		||||
            data-confirm={
 | 
			
		||||
              dgettext("prompts", "Are you sure you want to delete %{name}?", name: tag.name)
 | 
			
		||||
            }
 | 
			
		||||
            aria-label={dgettext("actions", "Delete %{tag_name}", tag_name: tag.name)}
 | 
			
		||||
          >
 | 
			
		||||
            <i class="fa-fw fa-lg fas fa-trash"></i>
 | 
			
		||||
          </.link>
 | 
			
		||||
        </.tag_card>
 | 
			
		||||
      </div>
 | 
			
		||||
    <% end %>
 | 
			
		||||
  <% end %>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user