From e4ef22184e86b9321dc990edd714780e71d3db67 Mon Sep 17 00:00:00 2001 From: shibao Date: Tue, 15 Feb 2022 17:33:45 -0500 Subject: [PATCH] add range mode --- lib/cannery/activity_log.ex | 198 ++++++++++++++++ lib/cannery/activity_log/shot_group.ex | 57 +++++ lib/cannery/ammo.ex | 24 +- lib/cannery/ammo/ammo_group.ex | 14 +- .../components/add_shot_group_component.ex | 90 ++++++++ .../add_shot_group_component.html.heex | 47 ++++ lib/cannery_web/components/ammo_group_card.ex | 6 + lib/cannery_web/components/topbar.ex | 6 + lib/cannery_web/live/ammo_group_live/index.ex | 21 +- .../live/ammo_group_live/index.html.heex | 80 +++++-- lib/cannery_web/live/ammo_group_live/show.ex | 38 +++- .../live/ammo_group_live/show.html.heex | 27 ++- .../live/range_live/form_component.ex | 64 ++++++ .../live/range_live/form_component.html.heex | 45 ++++ lib/cannery_web/live/range_live/index.ex | 82 +++++++ .../live/range_live/index.html.heex | 135 +++++++++++ lib/cannery_web/router.ex | 6 + priv/gettext/actions.pot | 37 ++- priv/gettext/default.pot | 215 +++++++++++++----- priv/gettext/errors.pot | 11 + priv/gettext/prompts.pot | 76 +++++-- .../20220214031736_create_shot_groups.exs | 25 ++ test/cannery/activity_log_test.exs | 68 ++++++ .../cannery_web/live/ammo_group_live_test.exs | 2 +- test/cannery_web/live/ammo_type_live_test.exs | 2 +- test/cannery_web/live/container_live_test.exs | 2 +- test/cannery_web/live/invite_live_test.exs | 2 +- .../cannery_web/live/shot_group_live_test.exs | 122 ++++++++++ test/cannery_web/live/tag_live_test.exs | 2 +- .../support/fixtures/activity_log_fixtures.ex | 22 ++ 30 files changed, 1394 insertions(+), 132 deletions(-) create mode 100644 lib/cannery/activity_log.ex create mode 100644 lib/cannery/activity_log/shot_group.ex create mode 100644 lib/cannery_web/components/add_shot_group_component.ex create mode 100644 lib/cannery_web/components/add_shot_group_component.html.heex create mode 100644 lib/cannery_web/live/range_live/form_component.ex create mode 100644 lib/cannery_web/live/range_live/form_component.html.heex create mode 100644 lib/cannery_web/live/range_live/index.ex create mode 100644 lib/cannery_web/live/range_live/index.html.heex create mode 100644 priv/repo/migrations/20220214031736_create_shot_groups.exs create mode 100644 test/cannery/activity_log_test.exs create mode 100644 test/cannery_web/live/shot_group_live_test.exs create mode 100644 test/support/fixtures/activity_log_fixtures.ex diff --git a/lib/cannery/activity_log.ex b/lib/cannery/activity_log.ex new file mode 100644 index 0000000..354872d --- /dev/null +++ b/lib/cannery/activity_log.ex @@ -0,0 +1,198 @@ +defmodule Cannery.ActivityLog do + @moduledoc """ + The ActivityLog context. + """ + + import Ecto.Query, warn: false + import CanneryWeb.Gettext + alias Cannery.{Accounts.User, ActivityLog.ShotGroup, Ammo, Ammo.AmmoGroup, Repo} + alias Ecto.{Changeset, Multi} + + @doc """ + Returns the list of shot_groups. + + ## Examples + + iex> list_shot_groups(%User{id: 123}) + [%ShotGroup{}, ...] + + """ + @spec list_shot_groups(User.t()) :: [ShotGroup.t()] + def list_shot_groups(%User{id: user_id}) do + Repo.all(from(sg in ShotGroup, where: sg.user_id == ^user_id)) + end + + @doc """ + Gets a single shot_group. + + Raises `Ecto.NoResultsError` if the Shot group does not exist. + + ## Examples + + iex> get_shot_group!(123, %User{id: 123}) + %ShotGroup{} + + iex> get_shot_group!(456, %User{id: 123}) + ** (Ecto.NoResultsError) + + """ + @spec get_shot_group!(ShotGroup.id(), User.t()) :: ShotGroup.t() + def get_shot_group!(id, %User{id: user_id}) do + Repo.one!( + from sg in ShotGroup, + where: sg.id == ^id, + where: sg.user_id == ^user_id, + order_by: sg.date + ) + end + + @doc """ + Creates a shot_group. + + ## Examples + + iex> create_shot_group(%{field: value}, %User{id: 123}) + {:ok, %ShotGroup{}} + + iex> create_shot_group(%{field: bad_value}, %User{id: 123}) + {:error, %Ecto.Changeset{}} + + """ + @spec create_shot_group(attrs :: map(), User.t(), AmmoGroup.t()) :: + {:ok, ShotGroup.t()} | {:error, Changeset.t(ShotGroup.t()) | nil} + def create_shot_group( + attrs, + %User{id: user_id}, + %AmmoGroup{id: ammo_group_id, count: ammo_group_count, user_id: user_id} = ammo_group + ) do + attrs = attrs |> Map.merge(%{"user_id" => user_id, "ammo_group_id" => ammo_group_id}) + changeset = %ShotGroup{} |> ShotGroup.create_changeset(attrs) + shot_group_count = changeset |> Changeset.get_field(:count) + + if shot_group_count > ammo_group_count do + error = dgettext("errors", "Count must be less than %{count}", count: ammo_group_count) + changeset = changeset |> Changeset.add_error(:count, error) + {:error, changeset} + else + Multi.new() + |> Multi.insert(:create_shot_group, changeset) + |> Multi.update( + :update_ammo_group, + ammo_group |> AmmoGroup.range_changeset(%{"count" => ammo_group_count - shot_group_count}) + ) + |> Repo.transaction() + |> case do + {:ok, %{create_shot_group: shot_group}} -> {:ok, shot_group} + {:error, :create_shot_group, changeset, _changes_so_far} -> {:error, changeset} + {:error, _other_transaction, _value, _changes_so_far} -> {:error, nil} + end + end + end + + @doc """ + Updates a shot_group. + + ## Examples + + iex> update_shot_group(shot_group, %{field: new_value}, %User{id: 123}) + {:ok, %ShotGroup{}} + + iex> update_shot_group(shot_group, %{field: bad_value}, %User{id: 123}) + {:error, %Ecto.Changeset{}} + + """ + @spec update_shot_group(ShotGroup.t(), attrs :: map(), User.t()) :: + {:ok, ShotGroup.t()} | {:error, Changeset.t(ShotGroup.t()) | nil} + def update_shot_group( + %ShotGroup{count: count, user_id: user_id, ammo_group_id: ammo_group_id} = shot_group, + attrs, + %User{id: user_id} = user + ) do + %{count: ammo_group_count, user_id: ^user_id} = + ammo_group = ammo_group_id |> Ammo.get_ammo_group!(user) + + changeset = shot_group |> ShotGroup.update_changeset(attrs) + new_shot_group_count = changeset |> Changeset.get_field(:count) + shot_diff_to_add = new_shot_group_count - count + + cond do + shot_diff_to_add > ammo_group_count -> + error = dgettext("errors", "Count must be less than %{count}", count: ammo_group_count) + changeset = changeset |> Changeset.add_error(:count, error) + {:error, changeset} + + new_shot_group_count <= 0 -> + error = dgettext("errors", "Count must be at least 1") + changeset = changeset |> Changeset.add_error(:count, error) + {:error, changeset} + + true -> + Multi.new() + |> Multi.update(:update_shot_group, changeset) + |> Multi.update( + :update_ammo_group, + ammo_group + |> AmmoGroup.range_changeset(%{"count" => ammo_group_count - shot_diff_to_add}) + ) + |> Repo.transaction() + |> case do + {:ok, %{update_shot_group: shot_group}} -> {:ok, shot_group} + {:error, :update_shot_group, changeset, _changes_so_far} -> {:error, changeset} + {:error, _other_transaction, _value, _changes_so_far} -> {:error, nil} + end + end + end + + @doc """ + Deletes a shot_group. + + ## Examples + + iex> delete_shot_group(shot_group, %User{id: 123}) + {:ok, %ShotGroup{}} + + iex> delete_shot_group(shot_group, %User{id: 123}) + {:error, %Ecto.Changeset{}} + + """ + @spec delete_shot_group(ShotGroup.t(), User.t()) :: + {:ok, ShotGroup.t()} | {:error, Changeset.t(ShotGroup.t())} + def delete_shot_group( + %ShotGroup{count: count, user_id: user_id, ammo_group_id: ammo_group_id} = shot_group, + %User{id: user_id} = user + ) do + %{count: ammo_group_count, user_id: ^user_id} = + ammo_group = ammo_group_id |> Ammo.get_ammo_group!(user) + + Multi.new() + |> Multi.delete(:delete_shot_group, shot_group) + |> Multi.update( + :update_ammo_group, + ammo_group + |> AmmoGroup.range_changeset(%{"count" => ammo_group_count + count}) + ) + |> Repo.transaction() + |> case do + {:ok, %{delete_shot_group: shot_group}} -> {:ok, shot_group} + {:error, :delete_shot_group, changeset, _changes_so_far} -> {:error, changeset} + {:error, _other_transaction, _value, _changes_so_far} -> {:error, nil} + end + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking shot_group changes. + + ## Examples + + iex> change_shot_group(shot_group) + %Ecto.Changeset{data: %ShotGroup{}} + + """ + @spec change_shot_group(ShotGroup.t() | ShotGroup.new_shot_group()) :: + Changeset.t(ShotGroup.t() | ShotGroup.new_shot_group()) + @spec change_shot_group(ShotGroup.t() | ShotGroup.new_shot_group(), attrs :: map()) :: + Changeset.t(ShotGroup.t() | ShotGroup.new_shot_group()) + def change_shot_group(%ShotGroup{} = shot_group, attrs \\ %{}) do + shot_group |> ShotGroup.update_changeset(attrs) + end +end diff --git a/lib/cannery/activity_log/shot_group.ex b/lib/cannery/activity_log/shot_group.ex new file mode 100644 index 0000000..c50a4fa --- /dev/null +++ b/lib/cannery/activity_log/shot_group.ex @@ -0,0 +1,57 @@ +defmodule Cannery.ActivityLog.ShotGroup do + @moduledoc """ + A shot group records a group of ammo shot during a range trip + """ + + use Ecto.Schema + import Ecto.Changeset + alias Cannery.{Accounts.User, Ammo.AmmoGroup, ActivityLog.ShotGroup} + alias Ecto.{Changeset, UUID} + + @primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id + schema "shot_groups" do + field :count, :integer + field :date, :date + field :notes, :string + + belongs_to :user, User + belongs_to :ammo_group, AmmoGroup + + timestamps() + end + + @type t :: %ShotGroup{ + id: id(), + count: integer, + notes: String.t() | nil, + date: Date.t() | nil, + ammo_group: AmmoGroup.t() | nil, + ammo_group_id: AmmoGroup.id(), + user: User.t() | nil, + user_id: User.id(), + inserted_at: NaiveDateTime.t(), + updated_at: NaiveDateTime.t() + } + @type new_shot_group :: %ShotGroup{} + @type id :: UUID.t() + + @doc false + @spec create_changeset(new_shot_group(), attrs :: map()) :: Changeset.t(new_shot_group()) + def create_changeset(shot_group, attrs) do + shot_group + |> cast(attrs, [:count, :notes, :date, :ammo_group_id, :user_id]) + |> validate_number(:count, greater_than: 0) + |> validate_required([:count, :ammo_group_id, :user_id]) + end + + @doc false + @spec update_changeset(t() | new_shot_group(), attrs :: map()) :: + Changeset.t(t() | new_shot_group()) + def update_changeset(shot_group, attrs) do + shot_group + |> cast(attrs, [:count, :notes, :date]) + |> validate_number(:count, greater_than: 0) + |> validate_required([:count]) + end +end diff --git a/lib/cannery/ammo.ex b/lib/cannery/ammo.ex index 603d94b..f64b76b 100644 --- a/lib/cannery/ammo.ex +++ b/lib/cannery/ammo.ex @@ -177,8 +177,28 @@ defmodule Cannery.Ammo do """ @spec list_ammo_groups(User.t()) :: [AmmoGroup.t()] - def list_ammo_groups(%User{id: user_id}) do - Repo.all(from am in AmmoGroup, where: am.user_id == ^user_id) + @spec list_ammo_groups(User.t(), include_empty :: boolean()) :: [AmmoGroup.t()] + def list_ammo_groups(%User{id: user_id}, include_empty \\ false) do + if include_empty do + from am in AmmoGroup, where: am.user_id == ^user_id + else + from am in AmmoGroup, where: am.user_id == ^user_id, where: not (am.count == 0) + end + |> Repo.all() + end + + @doc """ + Returns the list of staged ammo_groups for a user. + + ## Examples + + iex> list_staged_ammo_groups(%User{id: 123}) + [%AmmoGroup{}, ...] + + """ + @spec list_staged_ammo_groups(User.t()) :: [AmmoGroup.t()] + def list_staged_ammo_groups(%User{id: user_id}) do + Repo.all(from am in AmmoGroup, where: am.user_id == ^user_id, where: am.staged == true) end @doc """ diff --git a/lib/cannery/ammo/ammo_group.ex b/lib/cannery/ammo/ammo_group.ex index b948c1f..8b15224 100644 --- a/lib/cannery/ammo/ammo_group.ex +++ b/lib/cannery/ammo/ammo_group.ex @@ -18,6 +18,7 @@ defmodule Cannery.Ammo.AmmoGroup do field :count, :integer field :notes, :string field :price_paid, :float + field :staged, :boolean, default: false belongs_to :ammo_type, AmmoType belongs_to :container, Container @@ -31,6 +32,7 @@ defmodule Cannery.Ammo.AmmoGroup do count: integer, notes: String.t() | nil, price_paid: float() | nil, + staged: boolean(), ammo_type: AmmoType.t() | nil, ammo_type_id: AmmoType.id(), container: Container.t() | nil, @@ -47,9 +49,9 @@ defmodule Cannery.Ammo.AmmoGroup do @spec create_changeset(new_ammo_group(), attrs :: map()) :: Changeset.t(new_ammo_group()) def create_changeset(ammo_group, attrs) do ammo_group - |> cast(attrs, [:count, :price_paid, :notes, :ammo_type_id, :container_id, :user_id]) + |> cast(attrs, [:count, :price_paid, :notes, :staged, :ammo_type_id, :container_id, :user_id]) |> validate_number(:count, greater_than: 0) - |> validate_required([:count, :ammo_type_id, :container_id, :user_id]) + |> validate_required([:count, :staged, :ammo_type_id, :container_id, :user_id]) end @doc false @@ -57,9 +59,9 @@ defmodule Cannery.Ammo.AmmoGroup do Changeset.t(t() | new_ammo_group()) def update_changeset(ammo_group, attrs) do ammo_group - |> cast(attrs, [:count, :price_paid, :notes, :ammo_type_id, :container_id]) + |> cast(attrs, [:count, :price_paid, :notes, :staged, :ammo_type_id, :container_id]) |> validate_number(:count, greater_than: 0) - |> validate_required([:count, :ammo_type_id, :container_id, :user_id]) + |> validate_required([:count, :staged, :ammo_type_id, :container_id, :user_id]) end @doc """ @@ -70,7 +72,7 @@ defmodule Cannery.Ammo.AmmoGroup do Changeset.t(t() | new_ammo_group()) def range_changeset(ammo_group, attrs) do ammo_group - |> cast(attrs, [:count]) - |> validate_required([:count, :ammo_type_id, :container_id, :user_id]) + |> cast(attrs, [:count, :staged]) + |> validate_required([:count, :staged, :ammo_type_id, :container_id, :user_id]) end end diff --git a/lib/cannery_web/components/add_shot_group_component.ex b/lib/cannery_web/components/add_shot_group_component.ex new file mode 100644 index 0000000..679e5f2 --- /dev/null +++ b/lib/cannery_web/components/add_shot_group_component.ex @@ -0,0 +1,90 @@ +defmodule CanneryWeb.Components.AddShotGroupComponent do + @moduledoc """ + Livecomponent that can create a ShotGroup + """ + + use CanneryWeb, :live_component + alias Cannery.{Accounts.User, ActivityLog, ActivityLog.ShotGroup, Ammo.AmmoGroup} + alias Phoenix.LiveView.Socket + + @impl true + @spec update( + %{ + required(:current_user) => User.t(), + required(:ammo_group) => AmmoGroup.t(), + optional(any()) => any() + }, + Socket.t() + ) :: {:ok, Socket.t()} + def update(%{ammo_group: _ammo_group, current_user: _current_user} = assigns, socket) do + changeset = + %ShotGroup{date: NaiveDateTime.utc_now(), count: 1} |> ActivityLog.change_shot_group() + + {:ok, socket |> assign(assigns) |> assign(:changeset, changeset)} + end + + @impl true + def handle_event( + "validate", + %{"shot_group" => shot_group_params}, + %{ + assigns: %{ + ammo_group: %AmmoGroup{id: ammo_group_id} = ammo_group, + current_user: %User{id: user_id} + } + } = socket + ) do + shot_group_params = + shot_group_params + |> process_params(ammo_group) + |> Map.merge(%{"ammo_group_id" => ammo_group_id, "user_id" => user_id}) + + changeset = + %ShotGroup{} + |> ActivityLog.change_shot_group(shot_group_params) + |> Map.put(:action, :validate) + + {:noreply, socket |> assign(:changeset, changeset)} + end + + def handle_event( + "save", + %{"shot_group" => shot_group_params}, + %{ + assigns: %{ + ammo_group: %{id: ammo_group_id} = ammo_group, + current_user: %{id: user_id} = current_user, + return_to: return_to + } + } = socket + ) do + socket = + shot_group_params + |> process_params(ammo_group) + |> Map.merge(%{"ammo_group_id" => ammo_group_id, "user_id" => user_id}) + |> ActivityLog.create_shot_group(current_user, ammo_group) + |> case do + {:ok, _shot_group} -> + prompt = dgettext("prompts", "Shots recorded successfully") + socket |> put_flash(:info, prompt) |> push_redirect(to: return_to) + + {:error, %Ecto.Changeset{} = changeset} -> + socket |> assign(changeset: changeset) + end + + {:noreply, socket} + end + + # calculate count from shots left + defp process_params(params, %AmmoGroup{count: count}) do + new_count = + if params |> Map.get("ammo_left", "0") == "" do + "0" + else + params |> Map.get("ammo_left", "0") + end + |> String.to_integer() + + params |> Map.put("count", count - new_count) + end +end diff --git a/lib/cannery_web/components/add_shot_group_component.html.heex b/lib/cannery_web/components/add_shot_group_component.html.heex new file mode 100644 index 0000000..eeb685d --- /dev/null +++ b/lib/cannery_web/components/add_shot_group_component.html.heex @@ -0,0 +1,47 @@ +
+

