add range mode
This commit is contained in:
		
							
								
								
									
										90
									
								
								lib/cannery_web/components/add_shot_group_component.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								lib/cannery_web/components/add_shot_group_component.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -0,0 +1,47 @@ | ||||
| <div> | ||||
|   <h2 class="text-center title text-xl text-primary-500"> | ||||
|     <%= gettext("Record shots") %> | ||||
|   </h2> | ||||
|  | ||||
|   <.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 %> | ||||
|       <div class="invalid-feedback col-span-3 text-center"> | ||||
|         <%= changeset_errors(@changeset) %> | ||||
|       </div> | ||||
|     <% 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...") | ||||
|     ) %> | ||||
|   </.form> | ||||
| </div> | ||||
| @@ -42,6 +42,12 @@ defmodule CanneryWeb.Components.AmmoGroupCard do | ||||
|           </span> | ||||
|         <% end %> | ||||
|       </div> | ||||
|  | ||||
|       <%= if assigns |> Map.has_key?(:inner_block) do %> | ||||
|         <div class="mt-4 flex space-x-4 justify-center items-center"> | ||||
|           <%= render_slot(@inner_block) %> | ||||
|         </div> | ||||
|       <% end %> | ||||
|     </div> | ||||
|     """ | ||||
|   end | ||||
|   | ||||
| @@ -55,6 +55,12 @@ defmodule CanneryWeb.Components.Topbar do | ||||
|                   to: Routes.ammo_group_index_path(Endpoint, :index) | ||||
|                 ) %> | ||||
|               </li> | ||||
|               <li> | ||||
|                 <%= link(gettext("Range"), | ||||
|                   class: "hover:underline", | ||||
|                   to: Routes.range_index_path(Endpoint, :index) | ||||
|                 ) %> | ||||
|               </li> | ||||
|               <%= if @current_user.role == :admin do %> | ||||
|                 <li> | ||||
|                   <%= link(gettext("Invites"), | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -22,6 +22,9 @@ | ||||
|       <table class="min-w-full table-auto text-center bg-white"> | ||||
|         <thead class="border-b border-primary-600"> | ||||
|           <tr> | ||||
|             <th class="p-2"> | ||||
|               <%= gettext("Ammo type") %> | ||||
|             </th> | ||||
|             <th class="p-2"> | ||||
|               <%= gettext("Count") %> | ||||
|             </th> | ||||
| @@ -31,6 +34,12 @@ | ||||
|             <th class="p-2"> | ||||
|               <%= gettext("Notes") %> | ||||
|             </th> | ||||
|             <th class="p-2"> | ||||
|               <%= gettext("Staging") %> | ||||
|             </th> | ||||
|             <th class="p-2"> | ||||
|               <%= gettext("Container") %> | ||||
|             </th> | ||||
|  | ||||
|             <th class="p-2"></th> | ||||
|           </tr> | ||||
| @@ -38,6 +47,13 @@ | ||||
|         <tbody id="ammo_groups"> | ||||
|           <%= for ammo_group <- @ammo_groups do %> | ||||
|             <tr id={"ammo_group-#{ammo_group.id}"}> | ||||
|               <td class="p-2"> | ||||
|                 <%= live_patch(ammo_group.ammo_type.name, | ||||
|                   to: Routes.ammo_type_show_path(Endpoint, :show, ammo_group.ammo_type), | ||||
|                   class: "link" | ||||
|                 ) %> | ||||
|               </td> | ||||
|  | ||||
|               <td class="p-2"> | ||||
|                 <%= ammo_group.count %> | ||||
|               </td> | ||||
| @@ -52,23 +68,41 @@ | ||||
|                 <%= ammo_group.notes %> | ||||
|               </td> | ||||
|  | ||||
|               <td class="p-2"> | ||||
|                 <button | ||||
|                   type="button" | ||||
|                   class="btn btn-primary" | ||||
|                   phx-click="toggle_staged" | ||||
|                   phx-value-ammo_group_id={ammo_group.id} | ||||
|                 > | ||||
|                   <%= if ammo_group.staged, do: gettext("Unstage from range"), else: gettext("Stage for range") %> | ||||
|                 </button> | ||||
|               </td> | ||||
|  | ||||
|               <td class="p-2"> | ||||
|                 <%= if ammo_group.container, do: ammo_group.container.name %> | ||||
|               </td> | ||||
|  | ||||
|               <td class="p-2 w-full h-full space-x-2 flex justify-center items-center"> | ||||
|                 <%= live_redirect(dgettext("actions", "View"), | ||||
|                   to: Routes.ammo_group_show_path(@socket, :show, ammo_group) | ||||
|                 ) %> | ||||
|                 <div class="px-4 py-2 space-x-4 flex justify-center items-center"> | ||||
|                   <%= live_redirect to: Routes.ammo_group_show_path(@socket, :show, ammo_group), | ||||
|                                 class: "text-primary-500 link" do %> | ||||
|                     <i class="fa-fw fa-lg fas fa-eye"></i> | ||||
|                   <% end %> | ||||
|  | ||||
|                 <%= live_patch to: Routes.ammo_group_index_path(@socket, :edit, ammo_group), | ||||
|                            class: "text-primary-500 link" do %> | ||||
|                   <i class="fa-fw fa-lg fas fa-edit"></i> | ||||
|                 <% end %> | ||||
|                   <%= live_patch to: Routes.ammo_group_index_path(@socket, :edit, ammo_group), | ||||
|                              class: "text-primary-500 link" do %> | ||||
|                     <i class="fa-fw fa-lg fas fa-edit"></i> | ||||
|                   <% 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 %> | ||||
|                   <i class="fa-fw fa-lg fas fa-trash"></i> | ||||
|                 <% 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 %> | ||||
|                     <i class="fa-fw fa-lg fas fa-trash"></i> | ||||
|                   <% end %> | ||||
|                 </div> | ||||
|               </td> | ||||
|             </tr> | ||||
|           <% end %> | ||||
| @@ -79,14 +113,28 @@ | ||||
| </div> | ||||
|  | ||||
| <%= 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} | ||||
|     /> | ||||
|   </.modal> | ||||
| <% 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} | ||||
|     /> | ||||
|   </.modal> | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -24,6 +24,11 @@ | ||||
|   </div> | ||||
|  | ||||
|   <div class="flex space-x-4 justify-center items-center text-primary-500"> | ||||
|     <%= 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 %> | ||||
|       <i class="fa-fw fa-lg fas fa-edit"></i> | ||||
| @@ -35,6 +40,10 @@ | ||||
|          data: [confirm: dgettext("prompts", "Are you sure you want to delete this ammo?")] do %> | ||||
|       <i class="fa-fw fa-lg fas fa-trash"></i> | ||||
|     <% end %> | ||||
|  | ||||
|     <button type="button" class="btn btn-primary" phx-click="toggle_staged"> | ||||
|       <%= if @ammo_group.staged, do: gettext("Unstage from range"), else: gettext("Stage for range") %> | ||||
|     </button> | ||||
|   </div> | ||||
|  | ||||
|   <hr class="mb-4 w-full"> | ||||
| @@ -53,14 +62,28 @@ | ||||
| </div> | ||||
|  | ||||
| <%= 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} | ||||
|     /> | ||||
|   </.modal> | ||||
| <% 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} | ||||
|     /> | ||||
|   </.modal> | ||||
|   | ||||
							
								
								
									
										64
									
								
								lib/cannery_web/live/range_live/form_component.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								lib/cannery_web/live/range_live/form_component.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										45
									
								
								lib/cannery_web/live/range_live/form_component.html.heex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/cannery_web/live/range_live/form_component.html.heex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| <div> | ||||
|   <h2 class="text-center title text-xl text-primary-500"> | ||||
|     <%= @title %> | ||||
|   </h2> | ||||
|  | ||||
|   <.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 %> | ||||
|       <div class="invalid-feedback col-span-3 text-center"> | ||||
|         <%= changeset_errors(@changeset) %> | ||||
|       </div> | ||||
|     <% 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...") | ||||
|     ) %> | ||||
|   </.form> | ||||
| </div> | ||||
							
								
								
									
										82
									
								
								lib/cannery_web/live/range_live/index.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								lib/cannery_web/live/range_live/index.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										135
									
								
								lib/cannery_web/live/range_live/index.html.heex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								lib/cannery_web/live/range_live/index.html.heex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| <div class="mx-8 flex flex-col space-y-8 justify-center items-center"> | ||||
|   <h1 class="title text-2xl title-primary-500"> | ||||
|     <%= gettext("Range day") %> | ||||
|   </h1> | ||||
|  | ||||
|   <%= if @ammo_groups |> Enum.empty?() do %> | ||||
|     <h1 class="title text-xl text-primary-500"> | ||||
|       <%= gettext("No ammo staged") %> 😔 | ||||
|     </h1> | ||||
|  | ||||
|     <%= 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}> | ||||
|         <button | ||||
|           type="button" | ||||
|           class="btn btn-primary" | ||||
|           phx-click="toggle_staged" | ||||
|           phx-value-ammo_group_id={ammo_group.id} | ||||
|           data-confirm={"#{dgettext("prompts", "Are you sure you want to unstage this ammo?")}"} | ||||
|         > | ||||
|           <%= if ammo_group.staged, do: gettext("Unstage from range"), else: gettext("Stage for range") %> | ||||
|         </button> | ||||
|  | ||||
|         <%= live_patch(dgettext("actions", "Record shots"), | ||||
|           to: Routes.range_index_path(Endpoint, :add_shot_group, ammo_group), | ||||
|           class: "btn btn-primary" | ||||
|         ) %> | ||||
|       </.ammo_group_card> | ||||
|     <% end %> | ||||
|   <% end %> | ||||
|  | ||||
|   <hr class="hr"> | ||||
|  | ||||
|   <%= if @shot_groups |> Enum.empty?() do %> | ||||
|     <h1 class="title text-xl text-primary-500"> | ||||
|       <%= gettext("No shots recorded") %> 😔 | ||||
|     </h1> | ||||
|   <% else %> | ||||
|     <div class="w-full overflow-x-auto border border-gray-600 rounded-lg shadow-lg bg-black"> | ||||
|       <table class="min-w-full table-auto text-center bg-white"> | ||||
|         <thead class="border-b border-primary-600"> | ||||
|           <tr> | ||||
|             <th class="p-2"> | ||||
|               <%= gettext("Ammo") %> | ||||
|             </th> | ||||
|             <th class="p-2"> | ||||
|               <%= gettext("Rounds shot") %> | ||||
|             </th> | ||||
|             <th class="p-2"> | ||||
|               <%= gettext("Notes") %> | ||||
|             </th> | ||||
|             <th class="p-2"> | ||||
|               <%= gettext("Date") %> | ||||
|             </th> | ||||
|  | ||||
|             <th class="p-2"></th> | ||||
|           </tr> | ||||
|         </thead> | ||||
|         <tbody id="shot_groups"> | ||||
|           <%= for shot_group <- @shot_groups do %> | ||||
|             <tr id={"shot_group-#{shot_group.id}"}> | ||||
|               <td class="p-2"> | ||||
|                 <%= live_patch(shot_group.ammo_group.ammo_type.name, | ||||
|                   to: Routes.ammo_group_show_path(Endpoint, :show, shot_group.ammo_group), | ||||
|                   class: "link" | ||||
|                 ) %> | ||||
|               </td> | ||||
|               <td class="p-2"> | ||||
|                 <%= shot_group.count %> | ||||
|               </td> | ||||
|               <td class="p-2"> | ||||
|                 <%= shot_group.notes %> | ||||
|               </td> | ||||
|               <td class="p-2"> | ||||
|                 <%= shot_group.date |> display_date() %> | ||||
|               </td> | ||||
|  | ||||
|               <td class="p-2 w-full h-full space-x-2 flex justify-center items-center"> | ||||
|                 <%= live_patch to: Routes.range_index_path(Endpoint, :edit, shot_group), | ||||
|                            class: "text-primary-500 link" do %> | ||||
|                   <i class="fa-fw fa-lg fas fa-edit"></i> | ||||
|                 <% 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 %> | ||||
|                   <i class="fa-fw fa-lg fas fa-trash"></i> | ||||
|                 <% end %> | ||||
|               </td> | ||||
|             </tr> | ||||
|           <% end %> | ||||
|         </tbody> | ||||
|       </table> | ||||
|     </div> | ||||
|   <% end %> | ||||
| </div> | ||||
|  | ||||
| <%= 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} | ||||
|     /> | ||||
|   </.modal> | ||||
| <% 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} | ||||
|     /> | ||||
|   </.modal> | ||||
| <% end %> | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user