+ <%= gettext("Record shots") %> +

+ + <.form + let={f} + for={@changeset} + id="shot-group-form" + class="grid grid-cols-3 justify-center items-center space-y-4" + phx-target={@myself} + phx-change="validate" + phx-submit="save" + > + <%= unless @changeset.valid? do %> +
+ <%= changeset_errors(@changeset) %> +
+ <% end %> + + <%= label(f, :ammo_left, gettext("Rounds left"), class: "title text-lg text-primary-500") %> + <%= number_input(f, :ammo_left, + min: 0, + max: @ammo_group.count - 1, + placeholder: 0, + class: "input input-primary col-span-2" + ) %> + <%= error_tag(f, :ammo_left, "col-span-3") %> + + <%= label(f, :notes, gettext("Notes"), class: "title text-lg text-primary-500") %> + <%= textarea(f, :notes, + class: "input input-primary col-span-2", + placeholder: "Really great weather", + phx_hook: "MaintainAttrs" + ) %> + <%= error_tag(f, :notes, "col-span-3") %> + + <%= label(f, :date, gettext("Date (UTC)"), class: "title text-lg text-primary-500") %> + <%= date_input(f, :date, class: "input input-primary col-span-2") %> + <%= error_tag(f, :notes, "col-span-3") %> + + <%= submit(dgettext("actions", "Save"), + class: "mx-auto btn btn-primary col-span-3", + phx_disable_with: dgettext("prompts", "Saving...") + ) %> + +
diff --git a/lib/cannery_web/components/ammo_group_card.ex b/lib/cannery_web/components/ammo_group_card.ex index be5fa69..4c201d1 100644 --- a/lib/cannery_web/components/ammo_group_card.ex +++ b/lib/cannery_web/components/ammo_group_card.ex @@ -42,6 +42,12 @@ defmodule CanneryWeb.Components.AmmoGroupCard do <% end %> + + <%= if assigns |> Map.has_key?(:inner_block) do %> +
+ <%= render_slot(@inner_block) %> +
+ <% end %> """ end diff --git a/lib/cannery_web/components/topbar.ex b/lib/cannery_web/components/topbar.ex index 0602ccb..36b043a 100644 --- a/lib/cannery_web/components/topbar.ex +++ b/lib/cannery_web/components/topbar.ex @@ -55,6 +55,12 @@ defmodule CanneryWeb.Components.Topbar do to: Routes.ammo_group_index_path(Endpoint, :index) ) %> +
  • + <%= link(gettext("Range"), + class: "hover:underline", + to: Routes.range_index_path(Endpoint, :index) + ) %> +
  • <%= if @current_user.role == :admin do %>
  • <%= link(gettext("Invites"), diff --git a/lib/cannery_web/live/ammo_group_live/index.ex b/lib/cannery_web/live/ammo_group_live/index.ex index de85b24..4c1d3dc 100644 --- a/lib/cannery_web/live/ammo_group_live/index.ex +++ b/lib/cannery_web/live/ammo_group_live/index.ex @@ -4,8 +4,8 @@ defmodule CanneryWeb.AmmoGroupLive.Index do """ use CanneryWeb, :live_view - alias Cannery.Ammo - alias Cannery.Ammo.AmmoGroup + alias Cannery.{Ammo, Ammo.AmmoGroup, Repo} + alias CanneryWeb.Endpoint @impl true def mount(_params, session, socket) do @@ -42,7 +42,22 @@ defmodule CanneryWeb.AmmoGroupLive.Index do {:noreply, socket |> put_flash(:info, prompt) |> display_ammo_groups()} end + @impl true + def handle_event( + "toggle_staged", + %{"ammo_group_id" => id}, + %{assigns: %{current_user: current_user}} = socket + ) do + ammo_group = Ammo.get_ammo_group!(id, current_user) + + {:ok, _ammo_group} = + ammo_group |> Ammo.update_ammo_group(%{"staged" => !ammo_group.staged}, current_user) + + {:noreply, socket |> display_ammo_groups()} + end + defp display_ammo_groups(%{assigns: %{current_user: current_user}} = socket) do - socket |> assign(:ammo_groups, Ammo.list_ammo_groups(current_user)) + ammo_groups = Ammo.list_ammo_groups(current_user) |> Repo.preload([:ammo_type, :container]) + socket |> assign(:ammo_groups, ammo_groups) end end diff --git a/lib/cannery_web/live/ammo_group_live/index.html.heex b/lib/cannery_web/live/ammo_group_live/index.html.heex index 91a5e75..4bb5371 100644 --- a/lib/cannery_web/live/ammo_group_live/index.html.heex +++ b/lib/cannery_web/live/ammo_group_live/index.html.heex @@ -22,6 +22,9 @@ + @@ -31,6 +34,12 @@ + + @@ -38,6 +47,13 @@ <%= for ammo_group <- @ammo_groups do %> + + @@ -52,23 +68,41 @@ <%= ammo_group.notes %> + + + + <% end %> @@ -79,14 +113,28 @@ <%= if @live_action in [:new, :edit] do %> - <.modal return_to={Routes.ammo_group_index_path(@socket, :index)}> + <.modal return_to={Routes.ammo_group_index_path(Endpoint, :index)}> <.live_component module={CanneryWeb.AmmoGroupLive.FormComponent} id={@ammo_group.id || :new} title={@page_title} action={@live_action} ammo_group={@ammo_group} - return_to={Routes.ammo_group_index_path(@socket, :index)} + return_to={Routes.ammo_group_index_path(Endpoint, :index)} + current_user={@current_user} + /> + +<% end %> + +<%= if @live_action in [:add_shot_group] do %> + <.modal return_to={Routes.ammo_group_index_path(Endpoint, :index)}> + <.live_component + module={CanneryWeb.Components.AddShotGroupComponent} + id={:new} + title={@page_title} + action={@live_action} + ammo_group={@ammo_group} + return_to={Routes.ammo_group_index_path(Endpoint, :index)} current_user={@current_user} /> diff --git a/lib/cannery_web/live/ammo_group_live/show.ex b/lib/cannery_web/live/ammo_group_live/show.ex index b93de87..776348c 100644 --- a/lib/cannery_web/live/ammo_group_live/show.ex +++ b/lib/cannery_web/live/ammo_group_live/show.ex @@ -6,6 +6,7 @@ defmodule CanneryWeb.AmmoGroupLive.Show do use CanneryWeb, :live_view import CanneryWeb.Components.ContainerCard alias Cannery.{Ammo, Repo} + alias CanneryWeb.Endpoint @impl true def mount(_params, session, socket) do @@ -13,11 +14,26 @@ defmodule CanneryWeb.AmmoGroupLive.Show do end @impl true - def handle_params( - %{"id" => id}, - _, - %{assigns: %{live_action: live_action, current_user: current_user}} = socket - ) do + def handle_params(params, _url, %{assigns: %{live_action: live_action}} = socket) do + socket |> assign(page_title: page_title(live_action)) |> apply_action(live_action, params) + end + + defp apply_action( + %{assigns: %{current_user: current_user}} = socket, + :add_shot_group, + %{"id" => id} + ) do + socket + |> assign(:page_title, gettext("Add Shot group")) + |> assign(:ammo_group, Ammo.get_ammo_group!(id, current_user)) + end + + defp apply_action( + %{assigns: %{live_action: live_action, current_user: current_user}} = socket, + action, + %{"id" => id} + ) + when action == :edit or action == :show do ammo_group = Ammo.get_ammo_group!(id, current_user) |> Repo.preload([:container, :ammo_type]) {:noreply, socket |> assign(page_title: page_title(live_action), ammo_group: ammo_group)} end @@ -36,6 +52,18 @@ defmodule CanneryWeb.AmmoGroupLive.Show do {:noreply, socket |> put_flash(:info, prompt) |> push_redirect(to: redirect_to)} end + @impl true + def handle_event( + "toggle_staged", + _, + %{assigns: %{ammo_group: ammo_group, current_user: current_user}} = socket + ) do + {:ok, ammo_group} = + ammo_group |> Ammo.update_ammo_group(%{"staged" => !ammo_group.staged}, current_user) + + {:noreply, socket |> assign(ammo_group: ammo_group)} + end + defp page_title(:show), do: gettext("Show Ammo group") defp page_title(:edit), do: gettext("Edit Ammo group") end diff --git a/lib/cannery_web/live/ammo_group_live/show.html.heex b/lib/cannery_web/live/ammo_group_live/show.html.heex index 58000e9..b832264 100644 --- a/lib/cannery_web/live/ammo_group_live/show.html.heex +++ b/lib/cannery_web/live/ammo_group_live/show.html.heex @@ -24,6 +24,11 @@
    + <%= live_patch(dgettext("actions", "Ammo Details"), + to: Routes.ammo_type_show_path(@socket, :show, @ammo_group.ammo_type), + class: "btn btn-primary" + ) %> + <%= live_patch to: Routes.ammo_group_show_path(@socket, :edit, @ammo_group), class: "text-primary-500 link" do %> @@ -35,6 +40,10 @@ data: [confirm: dgettext("prompts", "Are you sure you want to delete this ammo?")] do %> <% end %> + +

    @@ -53,14 +62,28 @@ <%= if @live_action in [:edit] do %> - <.modal return_to={Routes.ammo_group_show_path(@socket, :show, @ammo_group)}> + <.modal return_to={Routes.ammo_group_show_path(Endpoint, :show, @ammo_group)}> <.live_component module={CanneryWeb.AmmoGroupLive.FormComponent} id={@ammo_group.id} title={@page_title} action={@live_action} ammo_group={@ammo_group} - return_to={Routes.ammo_group_show_path(@socket, :show, @ammo_group)} + return_to={Routes.ammo_group_show_path(Endpoint, :show, @ammo_group)} + current_user={@current_user} + /> + +<% end %> + +<%= if @live_action in [:add_shot_group] do %> + <.modal return_to={Routes.ammo_group_show_path(Endpoint, :show, @ammo_group)}> + <.live_component + module={CanneryWeb.Components.AddShotGroupComponent} + id={:new} + title={@page_title} + action={@live_action} + ammo_group={@ammo_group} + return_to={Routes.ammo_group_show_path(Endpoint, :show, @ammo_group)} current_user={@current_user} /> diff --git a/lib/cannery_web/live/range_live/form_component.ex b/lib/cannery_web/live/range_live/form_component.ex new file mode 100644 index 0000000..770fe85 --- /dev/null +++ b/lib/cannery_web/live/range_live/form_component.ex @@ -0,0 +1,64 @@ +defmodule CanneryWeb.RangeLive.FormComponent do + @moduledoc """ + Livecomponent that can update or create a ShotGroup + """ + + use CanneryWeb, :live_component + alias Cannery.{Accounts.User, ActivityLog, ActivityLog.ShotGroup, Ammo, Ammo.AmmoGroup} + alias Phoenix.LiveView.Socket + + @impl true + @spec update( + %{ + required(:shot_group) => ShotGroup.t(), + required(:current_user) => User.t(), + optional(:ammo_group) => AmmoGroup.t(), + optional(any()) => any() + }, + Socket.t() + ) :: {:ok, Socket.t()} + def update( + %{ + shot_group: %ShotGroup{ammo_group_id: ammo_group_id} = shot_group, + current_user: current_user + } = assigns, + socket + ) do + changeset = shot_group |> ActivityLog.change_shot_group() + ammo_group = Ammo.get_ammo_group!(ammo_group_id, current_user) + {:ok, socket |> assign(assigns) |> assign(ammo_group: ammo_group, changeset: changeset)} + end + + @impl true + def handle_event( + "validate", + %{"shot_group" => shot_group_params}, + %{assigns: %{shot_group: shot_group}} = socket + ) do + changeset = + shot_group + |> ActivityLog.change_shot_group(shot_group_params) + |> Map.put(:action, :validate) + + {:noreply, assign(socket, :changeset, changeset)} + end + + def handle_event( + "save", + %{"shot_group" => shot_group_params}, + %{assigns: %{shot_group: shot_group, current_user: current_user, return_to: return_to}} = + socket + ) do + socket = + case ActivityLog.update_shot_group(shot_group, shot_group_params, current_user) do + {:ok, _shot_group} -> + prompt = dgettext("prompts", "Shot records updated successfully") + socket |> put_flash(:info, prompt) |> push_redirect(to: return_to) + + {:error, %Ecto.Changeset{} = changeset} -> + socket |> assign(:changeset, changeset) + end + + {:noreply, socket} + end +end diff --git a/lib/cannery_web/live/range_live/form_component.html.heex b/lib/cannery_web/live/range_live/form_component.html.heex new file mode 100644 index 0000000..fe1238f --- /dev/null +++ b/lib/cannery_web/live/range_live/form_component.html.heex @@ -0,0 +1,45 @@ +
    +

    + <%= @title %> +

    + + <.form + let={f} + for={@changeset} + id="shot-group-form" + class="grid grid-cols-3 justify-center items-center space-y-4" + phx-target={@myself} + phx-change="validate" + phx-submit="save" + > + <%= unless @changeset.valid? do %> +
    + <%= changeset_errors(@changeset) %> +
    + <% end %> + + <%= label(f, :count, gettext("Shots fired"), class: "title text-lg text-primary-500") %> + <%= number_input(f, :count, + min: 1, + max: @shot_group.count + @ammo_group.count, + class: "input input-primary col-span-2" + ) %> + <%= error_tag(f, :count, "col-span-3") %> + + <%= label(f, :notes, gettext("Notes"), class: "title text-lg text-primary-500") %> + <%= textarea(f, :notes, + class: "input input-primary col-span-2", + phx_hook: "MaintainAttrs" + ) %> + <%= error_tag(f, :notes, "col-span-3") %> + + <%= label(f, :date, gettext("Date (UTC)"), class: "title text-lg text-primary-500") %> + <%= date_input(f, :date, class: "input input-primary col-span-2") %> + <%= error_tag(f, :notes, "col-span-3") %> + + <%= submit(dgettext("actions", "Save"), + class: "mx-auto btn btn-primary col-span-3", + phx_disable_with: dgettext("prompts", "Saving...") + ) %> + +
    diff --git a/lib/cannery_web/live/range_live/index.ex b/lib/cannery_web/live/range_live/index.ex new file mode 100644 index 0000000..cabded5 --- /dev/null +++ b/lib/cannery_web/live/range_live/index.ex @@ -0,0 +1,82 @@ +defmodule CanneryWeb.RangeLive.Index do + @moduledoc """ + Main page for range day mode, where `AmmoGroup`s can be used up. + """ + + use CanneryWeb, :live_view + import CanneryWeb.Components.AmmoGroupCard + alias Cannery.{ActivityLog, ActivityLog.ShotGroup, Ammo, Repo} + alias CanneryWeb.Endpoint + alias Phoenix.LiveView.Socket + + @impl true + def mount(_params, session, socket) do + {:ok, socket |> assign_defaults(session) |> display_shot_groups()} + end + + @impl true + def handle_params(params, _url, %{assigns: %{live_action: live_action}} = socket) do + {:noreply, apply_action(socket, live_action, params)} + end + + defp apply_action( + %{assigns: %{current_user: current_user}} = socket, + :add_shot_group, + %{"id" => id} + ) do + socket + |> assign(:page_title, gettext("Record shots")) + |> assign(:ammo_group, Ammo.get_ammo_group!(id, current_user)) + end + + defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"id" => id}) do + socket + |> assign(:page_title, gettext("Edit Shot Records")) + |> assign(:shot_group, ActivityLog.get_shot_group!(id, current_user)) + end + + defp apply_action(socket, :new, _params) do + socket + |> assign(:page_title, gettext("New Shot Records")) + |> assign(:shot_group, %ShotGroup{}) + end + + defp apply_action(socket, :index, _params) do + socket + |> assign(:page_title, gettext("Shot Records")) + |> assign(:shot_group, nil) + end + + @impl true + def handle_event("delete", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do + {:ok, _} = + ActivityLog.get_shot_group!(id, current_user) + |> ActivityLog.delete_shot_group(current_user) + + prompt = dgettext("prompts", "Shot records deleted succesfully") + {:noreply, socket |> put_flash(:info, prompt) |> display_shot_groups()} + end + + def handle_event( + "toggle_staged", + %{"ammo_group_id" => ammo_group_id}, + %{assigns: %{current_user: current_user}} = socket + ) do + ammo_group = Ammo.get_ammo_group!(ammo_group_id, current_user) + + {:ok, _ammo_group} = + ammo_group |> Ammo.update_ammo_group(%{"staged" => !ammo_group.staged}, current_user) + + prompt = dgettext("prompts", "Ammo group unstaged succesfully") + {:noreply, socket |> put_flash(:info, prompt) |> display_shot_groups()} + end + + @spec display_shot_groups(Socket.t()) :: Socket.t() + defp display_shot_groups(%{assigns: %{current_user: current_user}} = socket) do + shot_groups = + ActivityLog.list_shot_groups(current_user) |> Repo.preload(ammo_group: :ammo_type) + + ammo_groups = Ammo.list_staged_ammo_groups(current_user) + socket |> assign(shot_groups: shot_groups, ammo_groups: ammo_groups) + end +end diff --git a/lib/cannery_web/live/range_live/index.html.heex b/lib/cannery_web/live/range_live/index.html.heex new file mode 100644 index 0000000..1854696 --- /dev/null +++ b/lib/cannery_web/live/range_live/index.html.heex @@ -0,0 +1,135 @@ +
    +

    + <%= gettext("Range day") %> +

    + + <%= if @ammo_groups |> Enum.empty?() do %> +

    + <%= gettext("No ammo staged") %> 😔 +

    + + <%= live_patch(dgettext("actions", "Why not get some ready to shoot?"), + to: Routes.ammo_group_index_path(Endpoint, :index), + class: "btn btn-primary" + ) %> + <% else %> + <%= live_patch(dgettext("actions", "Stage ammo"), + to: Routes.ammo_group_index_path(Endpoint, :index), + class: "btn btn-primary" + ) %> + + <%= for ammo_group <- @ammo_groups do %> + <.ammo_group_card ammo_group={ammo_group}> + + + <%= live_patch(dgettext("actions", "Record shots"), + to: Routes.range_index_path(Endpoint, :add_shot_group, ammo_group), + class: "btn btn-primary" + ) %> + + <% end %> + <% end %> + +
    + + <%= if @shot_groups |> Enum.empty?() do %> +

    + <%= gettext("No shots recorded") %> 😔 +

    + <% else %> +
    +
    + <%= gettext("Ammo type") %> + <%= gettext("Count") %> <%= gettext("Notes") %> + <%= gettext("Staging") %> + + <%= gettext("Container") %> +
    + <%= live_patch(ammo_group.ammo_type.name, + to: Routes.ammo_type_show_path(Endpoint, :show, ammo_group.ammo_type), + class: "link" + ) %> + <%= ammo_group.count %> + + + <%= if ammo_group.container, do: ammo_group.container.name %> + - <%= live_redirect(dgettext("actions", "View"), - to: Routes.ammo_group_show_path(@socket, :show, ammo_group) - ) %> +
    + <%= live_redirect to: Routes.ammo_group_show_path(@socket, :show, ammo_group), + class: "text-primary-500 link" do %> + + <% end %> - <%= live_patch to: Routes.ammo_group_index_path(@socket, :edit, ammo_group), - class: "text-primary-500 link" do %> - - <% end %> + <%= live_patch to: Routes.ammo_group_index_path(@socket, :edit, ammo_group), + class: "text-primary-500 link" do %> + + <% end %> - <%= link to: "#", - class: "text-primary-500 link", - phx_click: "delete", - phx_value_id: ammo_group.id, - data: [confirm: dgettext("prompts", "Are you sure you want to delete this ammo?")] do %> - - <% end %> + <%= link to: "#", + class: "text-primary-500 link", + phx_click: "delete", + phx_value_id: ammo_group.id, + data: [confirm: dgettext("prompts", "Are you sure you want to delete this ammo?")] do %> + + <% end %> +
    + + + + + + + + + + + + <%= for shot_group <- @shot_groups do %> + + + + + + + + + <% end %> + +
    + <%= gettext("Ammo") %> + + <%= gettext("Rounds shot") %> + + <%= gettext("Notes") %> + + <%= gettext("Date") %> +
    + <%= live_patch(shot_group.ammo_group.ammo_type.name, + to: Routes.ammo_group_show_path(Endpoint, :show, shot_group.ammo_group), + class: "link" + ) %> + + <%= shot_group.count %> + + <%= shot_group.notes %> + + <%= shot_group.date |> display_date() %> + + <%= live_patch to: Routes.range_index_path(Endpoint, :edit, shot_group), + class: "text-primary-500 link" do %> + + <% end %> + + <%= link to: "#", + class: "text-primary-500 link", + phx_click: "delete", + phx_value_id: shot_group.id, + data: [confirm: dgettext("prompts", "Are you sure you want to delete this shot record?")] do %> + + <% end %> +
    + + <% end %> + + +<%= if @live_action in [:edit] do %> + <.modal return_to={Routes.range_index_path(Endpoint, :index)}> + <.live_component + module={CanneryWeb.RangeLive.FormComponent} + id={@shot_group.id} + title={@page_title} + action={@live_action} + shot_group={@shot_group} + return_to={Routes.range_index_path(Endpoint, :index)} + current_user={@current_user} + /> + +<% end %> + +<%= if @live_action in [:add_shot_group] do %> + <.modal return_to={Routes.range_index_path(Endpoint, :index)}> + <.live_component + module={CanneryWeb.Components.AddShotGroupComponent} + id={:new} + title={@page_title} + action={@live_action} + ammo_group={@ammo_group} + return_to={Routes.range_index_path(Endpoint, :index)} + current_user={@current_user} + /> + +<% end %> diff --git a/lib/cannery_web/router.ex b/lib/cannery_web/router.ex index f1f4dc7..858aa6b 100644 --- a/lib/cannery_web/router.ex +++ b/lib/cannery_web/router.ex @@ -72,9 +72,15 @@ defmodule CanneryWeb.Router do live "/ammo_groups", AmmoGroupLive.Index, :index live "/ammo_groups/new", AmmoGroupLive.Index, :new live "/ammo_groups/:id/edit", AmmoGroupLive.Index, :edit + live "/ammo_groups/:id/add_shot_group", AmmoGroupLive.Index, :add_shot_group live "/ammo_groups/:id", AmmoGroupLive.Show, :show live "/ammo_groups/:id/show/edit", AmmoGroupLive.Show, :edit + live "/ammo_groups/:id/show/add_shot_group", AmmoGroupLive.Show, :add_shot_group + + live "/range", RangeLive.Index, :index + live "/range/:id/edit", RangeLive.Index, :edit + live "/range/:id/add_shot_group", RangeLive.Index, :add_shot_group end scope "/", CanneryWeb do diff --git a/priv/gettext/actions.pot b/priv/gettext/actions.pot index 2ca00e1..ff9abd1 100644 --- a/priv/gettext/actions.pot +++ b/priv/gettext/actions.pot @@ -124,10 +124,12 @@ msgid "Reset password" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/form_component.ex:102 -#: lib/cannery_web/live/ammo_type_live/form_component.ex:161 -#: lib/cannery_web/live/container_live/form_component.ex:90 -#: lib/cannery_web/live/invite_live/form_component.ex:63 +#: lib/cannery_web/components/add_shot_group_component.html.heex:42 +#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:54 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:122 +#: lib/cannery_web/live/container_live/form_component.html.heex:50 +#: lib/cannery_web/live/invite_live/form_component.html.heex:28 +#: lib/cannery_web/live/range_live/form_component.html.heex:40 #: lib/cannery_web/live/tag_live/form_component.ex:66 msgid "Save" msgstr "" @@ -137,17 +139,32 @@ msgstr "" msgid "Send instructions to reset password" msgstr "" -#, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/index.html.heex:56 -msgid "View" -msgstr "" - #, elixir-format, ex-autogen #: lib/cannery_web/live/container_live/show.html.heex:50 msgid "Why not add one?" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/container_live/add_tag_component.ex:66 +#: lib/cannery_web/live/container_live/add_tag_component.html.heex:17 msgid "Add" msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.html.heex:16 +msgid "Stage ammo" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.html.heex:11 +msgid "Why not get some ready to shoot?" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.html.heex:33 +msgid "Record shots" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/ammo_group_live/show.html.heex:27 +msgid "Ammo Details" +msgstr "" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 4b79033..a26288b 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -32,11 +32,14 @@ msgstr "" #, elixir-format, ex-autogen #: lib/cannery_web/components/topbar.ex:47 +#: lib/cannery_web/live/ammo_group_live/index.html.heex:3 +#: lib/cannery_web/live/range_live/index.html.heex:53 msgid "Ammo" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/form_component.ex:69 +#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:21 +#: lib/cannery_web/live/ammo_group_live/index.html.heex:26 msgid "Ammo type" msgstr "" @@ -51,70 +54,72 @@ msgid "Background color" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:145 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:106 #: lib/cannery_web/live/ammo_type_live/index.html.heex:38 msgid "Blank" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:107 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:68 msgid "Brass" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:83 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:44 #: lib/cannery_web/live/ammo_type_live/index.html.heex:28 #: lib/cannery_web/live/ammo_type_live/show.html.heex:37 msgid "Bullet core" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:76 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:37 #: lib/cannery_web/live/ammo_type_live/index.html.heex:27 #: lib/cannery_web/live/ammo_type_live/show.html.heex:36 msgid "Bullet type" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:97 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:58 #: lib/cannery_web/live/ammo_type_live/index.html.heex:30 #: lib/cannery_web/live/ammo_type_live/show.html.heex:39 msgid "Caliber" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:90 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:51 #: lib/cannery_web/live/ammo_type_live/index.html.heex:29 #: lib/cannery_web/live/ammo_type_live/show.html.heex:38 msgid "Cartridge" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:104 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:65 #: lib/cannery_web/live/ammo_type_live/index.html.heex:31 #: lib/cannery_web/live/ammo_type_live/show.html.heex:40 msgid "Case material" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/form_component.ex:96 +#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:48 +#: lib/cannery_web/live/ammo_group_live/index.html.heex:41 msgid "Container" msgstr "" #, elixir-format, ex-autogen #: lib/cannery_web/components/topbar.ex:41 +#: lib/cannery_web/live/container_live/index.html.heex:3 msgid "Containers" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:149 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:110 #: lib/cannery_web/live/ammo_type_live/index.html.heex:39 msgid "Corrosive" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/form_component.ex:75 -#: lib/cannery_web/live/ammo_group_live/index.html.heex:26 +#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:27 +#: lib/cannery_web/live/ammo_group_live/index.html.heex:29 msgid "Count" msgstr "" @@ -125,8 +130,8 @@ msgid "Count:" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:63 -#: lib/cannery_web/live/container_live/form_component.ex:67 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:24 +#: lib/cannery_web/live/container_live/form_component.html.heex:27 msgid "Description" msgstr "" @@ -148,7 +153,7 @@ msgstr "" #, elixir-format, ex-autogen #: lib/cannery_web/live/ammo_group_live/index.ex:22 -#: lib/cannery_web/live/ammo_group_live/show.ex:40 +#: lib/cannery_web/live/ammo_group_live/show.ex:68 msgid "Edit Ammo group" msgstr "" @@ -180,24 +185,24 @@ msgid "Enable" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:74 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:35 msgid "Example bullet type abbreviations" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:79 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:40 msgid "FMJ" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:111 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:72 #: lib/cannery_web/live/ammo_type_live/index.html.heex:32 #: lib/cannery_web/live/ammo_type_live/show.html.heex:41 msgid "Grains" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:141 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:102 #: lib/cannery_web/live/ammo_type_live/index.html.heex:37 msgid "Incendiary" msgstr "" @@ -219,6 +224,7 @@ msgstr "" #, elixir-format, ex-autogen #: lib/cannery_web/components/topbar.ex:66 +#: lib/cannery_web/live/invite_live/index.html.heex:3 msgid "Invites" msgstr "" @@ -227,21 +233,6 @@ msgstr "" msgid "Keep me logged in for 60 days" msgstr "" -#, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/index.html.heex:3 -msgid "Listing Ammo" -msgstr "" - -#, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/index.html.heex:3 -msgid "Listing Ammo Types" -msgstr "" - -#, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/index.ex:33 -msgid "Listing Ammo groups" -msgstr "" - #, elixir-format, ex-autogen #: lib/cannery_web/live/ammo_type_live/index.ex:34 msgid "Listing Ammo types" @@ -249,24 +240,21 @@ msgstr "" #, elixir-format, ex-autogen #: lib/cannery_web/live/container_live/index.ex:32 -#: lib/cannery_web/live/container_live/index.html.heex:3 msgid "Listing Containers" msgstr "" #, elixir-format, ex-autogen #: lib/cannery_web/live/invite_live/index.ex:42 -#: lib/cannery_web/live/invite_live/index.html.heex:3 msgid "Listing Invites" msgstr "" #, elixir-format, ex-autogen #: lib/cannery_web/live/tag_live/index.ex:34 -#: lib/cannery_web/live/tag_live/index.html.heex:3 msgid "Listing Tags" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/container_live/form_component.ex:82 +#: lib/cannery_web/live/container_live/form_component.html.heex:42 msgid "Location" msgstr "" @@ -277,7 +265,7 @@ msgid "Location:" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/container_live/form_component.ex:78 +#: lib/cannery_web/live/container_live/form_component.html.heex:38 msgid "Magazine, Clip, Ammo Box, etc" msgstr "" @@ -287,26 +275,26 @@ msgid "Manage" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:153 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:114 #: lib/cannery_web/live/ammo_type_live/index.html.heex:40 msgid "Manufacturer" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/container_live/form_component.ex:71 +#: lib/cannery_web/live/container_live/form_component.html.heex:31 msgid "Metal ammo can with the anime girl sticker" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/container_live/form_component.ex:63 +#: lib/cannery_web/live/container_live/form_component.html.heex:23 msgid "My cool ammo can" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:59 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:20 #: lib/cannery_web/live/ammo_type_live/index.html.heex:26 -#: lib/cannery_web/live/container_live/form_component.ex:60 -#: lib/cannery_web/live/invite_live/form_component.ex:55 +#: lib/cannery_web/live/container_live/form_component.html.heex:20 +#: lib/cannery_web/live/invite_live/form_component.html.heex:20 #: lib/cannery_web/live/tag_live/form_component.ex:50 msgid "Name" msgstr "" @@ -367,8 +355,11 @@ msgid "No tags" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/form_component.ex:89 -#: lib/cannery_web/live/ammo_group_live/index.html.heex:32 +#: lib/cannery_web/components/add_shot_group_component.html.heex:30 +#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:41 +#: lib/cannery_web/live/ammo_group_live/index.html.heex:35 +#: lib/cannery_web/live/range_live/form_component.html.heex:29 +#: lib/cannery_web/live/range_live/index.html.heex:59 msgid "Notes" msgstr "" @@ -379,20 +370,20 @@ msgid "Notes:" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/container_live/form_component.ex:86 +#: lib/cannery_web/live/container_live/form_component.html.heex:46 msgid "On the bookshelf" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:119 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:80 #: lib/cannery_web/live/ammo_type_live/index.html.heex:33 #: lib/cannery_web/live/ammo_type_live/show.html.heex:42 msgid "Pressure" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/form_component.ex:82 -#: lib/cannery_web/live/ammo_group_live/index.html.heex:29 +#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:34 +#: lib/cannery_web/live/ammo_group_live/index.html.heex:32 msgid "Price paid" msgstr "" @@ -403,7 +394,7 @@ msgid "Price paid:" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:126 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:87 #: lib/cannery_web/live/ammo_type_live/index.html.heex:34 #: lib/cannery_web/live/ammo_type_live/show.html.heex:43 msgid "Primer type" @@ -415,13 +406,13 @@ msgid "Public Signups" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:133 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:94 #: lib/cannery_web/live/ammo_type_live/index.html.heex:35 msgid "Rimfire" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:157 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:118 msgid "SKU" msgstr "" @@ -446,7 +437,7 @@ msgid "Settings" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/show.ex:39 +#: lib/cannery_web/live/ammo_group_live/show.ex:67 msgid "Show Ammo group" msgstr "" @@ -471,18 +462,19 @@ msgid "Sku" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:86 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:47 msgid "Steel" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/show.html.heex:45 +#: lib/cannery_web/live/ammo_group_live/show.html.heex:54 msgid "Stored in" msgstr "" #, elixir-format, ex-autogen #: lib/cannery_web/components/topbar.ex:35 #: lib/cannery_web/live/container_live/show.html.heex:57 +#: lib/cannery_web/live/tag_live/index.html.heex:3 msgid "Tags" msgstr "" @@ -502,18 +494,18 @@ msgid "The self-hosted firearm tracker website" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/show.html.heex:50 +#: lib/cannery_web/live/ammo_group_live/show.html.heex:59 msgid "This ammo group is not in a container" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:137 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:98 #: lib/cannery_web/live/ammo_type_live/index.html.heex:36 msgid "Tracer" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/container_live/form_component.ex:75 +#: lib/cannery_web/live/container_live/form_component.html.heex:35 msgid "Type" msgstr "" @@ -534,7 +526,7 @@ msgid "Uses Left:" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/invite_live/form_component.ex:59 +#: lib/cannery_web/live/invite_live/form_component.html.heex:24 msgid "Uses left" msgstr "" @@ -557,3 +549,104 @@ msgstr "" #: lib/cannery_web/live/container_live/show.html.heex:47 msgid "No tags for this container" msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/components/topbar.ex:59 +msgid "Range" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.html.heex:3 +msgid "Range day" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.html.heex:62 +msgid "Date" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/form_component.html.heex:21 +msgid "Shots fired" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.html.heex:8 +msgid "No ammo staged" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/ammo_group_live/index.html.heex:78 +#: lib/cannery_web/live/ammo_group_live/show.html.heex:45 +#: lib/cannery_web/live/range_live/index.html.heex:30 +msgid "Stage for range" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/ammo_group_live/index.html.heex:78 +#: lib/cannery_web/live/ammo_group_live/show.html.heex:45 +#: lib/cannery_web/live/range_live/index.html.heex:30 +msgid "Unstage from range" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/ammo_group_live/index.html.heex:38 +msgid "Staging" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/ammo_group_live/show.ex:27 +msgid "Add Shot group" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/components/add_shot_group_component.html.heex:3 +#: lib/cannery_web/live/range_live/index.ex:28 +msgid "Record shots" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/ammo_type_live/index.html.heex:3 +msgid "Ammo Types" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/ammo_group_live/index.ex:33 +msgid "Ammo groups" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/components/add_shot_group_component.html.heex:38 +#: lib/cannery_web/live/range_live/form_component.html.heex:36 +msgid "Date (UTC)" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.ex:34 +msgid "Edit Shot Records" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.ex:40 +msgid "New Shot Records" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.html.heex:45 +msgid "No shots recorded" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/components/add_shot_group_component.html.heex:21 +msgid "Rounds left" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.html.heex:56 +msgid "Rounds shot" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.ex:46 +msgid "Shot Records" +msgstr "" diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot index 5354836..709485b 100644 --- a/priv/gettext/errors.pot +++ b/priv/gettext/errors.pot @@ -140,3 +140,14 @@ msgstr "" #: lib/cannery_web/live/container_live/add_tag_component.ex:35 msgid "Tag could not be added" msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery/activity_log.ex:125 +msgid "Count must be at least 1" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery/activity_log.ex:73 +#: lib/cannery/activity_log.ex:120 +msgid "Count must be less than %{count}" +msgstr "" diff --git a/priv/gettext/prompts.pot b/priv/gettext/prompts.pot index f167019..03dee4f 100644 --- a/priv/gettext/prompts.pot +++ b/priv/gettext/prompts.pot @@ -11,9 +11,9 @@ msgid "" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:197 -#: lib/cannery_web/live/container_live/form_component.ex:126 -#: lib/cannery_web/live/invite_live/form_component.ex:98 +#: lib/cannery_web/live/ammo_type_live/form_component.ex:64 +#: lib/cannery_web/live/container_live/form_component.ex:65 +#: lib/cannery_web/live/invite_live/form_component.ex:59 #: lib/cannery_web/live/tag_live/form_component.ex:101 msgid "%{name} created successfully" msgstr "" @@ -23,7 +23,7 @@ msgstr "" #: lib/cannery_web/live/ammo_type_live/show.ex:40 #: lib/cannery_web/live/invite_live/index.ex:54 #: lib/cannery_web/live/invite_live/index.ex:120 -#: lib/cannery_web/live/tag_live/index.ex:41 +#: lib/cannery_web/live/tag_live/index.ex:40 msgid "%{name} deleted succesfully" msgstr "" @@ -49,9 +49,9 @@ msgid "%{name} updated succesfully" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_type_live/form_component.ex:179 -#: lib/cannery_web/live/container_live/form_component.ex:108 -#: lib/cannery_web/live/invite_live/form_component.ex:80 +#: lib/cannery_web/live/ammo_type_live/form_component.ex:46 +#: lib/cannery_web/live/container_live/form_component.ex:47 +#: lib/cannery_web/live/invite_live/form_component.ex:41 #: lib/cannery_web/live/tag_live/form_component.ex:83 msgid "%{name} updated successfully" msgstr "" @@ -62,18 +62,18 @@ msgid "A link to confirm your email change has been sent to the new address." msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/form_component.ex:151 +#: lib/cannery_web/live/ammo_group_live/form_component.ex:87 msgid "Ammo group created successfully" msgstr "" #, elixir-format, ex-autogen #: lib/cannery_web/live/ammo_group_live/index.ex:40 -#: lib/cannery_web/live/ammo_group_live/show.ex:33 +#: lib/cannery_web/live/ammo_group_live/show.ex:49 msgid "Ammo group deleted succesfully" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/form_component.ex:133 +#: lib/cannery_web/live/ammo_group_live/form_component.ex:69 msgid "Ammo group updated successfully" msgstr "" @@ -97,8 +97,8 @@ msgid "Are you sure you want to delete the invite for %{name}?" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/index.html.heex:69 -#: lib/cannery_web/live/ammo_group_live/show.html.heex:35 +#: lib/cannery_web/live/ammo_group_live/index.html.heex:102 +#: lib/cannery_web/live/ammo_group_live/show.html.heex:40 #: lib/cannery_web/live/ammo_type_live/index.html.heex:104 msgid "Are you sure you want to delete this ammo?" msgstr "" @@ -159,19 +159,16 @@ msgid "Register to setup %{name}" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/ammo_group_live/form_component.ex:103 -#: lib/cannery_web/live/ammo_type_live/form_component.ex:162 -#: lib/cannery_web/live/container_live/form_component.ex:92 -#: lib/cannery_web/live/invite_live/form_component.ex:65 +#: lib/cannery_web/components/add_shot_group_component.html.heex:44 +#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:55 +#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:123 +#: lib/cannery_web/live/container_live/form_component.html.heex:52 +#: lib/cannery_web/live/invite_live/form_component.html.heex:30 +#: lib/cannery_web/live/range_live/form_component.html.heex:42 #: lib/cannery_web/live/tag_live/form_component.ex:68 msgid "Saving..." msgstr "" -#, elixir-format, ex-autogen -#: lib/cannery_web/controllers/user_confirmation_controller.ex:37 -msgid "User confirmed successfully." -msgstr "" - #, elixir-format, ex-autogen #: lib/cannery_web/controllers/user_settings_controller.ex:78 msgid "Your account has been deleted" @@ -193,6 +190,41 @@ msgid "%{tag_name} has been removed from %{container_name}" msgstr "" #, elixir-format, ex-autogen -#: lib/cannery_web/live/container_live/add_tag_component.ex:68 +#: lib/cannery_web/live/container_live/add_tag_component.html.heex:19 msgid "Adding..." msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/components/add_shot_group_component.ex:68 +msgid "Shots recorded successfully" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.html.heex:28 +msgid "Are you sure you want to unstage this ammo?" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.ex:70 +msgid "Ammo group unstaged succesfully" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.html.heex:97 +msgid "Are you sure you want to delete this shot record?" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/index.ex:56 +msgid "Shot records deleted succesfully" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/live/range_live/form_component.ex:55 +msgid "Shot records updated successfully" +msgstr "" + +#, elixir-format, ex-autogen +#: lib/cannery_web/controllers/user_confirmation_controller.ex:37 +msgid "%{email} confirmed successfully." +msgstr "" diff --git a/priv/repo/migrations/20220214031736_create_shot_groups.exs b/priv/repo/migrations/20220214031736_create_shot_groups.exs new file mode 100644 index 0000000..06c9dd3 --- /dev/null +++ b/priv/repo/migrations/20220214031736_create_shot_groups.exs @@ -0,0 +1,25 @@ +defmodule Cannery.Repo.Migrations.CreateShotGroups do + use Ecto.Migration + + def change do + create table(:shot_groups, primary_key: false) do + add :id, :binary_id, primary_key: true + add :count, :integer + add :notes, :string + add :date, :date + + add :user_id, references(:users, on_delete: :delete_all, type: :binary_id) + add :ammo_group_id, references(:ammo_groups, on_delete: :delete_all, type: :binary_id) + + timestamps() + end + + create index(:shot_groups, [:id]) + create index(:shot_groups, [:user_id]) + create index(:shot_groups, [:ammo_group_id]) + + alter table(:ammo_groups) do + add :staged, :boolean, null: false, default: false + end + end +end diff --git a/test/cannery/activity_log_test.exs b/test/cannery/activity_log_test.exs new file mode 100644 index 0000000..b5439ea --- /dev/null +++ b/test/cannery/activity_log_test.exs @@ -0,0 +1,68 @@ +defmodule Cannery.ActivityLogTest do + use Cannery.DataCase + + alias Cannery.ActivityLog + + describe "shot_groups" do + alias Cannery.ActivityLog.ShotGroup + + import Cannery.ActivityLogFixtures + + @invalid_attrs %{count: nil, date: nil, notes: nil} + + test "list_shot_groups/0 returns all shot_groups" do + shot_group = shot_group_fixture() + assert ActivityLog.list_shot_groups() == [shot_group] + end + + test "get_shot_group!/1 returns the shot_group with given id" do + shot_group = shot_group_fixture() + assert ActivityLog.get_shot_group!(shot_group.id) == shot_group + end + + test "create_shot_group/1 with valid data creates a shot_group" do + valid_attrs = %{count: 42, date: ~N[2022-02-13 03:17:00], notes: "some notes"} + + assert {:ok, %ShotGroup{} = shot_group} = ActivityLog.create_shot_group(valid_attrs) + assert shot_group.count == 42 + assert shot_group.date == ~N[2022-02-13 03:17:00] + assert shot_group.notes == "some notes" + end + + test "create_shot_group/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = ActivityLog.create_shot_group(@invalid_attrs) + end + + test "update_shot_group/2 with valid data updates the shot_group" do + shot_group = shot_group_fixture() + update_attrs = %{count: 43, date: ~N[2022-02-14 03:17:00], notes: "some updated notes"} + + assert {:ok, %ShotGroup{} = shot_group} = + ActivityLog.update_shot_group(shot_group, update_attrs) + + assert shot_group.count == 43 + assert shot_group.date == ~N[2022-02-14 03:17:00] + assert shot_group.notes == "some updated notes" + end + + test "update_shot_group/2 with invalid data returns error changeset" do + shot_group = shot_group_fixture() + + assert {:error, %Ecto.Changeset{}} = + ActivityLog.update_shot_group(shot_group, @invalid_attrs) + + assert shot_group == ActivityLog.get_shot_group!(shot_group.id) + end + + test "delete_shot_group/1 deletes the shot_group" do + shot_group = shot_group_fixture() + assert {:ok, %ShotGroup{}} = ActivityLog.delete_shot_group(shot_group) + assert_raise Ecto.NoResultsError, fn -> ActivityLog.get_shot_group!(shot_group.id) end + end + + test "change_shot_group/1 returns a shot_group changeset" do + shot_group = shot_group_fixture() + assert %Ecto.Changeset{} = ActivityLog.change_shot_group(shot_group) + end + end +end diff --git a/test/cannery_web/live/ammo_group_live_test.exs b/test/cannery_web/live/ammo_group_live_test.exs index cb7903e..886c0fc 100644 --- a/test/cannery_web/live/ammo_group_live_test.exs +++ b/test/cannery_web/live/ammo_group_live_test.exs @@ -24,7 +24,7 @@ defmodule CanneryWeb.AmmoGroupLiveTest do test "lists all ammo_groups", %{conn: conn, ammo_group: ammo_group} do {:ok, _index_live, html} = live(conn, Routes.ammo_group_index_path(conn, :index)) - assert html =~ "Listing Ammo groups" + assert html =~ "Ammo groups" assert html =~ ammo_group.notes end diff --git a/test/cannery_web/live/ammo_type_live_test.exs b/test/cannery_web/live/ammo_type_live_test.exs index 00b5514..6fcaff8 100644 --- a/test/cannery_web/live/ammo_type_live_test.exs +++ b/test/cannery_web/live/ammo_type_live_test.exs @@ -45,7 +45,7 @@ defmodule CanneryWeb.AmmoTypeLiveTest do test "lists all ammo_types", %{conn: conn, ammo_type: ammo_type} do {:ok, _index_live, html} = live(conn, Routes.ammo_type_index_path(conn, :index)) - assert html =~ "Listing Ammo types" + assert html =~ "Ammo types" assert html =~ ammo_type.bullet_type end diff --git a/test/cannery_web/live/container_live_test.exs b/test/cannery_web/live/container_live_test.exs index c1fba86..5aebec2 100644 --- a/test/cannery_web/live/container_live_test.exs +++ b/test/cannery_web/live/container_live_test.exs @@ -38,7 +38,7 @@ defmodule CanneryWeb.ContainerLiveTest do test "lists all containers", %{conn: conn, container: container} do {:ok, _index_live, html} = live(conn, Routes.container_index_path(conn, :index)) - assert html =~ gettext("Listing Containers") + assert html =~ gettext("Containers") assert html =~ container.desc end diff --git a/test/cannery_web/live/invite_live_test.exs b/test/cannery_web/live/invite_live_test.exs index 5d1556d..1b5d7e6 100644 --- a/test/cannery_web/live/invite_live_test.exs +++ b/test/cannery_web/live/invite_live_test.exs @@ -24,7 +24,7 @@ defmodule CanneryWeb.InviteLiveTest do test "lists all invites", %{conn: conn, invite: invite} do {:ok, _index_live, html} = live(conn, Routes.invite_index_path(conn, :index)) - assert html =~ "Listing Invites" + assert html =~ "Invites" assert html =~ invite.name end diff --git a/test/cannery_web/live/shot_group_live_test.exs b/test/cannery_web/live/shot_group_live_test.exs new file mode 100644 index 0000000..6748033 --- /dev/null +++ b/test/cannery_web/live/shot_group_live_test.exs @@ -0,0 +1,122 @@ +defmodule CanneryWeb.ShotGroupLiveTest do + use CanneryWeb.ConnCase + + import Phoenix.LiveViewTest + import Cannery.ActivityLogFixtures + + @create_attrs %{ + count: 42, + date: %{day: 13, hour: 3, minute: 17, month: 2, year: 2022}, + notes: "some notes" + } + @update_attrs %{ + count: 43, + date: %{day: 14, hour: 3, minute: 17, month: 2, year: 2022}, + notes: "some updated notes" + } + @invalid_attrs %{ + count: nil, + date: %{day: 30, hour: 3, minute: 17, month: 2, year: 2022}, + notes: nil + } + + defp create_shot_group(_) do + shot_group = shot_group_fixture() + %{shot_group: shot_group} + end + + describe "Index" do + setup [:create_shot_group] + + test "lists all shot_groups", %{conn: conn, shot_group: shot_group} do + {:ok, _index_live, html} = live(conn, Routes.shot_group_index_path(conn, :index)) + + assert html =~ "Shot records" + assert html =~ shot_group.notes + end + + test "saves new shot_group", %{conn: conn} do + {:ok, index_live, _html} = live(conn, Routes.shot_group_index_path(conn, :index)) + + assert index_live |> element("a", "New Shot group") |> render_click() =~ + "New Shot group" + + assert_patch(index_live, Routes.shot_group_index_path(conn, :new)) + + assert index_live + |> form("#shot_group-form", shot_group: @invalid_attrs) + |> render_change() =~ "is invalid" + + {:ok, _, html} = + index_live + |> form("#shot_group-form", shot_group: @create_attrs) + |> render_submit() + |> follow_redirect(conn, Routes.shot_group_index_path(conn, :index)) + + assert html =~ "Shot group created successfully" + assert html =~ "some notes" + end + + test "updates shot_group in listing", %{conn: conn, shot_group: shot_group} do + {:ok, index_live, _html} = live(conn, Routes.shot_group_index_path(conn, :index)) + + assert index_live |> element("#shot_group-#{shot_group.id} a", "Edit") |> render_click() =~ + "Edit Shot group" + + assert_patch(index_live, Routes.shot_group_index_path(conn, :edit, shot_group)) + + assert index_live + |> form("#shot_group-form", shot_group: @invalid_attrs) + |> render_change() =~ "is invalid" + + {:ok, _, html} = + index_live + |> form("#shot_group-form", shot_group: @update_attrs) + |> render_submit() + |> follow_redirect(conn, Routes.shot_group_index_path(conn, :index)) + + assert html =~ "Shot group updated successfully" + assert html =~ "some updated notes" + end + + test "deletes shot_group in listing", %{conn: conn, shot_group: shot_group} do + {:ok, index_live, _html} = live(conn, Routes.shot_group_index_path(conn, :index)) + + assert index_live |> element("#shot_group-#{shot_group.id} a", "Delete") |> render_click() + refute has_element?(index_live, "#shot_group-#{shot_group.id}") + end + end + + describe "Show" do + setup [:create_shot_group] + + test "displays shot_group", %{conn: conn, shot_group: shot_group} do + {:ok, _show_live, html} = live(conn, Routes.shot_group_show_path(conn, :show, shot_group)) + + assert html =~ "Show Shot group" + assert html =~ shot_group.notes + end + + test "updates shot_group within modal", %{conn: conn, shot_group: shot_group} do + {:ok, show_live, _html} = live(conn, Routes.shot_group_show_path(conn, :show, shot_group)) + + assert show_live |> element("a", "Edit") |> render_click() =~ + "Edit Shot group" + + assert_patch(show_live, Routes.shot_group_show_path(conn, :edit, shot_group)) + + assert show_live + |> form("#shot_group-form", shot_group: @invalid_attrs) + |> render_change() =~ "is invalid" + + {:ok, _, html} = + show_live + |> form("#shot_group-form", shot_group: @update_attrs) + |> render_submit() + |> follow_redirect(conn, Routes.shot_group_show_path(conn, :show, shot_group)) + + assert html =~ "Shot group updated successfully" + assert html =~ "some updated notes" + end + end +end diff --git a/test/cannery_web/live/tag_live_test.exs b/test/cannery_web/live/tag_live_test.exs index 62104f8..77fa94a 100644 --- a/test/cannery_web/live/tag_live_test.exs +++ b/test/cannery_web/live/tag_live_test.exs @@ -36,7 +36,7 @@ defmodule CanneryWeb.TagLiveTest do test "lists all tags", %{conn: conn, tag: tag} do {:ok, _index_live, html} = live(conn, Routes.tag_index_path(conn, :index)) - assert html =~ "Listing Tags" + assert html =~ "Tags" assert html =~ tag.bg_color end diff --git a/test/support/fixtures/activity_log_fixtures.ex b/test/support/fixtures/activity_log_fixtures.ex new file mode 100644 index 0000000..0105b47 --- /dev/null +++ b/test/support/fixtures/activity_log_fixtures.ex @@ -0,0 +1,22 @@ +defmodule Cannery.ActivityLogFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Cannery.ActivityLog` context. + """ + + @doc """ + Generate a shot_group. + """ + def shot_group_fixture(attrs \\ %{}) do + {:ok, shot_group} = + attrs + |> Enum.into(%{ + count: 42, + date: ~N[2022-02-13 03:17:00], + notes: "some notes" + }) + |> Cannery.ActivityLog.create_shot_group() + + shot_group + end +end