Compare commits

...

16 Commits

Author SHA1 Message Date
440dc5061b fix textareas resizing when typing in
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-18 22:14:04 -04:00
c0d2c69144 run npm audit fix 2023-03-18 22:02:27 -04:00
7a7359fa66 run npx npm-check-updates -u 2023-03-18 22:01:07 -04:00
9e8fd00d65 add ncu as dev dependency 2023-03-18 21:56:21 -04:00
f5f72b53e6 use hooks for datetime, remove alpinejs 2023-03-18 21:54:57 -04:00
a54cf8b87d use strict context boundaries and remove all n+1 queries
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-18 21:06:50 -04:00
0b7146ba32 fix shot record error message 2023-03-18 01:05:09 -04:00
c0441957b6 use .link helpers 2023-03-18 01:03:55 -04:00
7fa9933a9b use core components 2023-03-18 00:25:18 -04:00
f4c7f22460 fix error message in ga locale
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-17 21:10:34 -04:00
a01d97e360 use better domain for gettexts
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-17 21:00:24 -04:00
pehoulihan
a53b352cf7 Translated using Weblate (Irish)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (14 of 14 strings)

Translation: cannery/emails
Translate-URL: https://weblate.bubbletea.dev/projects/cannery/emails/ga/
2023-03-18 00:52:59 +00:00
ce07cc2569 use live navigation to update state
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-16 17:53:49 -04:00
3acecb9a93 remove extra @impl true
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-16 17:41:25 -04:00
ab8561fcf0 use component macros for live_helper components
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 00:48:07 -04:00
8163b906a2 remove data-qa 2023-03-15 00:47:15 -04:00
126 changed files with 13928 additions and 5339 deletions

View File

@ -1,3 +1,13 @@
# v0.8.4
- Improve accessibility
- Code quality improvements
- Fix dead link of example bullet abbreviations
- Fix inaccurate error message when updating shot records
- Fix tables not sorting dates correctly
- Fix dates displaying incorrectly
- Fix container table not displaying all fields
- Fix textareas resizing when typing in them
# v0.8.3 # v0.8.3
- Improve some styles - Improve some styles
- Improve server log - Improve server log

View File

@ -27,23 +27,15 @@ import { LiveSocket } from 'phoenix_live_view'
import topbar from 'topbar' import topbar from 'topbar'
import MaintainAttrs from './maintain_attrs' import MaintainAttrs from './maintain_attrs'
import ShotLogChart from './shot_log_chart' import ShotLogChart from './shot_log_chart'
import Alpine from 'alpinejs' import Date from './date'
import DateTime from './datetime'
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute('content') const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute('content')
const liveSocket = new LiveSocket('/live', Socket, { const liveSocket = new LiveSocket('/live', Socket, {
dom: {
onBeforeElUpdated (from, to) {
if (from._x_dataStack) { window.Alpine.clone(from, to) }
}
},
params: { _csrf_token: csrfToken }, params: { _csrf_token: csrfToken },
hooks: { MaintainAttrs, ShotLogChart } hooks: { Date, DateTime, MaintainAttrs, ShotLogChart }
}) })
// alpine.js
window.Alpine = Alpine
Alpine.start()
// Show progress bar on live navigation and form submits // Show progress bar on live navigation and form submits
topbar.config({ barColors: { 0: '#29d' }, shadowColor: 'rgba(0, 0, 0, .3)' }) topbar.config({ barColors: { 0: '#29d' }, shadowColor: 'rgba(0, 0, 0, .3)' })
window.addEventListener('phx:page-loading-start', info => topbar.show()) window.addEventListener('phx:page-loading-start', info => topbar.show())

11
assets/js/date.js Normal file
View File

@ -0,0 +1,11 @@
export default {
displayDate (el) {
const date =
Intl.DateTimeFormat([], { timeZone: 'Etc/UTC', dateStyle: 'short' })
.format(new Date(el.dateTime))
el.innerText = date
},
mounted () { this.displayDate(this.el) },
updated () { this.displayDate(this.el) }
}

11
assets/js/datetime.js Normal file
View File

@ -0,0 +1,11 @@
export default {
displayDateTime (el) {
const date =
Intl.DateTimeFormat([], { dateStyle: 'short', timeStyle: 'long' })
.format(new Date(el.dateTime))
el.innerText = date
},
mounted () { this.displayDateTime(this.el) },
updated () { this.displayDateTime(this.el) }
}

9280
assets/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "v18.9.1", "node": "v18.9.1",
"npm": "8.10.0" "npm": "8.19.1"
}, },
"scripts": { "scripts": {
"deploy": "NODE_ENV=production webpack --mode production", "deploy": "NODE_ENV=production webpack --mode production",
@ -13,37 +13,37 @@
"test": "standard" "test": "standard"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.1.1", "@fortawesome/fontawesome-free": "^6.3.0",
"alpinejs": "^3.10.2", "chart.js": "^4.2.1",
"chart.js": "^3.9.1", "chartjs-adapter-date-fns": "^3.0.0",
"chartjs-adapter-date-fns": "^2.0.0",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
"phoenix": "file:../deps/phoenix", "phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html", "phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view", "phoenix_live_view": "file:../deps/phoenix_live_view",
"topbar": "^1.0.1" "topbar": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.10", "@babel/core": "^7.21.3",
"@babel/preset-env": "^7.17.10", "@babel/preset-env": "^7.20.2",
"autoprefixer": "^10.4.7", "autoprefixer": "^10.4.14",
"babel-loader": "^8.2.5", "babel-loader": "^9.1.2",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1", "css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^3.4.1", "css-minimizer-webpack-plugin": "^4.2.2",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"mini-css-extract-plugin": "^2.6.0", "mini-css-extract-plugin": "^2.7.5",
"postcss": "^8.4.13", "npm-check-updates": "^16.7.12",
"postcss-import": "^14.1.0", "postcss": "^8.4.21",
"postcss-loader": "^6.2.1", "postcss-import": "^15.1.0",
"postcss-preset-env": "^7.5.0", "postcss-loader": "^7.1.0",
"sass": "^1.56.0", "postcss-preset-env": "^8.0.1",
"sass-loader": "^12.6.0", "sass": "^1.59.3",
"sass-loader": "^13.2.1",
"standard": "^17.0.0", "standard": "^17.0.0",
"tailwindcss": "^3.0.24", "tailwindcss": "^3.2.7",
"terser-webpack-plugin": "^5.3.1", "terser-webpack-plugin": "^5.3.7",
"webpack": "^5.72.0", "webpack": "^5.76.2",
"webpack-cli": "^4.9.2", "webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.9.0" "webpack-dev-server": "^4.13.1"
} }
} }

View File

@ -385,8 +385,18 @@ defmodule Cannery.Accounts do
""" """
@spec allow_registration?() :: boolean() @spec allow_registration?() :: boolean()
def allow_registration? do def allow_registration? do
Application.get_env(:cannery, Cannery.Accounts)[:registration] == "public" or registration_mode() == :public or list_users_by_role(:admin) |> Enum.empty?()
list_users_by_role(:admin) |> Enum.empty?() end
@doc """
Returns an atom representing the current configured registration mode
"""
@spec registration_mode() :: :public | :invite_only
def registration_mode do
case Application.get_env(:cannery, Cannery.Accounts)[:registration] do
"public" -> :public
_other -> :invite_only
end
end end
@doc """ @doc """

View File

@ -100,13 +100,23 @@ defmodule Cannery.Accounts.Invites do
end end
end end
@spec get_use_count(Invite.t(), User.t()) :: non_neg_integer() @spec get_use_count(Invite.t(), User.t()) :: non_neg_integer() | nil
def get_use_count(%Invite{id: invite_id}, %User{role: :admin}) do def get_use_count(%Invite{id: invite_id} = invite, user) do
Repo.one( [invite] |> get_use_counts(user) |> Map.get(invite_id)
end
@spec get_use_counts([Invite.t()], User.t()) ::
%{optional(Invite.id()) => non_neg_integer()}
def get_use_counts(invites, %User{role: :admin}) do
invite_ids = invites |> Enum.map(fn %{id: invite_id} -> invite_id end)
Repo.all(
from u in User, from u in User,
where: u.invite_id == ^invite_id, where: u.invite_id in ^invite_ids,
select: count(u.id) group_by: u.invite_id,
select: {u.invite_id, count(u.id)}
) )
|> Map.new()
end end
@spec decrement_invite_changeset(Invite.t()) :: Invite.changeset() @spec decrement_invite_changeset(Invite.t()) :: Invite.changeset()

View File

@ -4,7 +4,8 @@ defmodule Cannery.ActivityLog do
""" """
import Ecto.Query, warn: false import Ecto.Query, warn: false
alias Cannery.{Accounts.User, ActivityLog.ShotGroup, Ammo.AmmoGroup, Repo} alias Cannery.Ammo.{AmmoGroup, AmmoType}
alias Cannery.{Accounts.User, ActivityLog.ShotGroup, Repo}
alias Ecto.Multi alias Ecto.Multi
@doc """ @doc """
@ -31,8 +32,10 @@ defmodule Cannery.ActivityLog do
Repo.all( Repo.all(
from sg in ShotGroup, from sg in ShotGroup,
left_join: ag in assoc(sg, :ammo_group), left_join: ag in AmmoGroup,
left_join: at in assoc(ag, :ammo_type), on: sg.ammo_group_id == ag.id,
left_join: at in AmmoType,
on: ag.ammo_type_id == at.id,
where: sg.user_id == ^user_id, where: sg.user_id == ^user_id,
where: where:
fragment( fragment(
@ -61,6 +64,18 @@ defmodule Cannery.ActivityLog do
) )
end end
@spec list_shot_groups_for_ammo_group(AmmoGroup.t(), User.t()) :: [ShotGroup.t()]
def list_shot_groups_for_ammo_group(
%AmmoGroup{id: ammo_group_id, user_id: user_id},
%User{id: user_id}
) do
Repo.all(
from sg in ShotGroup,
where: sg.ammo_group_id == ^ammo_group_id,
where: sg.user_id == ^user_id
)
end
@doc """ @doc """
Gets a single shot_group. Gets a single shot_group.
@ -107,9 +122,15 @@ defmodule Cannery.ActivityLog do
) )
|> Multi.run( |> Multi.run(
:ammo_group, :ammo_group,
fn repo, %{create_shot_group: %{ammo_group_id: ammo_group_id, user_id: user_id}} -> fn _repo, %{create_shot_group: %{ammo_group_id: ammo_group_id, user_id: user_id}} ->
{:ok, ammo_group =
repo.one(from ag in AmmoGroup, where: ag.id == ^ammo_group_id and ag.user_id == ^user_id)} Repo.one(
from ag in AmmoGroup,
where: ag.id == ^ammo_group_id,
where: ag.user_id == ^user_id
)
{:ok, ammo_group}
end end
) )
|> Multi.update( |> Multi.update(
@ -220,4 +241,112 @@ defmodule Cannery.ActivityLog do
{:error, _other_transaction, _value, _changes_so_far} -> {:error, nil} {:error, _other_transaction, _value, _changes_so_far} -> {:error, nil}
end end
end end
@doc """
Returns the number of shot rounds for an ammo group
"""
@spec get_used_count(AmmoGroup.t(), User.t()) :: non_neg_integer()
def get_used_count(%AmmoGroup{id: ammo_group_id} = ammo_group, user) do
[ammo_group]
|> get_used_counts(user)
|> Map.get(ammo_group_id, 0)
end
@doc """
Returns the number of shot rounds for multiple ammo groups
"""
@spec get_used_counts([AmmoGroup.t()], User.t()) ::
%{optional(AmmoGroup.id()) => non_neg_integer()}
def get_used_counts(ammo_groups, %User{id: user_id}) do
ammo_group_ids =
ammo_groups
|> Enum.map(fn %{id: ammo_group_id} -> ammo_group_id end)
Repo.all(
from sg in ShotGroup,
where: sg.ammo_group_id in ^ammo_group_ids,
where: sg.user_id == ^user_id,
group_by: sg.ammo_group_id,
select: {sg.ammo_group_id, sum(sg.count)}
)
|> Map.new()
end
@doc """
Returns the last entered shot group date for an ammo group
"""
@spec get_last_used_date(AmmoGroup.t(), User.t()) :: Date.t() | nil
def get_last_used_date(%AmmoGroup{id: ammo_group_id} = ammo_group, user) do
[ammo_group]
|> get_last_used_dates(user)
|> Map.get(ammo_group_id)
end
@doc """
Returns the last entered shot group date for an ammo group
"""
@spec get_last_used_dates([AmmoGroup.t()], User.t()) :: %{optional(AmmoGroup.id()) => Date.t()}
def get_last_used_dates(ammo_groups, %User{id: user_id}) do
ammo_group_ids =
ammo_groups
|> Enum.map(fn %AmmoGroup{id: ammo_group_id, user_id: ^user_id} -> ammo_group_id end)
Repo.all(
from sg in ShotGroup,
where: sg.ammo_group_id in ^ammo_group_ids,
where: sg.user_id == ^user_id,
group_by: sg.ammo_group_id,
select: {sg.ammo_group_id, max(sg.date)}
)
|> Map.new()
end
@doc """
Gets the total number of rounds shot for an ammo type
Raises `Ecto.NoResultsError` if the Ammo type does not exist.
## Examples
iex> get_used_count_for_ammo_type(123, %User{id: 123})
35
iex> get_used_count_for_ammo_type(456, %User{id: 123})
** (Ecto.NoResultsError)
"""
@spec get_used_count_for_ammo_type(AmmoType.t(), User.t()) :: non_neg_integer()
def get_used_count_for_ammo_type(%AmmoType{id: ammo_type_id} = ammo_type, user) do
[ammo_type]
|> get_used_count_for_ammo_types(user)
|> Map.get(ammo_type_id, 0)
end
@doc """
Gets the total number of rounds shot for multiple ammo types
## Examples
iex> get_used_count_for_ammo_types(123, %User{id: 123})
35
"""
@spec get_used_count_for_ammo_types([AmmoType.t()], User.t()) ::
%{optional(AmmoType.id()) => non_neg_integer()}
def get_used_count_for_ammo_types(ammo_types, %User{id: user_id}) do
ammo_type_ids =
ammo_types
|> Enum.map(fn %AmmoType{id: ammo_type_id, user_id: ^user_id} -> ammo_type_id end)
Repo.all(
from ag in AmmoGroup,
left_join: sg in ShotGroup,
on: ag.id == sg.ammo_group_id,
where: ag.ammo_type_id in ^ammo_type_ids,
where: not (sg.count |> is_nil()),
group_by: ag.ammo_type_id,
select: {ag.ammo_type_id, sum(sg.count)}
)
|> Map.new()
end
end end

View File

@ -6,7 +6,7 @@ defmodule Cannery.ActivityLog.ShotGroup do
use Ecto.Schema use Ecto.Schema
import CanneryWeb.Gettext import CanneryWeb.Gettext
import Ecto.Changeset import Ecto.Changeset
alias Cannery.{Accounts.User, ActivityLog.ShotGroup, Ammo.AmmoGroup, Repo} alias Cannery.{Accounts.User, Ammo, Ammo.AmmoGroup}
alias Ecto.{Changeset, UUID} alias Ecto.{Changeset, UUID}
@derive {Jason.Encoder, @derive {Jason.Encoder,
@ -24,25 +24,23 @@ defmodule Cannery.ActivityLog.ShotGroup do
field :date, :date field :date, :date
field :notes, :string field :notes, :string
belongs_to :user, User field :user_id, :binary_id
belongs_to :ammo_group, AmmoGroup field :ammo_group_id, :binary_id
timestamps() timestamps()
end end
@type t :: %ShotGroup{ @type t :: %__MODULE__{
id: id(), id: id(),
count: integer, count: integer,
notes: String.t() | nil, notes: String.t() | nil,
date: Date.t() | nil, date: Date.t() | nil,
ammo_group: AmmoGroup.t() | nil,
ammo_group_id: AmmoGroup.id(), ammo_group_id: AmmoGroup.id(),
user: User.t() | nil,
user_id: User.id(), user_id: User.id(),
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_shot_group :: %ShotGroup{} @type new_shot_group :: %__MODULE__{}
@type id :: UUID.t() @type id :: UUID.t()
@type changeset :: Changeset.t(t() | new_shot_group()) @type changeset :: Changeset.t(t() | new_shot_group())
@ -58,42 +56,47 @@ defmodule Cannery.ActivityLog.ShotGroup do
%User{id: user_id}, %User{id: user_id},
%AmmoGroup{id: ammo_group_id, user_id: user_id} = ammo_group, %AmmoGroup{id: ammo_group_id, user_id: user_id} = ammo_group,
attrs attrs
) ) do
when not (user_id |> is_nil()) and not (ammo_group_id |> is_nil()) do
shot_group shot_group
|> change(user_id: user_id) |> change(user_id: user_id)
|> change(ammo_group_id: ammo_group_id) |> change(ammo_group_id: ammo_group_id)
|> cast(attrs, [:count, :notes, :date]) |> cast(attrs, [:count, :notes, :date])
|> validate_number(:count, greater_than: 0)
|> validate_create_shot_group_count(ammo_group) |> validate_create_shot_group_count(ammo_group)
|> validate_required([:count, :date, :ammo_group_id, :user_id]) |> validate_required([:date, :ammo_group_id, :user_id])
end end
def create_changeset(shot_group, _invalid_user, _invalid_ammo_group, attrs) do def create_changeset(shot_group, _invalid_user, _invalid_ammo_group, attrs) do
shot_group shot_group
|> cast(attrs, [:count, :notes, :date]) |> cast(attrs, [:count, :notes, :date])
|> validate_number(:count, greater_than: 0) |> validate_required([:ammo_group_id, :user_id])
|> validate_required([:count, :ammo_group_id, :user_id])
|> add_error(:invalid, dgettext("errors", "Please select a valid user and ammo pack")) |> add_error(:invalid, dgettext("errors", "Please select a valid user and ammo pack"))
end end
defp validate_create_shot_group_count(changeset, %AmmoGroup{count: ammo_group_count}) do defp validate_create_shot_group_count(changeset, %AmmoGroup{count: ammo_group_count}) do
if changeset |> Changeset.get_field(:count) > ammo_group_count do case changeset |> Changeset.get_field(:count) do
error = dgettext("errors", "Count must be less than %{count}", count: ammo_group_count) nil ->
changeset |> Changeset.add_error(:count, error) changeset |> Changeset.add_error(:ammo_left, dgettext("errors", "can't be blank"))
else
count when count > ammo_group_count ->
changeset
|> Changeset.add_error(:ammo_left, dgettext("errors", "Ammo left must be at least 0"))
count when count <= 0 ->
error =
dgettext("errors", "Ammo left can be at most %{count} rounds",
count: ammo_group_count - 1
)
changeset |> Changeset.add_error(:ammo_left, error)
_valid_count ->
changeset changeset
end end
end end
@doc false @doc false
@spec update_changeset(t() | new_shot_group(), User.t(), attrs :: map()) :: changeset() @spec update_changeset(t() | new_shot_group(), User.t(), attrs :: map()) :: changeset()
def update_changeset( def update_changeset(%__MODULE__{} = shot_group, user, attrs) do
%ShotGroup{user_id: user_id} = shot_group,
%User{id: user_id} = user,
attrs
)
when not (user_id |> is_nil()) do
shot_group shot_group
|> cast(attrs, [:count, :notes, :date]) |> cast(attrs, [:count, :notes, :date])
|> validate_number(:count, greater_than: 0) |> validate_number(:count, greater_than: 0)
@ -103,25 +106,20 @@ defmodule Cannery.ActivityLog.ShotGroup do
defp validate_update_shot_group_count( defp validate_update_shot_group_count(
changeset, changeset,
%ShotGroup{count: count} = shot_group, %__MODULE__{ammo_group_id: ammo_group_id, count: count},
%User{id: user_id} user
) ) do
when not (user_id |> is_nil()) do %{count: ammo_group_count} = Ammo.get_ammo_group!(ammo_group_id, user)
%{ammo_group: %AmmoGroup{count: ammo_group_count, user_id: ^user_id}} =
shot_group |> Repo.preload(:ammo_group)
new_shot_group_count = changeset |> Changeset.get_field(:count) new_shot_group_count = changeset |> Changeset.get_field(:count)
shot_diff_to_add = new_shot_group_count - count shot_diff_to_add = new_shot_group_count - count
cond do if shot_diff_to_add > ammo_group_count do
shot_diff_to_add > ammo_group_count -> error =
error = dgettext("errors", "Count must be less than %{count}", count: ammo_group_count) dgettext("errors", "Count can be at most %{count} shots", count: ammo_group_count + count)
changeset |> Changeset.add_error(:count, error) changeset |> Changeset.add_error(:count, error)
else
new_shot_group_count <= 0 ->
changeset |> Changeset.add_error(:count, dgettext("errors", "Count must be at least 1"))
true ->
changeset changeset
end end
end end

File diff suppressed because it is too large Load Diff

View File

@ -9,8 +9,8 @@ defmodule Cannery.Ammo.AmmoGroup do
use Ecto.Schema use Ecto.Schema
import CanneryWeb.Gettext import CanneryWeb.Gettext
import Ecto.Changeset import Ecto.Changeset
alias Cannery.Ammo.{AmmoGroup, AmmoType} alias Cannery.Ammo.AmmoType
alias Cannery.{Accounts.User, ActivityLog.ShotGroup, Containers, Containers.Container} alias Cannery.{Accounts.User, Containers, Containers.Container}
alias Ecto.{Changeset, UUID} alias Ecto.{Changeset, UUID}
@derive {Jason.Encoder, @derive {Jason.Encoder,
@ -33,15 +33,13 @@ defmodule Cannery.Ammo.AmmoGroup do
field :purchased_on, :date field :purchased_on, :date
belongs_to :ammo_type, AmmoType belongs_to :ammo_type, AmmoType
belongs_to :container, Container field :container_id, :binary_id
belongs_to :user, User field :user_id, :binary_id
has_many :shot_groups, ShotGroup
timestamps() timestamps()
end end
@type t :: %AmmoGroup{ @type t :: %__MODULE__{
id: id(), id: id(),
count: integer, count: integer,
notes: String.t() | nil, notes: String.t() | nil,
@ -50,14 +48,12 @@ defmodule Cannery.Ammo.AmmoGroup do
purchased_on: Date.t(), purchased_on: Date.t(),
ammo_type: AmmoType.t() | nil, ammo_type: AmmoType.t() | nil,
ammo_type_id: AmmoType.id(), ammo_type_id: AmmoType.id(),
container: Container.t() | nil,
container_id: Container.id(), container_id: Container.id(),
user: User.t() | nil,
user_id: User.id(), user_id: User.id(),
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_ammo_group :: %AmmoGroup{} @type new_ammo_group :: %__MODULE__{}
@type id :: UUID.t() @type id :: UUID.t()
@type changeset :: Changeset.t(t() | new_ammo_group()) @type changeset :: Changeset.t(t() | new_ammo_group())
@ -76,8 +72,7 @@ defmodule Cannery.Ammo.AmmoGroup do
%User{id: user_id}, %User{id: user_id},
attrs attrs
) )
when not (ammo_type_id |> is_nil()) and not (container_id |> is_nil()) and when is_binary(ammo_type_id) and is_binary(container_id) and is_binary(user_id) do
not (user_id |> is_nil()) do
ammo_group ammo_group
|> change(ammo_type_id: ammo_type_id) |> change(ammo_type_id: ammo_type_id)
|> change(user_id: user_id) |> change(user_id: user_id)

View File

@ -8,7 +8,7 @@ defmodule Cannery.Ammo.AmmoType do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Cannery.Accounts.User alias Cannery.Accounts.User
alias Cannery.Ammo.{AmmoGroup, AmmoType} alias Cannery.Ammo.AmmoGroup
alias Ecto.{Changeset, UUID} alias Ecto.{Changeset, UUID}
@derive {Jason.Encoder, @derive {Jason.Encoder,
@ -64,14 +64,14 @@ defmodule Cannery.Ammo.AmmoType do
field :manufacturer, :string field :manufacturer, :string
field :upc, :string field :upc, :string
belongs_to :user, User field :user_id, :binary_id
has_many :ammo_groups, AmmoGroup has_many :ammo_groups, AmmoGroup
timestamps() timestamps()
end end
@type t :: %AmmoType{ @type t :: %__MODULE__{
id: id(), id: id(),
name: String.t(), name: String.t(),
desc: String.t() | nil, desc: String.t() | nil,
@ -95,12 +95,11 @@ defmodule Cannery.Ammo.AmmoType do
manufacturer: String.t() | nil, manufacturer: String.t() | nil,
upc: String.t() | nil, upc: String.t() | nil,
user_id: User.id(), user_id: User.id(),
user: User.t() | nil,
ammo_groups: [AmmoGroup.t()] | nil, ammo_groups: [AmmoGroup.t()] | nil,
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_ammo_type :: %AmmoType{} @type new_ammo_type :: %__MODULE__{}
@type id :: UUID.t() @type id :: UUID.t()
@type changeset :: Changeset.t(t() | new_ammo_type()) @type changeset :: Changeset.t(t() | new_ammo_type())

View File

@ -5,10 +5,12 @@ defmodule Cannery.Containers do
import CanneryWeb.Gettext import CanneryWeb.Gettext
import Ecto.Query, warn: false import Ecto.Query, warn: false
alias Cannery.{Accounts.User, Ammo.AmmoGroup, Repo, Tags.Tag} alias Cannery.{Accounts.User, Ammo.AmmoGroup, Repo}
alias Cannery.Containers.{Container, ContainerTag} alias Cannery.Containers.{Container, ContainerTag, Tag}
alias Ecto.Changeset alias Ecto.Changeset
@container_preloads [:tags]
@doc """ @doc """
Returns the list of containers. Returns the list of containers.
@ -28,11 +30,9 @@ defmodule Cannery.Containers do
as: :c, as: :c,
left_join: t in assoc(c, :tags), left_join: t in assoc(c, :tags),
as: :t, as: :t,
left_join: ag in assoc(c, :ammo_groups),
as: :ag,
where: c.user_id == ^user_id, where: c.user_id == ^user_id,
order_by: c.name, order_by: c.name,
preload: [tags: t, ammo_groups: ag] preload: ^@container_preloads
) )
|> list_containers_search(search) |> list_containers_search(search)
|> Repo.all() |> Repo.all()
@ -106,12 +106,10 @@ defmodule Cannery.Containers do
def get_container!(id, %User{id: user_id}) do def get_container!(id, %User{id: user_id}) do
Repo.one!( Repo.one!(
from c in Container, from c in Container,
left_join: t in assoc(c, :tags),
left_join: ag in assoc(c, :ammo_groups),
where: c.user_id == ^user_id, where: c.user_id == ^user_id,
where: c.id == ^id, where: c.id == ^id,
order_by: c.name, order_by: c.name,
preload: [tags: t, ammo_groups: ag] preload: ^@container_preloads
) )
end end
@ -130,7 +128,19 @@ defmodule Cannery.Containers do
@spec create_container(attrs :: map(), User.t()) :: @spec create_container(attrs :: map(), User.t()) ::
{:ok, Container.t()} | {:error, Container.changeset()} {:ok, Container.t()} | {:error, Container.changeset()}
def create_container(attrs, %User{} = user) do def create_container(attrs, %User{} = user) do
%Container{} |> Container.create_changeset(user, attrs) |> Repo.insert() %Container{}
|> Container.create_changeset(user, attrs)
|> Repo.insert()
|> case do
{:ok, container} -> {:ok, container |> preload_container()}
{:error, changeset} -> {:error, changeset}
end
end
@spec preload_container(Container.t()) :: Container.t()
@spec preload_container([Container.t()]) :: [Container.t()]
def preload_container(container) do
container |> Repo.preload(@container_preloads)
end end
@doc """ @doc """
@ -148,7 +158,13 @@ defmodule Cannery.Containers do
@spec update_container(Container.t(), User.t(), attrs :: map()) :: @spec update_container(Container.t(), User.t(), attrs :: map()) ::
{:ok, Container.t()} | {:error, Container.changeset()} {:ok, Container.t()} | {:error, Container.changeset()}
def update_container(%Container{user_id: user_id} = container, %User{id: user_id}, attrs) do def update_container(%Container{user_id: user_id} = container, %User{id: user_id}, attrs) do
container |> Container.update_changeset(attrs) |> Repo.update() container
|> Container.update_changeset(attrs)
|> Repo.update()
|> case do
{:ok, container} -> {:ok, container |> preload_container()}
{:error, changeset} -> {:error, changeset}
end
end end
@doc """ @doc """
@ -173,7 +189,12 @@ defmodule Cannery.Containers do
) )
|> case do |> case do
0 -> 0 ->
container |> Repo.delete() container
|> Repo.delete()
|> case do
{:ok, container} -> {:ok, container |> preload_container()}
{:error, changeset} -> {:error, changeset}
end
_amount -> _amount ->
error = dgettext("errors", "Container must be empty before deleting") error = dgettext("errors", "Container must be empty before deleting")
@ -214,8 +235,11 @@ defmodule Cannery.Containers do
%Container{user_id: user_id} = container, %Container{user_id: user_id} = container,
%Tag{user_id: user_id} = tag, %Tag{user_id: user_id} = tag,
%User{id: user_id} %User{id: user_id}
), ) do
do: %ContainerTag{} |> ContainerTag.create_changeset(tag, container) |> Repo.insert!() %ContainerTag{}
|> ContainerTag.create_changeset(tag, container)
|> Repo.insert!()
end
@doc """ @doc """
Removes a tag from a container Removes a tag from a container
@ -226,45 +250,175 @@ defmodule Cannery.Containers do
%Container{} %Container{}
""" """
@spec remove_tag!(Container.t(), Tag.t(), User.t()) :: non_neg_integer() @spec remove_tag!(Container.t(), Tag.t(), User.t()) :: {non_neg_integer(), [ContainerTag.t()]}
def remove_tag!( def remove_tag!(
%Container{id: container_id, user_id: user_id}, %Container{id: container_id, user_id: user_id},
%Tag{id: tag_id, user_id: user_id}, %Tag{id: tag_id, user_id: user_id},
%User{id: user_id} %User{id: user_id}
) do ) do
{count, _} = {count, results} =
Repo.delete_all( Repo.delete_all(
from ct in ContainerTag, from ct in ContainerTag,
where: ct.container_id == ^container_id, where: ct.container_id == ^container_id,
where: ct.tag_id == ^tag_id where: ct.tag_id == ^tag_id,
select: ct
) )
if count == 0, do: raise("could not delete container tag"), else: count if count == 0, do: raise("could not delete container tag"), else: {count, results}
end
# Container Tags
@doc """
Returns the list of tags.
## Examples
iex> list_tags(%User{id: 123})
[%Tag{}, ...]
iex> list_tags("cool", %User{id: 123})
[%Tag{name: "my cool tag"}, ...]
"""
@spec list_tags(User.t()) :: [Tag.t()]
@spec list_tags(search :: nil | String.t(), User.t()) :: [Tag.t()]
def list_tags(search \\ nil, user)
def list_tags(search, %{id: user_id}) when search |> is_nil() or search == "",
do: Repo.all(from t in Tag, where: t.user_id == ^user_id, order_by: t.name)
def list_tags(search, %{id: user_id}) when search |> is_binary() do
trimmed_search = String.trim(search)
Repo.all(
from t in Tag,
where: t.user_id == ^user_id,
where:
fragment(
"? @@ websearch_to_tsquery('english', ?)",
t.search,
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(?, websearch_to_tsquery('english', ?), 4)",
t.search,
^trimmed_search
)
}
)
end end
@doc """ @doc """
Returns number of rounds in container. If data is already preloaded, then Gets a single tag.
there will be no db hit.
## Examples
iex> get_tag(123, %User{id: 123})
{:ok, %Tag{}}
iex> get_tag(456, %User{id: 123})
{:error, :not_found}
""" """
@spec get_container_ammo_group_count!(Container.t()) :: non_neg_integer() @spec get_tag(Tag.id(), User.t()) :: {:ok, Tag.t()} | {:error, :not_found}
def get_container_ammo_group_count!(%Container{} = container) do def get_tag(id, %User{id: user_id}) do
container Repo.one(from t in Tag, where: t.id == ^id and t.user_id == ^user_id)
|> Repo.preload(:ammo_groups) |> case do
|> Map.fetch!(:ammo_groups) nil -> {:error, :not_found}
|> Enum.reject(fn %{count: count} -> count == 0 end) tag -> {:ok, tag}
|> Enum.count() end
end end
@doc """ @doc """
Returns number of rounds in container. If data is already preloaded, then Gets a single tag.
there will be no db hit.
Raises `Ecto.NoResultsError` if the Tag does not exist.
## Examples
iex> get_tag!(123, %User{id: 123})
%Tag{}
iex> get_tag!(456, %User{id: 123})
** (Ecto.NoResultsError)
""" """
@spec get_container_rounds!(Container.t()) :: non_neg_integer() @spec get_tag!(Tag.id(), User.t()) :: Tag.t()
def get_container_rounds!(%Container{} = container) do def get_tag!(id, %User{id: user_id}) do
container Repo.one!(
|> Repo.preload(:ammo_groups) from t in Tag,
|> Map.fetch!(:ammo_groups) where: t.id == ^id,
|> Enum.map(fn %{count: count} -> count end) where: t.user_id == ^user_id
|> Enum.sum() )
end
@doc """
Creates a tag.
## Examples
iex> create_tag(%{field: value}, %User{id: 123})
{:ok, %Tag{}}
iex> create_tag(%{field: bad_value}, %User{id: 123})
{:error, %Changeset{}}
"""
@spec create_tag(attrs :: map(), User.t()) ::
{:ok, Tag.t()} | {:error, Tag.changeset()}
def create_tag(attrs, %User{} = user) do
%Tag{} |> Tag.create_changeset(user, attrs) |> Repo.insert()
end
@doc """
Updates a tag.
## Examples
iex> update_tag(tag, %{field: new_value}, %User{id: 123})
{:ok, %Tag{}}
iex> update_tag(tag, %{field: bad_value}, %User{id: 123})
{:error, %Changeset{}}
"""
@spec update_tag(Tag.t(), attrs :: map(), User.t()) ::
{:ok, Tag.t()} | {:error, Tag.changeset()}
def update_tag(%Tag{user_id: user_id} = tag, attrs, %User{id: user_id}) do
tag |> Tag.update_changeset(attrs) |> Repo.update()
end
@doc """
Deletes a tag.
## Examples
iex> delete_tag(tag, %User{id: 123})
{:ok, %Tag{}}
iex> delete_tag(tag, %User{id: 123})
{:error, %Changeset{}}
"""
@spec delete_tag(Tag.t(), User.t()) :: {:ok, Tag.t()} | {:error, Tag.changeset()}
def delete_tag(%Tag{user_id: user_id} = tag, %User{id: user_id}) do
tag |> Repo.delete()
end
@doc """
Deletes a tag.
## Examples
iex> delete_tag!(tag, %User{id: 123})
%Tag{}
"""
@spec delete_tag!(Tag.t(), User.t()) :: Tag.t()
def delete_tag!(%Tag{user_id: user_id} = tag, %User{id: user_id}) do
tag |> Repo.delete!()
end end
end end

View File

@ -6,8 +6,7 @@ defmodule Cannery.Containers.Container do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Ecto.{Changeset, UUID} alias Ecto.{Changeset, UUID}
alias Cannery.Containers.{Container, ContainerTag} alias Cannery.{Accounts.User, Containers.ContainerTag, Containers.Tag}
alias Cannery.{Accounts.User, Ammo.AmmoGroup, Tags.Tag}
@derive {Jason.Encoder, @derive {Jason.Encoder,
only: [ only: [
@ -26,28 +25,25 @@ defmodule Cannery.Containers.Container do
field :location, :string field :location, :string
field :type, :string field :type, :string
belongs_to :user, User field :user_id, :binary_id
has_many :ammo_groups, AmmoGroup
many_to_many :tags, Tag, join_through: ContainerTag many_to_many :tags, Tag, join_through: ContainerTag
timestamps() timestamps()
end end
@type t :: %Container{ @type t :: %__MODULE__{
id: id(), id: id(),
name: String.t(), name: String.t(),
desc: String.t(), desc: String.t(),
location: String.t(), location: String.t(),
type: String.t(), type: String.t(),
user: User.t(),
user_id: User.id(), user_id: User.id(),
ammo_groups: [AmmoGroup.t()] | nil,
tags: [Tag.t()] | nil, tags: [Tag.t()] | nil,
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_container :: %Container{} @type new_container :: %__MODULE__{}
@type id :: UUID.t() @type id :: UUID.t()
@type changeset :: Changeset.t(t() | new_container()) @type changeset :: Changeset.t(t() | new_container())

View File

@ -1,12 +1,12 @@
defmodule Cannery.Containers.ContainerTag do defmodule Cannery.Containers.ContainerTag do
@moduledoc """ @moduledoc """
Thru-table struct for associating Cannery.Containers.Container and Thru-table struct for associating Cannery.Containers.Container and
Cannery.Tags.Tag. Cannery.Containers.Tag.
""" """
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Cannery.{Containers.Container, Containers.ContainerTag, Tags.Tag} alias Cannery.Containers.{Container, Tag}
alias Ecto.{Changeset, UUID} alias Ecto.{Changeset, UUID}
@primary_key {:id, :binary_id, autogenerate: true} @primary_key {:id, :binary_id, autogenerate: true}
@ -18,7 +18,7 @@ defmodule Cannery.Containers.ContainerTag do
timestamps() timestamps()
end end
@type t :: %ContainerTag{ @type t :: %__MODULE__{
id: id(), id: id(),
container: Container.t(), container: Container.t(),
container_id: Container.id(), container_id: Container.id(),
@ -27,7 +27,7 @@ defmodule Cannery.Containers.ContainerTag do
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_container_tag :: %ContainerTag{} @type new_container_tag :: %__MODULE__{}
@type id :: UUID.t() @type id :: UUID.t()
@type changeset :: Changeset.t(t() | new_container_tag()) @type changeset :: Changeset.t(t() | new_container_tag())

View File

@ -1,4 +1,4 @@
defmodule Cannery.Tags.Tag do defmodule Cannery.Containers.Tag do
@moduledoc """ @moduledoc """
Tags are added to containers to help organize, and can include custom-defined Tags are added to containers to help organize, and can include custom-defined
text and bg colors. text and bg colors.
@ -6,8 +6,8 @@ defmodule Cannery.Tags.Tag do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Cannery.Accounts.User
alias Ecto.{Changeset, UUID} alias Ecto.{Changeset, UUID}
alias Cannery.{Accounts.User, Tags.Tag}
@derive {Jason.Encoder, @derive {Jason.Encoder,
only: [ only: [
@ -23,22 +23,21 @@ defmodule Cannery.Tags.Tag do
field :bg_color, :string field :bg_color, :string
field :text_color, :string field :text_color, :string
belongs_to :user, User field :user_id, :binary_id
timestamps() timestamps()
end end
@type t :: %Tag{ @type t :: %__MODULE__{
id: id(), id: id(),
name: String.t(), name: String.t(),
bg_color: String.t(), bg_color: String.t(),
text_color: String.t(), text_color: String.t(),
user: User.t() | nil,
user_id: User.id(), user_id: User.id(),
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_tag() :: %Tag{} @type new_tag() :: %__MODULE__{}
@type id() :: UUID.t() @type id() :: UUID.t()
@type changeset() :: Changeset.t(t() | new_tag()) @type changeset() :: Changeset.t(t() | new_tag())

View File

@ -1,149 +0,0 @@
defmodule Cannery.Tags do
@moduledoc """
The Tags context.
"""
import Ecto.Query, warn: false
import CanneryWeb.Gettext
alias Cannery.{Accounts.User, Repo, Tags.Tag}
@doc """
Returns the list of tags.
## Examples
iex> list_tags(%User{id: 123})
[%Tag{}, ...]
iex> list_tags("cool", %User{id: 123})
[%Tag{name: "my cool tag"}, ...]
"""
@spec list_tags(User.t()) :: [Tag.t()]
@spec list_tags(search :: nil | String.t(), User.t()) :: [Tag.t()]
def list_tags(search \\ nil, user)
def list_tags(search, %{id: user_id}) when search |> is_nil() or search == "",
do: Repo.all(from t in Tag, where: t.user_id == ^user_id, order_by: t.name)
def list_tags(search, %{id: user_id}) when search |> is_binary() do
trimmed_search = String.trim(search)
Repo.all(
from t in Tag,
where: t.user_id == ^user_id,
where:
fragment(
"search @@ websearch_to_tsquery('english', ?)",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, websearch_to_tsquery('english', ?), 4)",
^trimmed_search
)
}
)
end
@doc """
Gets a single tag.
## Examples
iex> get_tag(123, %User{id: 123})
{:ok, %Tag{}}
iex> get_tag(456, %User{id: 123})
{:error, "tag not found"}
"""
@spec get_tag(Tag.id(), User.t()) :: {:ok, Tag.t()} | {:error, String.t()}
def get_tag(id, %User{id: user_id}) do
Repo.one(from t in Tag, where: t.id == ^id and t.user_id == ^user_id)
|> case do
nil -> {:error, dgettext("errors", "Tag not found")}
tag -> {:ok, tag}
end
end
@doc """
Gets a single tag.
Raises `Ecto.NoResultsError` if the Tag does not exist.
## Examples
iex> get_tag!(123, %User{id: 123})
%Tag{}
iex> get_tag!(456, %User{id: 123})
** (Ecto.NoResultsError)
"""
@spec get_tag!(Tag.id(), User.t()) :: Tag.t()
def get_tag!(id, %User{id: user_id}),
do: Repo.one!(from t in Tag, where: t.id == ^id and t.user_id == ^user_id)
@doc """
Creates a tag.
## Examples
iex> create_tag(%{field: value}, %User{id: 123})
{:ok, %Tag{}}
iex> create_tag(%{field: bad_value}, %User{id: 123})
{:error, %Changeset{}}
"""
@spec create_tag(attrs :: map(), User.t()) ::
{:ok, Tag.t()} | {:error, Tag.changeset()}
def create_tag(attrs, %User{} = user),
do: %Tag{} |> Tag.create_changeset(user, attrs) |> Repo.insert()
@doc """
Updates a tag.
## Examples
iex> update_tag(tag, %{field: new_value}, %User{id: 123})
{:ok, %Tag{}}
iex> update_tag(tag, %{field: bad_value}, %User{id: 123})
{:error, %Changeset{}}
"""
@spec update_tag(Tag.t(), attrs :: map(), User.t()) ::
{:ok, Tag.t()} | {:error, Tag.changeset()}
def update_tag(%Tag{user_id: user_id} = tag, attrs, %User{id: user_id}),
do: tag |> Tag.update_changeset(attrs) |> Repo.update()
@doc """
Deletes a tag.
## Examples
iex> delete_tag(tag, %User{id: 123})
{:ok, %Tag{}}
iex> delete_tag(tag, %User{id: 123})
{:error, %Changeset{}}
"""
@spec delete_tag(Tag.t(), User.t()) :: {:ok, Tag.t()} | {:error, Tag.changeset()}
def delete_tag(%Tag{user_id: user_id} = tag, %User{id: user_id}), do: tag |> Repo.delete()
@doc """
Deletes a tag.
## Examples
iex> delete_tag!(tag, %User{id: 123})
%Tag{}
"""
@spec delete_tag!(Tag.t(), User.t()) :: Tag.t()
def delete_tag!(%Tag{user_id: user_id} = tag, %User{id: user_id}), do: tag |> Repo.delete!()
end

View File

@ -44,8 +44,7 @@ defmodule CanneryWeb do
def live_view do def live_view do
quote do quote do
use Phoenix.LiveView, use Phoenix.LiveView, layout: {CanneryWeb.LayoutView, :live}
layout: {CanneryWeb.LayoutView, "live.html"}
on_mount CanneryWeb.InitAssigns on_mount CanneryWeb.InitAssigns
unquote(view_helpers()) unquote(view_helpers())
@ -94,7 +93,7 @@ defmodule CanneryWeb do
# Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
# Import basic rendering functionality (render, render_layout, etc) # Import basic rendering functionality (render, render_layout, etc)
import CanneryWeb.{ErrorHelpers, Gettext, LiveHelpers, ViewHelpers} import CanneryWeb.{ErrorHelpers, Gettext, CoreComponents, ViewHelpers}
import Phoenix.{Component, View} import Phoenix.{Component, View}
alias CanneryWeb.Endpoint alias CanneryWeb.Endpoint

View File

@ -5,6 +5,7 @@ defmodule CanneryWeb.Components.AddShotGroupComponent do
use CanneryWeb, :live_component use CanneryWeb, :live_component
alias Cannery.{Accounts.User, ActivityLog, ActivityLog.ShotGroup, Ammo.AmmoGroup} alias Cannery.{Accounts.User, ActivityLog, ActivityLog.ShotGroup, Ammo.AmmoGroup}
alias Ecto.Changeset
alias Phoenix.LiveView.{JS, Socket} alias Phoenix.LiveView.{JS, Socket}
@impl true @impl true
@ -18,7 +19,7 @@ defmodule CanneryWeb.Components.AddShotGroupComponent do
) :: {:ok, Socket.t()} ) :: {:ok, Socket.t()}
def update(%{ammo_group: ammo_group, current_user: current_user} = assigns, socket) do def update(%{ammo_group: ammo_group, current_user: current_user} = assigns, socket) do
changeset = changeset =
%ShotGroup{date: NaiveDateTime.utc_now(), count: 1} %ShotGroup{date: Date.utc_today()}
|> ShotGroup.create_changeset(current_user, ammo_group, %{}) |> ShotGroup.create_changeset(current_user, ammo_group, %{})
{:ok, socket |> assign(assigns) |> assign(:changeset, changeset)} {:ok, socket |> assign(assigns) |> assign(:changeset, changeset)}
@ -32,10 +33,13 @@ defmodule CanneryWeb.Components.AddShotGroupComponent do
) do ) do
params = shot_group_params |> process_params(ammo_group) params = shot_group_params |> process_params(ammo_group)
changeset = %ShotGroup{} |> ShotGroup.create_changeset(current_user, ammo_group, params)
changeset = changeset =
%ShotGroup{} case changeset |> Changeset.apply_action(:validate) do
|> ShotGroup.create_changeset(current_user, ammo_group, params) {:ok, _data} -> changeset
|> Map.put(:action, :validate) {:error, changeset} -> changeset
end
{:noreply, socket |> assign(:changeset, changeset)} {:noreply, socket |> assign(:changeset, changeset)}
end end
@ -56,7 +60,7 @@ defmodule CanneryWeb.Components.AddShotGroupComponent do
prompt = dgettext("prompts", "Shots recorded successfully") prompt = dgettext("prompts", "Shots recorded successfully")
socket |> put_flash(:info, prompt) |> push_navigate(to: return_to) socket |> put_flash(:info, prompt) |> push_navigate(to: return_to)
{:error, %Ecto.Changeset{} = changeset} -> {:error, %Changeset{} = changeset} ->
socket |> assign(changeset: changeset) socket |> assign(changeset: changeset)
end end
@ -65,14 +69,14 @@ defmodule CanneryWeb.Components.AddShotGroupComponent do
# calculate count from shots left # calculate count from shots left
defp process_params(params, %AmmoGroup{count: count}) do defp process_params(params, %AmmoGroup{count: count}) do
new_count = shot_group_count =
if params |> Map.get("ammo_left", "0") == "" do if params |> Map.get("ammo_left", "") == "" do
"0" nil
else else
params |> Map.get("ammo_left", "0") new_count = params |> Map.get("ammo_left") |> String.to_integer()
count - new_count
end end
|> String.to_integer()
params |> Map.put("count", count - new_count) params |> Map.put("count", shot_group_count)
end end
end end

View File

@ -37,9 +37,11 @@
<%= label(f, :notes, gettext("Notes"), class: "title text-lg text-primary-600") %> <%= label(f, :notes, gettext("Notes"), class: "title text-lg text-primary-600") %>
<%= textarea(f, :notes, <%= textarea(f, :notes,
id: "add-shot-group-form-notes",
class: "input input-primary col-span-2", class: "input input-primary col-span-2",
placeholder: "Really great weather", placeholder: gettext("Really great weather"),
phx_hook: "MaintainAttrs" phx_hook: "MaintainAttrs",
phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :notes, "col-span-3") %> <%= error_tag(f, :notes, "col-span-3") %>

View File

@ -1,103 +0,0 @@
defmodule CanneryWeb.Components.AmmoGroupCard do
@moduledoc """
Display card for an ammo group
"""
use CanneryWeb, :component
alias Cannery.{Ammo, Ammo.AmmoGroup, Repo}
alias CanneryWeb.Endpoint
attr :ammo_group, AmmoGroup, required: true
attr :show_container, :boolean, default: false
slot(:inner_block)
def ammo_group_card(%{ammo_group: ammo_group} = assigns) do
assigns =
%{show_container: show_container} = assigns |> assign_new(:show_container, fn -> false end)
preloads = if show_container, do: [:ammo_type, :container], else: [:ammo_type]
ammo_group = ammo_group |> Repo.preload(preloads)
assigns = assigns |> assign(:ammo_group, ammo_group)
~H"""
<div
id={"ammo_group-#{@ammo_group.id}"}
class="mx-4 my-2 px-8 py-4
flex flex-col justify-center items-center
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out"
>
<.link navigate={Routes.ammo_group_show_path(Endpoint, :show, @ammo_group)} class="mb-2 link">
<h1 class="title text-xl title-primary-500">
<%= @ammo_group.ammo_type.name %>
</h1>
</.link>
<div class="flex flex-col justify-center items-center">
<span class="rounded-lg title text-lg">
<%= gettext("Count:") %>
<%= if @ammo_group.count == 0, do: gettext("Empty"), else: @ammo_group.count %>
</span>
<span
:if={@ammo_group |> Ammo.get_original_count() != @ammo_group.count}
class="rounded-lg title text-lg"
>
<%= gettext("Original Count:") %>
<%= @ammo_group |> Ammo.get_original_count() %>
</span>
<span :if={@ammo_group.notes} class="rounded-lg title text-lg">
<%= gettext("Notes:") %>
<%= @ammo_group.notes %>
</span>
<span class="rounded-lg title text-lg">
<%= gettext("Purchased on:") %>
<.date date={@ammo_group.purchased_on} />
</span>
<span :if={@ammo_group |> Ammo.get_last_used_shot_group()} class="rounded-lg title text-lg">
<%= gettext("Last used on:") %>
<.date date={@ammo_group |> Ammo.get_last_used_shot_group() |> Map.get(:date)} />
</span>
<%= if @ammo_group.price_paid do %>
<span class="rounded-lg title text-lg">
<%= gettext("Price paid:") %>
<%= gettext("$%{amount}",
amount: @ammo_group.price_paid |> :erlang.float_to_binary(decimals: 2)
) %>
</span>
<span class="rounded-lg title text-lg">
<%= gettext("CPR:") %>
<%= gettext("$%{amount}",
amount: @ammo_group |> Ammo.get_cpr() |> :erlang.float_to_binary(decimals: 2)
) %>
</span>
<% end %>
<span :if={@show_container and @ammo_group.container} class="rounded-lg title text-lg">
<%= gettext("Container:") %>
<.link
navigate={Routes.container_show_path(Endpoint, :show, @ammo_group.container)}
class="link"
>
<%= @ammo_group.container.name %>
</.link>
</span>
</div>
<div
:if={assigns |> Map.has_key?(:inner_block)}
class="mt-4 flex space-x-4 justify-center items-center"
>
<%= render_slot(@inner_block) %>
</div>
</div>
"""
end
end

View File

@ -3,7 +3,7 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do
A component that displays a list of ammo groups A component that displays a list of ammo groups
""" """
use CanneryWeb, :live_component use CanneryWeb, :live_component
alias Cannery.{Accounts.User, Ammo, Ammo.AmmoGroup, Repo} alias Cannery.{Accounts.User, ActivityLog, Ammo, Ammo.AmmoGroup, Containers}
alias Ecto.UUID alias Ecto.UUID
alias Phoenix.LiveView.{Rendered, Socket} alias Phoenix.LiveView.{Rendered, Socket}
@ -54,8 +54,8 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do
end end
columns = [ columns = [
%{label: gettext("Purchased on"), key: :purchased_on}, %{label: gettext("Purchased on"), key: :purchased_on, type: Date},
%{label: gettext("Last used on"), key: :used_up_on} | columns %{label: gettext("Last used on"), key: :used_up_on, type: Date} | columns
] ]
columns = columns =
@ -94,13 +94,15 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do
ammo_type: ammo_type, ammo_type: ammo_type,
columns: columns, columns: columns,
container: container, container: container,
original_counts: Ammo.get_original_counts(ammo_groups, current_user),
cprs: Ammo.get_cprs(ammo_groups, current_user),
last_used_dates: ActivityLog.get_last_used_dates(ammo_groups, current_user),
actions: actions, actions: actions,
range: range range: range
} }
rows = rows =
ammo_groups ammo_groups
|> Repo.preload([:ammo_type, :container])
|> Enum.map(fn ammo_group -> |> Enum.map(fn ammo_group ->
ammo_group |> get_row_data_for_ammo_group(extra_data) ammo_group |> get_row_data_for_ammo_group(extra_data)
end) end)
@ -124,8 +126,6 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do
@spec get_row_data_for_ammo_group(AmmoGroup.t(), additional_data :: map()) :: map() @spec get_row_data_for_ammo_group(AmmoGroup.t(), additional_data :: map()) :: map()
defp get_row_data_for_ammo_group(ammo_group, %{columns: columns} = additional_data) do defp get_row_data_for_ammo_group(ammo_group, %{columns: columns} = additional_data) do
ammo_group = ammo_group |> Repo.preload([:ammo_type, :container])
columns columns
|> Map.new(fn %{key: key} -> |> Map.new(fn %{key: key} ->
{key, get_value_for_key(key, ammo_group, additional_data)} {key, get_value_for_key(key, ammo_group, additional_data)}
@ -150,30 +150,23 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do
defp get_value_for_key(:price_paid, %{price_paid: nil}, _additional_data), do: {"", nil} defp get_value_for_key(:price_paid, %{price_paid: nil}, _additional_data), do: {"", nil}
defp get_value_for_key(:price_paid, %{price_paid: price_paid}, _additional_data), defp get_value_for_key(:price_paid, %{price_paid: price_paid}, _additional_data),
do: gettext("$%{amount}", amount: price_paid |> :erlang.float_to_binary(decimals: 2)) do: gettext("$%{amount}", amount: display_currency(price_paid))
defp get_value_for_key(:purchased_on, %{purchased_on: purchased_on}, _additional_data) do
assigns = %{purchased_on: purchased_on}
defp get_value_for_key(:purchased_on, %{purchased_on: purchased_on} = assigns, _additional_data) do
{purchased_on, {purchased_on,
~H""" ~H"""
<.date date={@purchased_on} /> <.date id={"#{@id}-purchased-on"} date={@purchased_on} />
"""} """}
end end
defp get_value_for_key(:used_up_on, ammo_group, _additional_data) do defp get_value_for_key(:used_up_on, %{id: ammo_group_id}, %{last_used_dates: last_used_dates}) do
last_shot_group_date = last_used_date = last_used_dates |> Map.get(ammo_group_id)
case ammo_group |> Ammo.get_last_used_shot_group() do assigns = %{id: ammo_group_id, last_used_date: last_used_date}
%{date: last_shot_group_date} -> last_shot_group_date
_no_shot_groups -> nil
end
assigns = %{last_shot_group_date: last_shot_group_date} {last_used_date,
{last_shot_group_date,
~H""" ~H"""
<%= if @last_shot_group_date do %> <%= if @last_used_date do %>
<.date date={@last_shot_group_date} /> <.date id={"#{@id}-last-used-date"} date={@last_used_date} />
<% else %> <% else %>
<%= gettext("Never used") %> <%= gettext("Never used") %>
<% end %> <% end %>
@ -189,8 +182,11 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do
"""} """}
end end
defp get_value_for_key(:remaining, ammo_group, _additional_data), defp get_value_for_key(:remaining, ammo_group, %{current_user: current_user}),
do: gettext("%{percentage}%", percentage: ammo_group |> Ammo.get_percentage_remaining()) do:
gettext("%{percentage}%",
percentage: ammo_group |> Ammo.get_percentage_remaining(current_user)
)
defp get_value_for_key(:actions, ammo_group, %{actions: actions}) do defp get_value_for_key(:actions, ammo_group, %{actions: actions}) do
assigns = %{actions: actions, ammo_group: ammo_group} assigns = %{actions: actions, ammo_group: ammo_group}
@ -204,31 +200,40 @@ defmodule CanneryWeb.Components.AmmoGroupTableComponent do
defp get_value_for_key( defp get_value_for_key(
:container, :container,
%{container: %{name: container_name}} = ammo_group, %{container_id: container_id} = ammo_group,
%{container: container} %{container: container, current_user: current_user}
) do ) do
assigns = %{container: container, ammo_group: ammo_group} assigns = %{
container:
%{name: container_name} = container_id |> Containers.get_container!(current_user),
container_block: container,
ammo_group: ammo_group
}
{container_name, {container_name,
~H""" ~H"""
<%= render_slot(@container, @ammo_group) %> <%= render_slot(@container_block, {@ammo_group, @container}) %>
"""} """}
end end
defp get_value_for_key(:original_count, ammo_group, _additional_data), defp get_value_for_key(:original_count, %{id: ammo_group_id}, %{
do: ammo_group |> Ammo.get_original_count() original_counts: original_counts
}) do
Map.fetch!(original_counts, ammo_group_id)
end
defp get_value_for_key(:cpr, %{price_paid: nil}, _additional_data), defp get_value_for_key(:cpr, %{price_paid: nil}, _additional_data),
do: gettext("No cost information") do: gettext("No cost information")
defp get_value_for_key(:cpr, ammo_group, _additional_data) do defp get_value_for_key(:cpr, %{id: ammo_group_id}, %{cprs: cprs}) do
gettext("$%{amount}", gettext("$%{amount}", amount: display_currency(Map.fetch!(cprs, ammo_group_id)))
amount: ammo_group |> Ammo.get_cpr() |> :erlang.float_to_binary(decimals: 2)
)
end end
defp get_value_for_key(:count, %{count: count}, _additional_data), defp get_value_for_key(:count, %{count: count}, _additional_data),
do: if(count == 0, do: gettext("Empty"), else: count) do: if(count == 0, do: gettext("Empty"), else: count)
defp get_value_for_key(key, ammo_group, _additional_data), do: ammo_group |> Map.get(key) defp get_value_for_key(key, ammo_group, _additional_data), do: ammo_group |> Map.get(key)
@spec display_currency(float()) :: String.t()
defp display_currency(float), do: :erlang.float_to_binary(float, decimals: 2)
end end

View File

@ -3,7 +3,7 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
A component that displays a list of ammo type A component that displays a list of ammo type
""" """
use CanneryWeb, :live_component use CanneryWeb, :live_component
alias Cannery.{Accounts.User, Ammo, Ammo.AmmoType} alias Cannery.{Accounts.User, ActivityLog, Ammo, Ammo.AmmoType}
alias Ecto.UUID alias Ecto.UUID
alias Phoenix.LiveView.{Rendered, Socket} alias Phoenix.LiveView.{Rendered, Socket}
@ -103,13 +103,13 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
[ [
%{ %{
label: gettext("Used packs"), label: gettext("Used packs"),
key: :used_ammo_count, key: :used_packs_count,
type: :used_ammo_count type: :used_packs_count
}, },
%{ %{
label: gettext("Total ever packs"), label: gettext("Total ever packs"),
key: :historical_ammo_count, key: :historical_packs_count,
type: :historical_ammo_count type: :historical_packs_count
} }
] ]
else else
@ -121,7 +121,35 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
%{label: nil, key: "actions", type: :actions, sortable: false} %{label: nil, key: "actions", type: :actions, sortable: false}
]) ])
extra_data = %{actions: actions, current_user: current_user} round_counts = ammo_types |> Ammo.get_round_count_for_ammo_types(current_user)
used_counts =
show_used && ammo_types |> ActivityLog.get_used_count_for_ammo_types(current_user)
historical_round_counts =
show_used && ammo_types |> Ammo.get_historical_count_for_ammo_types(current_user)
packs_count = ammo_types |> Ammo.get_ammo_groups_count_for_types(current_user)
historical_packs_count =
show_used && ammo_types |> Ammo.get_ammo_groups_count_for_types(current_user, true)
used_packs_count =
show_used && ammo_types |> Ammo.get_used_ammo_groups_count_for_types(current_user)
average_costs = ammo_types |> Ammo.get_average_cost_for_ammo_types(current_user)
extra_data = %{
actions: actions,
current_user: current_user,
used_counts: used_counts,
round_counts: round_counts,
historical_round_counts: historical_round_counts,
packs_count: packs_count,
used_packs_count: used_packs_count,
historical_packs_count: historical_packs_count,
average_costs: average_costs
}
rows = rows =
ammo_types ammo_types
@ -156,28 +184,44 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
defp get_ammo_type_value(:boolean, key, ammo_type, _other_data), defp get_ammo_type_value(:boolean, key, ammo_type, _other_data),
do: ammo_type |> Map.get(key) |> humanize() do: ammo_type |> Map.get(key) |> humanize()
defp get_ammo_type_value(:round_count, _key, ammo_type, %{current_user: current_user}), defp get_ammo_type_value(:round_count, _key, %{id: ammo_type_id}, %{round_counts: round_counts}),
do: ammo_type |> Ammo.get_round_count_for_ammo_type(current_user) do: Map.get(round_counts, ammo_type_id)
defp get_ammo_type_value(:historical_round_count, _key, ammo_type, %{current_user: current_user}), defp get_ammo_type_value(
do: ammo_type |> Ammo.get_historical_count_for_ammo_type(current_user) :historical_round_count,
_key,
%{id: ammo_type_id},
%{historical_round_counts: historical_round_counts}
),
do: Map.get(historical_round_counts, ammo_type_id)
defp get_ammo_type_value(:used_round_count, _key, ammo_type, %{current_user: current_user}), defp get_ammo_type_value(:used_round_count, _key, %{id: ammo_type_id}, %{
do: ammo_type |> Ammo.get_used_count_for_ammo_type(current_user) used_counts: used_counts
}),
do: Map.get(used_counts, ammo_type_id)
defp get_ammo_type_value(:historical_ammo_count, _key, ammo_type, %{current_user: current_user}), defp get_ammo_type_value(
do: ammo_type |> Ammo.get_ammo_groups_count_for_type(current_user, true) :historical_packs_count,
_key,
%{id: ammo_type_id},
%{historical_packs_count: historical_packs_count}
),
do: Map.get(historical_packs_count, ammo_type_id)
defp get_ammo_type_value(:used_ammo_count, _key, ammo_type, %{current_user: current_user}), defp get_ammo_type_value(:used_packs_count, _key, %{id: ammo_type_id}, %{
do: ammo_type |> Ammo.get_used_ammo_groups_count_for_type(current_user) used_packs_count: used_packs_count
}),
do: Map.get(used_packs_count, ammo_type_id)
defp get_ammo_type_value(:ammo_count, _key, ammo_type, %{current_user: current_user}), defp get_ammo_type_value(:ammo_count, _key, %{id: ammo_type_id}, %{packs_count: packs_count}),
do: ammo_type |> Ammo.get_ammo_groups_count_for_type(current_user) do: Map.get(packs_count, ammo_type_id)
defp get_ammo_type_value(:avg_price_paid, _key, ammo_type, %{current_user: current_user}) do defp get_ammo_type_value(:avg_price_paid, _key, %{id: ammo_type_id}, %{
case ammo_type |> Ammo.get_average_cost_for_ammo_type!(current_user) do average_costs: average_costs
}) do
case Map.get(average_costs, ammo_type_id) do
nil -> gettext("No cost information") nil -> gettext("No cost information")
count -> gettext("$%{amount}", amount: count |> :erlang.float_to_binary(decimals: 2)) count -> gettext("$%{amount}", amount: display_currency(count))
end end
end end
@ -185,11 +229,7 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
assigns = %{ammo_type: ammo_type} assigns = %{ammo_type: ammo_type}
~H""" ~H"""
<.link <.link navigate={Routes.ammo_type_show_path(Endpoint, :show, @ammo_type)} class="link">
navigate={Routes.ammo_type_show_path(Endpoint, :show, @ammo_type)}
class="link"
data-qa={"view-name-#{@ammo_type.id}"}
>
<%= @ammo_type.name %> <%= @ammo_type.name %>
</.link> </.link>
""" """
@ -206,4 +246,7 @@ defmodule CanneryWeb.Components.AmmoTypeTableComponent do
defp get_ammo_type_value(nil, _key, _ammo_type, _other_data), do: nil defp get_ammo_type_value(nil, _key, _ammo_type, _other_data), do: nil
defp get_ammo_type_value(_other, key, ammo_type, _other_data), do: ammo_type |> Map.get(key) defp get_ammo_type_value(_other, key, ammo_type, _other_data), do: ammo_type |> Map.get(key)
@spec display_currency(float()) :: String.t()
defp display_currency(float), do: :erlang.float_to_binary(float, decimals: 2)
end end

View File

@ -1,81 +0,0 @@
defmodule CanneryWeb.Components.ContainerCard do
@moduledoc """
Display card for a container
"""
use CanneryWeb, :component
import CanneryWeb.Components.TagCard
alias Cannery.{Containers, Containers.Container, Repo}
alias CanneryWeb.Endpoint
alias Phoenix.LiveView.Rendered
attr :container, Container, required: true
slot(:tag_actions)
slot(:inner_block)
@spec container_card(assigns :: map()) :: Rendered.t()
def container_card(%{container: container} = assigns) do
assigns =
assigns
|> assign(container: container |> Repo.preload([:tags, :ammo_groups]))
|> assign_new(:tag_actions, fn -> [] end)
~H"""
<div
id={"container-#{@container.id}"}
class="overflow-hidden max-w-full mx-4 mb-4 px-8 py-4
flex flex-col justify-center items-center space-y-4
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out"
>
<div class="max-w-full mb-4 flex flex-col justify-center items-center space-y-2">
<.link navigate={Routes.container_show_path(Endpoint, :show, @container)} class="link">
<h1 class="px-4 py-2 rounded-lg title text-xl">
<%= @container.name %>
</h1>
</.link>
<span :if={@container.desc} class="rounded-lg title text-lg">
<%= gettext("Description:") %>
<%= @container.desc %>
</span>
<span class="rounded-lg title text-lg">
<%= gettext("Type:") %>
<%= @container.type %>
</span>
<span :if={@container.location} class="rounded-lg title text-lg">
<%= gettext("Location:") %>
<%= @container.location %>
</span>
<%= unless @container.ammo_groups |> Enum.empty?() do %>
<span class="rounded-lg title text-lg">
<%= gettext("Packs:") %>
<%= @container |> Containers.get_container_ammo_group_count!() %>
</span>
<span class="rounded-lg title text-lg">
<%= gettext("Rounds:") %>
<%= @container |> Containers.get_container_rounds!() %>
</span>
<% end %>
<div class="flex flex-wrap justify-center items-center">
<.simple_tag_card :for={tag <- @container.tags} :if={@container.tags} tag={tag} />
<%= render_slot(@tag_actions) %>
</div>
</div>
<div
:if={assigns |> Map.has_key?(:inner_block)}
class="flex space-x-4 justify-center items-center"
>
<%= render_slot(@inner_block) %>
</div>
</div>
"""
end
end

View File

@ -3,8 +3,7 @@ defmodule CanneryWeb.Components.ContainerTableComponent do
A component that displays a list of containers A component that displays a list of containers
""" """
use CanneryWeb, :live_component use CanneryWeb, :live_component
alias Cannery.{Accounts.User, Containers, Containers.Container, Repo} alias Cannery.{Accounts.User, Ammo, Containers.Container}
alias CanneryWeb.Components.TagCard
alias Ecto.UUID alias Ecto.UUID
alias Phoenix.LiveView.{Rendered, Socket} alias Phoenix.LiveView.{Rendered, Socket}
@ -46,11 +45,7 @@ defmodule CanneryWeb.Components.ContainerTableComponent do
%{label: gettext("Name"), key: :name, type: :string}, %{label: gettext("Name"), key: :name, type: :string},
%{label: gettext("Description"), key: :desc, type: :string}, %{label: gettext("Description"), key: :desc, type: :string},
%{label: gettext("Location"), key: :location, type: :string}, %{label: gettext("Location"), key: :location, type: :string},
%{label: gettext("Type"), key: :type, type: :string}, %{label: gettext("Type"), key: :type, type: :string}
%{label: gettext("Packs"), key: :packs, type: :integer},
%{label: gettext("Rounds"), key: :rounds, type: :string},
%{label: gettext("Tags"), key: :tags, type: :tags},
%{label: nil, key: :actions, sortable: false, type: :actions}
] ]
|> Enum.filter(fn %{key: key, type: type} -> |> Enum.filter(fn %{key: key, type: type} ->
# remove columns if all values match defaults # remove columns if all values match defaults
@ -65,11 +60,19 @@ defmodule CanneryWeb.Components.ContainerTableComponent do
type in [:tags, :actions] or not (container |> Map.get(key) == default_value) type in [:tags, :actions] or not (container |> Map.get(key) == default_value)
end) end)
end) end)
|> Enum.concat([
%{label: gettext("Packs"), key: :packs, type: :integer},
%{label: gettext("Rounds"), key: :rounds, type: :integer},
%{label: gettext("Tags"), key: :tags, type: :tags},
%{label: nil, key: :actions, sortable: false, type: :actions}
])
extra_data = %{ extra_data = %{
current_user: current_user, current_user: current_user,
tag_actions: tag_actions, tag_actions: tag_actions,
actions: actions actions: actions,
pack_count: Ammo.get_ammo_groups_count_for_containers(containers, current_user),
round_count: Ammo.get_round_count_for_containers(containers, current_user)
} }
rows = rows =
@ -101,8 +104,6 @@ defmodule CanneryWeb.Components.ContainerTableComponent do
@spec get_row_data_for_container(Container.t(), columns :: [map()], extra_data :: map) :: map() @spec get_row_data_for_container(Container.t(), columns :: [map()], extra_data :: map) :: map()
defp get_row_data_for_container(container, columns, extra_data) do defp get_row_data_for_container(container, columns, extra_data) do
container = container |> Repo.preload([:ammo_groups, :tags])
columns columns
|> Map.new(fn %{key: key} -> {key, get_value_for_key(key, container, extra_data)} end) |> Map.new(fn %{key: key} -> {key, get_value_for_key(key, container, extra_data)} end)
end end
@ -121,21 +122,27 @@ defmodule CanneryWeb.Components.ContainerTableComponent do
"""} """}
end end
defp get_value_for_key(:packs, container, _extra_data) do defp get_value_for_key(:packs, %{id: container_id}, %{pack_count: pack_count}) do
container |> Containers.get_container_ammo_group_count!() pack_count |> Map.get(container_id, 0)
end end
defp get_value_for_key(:rounds, container, _extra_data) do defp get_value_for_key(:rounds, %{id: container_id}, %{round_count: round_count}) do
container |> Containers.get_container_rounds!() round_count |> Map.get(container_id, 0)
end end
defp get_value_for_key(:tags, container, %{tag_actions: tag_actions}) do defp get_value_for_key(:tags, container, %{tag_actions: tag_actions}) do
assigns = %{tag_actions: tag_actions, container: container} assigns = %{tag_actions: tag_actions, container: container}
{container.tags |> Enum.map(fn %{name: name} -> name end), tag_names =
container.tags
|> Enum.map(fn %{name: name} -> name end)
|> Enum.sort()
|> Enum.join(" ")
{tag_names,
~H""" ~H"""
<div class="flex flex-wrap justify-center items-center"> <div class="flex flex-wrap justify-center items-center">
<TagCard.simple_tag_card :for={tag <- @container.tags} :if={@container.tags} tag={tag} /> <.simple_tag_card :for={tag <- @container.tags} :if={@container.tags} tag={tag} />
<%= render_slot(@tag_actions, @container) %> <%= render_slot(@tag_actions, @container) %>
</div> </div>

View File

@ -0,0 +1,149 @@
defmodule CanneryWeb.CoreComponents do
@moduledoc """
Provides core UI components.
"""
use Phoenix.Component
import CanneryWeb.{Gettext, ViewHelpers}
alias Cannery.{Accounts, Accounts.Invite, Accounts.User}
alias Cannery.{Ammo, Ammo.AmmoGroup}
alias Cannery.{Containers, Containers.Container, Containers.Tag}
alias CanneryWeb.{Endpoint, HomeLive}
alias CanneryWeb.Router.Helpers, as: Routes
alias Phoenix.LiveView.{JS, Rendered}
embed_templates "core_components/*"
attr :title_content, :string, default: nil
attr :current_user, User, default: nil
def topbar(assigns)
attr :return_to, :string, required: true
slot(:inner_block)
@doc """
Renders a live component inside a modal.
The rendered modal receives a `:return_to` option to properly update
the URL when the modal is closed.
## Examples
<.modal return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}>
<.live_component
module={<%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent}
id={@<%= schema.singular %>.id || :new}
title={@page_title}
action={@live_action}
return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}
<%= schema.singular %>: @<%= schema.singular %>
/>
</.modal>
"""
def modal(assigns)
defp hide_modal(js \\ %JS{}) do
js
|> JS.hide(to: "#modal", transition: "fade-out")
|> JS.hide(to: "#modal-bg", transition: "fade-out")
|> JS.hide(to: "#modal-content", transition: "fade-out-scale")
end
attr :action, :string, required: true
attr :value, :boolean, required: true
attr :id, :string, default: nil
slot(:inner_block)
@doc """
A toggle button element that can be directed to a liveview or a
live_component's `handle_event/3`.
## Examples
<.toggle_button action="my_liveview_action" value={@some_value}>
<span>Toggle me!</span>
</.toggle_button>
<.toggle_button action="my_live_component_action" target={@myself} value={@some_value}>
<span>Whatever you want</span>
</.toggle_button>
"""
def toggle_button(assigns)
attr :container, Container, required: true
attr :current_user, User, required: true
slot(:tag_actions)
slot(:inner_block)
@spec container_card(assigns :: map()) :: Rendered.t()
def container_card(assigns)
attr :tag, Tag, required: true
slot(:inner_block, required: true)
def tag_card(assigns)
attr :tag, Tag, required: true
def simple_tag_card(assigns)
attr :ammo_group, AmmoGroup, required: true
attr :current_user, User, required: true
attr :original_count, :integer, default: nil
attr :cpr, :integer, default: nil
attr :last_used_date, Date, default: nil
attr :show_container, :boolean, default: false
slot(:inner_block)
def ammo_group_card(assigns)
@spec display_currency(float()) :: String.t()
defp display_currency(float), do: :erlang.float_to_binary(float, decimals: 2)
attr :user, User, required: true
slot(:inner_block, required: true)
def user_card(assigns)
attr :invite, Invite, required: true
attr :use_count, :integer, default: nil
attr :current_user, User, required: true
slot(:inner_block)
slot(:code_actions)
def invite_card(assigns)
attr :content, :string, required: true
attr :filename, :string, default: "qrcode", doc: "filename without .png extension"
attr :image_class, :string, default: "w-64 h-max"
attr :width, :integer, default: 384, doc: "width of png to generate"
@doc """
Creates a downloadable QR Code element
"""
def qr_code(assigns)
attr :id, :string, required: true
attr :date, :any, required: true, doc: "A `Date` struct or nil"
@doc """
Phoenix.Component for a <date> element that renders the Date in the user's
local timezone
"""
def date(assigns)
attr :id, :string, required: true
attr :datetime, :any, required: true, doc: "A `DateTime` struct or nil"
@doc """
Phoenix.Component for a <time> element that renders the naivedatetime in the
user's local timezone
"""
def datetime(assigns)
@spec cast_datetime(NaiveDateTime.t() | nil) :: String.t()
defp cast_datetime(%NaiveDateTime{} = datetime) do
datetime |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_iso8601(:extended)
end
defp cast_datetime(_datetime), do: ""
end

View File

@ -0,0 +1,68 @@
<div
id={"ammo_group-#{@ammo_group.id}"}
class="mx-4 my-2 px-8 py-4
flex flex-col justify-center items-center
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out"
>
<.link navigate={Routes.ammo_group_show_path(Endpoint, :show, @ammo_group)} class="mb-2 link">
<h1 class="title text-xl title-primary-500">
<%= @ammo_group.ammo_type.name %>
</h1>
</.link>
<div class="flex flex-col justify-center items-center">
<span class="rounded-lg title text-lg">
<%= gettext("Count:") %>
<%= if @ammo_group.count == 0, do: gettext("Empty"), else: @ammo_group.count %>
</span>
<span :if={@original_count != @ammo_group.count} class="rounded-lg title text-lg">
<%= gettext("Original Count:") %>
<%= @original_count %>
</span>
<span :if={@ammo_group.notes} class="rounded-lg title text-lg">
<%= gettext("Notes:") %>
<%= @ammo_group.notes %>
</span>
<span class="rounded-lg title text-lg">
<%= gettext("Purchased on:") %>
<.date id={"#{@ammo_group.id}-purchased-on"} date={@ammo_group.purchased_on} />
</span>
<span :if={@last_used_date} class="rounded-lg title text-lg">
<%= gettext("Last used on:") %>
<.date id={"#{@ammo_group.id}-last-used-on"} date={@last_used_date} />
</span>
<span :if={@ammo_group.price_paid} class="rounded-lg title text-lg">
<%= gettext("Price paid:") %>
<%= gettext("$%{amount}", amount: display_currency(@ammo_group.price_paid)) %>
</span>
<span :if={@cpr} class="rounded-lg title text-lg">
<%= gettext("CPR:") %>
<%= gettext("$%{amount}", amount: display_currency(@cpr)) %>
</span>
<span
:if={@show_container && Containers.get_container!(@ammo_group.container_id, @current_user)}
class="rounded-lg title text-lg"
>
<%= gettext("Container:") %>
<.link
navigate={Routes.container_show_path(Endpoint, :show, @ammo_group.container_id)}
class="link"
>
<%= Containers.get_container!(@ammo_group.container_id, @current_user).name %>
</.link>
</span>
</div>
<div :if={@inner_block} class="mt-4 flex space-x-4 justify-center items-center">
<%= render_slot(@inner_block) %>
</div>
</div>

View File

@ -0,0 +1,58 @@
<div
id={"container-#{@container.id}"}
class="overflow-hidden max-w-full mx-4 mb-4 px-8 py-4
flex flex-col justify-around items-center space-y-4
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out"
>
<.link navigate={Routes.container_show_path(Endpoint, :show, @container)} class="link">
<h1 class="px-4 py-2 rounded-lg title text-xl">
<%= @container.name %>
</h1>
</.link>
<div class="flex flex-col justify-center items-center space-y-2">
<span :if={@container.desc} class="rounded-lg title text-lg">
<%= gettext("Description:") %>
<%= @container.desc %>
</span>
<span class="rounded-lg title text-lg">
<%= gettext("Type:") %>
<%= @container.type %>
</span>
<span :if={@container.location} class="rounded-lg title text-lg">
<%= gettext("Location:") %>
<%= @container.location %>
</span>
<%= if @container |> Ammo.get_ammo_groups_count_for_container!(@current_user) != 0 do %>
<span class="rounded-lg title text-lg">
<%= gettext("Packs:") %>
<%= @container |> Ammo.get_ammo_groups_count_for_container!(@current_user) %>
</span>
<span class="rounded-lg title text-lg">
<%= gettext("Rounds:") %>
<%= @container |> Ammo.get_round_count_for_container!(@current_user) %>
</span>
<% end %>
<div
:if={@tag_actions || @container.tags != []}
class="flex flex-wrap justify-center items-center"
>
<.simple_tag_card :for={tag <- @container.tags} tag={tag} />
<%= if @tag_actions, do: render_slot(@tag_actions) %>
</div>
</div>
<div
:if={assigns |> Map.has_key?(:inner_block)}
class="flex space-x-4 justify-center items-center"
>
<%= render_slot(@inner_block) %>
</div>
</div>

View File

@ -0,0 +1,3 @@
<time :if={@date} id={@id} datetime={Date.to_iso8601(@date, :extended)} phx-hook="Date">
<%= Date.to_iso8601(@date, :extended) %>
</time>

View File

@ -0,0 +1,3 @@
<time :if={@datetime} id={@id} datetime={cast_datetime(@datetime)} phx-hook="DateTime">
<%= cast_datetime(@datetime) %>
</time>

View File

@ -0,0 +1,46 @@
<div class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center space-y-4
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out">
<h1 class="title text-xl">
<%= @invite.name %>
</h1>
<%= if @invite.disabled_at |> is_nil() do %>
<h2 class="title text-md">
<%= if @invite.uses_left do %>
<%= gettext(
"Uses Left: %{uses_left_count}",
uses_left_count: @invite.uses_left
) %>
<% else %>
<%= gettext("Uses Left: Unlimited") %>
<% end %>
</h2>
<% else %>
<h2 class="title text-md">
<%= gettext("Invite Disabled") %>
</h2>
<% end %>
<.qr_code
content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)}
filename={@invite.name}
/>
<h2 :if={@use_count && @use_count != 0} class="title text-md">
<%= gettext("Uses: %{uses_count}", uses_count: @use_count) %>
</h2>
<div class="flex flex-row flex-wrap justify-center items-center">
<code
id={"code-#{@invite.id}"}
class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all text-gray-100 bg-primary-800"
phx-no-format
><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %></code>
<%= if @code_actions, do: render_slot(@code_actions) %>
</div>
<div :if={@inner_block} class="flex space-x-4 justify-center items-center">
<%= render_slot(@inner_block) %>
</div>
</div>

View File

@ -0,0 +1,42 @@
<.link
patch={@return_to}
id="modal-bg"
class="fade-in fixed z-10 left-0 top-0
w-full h-full overflow-hidden
p-8 flex flex-col justify-center items-center cursor-auto"
style="background-color: rgba(0,0,0,0.4);"
phx-remove={hide_modal()}
>
<span class="hidden"></span>
</.link>
<div
id="modal"
class="fixed z-10 left-0 top-0 pointer-events-none
w-full h-full overflow-hidden
p-4 sm:p-8 flex flex-col justify-center items-center"
>
<div
id="modal-content"
class="fade-in-scale w-full max-w-3xl relative
pointer-events-auto overflow-hidden
px-8 py-4 sm:py-8
flex flex-col justify-start items-center
bg-white border-2 rounded-lg"
>
<.link
patch={@return_to}
id="close"
class="absolute top-8 right-10
text-gray-500 hover:text-gray-800
transition-all duration-500 ease-in-out"
phx-remove={hide_modal()}
>
<i class="fa-fw fa-lg fas fa-times"></i>
</.link>
<div class="overflow-x-hidden overflow-y-auto w-full p-8 flex flex-col space-y-4 justify-start items-center">
<%= render_slot(@inner_block) %>
</div>
</div>
</div>

View File

@ -0,0 +1,3 @@
<a href={qr_code_image(@content)} download={@filename <> ".png"}>
<img class={@image_class} alt={@filename} src={qr_code_image(@content)} />
</a>

View File

@ -0,0 +1,6 @@
<h1
class="inline-block break-all mx-2 my-1 px-4 py-2 rounded-lg title text-xl"
style={"color: #{@tag.text_color}; background-color: #{@tag.bg_color}"}
>
<%= @tag.name %>
</h1>

View File

@ -0,0 +1,9 @@
<div
id={"tag-#{@tag.id}"}
class="mx-4 mb-4 px-8 py-4 space-x-4 flex justify-center items-center
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out"
>
<.simple_tag_card tag={@tag} />
<%= render_slot(@inner_block) %>
</div>

View File

@ -0,0 +1,30 @@
<label for={@id || @action} class="inline-flex relative items-center cursor-pointer">
<input
id={@id || @action}
type="checkbox"
value={@value}
checked={@value}
class="sr-only peer"
aria-labelledby={"#{@id || @action}-label"}
{
if assigns |> Map.has_key?(:target),
do: %{"phx-click": @action, "phx-value-value": @value, "phx-target": @target},
else: %{"phx-click": @action, "phx-value-value": @value}
}
/>
<div class="w-11 h-6 bg-gray-300 rounded-full peer
peer-focus:ring-4 peer-focus:ring-teal-300 dark:peer-focus:ring-teal-800
peer-checked:bg-gray-600
peer-checked:after:translate-x-full peer-checked:after:border-white
after:content-[''] after:absolute after:top-1 after:left-[2px] after:bg-white after:border-gray-300
after:border after:rounded-full after:h-5 after:w-5
after:transition-all after:duration-250 after:ease-in-out
transition-colors duration-250 ease-in-out">
</div>
<span
id={"#{@id || @action}-label"}
class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"
>
<%= render_slot(@inner_block) %>
</span>
</label>

View File

@ -0,0 +1,128 @@
<nav role="navigation" class="mb-8 px-8 py-4 w-full bg-primary-400">
<div class="flex flex-col sm:flex-row justify-between items-center">
<div class="mb-4 sm:mb-0 sm:mr-8 flex flex-row justify-start items-center space-x-2">
<.link
navigate={Routes.live_path(Endpoint, HomeLive)}
class="inline mx-2 my-1 leading-5 text-xl text-white"
>
<img
src={Routes.static_path(Endpoint, "/images/cannery.svg")}
alt={gettext("Cannery logo")}
class="inline-block h-8 mx-1"
/>
<h1 class="inline hover:underline">Cannery</h1>
</.link>
<%= if @title_content do %>
<span class="mx-2 my-1">
|
</span>
<%= @title_content %>
<% end %>
</div>
<hr class="mb-2 sm:hidden hr-light" />
<ul class="flex flex-row flex-wrap justify-center items-center
text-lg text-white text-ellipsis">
<%= if @current_user do %>
<li class="mx-2 my-1">
<.link
navigate={Routes.tag_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Tags") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.container_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Containers") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.ammo_type_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Catalog") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.ammo_group_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Ammo") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.range_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Range") %>
</.link>
</li>
<li :if={@current_user |> Accounts.is_already_admin?()} class="mx-2 my-1">
<.link
navigate={Routes.invite_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Invites") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
href={Routes.user_settings_path(Endpoint, :edit)}
class="text-white hover:underline truncate"
>
<%= @current_user.email %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
href={Routes.user_session_path(Endpoint, :delete)}
method="delete"
data-confirm={dgettext("prompts", "Are you sure you want to log out?")}
>
<i class="fas fa-sign-out-alt"></i>
</.link>
</li>
<li
:if={
@current_user |> Accounts.is_already_admin?() and
function_exported?(Routes, :live_dashboard_path, 2)
}
class="mx-2 my-1"
>
<.link
navigate={Routes.live_dashboard_path(Endpoint, :home)}
class="text-white hover:underline"
>
<i class="fas fa-gauge"></i>
</.link>
</li>
<% else %>
<li :if={Accounts.allow_registration?()} class="mx-2 my-1">
<.link
href={Routes.user_registration_path(Endpoint, :new)}
class="text-white hover:underline truncate"
>
<%= dgettext("actions", "Register") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
href={Routes.user_session_path(Endpoint, :new)}
class="text-white hover:underline truncate"
>
<%= dgettext("actions", "Log in") %>
</.link>
</li>
<% end %>
</ul>
</div>
</nav>

View File

@ -0,0 +1,36 @@
<div
id={"user-#{@user.id}"}
class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center text-center
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out"
>
<h1 class="px-4 py-2 rounded-lg title text-xl break-all">
<%= @user.email %>
</h1>
<h3 class="px-4 py-2 rounded-lg title text-lg">
<p>
<%= if @user.confirmed_at do %>
<%= gettext(
"User was confirmed at%{confirmed_datetime}",
confirmed_datetime: ""
) %>
<.datetime id={"#{@user.id}-confirmed-at"} datetime={@user.confirmed_at} />
<% else %>
<%= gettext("Email unconfirmed") %>
<% end %>
</p>
<p>
<%= gettext(
"User registered on%{registered_datetime}",
registered_datetime: ""
) %>
<.datetime id={"#{@user.id}-inserted-at"} datetime={@user.inserted_at} />
</p>
</h3>
<div :if={@inner_block} class="px-4 py-2 flex space-x-4 justify-center items-center">
<%= render_slot(@inner_block) %>
</div>
</div>

View File

@ -1,70 +0,0 @@
defmodule CanneryWeb.Components.InviteCard do
@moduledoc """
Display card for an invite
"""
use CanneryWeb, :component
alias Cannery.Accounts.{Invite, Invites, User}
alias CanneryWeb.Endpoint
attr :invite, Invite, required: true
attr :current_user, User, required: true
slot(:inner_block)
slot(:code_actions)
def invite_card(%{invite: invite, current_user: current_user} = assigns) do
assigns =
assigns
|> assign(:use_count, Invites.get_use_count(invite, current_user))
|> assign_new(:code_actions, fn -> [] end)
~H"""
<div class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center space-y-4
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out">
<h1 class="title text-xl">
<%= @invite.name %>
</h1>
<%= if @invite.disabled_at |> is_nil() do %>
<h2 class="title text-md">
<%= if @invite.uses_left do %>
<%= gettext(
"Uses Left: %{uses_left_count}",
uses_left_count: @invite.uses_left
) %>
<% else %>
<%= gettext("Uses Left: Unlimited") %>
<% end %>
</h2>
<% else %>
<h2 class="title text-md">
<%= gettext("Invite Disabled") %>
</h2>
<% end %>
<.qr_code
content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)}
filename={@invite.name}
/>
<h2 :if={@use_count != 0} class="title text-md">
<%= gettext("Uses: %{uses_count}", uses_count: @use_count) %>
</h2>
<div class="flex flex-row flex-wrap justify-center items-center">
<code
id={"code-#{@invite.id}"}
class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all text-gray-100 bg-primary-800"
phx-no-format
><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %></code>
<%= render_slot(@code_actions) %>
</div>
<div :if={@inner_block} class="flex space-x-4 justify-center items-center">
<%= render_slot(@inner_block) %>
</div>
</div>
"""
end
end

View File

@ -6,6 +6,7 @@ defmodule CanneryWeb.Components.MoveAmmoGroupComponent do
use CanneryWeb, :live_component use CanneryWeb, :live_component
alias Cannery.{Accounts.User, Ammo, Ammo.AmmoGroup, Containers, Containers.Container} alias Cannery.{Accounts.User, Ammo, Ammo.AmmoGroup, Containers, Containers.Container}
alias CanneryWeb.Endpoint alias CanneryWeb.Endpoint
alias Ecto.Changeset
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@impl true @impl true
@ -51,10 +52,9 @@ defmodule CanneryWeb.Components.MoveAmmoGroupComponent do
|> case do |> case do
{:ok, _ammo_group} -> {:ok, _ammo_group} ->
prompt = dgettext("prompts", "Ammo moved to %{name} successfully", name: container_name) prompt = dgettext("prompts", "Ammo moved to %{name} successfully", name: container_name)
socket |> put_flash(:info, prompt) |> push_navigate(to: return_to) socket |> put_flash(:info, prompt) |> push_navigate(to: return_to)
{:error, %Ecto.Changeset{} = changeset} -> {:error, %Changeset{} = changeset} ->
socket |> assign(changeset: changeset) socket |> assign(changeset: changeset)
end end
@ -64,10 +64,10 @@ defmodule CanneryWeb.Components.MoveAmmoGroupComponent do
@impl true @impl true
def render(%{containers: containers} = assigns) do def render(%{containers: containers} = assigns) do
columns = [ columns = [
%{label: gettext("Container"), key: "name"}, %{label: gettext("Container"), key: :name},
%{label: gettext("Type"), key: "type"}, %{label: gettext("Type"), key: :type},
%{label: gettext("Location"), key: "location"}, %{label: gettext("Location"), key: :location},
%{label: nil, key: "actions", sortable: false} %{label: nil, key: :actions, sortable: false}
] ]
rows = containers |> get_rows_for_containers(assigns, columns) rows = containers |> get_rows_for_containers(assigns, columns)
@ -110,8 +110,8 @@ defmodule CanneryWeb.Components.MoveAmmoGroupComponent do
end) end)
end end
@spec get_row_value_by_key(String.t(), Container.t(), map()) :: any() @spec get_row_value_by_key(atom(), Container.t(), map()) :: any()
defp get_row_value_by_key("actions", container, assigns) do defp get_row_value_by_key(:actions, container, assigns) do
assigns = assigns |> Map.put(:container, container) assigns = assigns |> Map.put(:container, container)
~H""" ~H"""
@ -129,6 +129,5 @@ defmodule CanneryWeb.Components.MoveAmmoGroupComponent do
""" """
end end
defp get_row_value_by_key(key, container, _assigns), defp get_row_value_by_key(key, container, _assigns), do: container |> Map.get(key)
do: container |> Map.get(key |> String.to_existing_atom())
end end

View File

@ -3,7 +3,7 @@ defmodule CanneryWeb.Components.ShotGroupTableComponent do
A component that displays a list of shot groups A component that displays a list of shot groups
""" """
use CanneryWeb, :live_component use CanneryWeb, :live_component
alias Cannery.{Accounts.User, ActivityLog.ShotGroup, Repo} alias Cannery.{Accounts.User, ActivityLog.ShotGroup, Ammo}
alias Ecto.UUID alias Ecto.UUID
alias Phoenix.LiveView.{Rendered, Socket} alias Phoenix.LiveView.{Rendered, Socket}
@ -41,11 +41,16 @@ defmodule CanneryWeb.Components.ShotGroupTableComponent do
%{label: gettext("Ammo"), key: :name}, %{label: gettext("Ammo"), key: :name},
%{label: gettext("Rounds shot"), key: :count}, %{label: gettext("Rounds shot"), key: :count},
%{label: gettext("Notes"), key: :notes}, %{label: gettext("Notes"), key: :notes},
%{label: gettext("Date"), key: :date}, %{label: gettext("Date"), key: :date, type: Date},
%{label: nil, key: :actions, sortable: false} %{label: nil, key: :actions, sortable: false}
] ]
extra_data = %{current_user: current_user, actions: actions} ammo_groups =
shot_groups
|> Enum.map(fn %{ammo_group_id: ammo_group_id} -> ammo_group_id end)
|> Ammo.get_ammo_groups(current_user)
extra_data = %{current_user: current_user, actions: actions, ammo_groups: ammo_groups}
rows = rows =
shot_groups shot_groups
@ -79,34 +84,28 @@ defmodule CanneryWeb.Components.ShotGroupTableComponent do
@spec get_row_data_for_shot_group(ShotGroup.t(), columns :: [map()], extra_data :: map()) :: @spec get_row_data_for_shot_group(ShotGroup.t(), columns :: [map()], extra_data :: map()) ::
map() map()
defp get_row_data_for_shot_group(shot_group, columns, extra_data) do defp get_row_data_for_shot_group(shot_group, columns, extra_data) do
shot_group = shot_group |> Repo.preload(ammo_group: :ammo_type)
columns columns
|> Map.new(fn %{key: key} -> |> Map.new(fn %{key: key} ->
{key, get_row_value(key, shot_group, extra_data)} {key, get_row_value(key, shot_group, extra_data)}
end) end)
end end
defp get_row_value( defp get_row_value(:name, %{ammo_group_id: ammo_group_id}, %{ammo_groups: ammo_groups}) do
:name, assigns = %{ammo_group: ammo_group = Map.fetch!(ammo_groups, ammo_group_id)}
%{ammo_group: %{ammo_type: %{name: ammo_type_name} = ammo_group}},
_extra_data
) do
assigns = %{ammo_group: ammo_group, ammo_type_name: ammo_type_name}
name_block = ~H""" {ammo_group.ammo_type.name,
~H"""
<.link navigate={Routes.ammo_group_show_path(Endpoint, :show, @ammo_group)} class="link"> <.link navigate={Routes.ammo_group_show_path(Endpoint, :show, @ammo_group)} class="link">
<%= @ammo_type_name %> <%= @ammo_group.ammo_type.name %>
</.link> </.link>
""" """}
{ammo_type_name, name_block}
end end
defp get_row_value(:date, %{date: _date} = assigns, _extra_data) do defp get_row_value(:date, %{date: date} = assigns, _extra_data) do
{date,
~H""" ~H"""
<.date date={@date} /> <.date id={"#{@id}-date"} date={@date} />
""" """}
end end
defp get_row_value(:actions, shot_group, %{actions: actions}) do defp get_row_value(:actions, shot_group, %{actions: actions}) do

View File

@ -33,7 +33,8 @@ defmodule CanneryWeb.Components.TableComponent do
optional(:class) => String.t(), optional(:class) => String.t(),
optional(:row_class) => String.t(), optional(:row_class) => String.t(),
optional(:alternate_row_class) => String.t(), optional(:alternate_row_class) => String.t(),
optional(:sortable) => false optional(:sortable) => false,
optional(:type) => module()
}), }),
required(:rows) => required(:rows) =>
list(%{ list(%{
@ -60,7 +61,8 @@ defmodule CanneryWeb.Components.TableComponent do
:asc :asc
end end
rows = rows |> sort_by_custom_sort_value_or_value(initial_key, initial_sort_mode) type = columns |> Enum.find(%{}, fn %{key: key} -> key == initial_key end) |> Map.get(:type)
rows = rows |> sort_by_custom_sort_value_or_value(initial_key, initial_sort_mode, type)
socket = socket =
socket socket
@ -68,6 +70,7 @@ defmodule CanneryWeb.Components.TableComponent do
|> assign( |> assign(
columns: columns, columns: columns,
rows: rows, rows: rows,
key: initial_key,
last_sort_key: initial_key, last_sort_key: initial_key,
sort_mode: initial_sort_mode sort_mode: initial_sort_mode
) )
@ -81,7 +84,14 @@ defmodule CanneryWeb.Components.TableComponent do
def handle_event( def handle_event(
"sort_by", "sort_by",
%{"sort-key" => key}, %{"sort-key" => key},
%{assigns: %{rows: rows, last_sort_key: last_sort_key, sort_mode: sort_mode}} = socket %{
assigns: %{
columns: columns,
rows: rows,
last_sort_key: last_sort_key,
sort_mode: sort_mode
}
} = socket
) do ) do
key = key |> String.to_existing_atom() key = key |> String.to_existing_atom()
@ -92,11 +102,28 @@ defmodule CanneryWeb.Components.TableComponent do
{_new_sort_key, _last_sort_mode} -> :asc {_new_sort_key, _last_sort_mode} -> :asc
end end
rows = rows |> sort_by_custom_sort_value_or_value(key, sort_mode) type =
columns |> Enum.find(%{}, fn %{key: column_key} -> column_key == key end) |> Map.get(:type)
rows = rows |> sort_by_custom_sort_value_or_value(key, sort_mode, type)
{:noreply, socket |> assign(last_sort_key: key, sort_mode: sort_mode, rows: rows)} {:noreply, socket |> assign(last_sort_key: key, sort_mode: sort_mode, rows: rows)}
end end
defp sort_by_custom_sort_value_or_value(rows, key, sort_mode) do defp sort_by_custom_sort_value_or_value(rows, key, sort_mode, type)
when type in [Date, DateTime] do
rows
|> Enum.sort_by(
fn row ->
case row |> Map.get(key) do
{custom_sort_key, _value} -> custom_sort_key
value -> value
end
end,
{sort_mode, type}
)
end
defp sort_by_custom_sort_value_or_value(rows, key, sort_mode, _type) do
rows rows
|> Enum.sort_by( |> Enum.sort_by(
fn row -> fn row ->

View File

@ -1,38 +0,0 @@
defmodule CanneryWeb.Components.TagCard do
@moduledoc """
Display card for a tag
"""
use CanneryWeb, :component
alias Cannery.Tags.Tag
attr :tag, Tag, required: true
slot(:inner_block, required: true)
def tag_card(assigns) do
~H"""
<div
id={"tag-#{@tag.id}"}
class="mx-4 mb-4 px-8 py-4 space-x-4 flex justify-center items-center
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out"
>
<.simple_tag_card tag={@tag} />
<%= render_slot(@inner_block) %>
</div>
"""
end
attr :tag, Tag, required: true
def simple_tag_card(assigns) do
~H"""
<h1
class="inline-block break-all mx-2 my-1 px-4 py-2 rounded-lg title text-xl"
style={"color: #{@tag.text_color}; background-color: #{@tag.bg_color}"}
>
<%= @tag.name %>
</h1>
"""
end
end

View File

@ -1,146 +0,0 @@
defmodule CanneryWeb.Components.Topbar do
@moduledoc """
Component that renders a topbar with user functions/links
"""
use CanneryWeb, :component
alias Cannery.Accounts
alias CanneryWeb.HomeLive
def topbar(assigns) do
assigns =
%{results: [], title_content: nil, flash: nil, current_user: nil} |> Map.merge(assigns)
~H"""
<nav role="navigation" class="mb-8 px-8 py-4 w-full bg-primary-400">
<div class="flex flex-col sm:flex-row justify-between items-center">
<div class="mb-4 sm:mb-0 sm:mr-8 flex flex-row justify-start items-center space-x-2">
<.link
navigate={Routes.live_path(Endpoint, HomeLive)}
class="inline mx-2 my-1 leading-5 text-xl text-white"
>
<img
src={Routes.static_path(Endpoint, "/images/cannery.svg")}
alt={gettext("Cannery logo")}
class="inline-block h-8 mx-1"
/>
<h1 class="inline hover:underline">Cannery</h1>
</.link>
<%= if @title_content do %>
<span class="mx-2 my-1">
|
</span>
<%= @title_content %>
<% end %>
</div>
<hr class="mb-2 sm:hidden hr-light" />
<ul class="flex flex-row flex-wrap justify-center items-center
text-lg text-white text-ellipsis">
<%= if @current_user do %>
<li class="mx-2 my-1">
<.link
navigate={Routes.tag_index_path(Endpoint, :index)}
class="text-primary-600 text-white hover:underline"
>
<%= gettext("Tags") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.container_index_path(Endpoint, :index)}
class="text-primary-600 text-white hover:underline"
>
<%= gettext("Containers") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.ammo_type_index_path(Endpoint, :index)}
class="text-primary-600 text-white hover:underline"
>
<%= gettext("Catalog") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.ammo_group_index_path(Endpoint, :index)}
class="text-primary-600 text-white hover:underline"
>
<%= gettext("Ammo") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.range_index_path(Endpoint, :index)}
class="text-primary-600 text-white hover:underline"
>
<%= gettext("Range") %>
</.link>
</li>
<li :if={@current_user |> Accounts.is_already_admin?()} class="mx-2 my-1">
<.link
navigate={Routes.invite_index_path(Endpoint, :index)}
class="text-primary-600 text-white hover:underline"
>
<%= gettext("Invites") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
href={Routes.user_settings_path(Endpoint, :edit)}
class="text-primary-600 text-white hover:underline truncate"
>
<%= @current_user.email %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
href={Routes.user_session_path(Endpoint, :delete)}
method="delete"
data-confirm={dgettext("prompts", "Are you sure you want to log out?")}
>
<i class="fas fa-sign-out-alt"></i>
</.link>
</li>
<li
:if={
@current_user |> Accounts.is_already_admin?() and
function_exported?(Routes, :live_dashboard_path, 2)
}
class="mx-2 my-1"
>
<.link
navigate={Routes.live_dashboard_path(Endpoint, :home)}
class="text-white text-white hover:underline"
>
<i class="fas fa-gauge"></i>
</.link>
</li>
<% else %>
<li :if={Accounts.allow_registration?()} class="mx-2 my-1">
<.link
href={Routes.user_registration_path(Endpoint, :new)}
class="text-white hover:underline truncate"
>
<%= dgettext("actions", "Register") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
href={Routes.user_session_path(Endpoint, :new)}
class="text-white hover:underline truncate"
>
<%= dgettext("actions", "Log in") %>
</.link>
</li>
<% end %>
</ul>
</div>
</nav>
"""
end
end

View File

@ -1,52 +0,0 @@
defmodule CanneryWeb.Components.UserCard do
@moduledoc """
Display card for a user
"""
use CanneryWeb, :component
alias Cannery.Accounts.User
attr :user, User, required: true
slot(:inner_block, required: true)
def user_card(assigns) do
~H"""
<div
id={"user-#{@user.id}"}
class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center text-center
border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out"
>
<h1 class="px-4 py-2 rounded-lg title text-xl break-all">
<%= @user.email %>
</h1>
<h3 class="px-4 py-2 rounded-lg title text-lg">
<p>
<%= if @user.confirmed_at do %>
<%= gettext(
"User was confirmed at%{confirmed_datetime}",
confirmed_datetime: ""
) %>
<.datetime datetime={@user.confirmed_at} />
<% else %>
<%= gettext("Email unconfirmed") %>
<% end %>
</p>
<p>
<%= gettext(
"User registered on%{registered_datetime}",
registered_datetime: ""
) %>
<.datetime datetime={@user.inserted_at} />
</p>
</h3>
<div :if={@inner_block} class="px-4 py-2 flex space-x-4 justify-center items-center">
<%= render_slot(@inner_block) %>
</div>
</div>
"""
end
end

View File

@ -3,41 +3,49 @@ defmodule CanneryWeb.ExportController do
alias Cannery.{ActivityLog, Ammo, Containers} alias Cannery.{ActivityLog, Ammo, Containers}
def export(%{assigns: %{current_user: current_user}} = conn, %{"mode" => "json"}) do def export(%{assigns: %{current_user: current_user}} = conn, %{"mode" => "json"}) do
ammo_types = ammo_types = Ammo.list_ammo_types(current_user)
Ammo.list_ammo_types(current_user) used_counts = ammo_types |> ActivityLog.get_used_count_for_ammo_types(current_user)
|> Enum.map(fn ammo_type -> round_counts = ammo_types |> Ammo.get_round_count_for_ammo_types(current_user)
average_cost = ammo_type |> Ammo.get_average_cost_for_ammo_type!(current_user) ammo_group_counts = ammo_types |> Ammo.get_ammo_groups_count_for_types(current_user)
round_count = ammo_type |> Ammo.get_round_count_for_ammo_type(current_user)
used_count = ammo_type |> Ammo.get_used_count_for_ammo_type(current_user)
ammo_group_count = ammo_type |> Ammo.get_ammo_groups_count_for_type(current_user, true)
total_ammo_group_counts =
ammo_types |> Ammo.get_ammo_groups_count_for_types(current_user, true)
average_costs = ammo_types |> Ammo.get_average_cost_for_ammo_types(current_user)
ammo_types =
ammo_types
|> Enum.map(fn %{id: ammo_type_id} = ammo_type ->
ammo_type ammo_type
|> Jason.encode!() |> Jason.encode!()
|> Jason.decode!() |> Jason.decode!()
|> Map.merge(%{ |> Map.merge(%{
"average_cost" => average_cost, "average_cost" => Map.get(average_costs, ammo_type_id),
"round_count" => round_count, "round_count" => Map.get(round_counts, ammo_type_id, 0),
"used_count" => used_count, "used_count" => Map.get(used_counts, ammo_type_id, 0),
"ammo_group_count" => ammo_group_count "ammo_group_count" => Map.get(ammo_group_counts, ammo_type_id, 0),
"total_ammo_group_count" => Map.get(total_ammo_group_counts, ammo_type_id, 0)
}) })
end) end)
ammo_groups = Ammo.list_ammo_groups(nil, true, current_user)
used_counts = ammo_groups |> ActivityLog.get_used_counts(current_user)
original_counts = ammo_groups |> Ammo.get_original_counts(current_user)
cprs = ammo_groups |> Ammo.get_cprs(current_user)
ammo_groups = ammo_groups =
Ammo.list_ammo_groups(nil, true, current_user) ammo_groups
|> Enum.map(fn ammo_group -> |> Enum.map(fn %{id: ammo_group_id} = ammo_group ->
cpr = ammo_group |> Ammo.get_cpr() percentage_remaining = ammo_group |> Ammo.get_percentage_remaining(current_user)
used_count = ammo_group |> Ammo.get_used_count()
original_count = ammo_group |> Ammo.get_original_count()
percentage_remaining = ammo_group |> Ammo.get_percentage_remaining()
ammo_group ammo_group
|> Jason.encode!() |> Jason.encode!()
|> Jason.decode!() |> Jason.decode!()
|> Map.merge(%{ |> Map.merge(%{
"used_count" => used_count, "used_count" => Map.get(used_counts, ammo_group_id),
"percentage_remaining" => percentage_remaining, "percentage_remaining" => percentage_remaining,
"original_count" => original_count, "original_count" => Map.get(original_counts, ammo_group_id),
"cpr" => cpr "cpr" => Map.get(cprs, ammo_group_id)
}) })
end) end)
@ -46,8 +54,8 @@ defmodule CanneryWeb.ExportController do
containers = containers =
Containers.list_containers(current_user) Containers.list_containers(current_user)
|> Enum.map(fn container -> |> Enum.map(fn container ->
ammo_group_count = container |> Containers.get_container_ammo_group_count!() ammo_group_count = container |> Ammo.get_ammo_groups_count_for_container!(current_user)
round_count = container |> Containers.get_container_rounds!() round_count = container |> Ammo.get_round_count_for_container!(current_user)
container container
|> Jason.encode!() |> Jason.encode!()

View File

@ -3,6 +3,7 @@ defmodule CanneryWeb.UserRegistrationController do
import CanneryWeb.Gettext import CanneryWeb.Gettext
alias Cannery.{Accounts, Accounts.Invites} alias Cannery.{Accounts, Accounts.Invites}
alias CanneryWeb.{Endpoint, HomeLive} alias CanneryWeb.{Endpoint, HomeLive}
alias Ecto.Changeset
def new(conn, %{"invite" => invite_token}) do def new(conn, %{"invite" => invite_token}) do
if Invites.valid_invite_token?(invite_token) do if Invites.valid_invite_token?(invite_token) do
@ -70,7 +71,7 @@ defmodule CanneryWeb.UserRegistrationController do
|> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired")) |> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired"))
|> redirect(to: Routes.live_path(Endpoint, HomeLive)) |> redirect(to: Routes.live_path(Endpoint, HomeLive))
{:error, %Ecto.Changeset{} = changeset} -> {:error, %Changeset{} = changeset} ->
conn |> render("new.html", changeset: changeset, invite_token: invite_token) conn |> render("new.html", changeset: changeset, invite_token: invite_token)
end end
end end

View File

@ -44,7 +44,7 @@ defmodule CanneryWeb.AmmoGroupLive.FormComponent do
@impl true @impl true
def handle_event("validate", %{"ammo_group" => ammo_group_params}, socket) do def handle_event("validate", %{"ammo_group" => ammo_group_params}, socket) do
{:noreply, socket |> assign_changeset(ammo_group_params)} {:noreply, socket |> assign_changeset(ammo_group_params, :validate)}
end end
def handle_event( def handle_event(
@ -56,6 +56,7 @@ defmodule CanneryWeb.AmmoGroupLive.FormComponent do
end end
# HTML Helpers # HTML Helpers
@spec container_options([Container.t()]) :: [{String.t(), Container.id()}] @spec container_options([Container.t()]) :: [{String.t(), Container.id()}]
defp container_options(containers) do defp container_options(containers) do
containers |> Enum.map(fn %{id: id, name: name} -> {name, id} end) containers |> Enum.map(fn %{id: id, name: name} -> {name, id} end)
@ -70,35 +71,28 @@ defmodule CanneryWeb.AmmoGroupLive.FormComponent do
defp assign_changeset( defp assign_changeset(
%{assigns: %{action: action, ammo_group: ammo_group, current_user: user}} = socket, %{assigns: %{action: action, ammo_group: ammo_group, current_user: user}} = socket,
ammo_group_params ammo_group_params,
changeset_action \\ nil
) do ) do
changeset_action = default_action =
cond do case action do
action in [:new, :clone] -> :insert create when create in [:new, :clone] -> :insert
action == :edit -> :update :edit -> :update
end end
changeset = changeset =
cond do case default_action do
action in [:new, :clone] -> :insert ->
ammo_type = ammo_type = maybe_get_ammo_type(ammo_group_params, user)
if ammo_group_params |> Map.has_key?("ammo_type_id"), container = maybe_get_container(ammo_group_params, user)
do: ammo_group_params |> Map.get("ammo_type_id") |> Ammo.get_ammo_type!(user),
else: nil
container =
if ammo_group_params |> Map.has_key?("container_id"),
do: ammo_group_params |> Map.get("container_id") |> Containers.get_container!(user),
else: nil
ammo_group |> AmmoGroup.create_changeset(ammo_type, container, user, ammo_group_params) ammo_group |> AmmoGroup.create_changeset(ammo_type, container, user, ammo_group_params)
action == :edit -> :update ->
ammo_group |> AmmoGroup.update_changeset(ammo_group_params, user) ammo_group |> AmmoGroup.update_changeset(ammo_group_params, user)
end end
changeset = changeset =
case changeset |> Changeset.apply_action(changeset_action) do case changeset |> Changeset.apply_action(changeset_action || default_action) do
{:ok, _data} -> changeset {:ok, _data} -> changeset
{:error, changeset} -> changeset {:error, changeset} -> changeset
end end
@ -106,6 +100,20 @@ defmodule CanneryWeb.AmmoGroupLive.FormComponent do
socket |> assign(:changeset, changeset) socket |> assign(:changeset, changeset)
end end
defp maybe_get_container(%{"container_id" => container_id}, user)
when is_binary(container_id) do
container_id |> Containers.get_container!(user)
end
defp maybe_get_container(_params_not_found, _user), do: nil
defp maybe_get_ammo_type(%{"ammo_type_id" => ammo_type_id}, user)
when is_binary(ammo_type_id) do
ammo_type_id |> Ammo.get_ammo_type!(user)
end
defp maybe_get_ammo_type(_params_not_found, _user), do: nil
defp save_ammo_group( defp save_ammo_group(
%{assigns: %{ammo_group: ammo_group, current_user: current_user, return_to: return_to}} = %{assigns: %{ammo_group: ammo_group, current_user: current_user, return_to: return_to}} =
socket, socket,
@ -146,16 +154,18 @@ defmodule CanneryWeb.AmmoGroupLive.FormComponent do
multiplier: multiplier multiplier: multiplier
) )
{:error, changeset} = save_multiplier_error(socket, changeset, error_msg)
changeset
|> Changeset.add_error(:multiplier, error_msg)
|> Changeset.apply_action(:insert)
socket |> assign(:changeset, changeset)
:error -> :error ->
error_msg = dgettext("errors", "Could not parse number of copies") error_msg = dgettext("errors", "Could not parse number of copies")
save_multiplier_error(socket, changeset, error_msg)
end
{:noreply, socket}
end
@spec save_multiplier_error(Socket.t(), Changeset.t(), String.t()) :: Socket.t()
defp save_multiplier_error(socket, changeset, error_msg) do
{:error, changeset} = {:error, changeset} =
changeset changeset
|> Changeset.add_error(:multiplier, error_msg) |> Changeset.add_error(:multiplier, error_msg)
@ -164,9 +174,6 @@ defmodule CanneryWeb.AmmoGroupLive.FormComponent do
socket |> assign(:changeset, changeset) socket |> assign(:changeset, changeset)
end end
{:noreply, socket}
end
defp create_multiple( defp create_multiple(
%{assigns: %{current_user: current_user, return_to: return_to}} = socket, %{assigns: %{current_user: current_user, return_to: return_to}} = socket,
ammo_group_params, ammo_group_params,

View File

@ -49,8 +49,10 @@
<%= label(f, :notes, gettext("Notes"), class: "title text-lg text-primary-600") %> <%= label(f, :notes, gettext("Notes"), class: "title text-lg text-primary-600") %>
<%= textarea(f, :notes, <%= textarea(f, :notes,
id: "ammo-group-form-notes",
class: "text-center col-span-2 input input-primary", class: "text-center col-span-2 input input-primary",
phx_hook: "MaintainAttrs" phx_hook: "MaintainAttrs",
phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :notes, "col-span-3 text-center") %> <%= error_tag(f, :notes, "col-span-3 text-center") %>

View File

@ -91,7 +91,6 @@ defmodule CanneryWeb.AmmoGroupLive.Index do
{:noreply, socket |> put_flash(:info, prompt) |> display_ammo_groups()} {:noreply, socket |> put_flash(:info, prompt) |> display_ammo_groups()}
end end
@impl true
def handle_event( def handle_event(
"toggle_staged", "toggle_staged",
%{"ammo_group_id" => id}, %{"ammo_group_id" => id},
@ -105,12 +104,10 @@ defmodule CanneryWeb.AmmoGroupLive.Index do
{:noreply, socket |> display_ammo_groups()} {:noreply, socket |> display_ammo_groups()}
end end
@impl true
def handle_event("toggle_show_used", _params, %{assigns: %{show_used: show_used}} = socket) do def handle_event("toggle_show_used", _params, %{assigns: %{show_used: show_used}} = socket) do
{:noreply, socket |> assign(:show_used, !show_used) |> display_ammo_groups()} {:noreply, socket |> assign(:show_used, !show_used) |> display_ammo_groups()}
end end
@impl true
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.ammo_group_index_path(Endpoint, :index))} {:noreply, socket |> push_patch(to: Routes.ammo_group_index_path(Endpoint, :index))}
end end

View File

@ -45,11 +45,11 @@
<div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl"> <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
<.form <.form
:let={f} :let={f}
for={:search} for={%{}}
as={:search}
phx-change="search" phx-change="search"
phx-submit="search" phx-submit="search"
class="grow self-stretch flex flex-col items-stretch" class="grow self-stretch flex flex-col items-stretch"
data-qa="ammo_group_search"
> >
<%= text_input(f, :search_term, <%= text_input(f, :search_term,
class: "input input-primary", class: "input input-primary",
@ -91,7 +91,9 @@
phx-click="toggle_staged" phx-click="toggle_staged"
phx-value-ammo_group_id={ammo_group.id} phx-value-ammo_group_id={ammo_group.id}
> >
<%= if ammo_group.staged, do: gettext("Unstage"), else: gettext("Stage") %> <%= if ammo_group.staged,
do: dgettext("actions", "Unstage"),
else: dgettext("actions", "Stage") %>
</button> </button>
<.link <.link
@ -102,7 +104,7 @@
</.link> </.link>
</div> </div>
</:range> </:range>
<:container :let={%{container: %{name: container_name} = container} = ammo_group}> <:container :let={{ammo_group, %{name: container_name} = container}}>
<div class="min-w-20 py-2 px-4 h-full flex flew-wrap justify-center items-center"> <div class="min-w-20 py-2 px-4 h-full flex flew-wrap justify-center items-center">
<.link <.link
navigate={Routes.container_show_path(Endpoint, :show, container)} navigate={Routes.container_show_path(Endpoint, :show, container)}
@ -115,16 +117,20 @@
patch={Routes.ammo_group_index_path(Endpoint, :move, ammo_group)} patch={Routes.ammo_group_index_path(Endpoint, :move, ammo_group)}
class="mx-2 my-1 text-sm btn btn-primary" class="mx-2 my-1 text-sm btn btn-primary"
> >
<%= gettext("Move ammo") %> <%= dgettext("actions", "Move ammo") %>
</.link> </.link>
</div> </div>
</:container> </:container>
<:actions :let={ammo_group}> <:actions :let={%{count: ammo_group_count} = ammo_group}>
<div class="py-2 px-4 h-full space-x-4 flex justify-center items-center"> <div class="py-2 px-4 h-full space-x-4 flex justify-center items-center">
<.link <.link
navigate={Routes.ammo_group_show_path(Endpoint, :show, ammo_group)} navigate={Routes.ammo_group_show_path(Endpoint, :show, ammo_group)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"view-#{ammo_group.id}"} aria-label={
dgettext("actions", "View ammo group of %{ammo_group_count} bullets",
ammo_group_count: ammo_group_count
)
}
> >
<i class="fa-fw fa-lg fas fa-eye"></i> <i class="fa-fw fa-lg fas fa-eye"></i>
</.link> </.link>
@ -132,7 +138,11 @@
<.link <.link
patch={Routes.ammo_group_index_path(Endpoint, :edit, ammo_group)} patch={Routes.ammo_group_index_path(Endpoint, :edit, ammo_group)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"edit-#{ammo_group.id}"} aria-label={
dgettext("actions", "Edit ammo group of %{ammo_group_count} bullets",
ammo_group_count: ammo_group_count
)
}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -140,7 +150,11 @@
<.link <.link
patch={Routes.ammo_group_index_path(Endpoint, :clone, ammo_group)} patch={Routes.ammo_group_index_path(Endpoint, :clone, ammo_group)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"clone-#{ammo_group.id}"} aria-label={
dgettext("actions", "Clone ammo group of %{ammo_group_count} bullets",
ammo_group_count: ammo_group_count
)
}
> >
<i class="fa-fw fa-lg fas fa-copy"></i> <i class="fa-fw fa-lg fas fa-copy"></i>
</.link> </.link>
@ -151,7 +165,11 @@
phx-click="delete" phx-click="delete"
phx-value-id={ammo_group.id} phx-value-id={ammo_group.id}
data-confirm={dgettext("prompts", "Are you sure you want to delete this ammo?")} data-confirm={dgettext("prompts", "Are you sure you want to delete this ammo?")}
data-qa={"delete-#{ammo_group.id}"} aria-label={
dgettext("actions", "Delete ammo group of %{ammo_group_count} bullets",
ammo_group_count: ammo_group_count
)
}
> >
<i class="fa-fw fa-lg fas fa-trash"></i> <i class="fa-fw fa-lg fas fa-trash"></i>
</.link> </.link>
@ -161,8 +179,8 @@
<% end %> <% end %>
</div> </div>
<%= cond do %> <%= case @live_action do %>
<% @live_action in [:new, :edit, :clone] -> %> <% create when create in [:new, :edit, :clone] -> %>
<.modal return_to={Routes.ammo_group_index_path(Endpoint, :index)}> <.modal return_to={Routes.ammo_group_index_path(Endpoint, :index)}>
<.live_component <.live_component
module={CanneryWeb.AmmoGroupLive.FormComponent} module={CanneryWeb.AmmoGroupLive.FormComponent}
@ -174,7 +192,7 @@
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% @live_action == :add_shot_group -> %> <% :add_shot_group -> %>
<.modal return_to={Routes.ammo_group_index_path(Endpoint, :index)}> <.modal return_to={Routes.ammo_group_index_path(Endpoint, :index)}>
<.live_component <.live_component
module={CanneryWeb.Components.AddShotGroupComponent} module={CanneryWeb.Components.AddShotGroupComponent}
@ -186,7 +204,7 @@
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% @live_action == :move -> %> <% :move -> %>
<.modal return_to={Routes.ammo_group_index_path(Endpoint, :index)}> <.modal return_to={Routes.ammo_group_index_path(Endpoint, :index)}>
<.live_component <.live_component
module={CanneryWeb.Components.MoveAmmoGroupComponent} module={CanneryWeb.Components.MoveAmmoGroupComponent}
@ -198,6 +216,5 @@
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% true -> %> <% _ -> %>
<%= nil %>
<% end %> <% end %>

View File

@ -4,8 +4,9 @@ defmodule CanneryWeb.AmmoGroupLive.Show do
""" """
use CanneryWeb, :live_view use CanneryWeb, :live_view
import CanneryWeb.Components.ContainerCard alias Cannery.{ActivityLog, ActivityLog.ShotGroup}
alias Cannery.{ActivityLog, ActivityLog.ShotGroup, Ammo, Ammo.AmmoGroup, Repo} alias Cannery.{Ammo, Ammo.AmmoGroup}
alias Cannery.Containers
alias CanneryWeb.Endpoint alias CanneryWeb.Endpoint
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@ -28,7 +29,6 @@ defmodule CanneryWeb.AmmoGroupLive.Show do
{:noreply, socket} {:noreply, socket}
end end
@impl true
def handle_params(%{"id" => id}, _url, %{assigns: %{live_action: live_action}} = socket) do def handle_params(%{"id" => id}, _url, %{assigns: %{live_action: live_action}} = socket) do
socket = socket =
socket socket
@ -58,7 +58,6 @@ defmodule CanneryWeb.AmmoGroupLive.Show do
{:noreply, socket |> put_flash(:info, prompt) |> push_navigate(to: redirect_to)} {:noreply, socket |> put_flash(:info, prompt) |> push_navigate(to: redirect_to)}
end end
@impl true
def handle_event( def handle_event(
"toggle_staged", "toggle_staged",
_params, _params,
@ -70,7 +69,6 @@ defmodule CanneryWeb.AmmoGroupLive.Show do
{:noreply, socket |> display_ammo_group(ammo_group)} {:noreply, socket |> display_ammo_group(ammo_group)}
end end
@impl true
def handle_event( def handle_event(
"delete_shot_group", "delete_shot_group",
%{"id" => id}, %{"id" => id},
@ -85,30 +83,45 @@ defmodule CanneryWeb.AmmoGroupLive.Show do
end end
@spec display_ammo_group(Socket.t(), AmmoGroup.t() | AmmoGroup.id()) :: Socket.t() @spec display_ammo_group(Socket.t(), AmmoGroup.t() | AmmoGroup.id()) :: Socket.t()
defp display_ammo_group(socket, %AmmoGroup{} = ammo_group) do defp display_ammo_group(
ammo_group = ammo_group |> Repo.preload([:container, :ammo_type, :shot_groups], force: true) %{assigns: %{current_user: current_user}} = socket,
%AmmoGroup{container_id: container_id} = ammo_group
) do
columns = [ columns = [
%{label: gettext("Rounds shot"), key: :count}, %{label: gettext("Rounds shot"), key: :count},
%{label: gettext("Notes"), key: :notes}, %{label: gettext("Notes"), key: :notes},
%{label: gettext("Date"), key: :date}, %{label: gettext("Date"), key: :date, type: Date},
%{label: nil, key: :actions, sortable: false} %{label: nil, key: :actions, sortable: false}
] ]
shot_groups = ActivityLog.list_shot_groups_for_ammo_group(ammo_group, current_user)
rows = rows =
ammo_group.shot_groups shot_groups
|> Enum.map(fn shot_group -> |> Enum.map(fn shot_group ->
ammo_group |> get_table_row_for_shot_group(shot_group, columns) ammo_group |> get_table_row_for_shot_group(shot_group, columns)
end) end)
socket |> assign(ammo_group: ammo_group, columns: columns, rows: rows) socket
|> assign(
ammo_group: ammo_group,
original_count: Ammo.get_original_count(ammo_group, current_user),
percentage_remaining: Ammo.get_percentage_remaining(ammo_group, current_user),
container: container_id && Containers.get_container!(container_id, current_user),
shot_groups: shot_groups,
columns: columns,
rows: rows
)
end end
defp display_ammo_group(%{assigns: %{current_user: current_user}} = socket, id), defp display_ammo_group(%{assigns: %{current_user: current_user}} = socket, id),
do: display_ammo_group(socket, Ammo.get_ammo_group!(id, current_user)) do: display_ammo_group(socket, Ammo.get_ammo_group!(id, current_user))
@spec display_currency(float()) :: String.t()
defp display_currency(float), do: :erlang.float_to_binary(float, decimals: 2)
@spec get_table_row_for_shot_group(AmmoGroup.t(), ShotGroup.t(), [map()]) :: map() @spec get_table_row_for_shot_group(AmmoGroup.t(), ShotGroup.t(), [map()]) :: map()
defp get_table_row_for_shot_group(ammo_group, %{date: date} = shot_group, columns) do defp get_table_row_for_shot_group(ammo_group, %{id: id, date: date} = shot_group, columns) do
assigns = %{ammo_group: ammo_group, shot_group: shot_group} assigns = %{ammo_group: ammo_group, shot_group: shot_group}
columns columns
@ -116,11 +129,11 @@ defmodule CanneryWeb.AmmoGroupLive.Show do
value = value =
case key do case key do
:date -> :date ->
assigns = %{date: date} assigns = %{id: id, date: date}
{date, {date,
~H""" ~H"""
<.date date={@date} /> <.date id={"#{@id}-date"} date={@date} />
"""} """}
:actions -> :actions ->
@ -129,7 +142,11 @@ defmodule CanneryWeb.AmmoGroupLive.Show do
<.link <.link
patch={Routes.ammo_group_show_path(Endpoint, :edit_shot_group, @ammo_group, @shot_group)} patch={Routes.ammo_group_show_path(Endpoint, :edit_shot_group, @ammo_group, @shot_group)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"edit-#{@shot_group.id}"} aria-label={
dgettext("actions", "Edit shot group of %{shot_group_count} shots",
shot_group_count: @shot_group.count
)
}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -140,7 +157,11 @@ defmodule CanneryWeb.AmmoGroupLive.Show do
phx-click="delete_shot_group" phx-click="delete_shot_group"
phx-value-id={@shot_group.id} phx-value-id={@shot_group.id}
data-confirm={dgettext("prompts", "Are you sure you want to delete this shot record?")} data-confirm={dgettext("prompts", "Are you sure you want to delete this shot record?")}
data-qa={"delete-#{@shot_group.id}"} aria-label={
dgettext("actions", "Delete shot record of %{shot_group_count} shots",
shot_group_count: @shot_group.count
)
}
> >
<i class="fa-fw fa-lg fas fa-trash"></i> <i class="fa-fw fa-lg fas fa-trash"></i>
</.link> </.link>

View File

@ -11,12 +11,12 @@
<span class="rounded-lg title text-lg"> <span class="rounded-lg title text-lg">
<%= gettext("Original count:") %> <%= gettext("Original count:") %>
<%= Ammo.get_original_count(@ammo_group) %> <%= @original_count %>
</span> </span>
<span class="rounded-lg title text-lg"> <span class="rounded-lg title text-lg">
<%= gettext("Percentage left:") %> <%= gettext("Percentage left:") %>
<%= gettext("%{percentage}%", percentage: @ammo_group |> Ammo.get_percentage_remaining()) %> <%= gettext("%{percentage}%", percentage: @percentage_remaining) %>
</span> </span>
<%= if @ammo_group.notes do %> <%= if @ammo_group.notes do %>
@ -28,23 +28,19 @@
<span class="rounded-lg title text-lg"> <span class="rounded-lg title text-lg">
<%= gettext("Purchased on:") %> <%= gettext("Purchased on:") %>
<.date date={@ammo_group.purchased_on} /> <.date id={"#{@ammo_group.id}-purchased-on"} date={@ammo_group.purchased_on} />
</span> </span>
<%= if @ammo_group.price_paid do %> <%= if @ammo_group.price_paid do %>
<span class="rounded-lg title text-lg"> <span class="rounded-lg title text-lg">
<%= gettext("Original cost:") %> <%= gettext("Original cost:") %>
<%= gettext("$%{amount}", <%= gettext("$%{amount}", amount: display_currency(@ammo_group.price_paid)) %>
amount: @ammo_group.price_paid |> :erlang.float_to_binary(decimals: 2)
) %>
</span> </span>
<span class="rounded-lg title text-lg"> <span class="rounded-lg title text-lg">
<%= gettext("Current value:") %> <%= gettext("Current value:") %>
<%= gettext("$%{amount}", <%= gettext("$%{amount}",
amount: amount: display_currency(@ammo_group.price_paid * @percentage_remaining / 100)
(@ammo_group.price_paid * Ammo.get_percentage_remaining(@ammo_group) / 100)
|> :erlang.float_to_binary(decimals: 2)
) %> ) %>
</span> </span>
<% end %> <% end %>
@ -55,7 +51,6 @@
<.link <.link
navigate={Routes.ammo_type_show_path(Endpoint, :show, @ammo_group.ammo_type)} navigate={Routes.ammo_type_show_path(Endpoint, :show, @ammo_group.ammo_type)}
class="mx-4 my-2 btn btn-primary" class="mx-4 my-2 btn btn-primary"
data-qa="details"
> >
<%= dgettext("actions", "View in Catalog") %> <%= dgettext("actions", "View in Catalog") %>
</.link> </.link>
@ -63,7 +58,11 @@
<.link <.link
patch={Routes.ammo_group_show_path(Endpoint, :edit, @ammo_group)} patch={Routes.ammo_group_show_path(Endpoint, :edit, @ammo_group)}
class="mx-4 my-2 text-primary-600 link" class="mx-4 my-2 text-primary-600 link"
data-qa="edit" aria-label={
dgettext("actions", "Edit ammo group of %{ammo_group_count} bullets",
ammo_group_count: @ammo_group.count
)
}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -73,7 +72,11 @@
class="mx-4 my-2 text-primary-600 link" class="mx-4 my-2 text-primary-600 link"
phx-click="delete" phx-click="delete"
data-confirm={dgettext("prompts", "Are you sure you want to delete this ammo?")} data-confirm={dgettext("prompts", "Are you sure you want to delete this ammo?")}
data-qa="delete" aria-label={
dgettext("actions", "Delete ammo group of %{ammo_group_count} bullets",
ammo_group_count: @ammo_group.count
)
}
> >
<i class="fa-fw fa-lg fas fa-trash"></i> <i class="fa-fw fa-lg fas fa-trash"></i>
</.link> </.link>
@ -89,9 +92,8 @@
<.link <.link
patch={Routes.ammo_group_show_path(Endpoint, :move, @ammo_group)} patch={Routes.ammo_group_show_path(Endpoint, :move, @ammo_group)}
class="btn btn-primary" class="btn btn-primary"
data-qa="move"
> >
<%= dgettext("actions", "Move containers") %> <%= dgettext("actions", "Move ammo") %>
</.link> </.link>
<.link <.link
@ -106,18 +108,18 @@
<hr class="mb-4 w-full" /> <hr class="mb-4 w-full" />
<div> <div>
<%= if @ammo_group.container do %> <%= if @container do %>
<h1 class="mb-4 px-4 py-2 text-center rounded-lg title text-xl"> <h1 class="mb-4 px-4 py-2 text-center rounded-lg title text-xl">
<%= gettext("Stored in") %> <%= gettext("Stored in") %>
</h1> </h1>
<.container_card container={@ammo_group.container} /> <.container_card container={@container} current_user={@current_user} />
<% else %> <% else %>
<%= gettext("This ammo is not in a container") %> <%= gettext("This ammo is not in a container") %>
<% end %> <% end %>
</div> </div>
<%= unless @ammo_group.shot_groups |> Enum.empty?() do %> <%= unless @shot_groups |> Enum.empty?() do %>
<hr class="mb-4 w-full" /> <hr class="mb-4 w-full" />
<h1 class="mb-4 px-4 py-2 text-center rounded-lg title text-xl"> <h1 class="mb-4 px-4 py-2 text-center rounded-lg title text-xl">

View File

@ -35,15 +35,18 @@ defmodule CanneryWeb.AmmoTypeLive.FormComponent do
ammo_type_params ammo_type_params
) do ) do
changeset_action = changeset_action =
cond do case action do
action in [:new, :clone] -> :insert create when create in [:new, :clone] -> :insert
action == :edit -> :update :edit -> :update
end end
changeset = changeset =
cond do case action do
action in [:new, :clone] -> ammo_type |> AmmoType.create_changeset(user, ammo_type_params) create when create in [:new, :clone] ->
action == :edit -> ammo_type |> AmmoType.update_changeset(ammo_type_params) ammo_type |> AmmoType.create_changeset(user, ammo_type_params)
:edit ->
ammo_type |> AmmoType.update_changeset(ammo_type_params)
end end
changeset = changeset =

View File

@ -24,17 +24,19 @@
<%= label(f, :desc, gettext("Description"), class: "title text-lg text-primary-600") %> <%= label(f, :desc, gettext("Description"), class: "title text-lg text-primary-600") %>
<%= textarea(f, :desc, <%= textarea(f, :desc,
id: "ammo-type-form-desc",
class: "text-center col-span-2 input input-primary", class: "text-center col-span-2 input input-primary",
phx_hook: "MaintainAttrs" phx_hook: "MaintainAttrs",
phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :desc, "col-span-3 text-center") %> <%= error_tag(f, :desc, "col-span-3 text-center") %>
<a <.link
href="https://en.wikipedia.org/wiki/Bullet#Abbreviations" href="https://shootersreference.com/reloadingdata/bullet_abbreviations/"
class="col-span-3 text-center link title text-md text-primary-600" class="col-span-3 text-center link title text-md text-primary-600"
> >
<%= gettext("Example bullet type abbreviations") %> <%= gettext("Example bullet type abbreviations") %>
</a> </.link>
<%= label(f, :bullet_type, gettext("Bullet type"), class: "title text-lg text-primary-600") %> <%= label(f, :bullet_type, gettext("Bullet type"), class: "title text-lg text-primary-600") %>
<%= text_input(f, :bullet_type, <%= text_input(f, :bullet_type,
class: "text-center col-span-2 input input-primary", class: "text-center col-span-2 input input-primary",
@ -52,14 +54,14 @@
<%= label(f, :cartridge, gettext("Cartridge"), class: "title text-lg text-primary-600") %> <%= label(f, :cartridge, gettext("Cartridge"), class: "title text-lg text-primary-600") %>
<%= text_input(f, :cartridge, <%= text_input(f, :cartridge,
class: "text-center col-span-2 input input-primary", class: "text-center col-span-2 input input-primary",
placeholder: "5.56x46mm NATO" placeholder: gettext("5.56x46mm NATO")
) %> ) %>
<%= error_tag(f, :cartridge, "col-span-3 text-center") %> <%= error_tag(f, :cartridge, "col-span-3 text-center") %>
<%= label(f, :caliber, gettext("Caliber"), class: "title text-lg text-primary-600") %> <%= label(f, :caliber, gettext("Caliber"), class: "title text-lg text-primary-600") %>
<%= text_input(f, :caliber, <%= text_input(f, :caliber,
class: "text-center col-span-2 input input-primary", class: "text-center col-span-2 input input-primary",
placeholder: ".223" placeholder: gettext(".223")
) %> ) %>
<%= error_tag(f, :caliber, "col-span-3 text-center") %> <%= error_tag(f, :caliber, "col-span-3 text-center") %>
@ -112,21 +114,21 @@
<%= label(f, :pressure, gettext("Pressure"), class: "title text-lg text-primary-600") %> <%= label(f, :pressure, gettext("Pressure"), class: "title text-lg text-primary-600") %>
<%= text_input(f, :pressure, <%= text_input(f, :pressure,
class: "text-center col-span-2 input input-primary", class: "text-center col-span-2 input input-primary",
placeholder: "+P" placeholder: gettext("+P")
) %> ) %>
<%= error_tag(f, :pressure, "col-span-3 text-center") %> <%= error_tag(f, :pressure, "col-span-3 text-center") %>
<%= label(f, :primer_type, gettext("Primer type"), class: "title text-lg text-primary-600") %> <%= label(f, :primer_type, gettext("Primer type"), class: "title text-lg text-primary-600") %>
<%= text_input(f, :primer_type, <%= text_input(f, :primer_type,
class: "text-center col-span-2 input input-primary", class: "text-center col-span-2 input input-primary",
placeholder: "Boxer" placeholder: gettext("Boxer")
) %> ) %>
<%= error_tag(f, :primer_type, "col-span-3 text-center") %> <%= error_tag(f, :primer_type, "col-span-3 text-center") %>
<%= label(f, :firing_type, gettext("Firing type"), class: "title text-lg text-primary-600") %> <%= label(f, :firing_type, gettext("Firing type"), class: "title text-lg text-primary-600") %>
<%= text_input(f, :firing_type, <%= text_input(f, :firing_type,
class: "text-center col-span-2 input input-primary", class: "text-center col-span-2 input input-primary",
placeholder: "Centerfire" placeholder: gettext("Centerfire")
) %> ) %>
<%= error_tag(f, :firing_type, "col-span-3 text-center") %> <%= error_tag(f, :firing_type, "col-span-3 text-center") %>

View File

@ -69,25 +69,21 @@ defmodule CanneryWeb.AmmoTypeLive.Index do
@impl true @impl true
def handle_event("delete", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do def handle_event("delete", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do
%{name: name} = Ammo.get_ammo_type!(id, current_user) |> Ammo.delete_ammo_type!(current_user) %{name: name} = Ammo.get_ammo_type!(id, current_user) |> Ammo.delete_ammo_type!(current_user)
prompt = dgettext("prompts", "%{name} deleted succesfully", name: name) prompt = dgettext("prompts", "%{name} deleted succesfully", name: name)
{:noreply, socket |> put_flash(:info, prompt) |> list_ammo_types()} {:noreply, socket |> put_flash(:info, prompt) |> list_ammo_types()}
end end
@impl true
def handle_event("toggle_show_used", _params, %{assigns: %{show_used: show_used}} = socket) do def handle_event("toggle_show_used", _params, %{assigns: %{show_used: show_used}} = socket) do
{:noreply, socket |> assign(:show_used, !show_used) |> list_ammo_types()} {:noreply, socket |> assign(:show_used, !show_used) |> list_ammo_types()}
end end
@impl true
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.ammo_type_index_path(Endpoint, :index))} {:noreply, socket |> push_patch(to: Routes.ammo_type_index_path(Endpoint, :index))}
end end
def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do
{:noreply, search_path = Routes.ammo_type_index_path(Endpoint, :search, search_term)
socket |> push_patch(to: Routes.ammo_type_index_path(Endpoint, :search, search_term))} {:noreply, socket |> push_patch(to: search_path)}
end end
defp list_ammo_types(%{assigns: %{search: search, current_user: current_user}} = socket) do defp list_ammo_types(%{assigns: %{search: search, current_user: current_user}} = socket) do

View File

@ -20,11 +20,11 @@
<div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl"> <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
<.form <.form
:let={f} :let={f}
for={:search} for={%{}}
as={:search}
phx-change="search" phx-change="search"
phx-submit="search" phx-submit="search"
class="grow self-stretch flex flex-col items-stretch" class="grow self-stretch flex flex-col items-stretch"
data-qa="ammo_type_search"
> >
<%= text_input(f, :search_term, <%= text_input(f, :search_term,
class: "input input-primary", class: "input input-primary",
@ -60,7 +60,9 @@
<.link <.link
navigate={Routes.ammo_type_show_path(Endpoint, :show, ammo_type)} navigate={Routes.ammo_type_show_path(Endpoint, :show, ammo_type)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"view-#{ammo_type.id}"} aria-label={
dgettext("actions", "View %{ammo_type_name}", ammo_type_name: ammo_type.name)
}
> >
<i class="fa-fw fa-lg fas fa-eye"></i> <i class="fa-fw fa-lg fas fa-eye"></i>
</.link> </.link>
@ -68,7 +70,9 @@
<.link <.link
patch={Routes.ammo_type_index_path(Endpoint, :edit, ammo_type)} patch={Routes.ammo_type_index_path(Endpoint, :edit, ammo_type)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"edit-#{ammo_type.id}"} aria-label={
dgettext("actions", "Edit %{ammo_type_name}", ammo_type_name: ammo_type.name)
}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -76,7 +80,9 @@
<.link <.link
patch={Routes.ammo_type_index_path(Endpoint, :clone, ammo_type)} patch={Routes.ammo_type_index_path(Endpoint, :clone, ammo_type)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"clone-#{ammo_type.id}"} aria-label={
dgettext("actions", "Clone %{ammo_type_name}", ammo_type_name: ammo_type.name)
}
> >
<i class="fa-fw fa-lg fas fa-copy"></i> <i class="fa-fw fa-lg fas fa-copy"></i>
</.link> </.link>
@ -93,7 +99,9 @@
name: ammo_type.name name: ammo_type.name
) )
} }
data-qa={"delete-#{ammo_type.id}"} aria-label={
dgettext("actions", "Delete %{ammo_type_name}", ammo_type_name: ammo_type.name)
}
> >
<i class="fa-lg fas fa-trash"></i> <i class="fa-lg fas fa-trash"></i>
</.link> </.link>

View File

@ -4,8 +4,7 @@ defmodule CanneryWeb.AmmoTypeLive.Show do
""" """
use CanneryWeb, :live_view use CanneryWeb, :live_view
import CanneryWeb.Components.AmmoGroupCard alias Cannery.{ActivityLog, Ammo, Ammo.AmmoType}
alias Cannery.{Ammo, Ammo.AmmoType}
alias CanneryWeb.Endpoint alias CanneryWeb.Endpoint
@fields_list [ @fields_list [
@ -58,12 +57,10 @@ defmodule CanneryWeb.AmmoTypeLive.Show do
{:noreply, socket |> put_flash(:info, prompt) |> push_navigate(to: redirect_to)} {:noreply, socket |> put_flash(:info, prompt) |> push_navigate(to: redirect_to)}
end end
@impl true
def handle_event("toggle_show_used", _params, %{assigns: %{show_used: show_used}} = socket) do def handle_event("toggle_show_used", _params, %{assigns: %{show_used: show_used}} = socket) do
{:noreply, socket |> assign(:show_used, !show_used) |> display_ammo_type()} {:noreply, socket |> assign(:show_used, !show_used) |> display_ammo_type()}
end end
@impl true
def handle_event( def handle_event(
"toggle_table", "toggle_table",
_params, _params,
@ -94,12 +91,27 @@ defmodule CanneryWeb.AmmoTypeLive.Show do
ammo_type |> Map.get(field) != default_value ammo_type |> Map.get(field) != default_value
end) end)
ammo_groups = ammo_type |> Ammo.list_ammo_groups_for_type(current_user, show_used)
original_counts = ammo_groups |> Ammo.get_original_counts(current_user)
cprs = ammo_groups |> Ammo.get_cprs(current_user)
historical_packs_count = ammo_type |> Ammo.get_ammo_groups_count_for_type(current_user, true)
last_used_dates = ammo_groups |> ActivityLog.get_last_used_dates(current_user)
socket socket
|> assign( |> assign(
page_title: page_title(live_action, ammo_type), page_title: page_title(live_action, ammo_type),
ammo_type: ammo_type, ammo_type: ammo_type,
ammo_groups: ammo_type |> Ammo.list_ammo_groups_for_type(current_user, show_used), ammo_groups: ammo_groups,
avg_cost_per_round: ammo_type |> Ammo.get_average_cost_for_ammo_type!(current_user), original_counts: original_counts,
cprs: cprs,
last_used_dates: last_used_dates,
avg_cost_per_round: ammo_type |> Ammo.get_average_cost_for_ammo_type(current_user),
rounds: ammo_type |> Ammo.get_round_count_for_ammo_type(current_user),
used_rounds: ammo_type |> ActivityLog.get_used_count_for_ammo_type(current_user),
historical_round_count: ammo_type |> Ammo.get_historical_count_for_ammo_type(current_user),
packs_count: ammo_type |> Ammo.get_ammo_groups_count_for_type(current_user),
used_packs_count: ammo_type |> Ammo.get_used_ammo_groups_count_for_type(current_user),
historical_packs_count: historical_packs_count,
fields_list: @fields_list, fields_list: @fields_list,
fields_to_display: fields_to_display fields_to_display: fields_to_display
) )
@ -113,6 +125,9 @@ defmodule CanneryWeb.AmmoTypeLive.Show do
socket |> display_ammo_type(ammo_type) socket |> display_ammo_type(ammo_type)
end end
@spec display_currency(float()) :: String.t()
defp display_currency(float), do: :erlang.float_to_binary(float, decimals: 2)
defp page_title(action, %{name: ammo_type_name}) when action in [:show, :table], defp page_title(action, %{name: ammo_type_name}) when action in [:show, :table],
do: ammo_type_name do: ammo_type_name

View File

@ -16,7 +16,7 @@
<.link <.link
patch={Routes.ammo_type_show_path(Endpoint, :edit, @ammo_type)} patch={Routes.ammo_type_show_path(Endpoint, :edit, @ammo_type)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa="edit" aria-label={dgettext("actions", "Edit %{ammo_type_name}", ammo_type_name: @ammo_type.name)}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -32,7 +32,9 @@
name: @ammo_type.name name: @ammo_type.name
) )
} }
data-qa="delete" aria-label={
dgettext("actions", "Delete %{ammo_type_name}", ammo_type_name: @ammo_type.name)
}
> >
<i class="fa-fw fa-lg fas fa-trash"></i> <i class="fa-fw fa-lg fas fa-trash"></i>
</.link> </.link>
@ -69,7 +71,7 @@
</h3> </h3>
<span class="text-primary-600"> <span class="text-primary-600">
<%= @ammo_type |> Ammo.get_round_count_for_ammo_type(@current_user) %> <%= @rounds %>
</span> </span>
<h3 class="title text-lg"> <h3 class="title text-lg">
@ -77,7 +79,7 @@
</h3> </h3>
<span class="text-primary-600"> <span class="text-primary-600">
<%= @ammo_type |> Ammo.get_used_count_for_ammo_type(@current_user) %> <%= @used_rounds %>
</span> </span>
<h3 class="title text-lg"> <h3 class="title text-lg">
@ -85,7 +87,7 @@
</h3> </h3>
<span class="text-primary-600"> <span class="text-primary-600">
<%= @ammo_type |> Ammo.get_historical_count_for_ammo_type(@current_user) %> <%= @historical_round_count %>
</span> </span>
</div> </div>
@ -97,7 +99,7 @@
</h3> </h3>
<span class="text-primary-600"> <span class="text-primary-600">
<%= @ammo_type |> Ammo.get_ammo_groups_count_for_type(@current_user) %> <%= @packs_count %>
</span> </span>
<h3 class="title text-lg"> <h3 class="title text-lg">
@ -105,7 +107,7 @@
</h3> </h3>
<span class="text-primary-600"> <span class="text-primary-600">
<%= @ammo_type |> Ammo.get_used_ammo_groups_count_for_type(@current_user) %> <%= @used_packs_count %>
</span> </span>
<h3 class="title text-lg"> <h3 class="title text-lg">
@ -113,7 +115,7 @@
</h3> </h3>
<span class="text-primary-600"> <span class="text-primary-600">
<%= @ammo_type |> Ammo.get_ammo_groups_count_for_type(@current_user, true) %> <%= @historical_packs_count %>
</span> </span>
</div> </div>
@ -125,7 +127,7 @@
</h3> </h3>
<span class="text-primary-600"> <span class="text-primary-600">
<.datetime datetime={@ammo_type.inserted_at} /> <.datetime id={"#{@ammo_type.id}-inserted-at"} datetime={@ammo_type.inserted_at} />
</span> </span>
<%= if @avg_cost_per_round do %> <%= if @avg_cost_per_round do %>
@ -134,9 +136,7 @@
</h3> </h3>
<span class="text-primary-600"> <span class="text-primary-600">
<%= gettext("$%{amount}", <%= gettext("$%{amount}", amount: display_currency(@avg_cost_per_round)) %>
amount: @avg_cost_per_round |> :erlang.float_to_binary(decimals: 2)
) %>
</span> </span>
<% else %> <% else %>
<h3 class="mx-8 my-4 title text-lg text-primary-600 col-span-2"> <h3 class="mx-8 my-4 title text-lg text-primary-600 col-span-2">
@ -175,7 +175,7 @@
ammo_groups={@ammo_groups} ammo_groups={@ammo_groups}
current_user={@current_user} current_user={@current_user}
> >
<:container :let={%{container: %{name: container_name} = container}}> <:container :let={{_ammo_group, %{name: container_name} = container}}>
<.link <.link
navigate={Routes.container_show_path(Endpoint, :show, container)} navigate={Routes.container_show_path(Endpoint, :show, container)}
class="mx-2 my-1 link" class="mx-2 my-1 link"
@ -187,8 +187,12 @@
<% else %> <% else %>
<div class="flex flex-wrap justify-center items-stretch"> <div class="flex flex-wrap justify-center items-stretch">
<.ammo_group_card <.ammo_group_card
:for={ammo_group <- @ammo_groups} :for={%{id: ammo_group_id} = ammo_group <- @ammo_groups}
ammo_group={ammo_group} ammo_group={ammo_group}
original_count={Map.fetch!(@original_counts, ammo_group_id)}
cpr={Map.get(@cprs, ammo_group_id)}
last_used_date={Map.get(@last_used_dates, ammo_group_id)}
current_user={@current_user}
show_container={true} show_container={true}
/> />
</div> </div>

View File

@ -4,25 +4,41 @@ defmodule CanneryWeb.ContainerLive.EditTagsComponent do
""" """
use CanneryWeb, :live_component use CanneryWeb, :live_component
alias Cannery.{Accounts.User, Containers, Containers.Container, Repo, Tags, Tags.Tag} alias Cannery.{Accounts.User, Containers}
alias Cannery.Containers.{Container, Tag}
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@impl true @impl true
@spec update( @spec update(
%{:container => Container.t(), :current_user => User.t(), optional(any) => any}, %{
:container => Container.t(),
:current_path => String.t(),
:current_user => User.t(),
optional(any) => any
},
Socket.t() Socket.t()
) :: {:ok, Socket.t()} ) :: {:ok, Socket.t()}
def update(%{container: container, current_user: current_user} = assigns, socket) do def update(
tags = Tags.list_tags(current_user) %{container: _container, current_path: _current_path, current_user: current_user} =
container = container |> Repo.preload(:tags) assigns,
{:ok, socket |> assign(assigns) |> assign(tags: tags, container: container)} socket
) do
tags = Containers.list_tags(current_user)
{:ok, socket |> assign(assigns) |> assign(:tags, tags)}
end end
@impl true @impl true
def handle_event( def handle_event(
"save", "save",
%{"tag" => %{"tag_id" => tag_id}}, %{"tag" => %{"tag_id" => tag_id}},
%{assigns: %{tags: tags, container: container, current_user: current_user}} = socket %{
assigns: %{
tags: tags,
container: container,
current_user: current_user,
current_path: current_path
}
} = socket
) do ) do
socket = socket =
case tags |> Enum.find(fn %{id: id} -> tag_id == id end) do case tags |> Enum.find(fn %{id: id} -> tag_id == id end) do
@ -32,19 +48,24 @@ defmodule CanneryWeb.ContainerLive.EditTagsComponent do
%{name: tag_name} = tag -> %{name: tag_name} = tag ->
_container_tag = Containers.add_tag!(container, tag, current_user) _container_tag = Containers.add_tag!(container, tag, current_user)
container = container |> Repo.preload(:tags, force: true)
prompt = dgettext("prompts", "%{name} added successfully", name: tag_name) prompt = dgettext("prompts", "%{name} added successfully", name: tag_name)
socket |> put_flash(:info, prompt) |> assign(container: container) socket |> put_flash(:info, prompt) |> push_patch(to: current_path)
end end
{:noreply, socket} {:noreply, socket}
end end
@impl true
def handle_event( def handle_event(
"delete", "delete",
%{"tag-id" => tag_id}, %{"tag-id" => tag_id},
%{assigns: %{tags: tags, container: container, current_user: current_user}} = socket %{
assigns: %{
tags: tags,
container: container,
current_user: current_user,
current_path: current_path
}
} = socket
) do ) do
socket = socket =
case tags |> Enum.find(fn %{id: id} -> tag_id == id end) do case tags |> Enum.find(fn %{id: id} -> tag_id == id end) do
@ -54,9 +75,8 @@ defmodule CanneryWeb.ContainerLive.EditTagsComponent do
%{name: tag_name} = tag -> %{name: tag_name} = tag ->
_container_tag = Containers.remove_tag!(container, tag, current_user) _container_tag = Containers.remove_tag!(container, tag, current_user)
container = container |> Repo.preload(:tags, force: true)
prompt = dgettext("prompts", "%{name} removed successfully", name: tag_name) prompt = dgettext("prompts", "%{name} removed successfully", name: tag_name)
socket |> put_flash(:info, prompt) |> assign(container: container) socket |> put_flash(:info, prompt) |> push_patch(to: current_path)
end end
{:noreply, socket} {:noreply, socket}

View File

@ -36,7 +36,8 @@
<.form <.form
:let={f} :let={f}
for={:tag} for={%{}}
as={:tag}
id="add-tag-to-container-form" id="add-tag-to-container-form"
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
phx-target={@myself} phx-target={@myself}

View File

@ -35,17 +35,17 @@ defmodule CanneryWeb.ContainerLive.FormComponent do
container_params container_params
) do ) do
changeset_action = changeset_action =
cond do case action do
action in [:new, :clone] -> :insert create when create in [:new, :clone] -> :insert
action == :edit -> :update :edit -> :update
end end
changeset = changeset =
cond do case action do
action in [:new, :clone] -> create when create in [:new, :clone] ->
container |> Container.create_changeset(user, container_params) container |> Container.create_changeset(user, container_params)
action == :edit -> :edit ->
container |> Container.update_changeset(container_params) container |> Container.update_changeset(container_params)
end end

View File

@ -27,9 +27,11 @@
<%= label(f, :desc, gettext("Description"), class: "title text-lg text-primary-600") %> <%= label(f, :desc, gettext("Description"), class: "title text-lg text-primary-600") %>
<%= textarea(f, :desc, <%= textarea(f, :desc,
id: "container-form-desc",
class: "input input-primary col-span-2", class: "input input-primary col-span-2",
placeholder: gettext("Metal ammo can with the anime girl sticker"),
phx_hook: "MaintainAttrs", phx_hook: "MaintainAttrs",
placeholder: gettext("Metal ammo can with the anime girl sticker") phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :desc, "col-span-3 text-center") %> <%= error_tag(f, :desc, "col-span-3 text-center") %>
@ -42,9 +44,11 @@
<%= label(f, :location, gettext("Location"), class: "title text-lg text-primary-600") %> <%= label(f, :location, gettext("Location"), class: "title text-lg text-primary-600") %>
<%= textarea(f, :location, <%= textarea(f, :location,
id: "container-form-location",
class: "input input-primary col-span-2", class: "input input-primary col-span-2",
placeholder: gettext("On the bookshelf"),
phx_hook: "MaintainAttrs", phx_hook: "MaintainAttrs",
placeholder: gettext("On the bookshelf") phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :location, "col-span-3 text-center") %> <%= error_tag(f, :location, "col-span-3 text-center") %>

View File

@ -4,8 +4,7 @@ defmodule CanneryWeb.ContainerLive.Index do
""" """
use CanneryWeb, :live_view use CanneryWeb, :live_view
import CanneryWeb.Components.ContainerCard alias Cannery.{Containers, Containers.Container}
alias Cannery.{Containers, Containers.Container, Repo}
alias Ecto.Changeset alias Ecto.Changeset
@impl true @impl true
@ -23,10 +22,7 @@ defmodule CanneryWeb.ContainerLive.Index do
end end
defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"id" => id}) do defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"id" => id}) do
%{name: container_name} = %{name: container_name} = container = Containers.get_container!(id, current_user)
container =
Containers.get_container!(id, current_user)
|> Repo.preload([:tags, :ammo_groups])
socket socket
|> assign(page_title: gettext("Edit %{name}", name: container_name), container: container) |> assign(page_title: gettext("Edit %{name}", name: container_name), container: container)
@ -62,9 +58,7 @@ defmodule CanneryWeb.ContainerLive.Index do
end end
defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit_tags, %{"id" => id}) do defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit_tags, %{"id" => id}) do
%{name: container_name} = %{name: container_name} = container = Containers.get_container!(id, current_user)
container =
Containers.get_container!(id, current_user) |> Repo.preload([:tags, :ammo_groups])
page_title = gettext("Edit %{name} tags", name: container_name) page_title = gettext("Edit %{name} tags", name: container_name)
socket |> assign(page_title: page_title, container: container) socket |> assign(page_title: page_title, container: container)
@ -106,12 +100,10 @@ defmodule CanneryWeb.ContainerLive.Index do
{:noreply, socket} {:noreply, socket}
end end
@impl true
def handle_event("toggle_table", _params, %{assigns: %{view_table: view_table}} = socket) do def handle_event("toggle_table", _params, %{assigns: %{view_table: view_table}} = socket) do
{:noreply, socket |> assign(:view_table, !view_table) |> display_containers()} {:noreply, socket |> assign(:view_table, !view_table) |> display_containers()}
end end
@impl true
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.container_index_path(Endpoint, :index))} {:noreply, socket |> push_patch(to: Routes.container_index_path(Endpoint, :index))}
end end
@ -122,10 +114,6 @@ defmodule CanneryWeb.ContainerLive.Index do
end end
defp display_containers(%{assigns: %{search: search, current_user: current_user}} = socket) do defp display_containers(%{assigns: %{search: search, current_user: current_user}} = socket) do
containers = socket |> assign(:containers, Containers.list_containers(search, current_user))
Containers.list_containers(search, current_user)
|> Repo.preload([:tags, :ammo_groups])
socket |> assign(:containers, containers)
end end
end end

View File

@ -20,11 +20,11 @@
<div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl"> <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
<.form <.form
:let={f} :let={f}
for={:search} for={%{}}
as={:search}
phx-change="search" phx-change="search"
phx-submit="search" phx-submit="search"
class="grow self-stretch flex flex-col items-stretch" class="grow self-stretch flex flex-col items-stretch"
data-qa="container_search"
> >
<%= text_input(f, :search_term, <%= text_input(f, :search_term,
class: "input input-primary", class: "input input-primary",
@ -61,6 +61,9 @@
<.link <.link
patch={Routes.container_index_path(Endpoint, :edit_tags, container)} patch={Routes.container_index_path(Endpoint, :edit_tags, container)}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={
dgettext("actions", "Tag %{container_name}", container_name: container.name)
}
> >
<i class="fa-fw fa-lg fas fa-tags"></i> <i class="fa-fw fa-lg fas fa-tags"></i>
</.link> </.link>
@ -70,7 +73,9 @@
<.link <.link
patch={Routes.container_index_path(Endpoint, :edit, container)} patch={Routes.container_index_path(Endpoint, :edit, container)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"edit-#{container.id}"} aria-label={
dgettext("actions", "Edit %{container_name}", container_name: container.name)
}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -78,7 +83,9 @@
<.link <.link
patch={Routes.container_index_path(Endpoint, :clone, container)} patch={Routes.container_index_path(Endpoint, :clone, container)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"clone-#{container.id}"} aria-label={
dgettext("actions", "Clone %{container_name}", container_name: container.name)
}
> >
<i class="fa-fw fa-lg fas fa-copy"></i> <i class="fa-fw fa-lg fas fa-copy"></i>
</.link> </.link>
@ -91,7 +98,9 @@
data-confirm={ data-confirm={
dgettext("prompts", "Are you sure you want to delete %{name}?", name: container.name) dgettext("prompts", "Are you sure you want to delete %{name}?", name: container.name)
} }
data-qa={"delete-#{container.id}"} aria-label={
dgettext("actions", "Delete %{container_name}", container_name: container.name)
}
> >
<i class="fa-fw fa-lg fas fa-trash"></i> <i class="fa-fw fa-lg fas fa-trash"></i>
</.link> </.link>
@ -99,12 +108,19 @@
</.live_component> </.live_component>
<% else %> <% else %>
<div class="w-full flex flex-row flex-wrap justify-center items-stretch"> <div class="w-full flex flex-row flex-wrap justify-center items-stretch">
<.container_card :for={container <- @containers} container={container}> <.container_card
:for={container <- @containers}
container={container}
current_user={@current_user}
>
<:tag_actions> <:tag_actions>
<div class="mx-4 my-2"> <div class="mx-4 my-2">
<.link <.link
patch={Routes.container_index_path(Endpoint, :edit_tags, container)} patch={Routes.container_index_path(Endpoint, :edit_tags, container)}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={
dgettext("actions", "Tag %{container_name}", container_name: container.name)
}
> >
<i class="fa-fw fa-lg fas fa-tags"></i> <i class="fa-fw fa-lg fas fa-tags"></i>
</.link> </.link>
@ -113,7 +129,9 @@
<.link <.link
patch={Routes.container_index_path(Endpoint, :edit, container)} patch={Routes.container_index_path(Endpoint, :edit, container)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"edit-#{container.id}"} aria-label={
dgettext("actions", "Edit %{container_name}", container_name: container.name)
}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -121,7 +139,9 @@
<.link <.link
patch={Routes.container_index_path(Endpoint, :clone, container)} patch={Routes.container_index_path(Endpoint, :clone, container)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"clone-#{container.id}"} aria-label={
dgettext("actions", "Clone %{container_name}", container_name: container.name)
}
> >
<i class="fa-fw fa-lg fas fa-copy"></i> <i class="fa-fw fa-lg fas fa-copy"></i>
</.link> </.link>
@ -134,7 +154,9 @@
data-confirm={ data-confirm={
dgettext("prompts", "Are you sure you want to delete %{name}?", name: container.name) dgettext("prompts", "Are you sure you want to delete %{name}?", name: container.name)
} }
data-qa={"delete-#{container.id}"} aria-label={
dgettext("actions", "Delete %{container_name}", container_name: container.name)
}
> >
<i class="fa-fw fa-lg fas fa-trash"></i> <i class="fa-fw fa-lg fas fa-trash"></i>
</.link> </.link>
@ -144,10 +166,9 @@
<% end %> <% end %>
</div> </div>
<.modal <%= case @live_action do %>
:if={@live_action in [:new, :edit, :clone]} <% modifying when modifying in [:new, :edit, :clone] -> %>
return_to={Routes.container_index_path(Endpoint, :index)} <.modal return_to={Routes.container_index_path(Endpoint, :index)}>
>
<.live_component <.live_component
module={CanneryWeb.ContainerLive.FormComponent} module={CanneryWeb.ContainerLive.FormComponent}
id={@container.id || :new} id={@container.id || :new}
@ -157,15 +178,18 @@
return_to={Routes.container_index_path(Endpoint, :index)} return_to={Routes.container_index_path(Endpoint, :index)}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :edit_tags -> %>
<.modal :if={@live_action == :edit_tags} return_to={Routes.container_index_path(Endpoint, :index)}> <.modal return_to={Routes.container_index_path(Endpoint, :index)}>
<.live_component <.live_component
module={CanneryWeb.ContainerLive.EditTagsComponent} module={CanneryWeb.ContainerLive.EditTagsComponent}
id={@container.id} id={@container.id}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
container={@container} container={@container}
current_path={Routes.container_index_path(Endpoint, :edit_tags, @container)}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% _ -> %>
<% end %>

View File

@ -4,8 +4,7 @@ defmodule CanneryWeb.ContainerLive.Show do
""" """
use CanneryWeb, :live_view use CanneryWeb, :live_view
import CanneryWeb.Components.{AmmoGroupCard, TagCard} alias Cannery.{Accounts.User, ActivityLog, Ammo, Containers, Containers.Container}
alias Cannery.{Accounts.User, Ammo, Containers, Containers.Container, Repo, Tags}
alias CanneryWeb.Endpoint alias CanneryWeb.Endpoint
alias Ecto.Changeset alias Ecto.Changeset
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@ -31,7 +30,7 @@ defmodule CanneryWeb.ContainerLive.Show do
%{assigns: %{container: container, current_user: current_user}} = socket %{assigns: %{container: container, current_user: current_user}} = socket
) do ) do
socket = socket =
case Tags.get_tag(tag_id, current_user) do case Containers.get_tag(tag_id, current_user) do
{:ok, tag} -> {:ok, tag} ->
_count = Containers.remove_tag!(container, tag, current_user) _count = Containers.remove_tag!(container, tag, current_user)
@ -43,14 +42,13 @@ defmodule CanneryWeb.ContainerLive.Show do
socket |> put_flash(:info, prompt) |> render_container() socket |> put_flash(:info, prompt) |> render_container()
{:error, error_string} -> {:error, :not_found} ->
socket |> put_flash(:error, error_string) socket |> put_flash(:error, dgettext("errors", "Tag not found"))
end end
{:noreply, socket} {:noreply, socket}
end end
@impl true
def handle_event( def handle_event(
"delete_container", "delete_container",
_params, _params,
@ -84,12 +82,10 @@ defmodule CanneryWeb.ContainerLive.Show do
{:noreply, socket} {:noreply, socket}
end end
@impl true
def handle_event("toggle_show_used", _params, %{assigns: %{show_used: show_used}} = socket) do def handle_event("toggle_show_used", _params, %{assigns: %{show_used: show_used}} = socket) do
{:noreply, socket |> assign(:show_used, !show_used) |> render_container()} {:noreply, socket |> assign(:show_used, !show_used) |> render_container()}
end end
@impl true
def handle_event("toggle_table", _params, %{assigns: %{view_table: view_table}} = socket) do def handle_event("toggle_table", _params, %{assigns: %{view_table: view_table}} = socket) do
{:noreply, socket |> assign(:view_table, !view_table) |> render_container()} {:noreply, socket |> assign(:view_table, !view_table) |> render_container()}
end end
@ -100,12 +96,11 @@ defmodule CanneryWeb.ContainerLive.Show do
id, id,
current_user current_user
) do ) do
%{name: container_name} = %{name: container_name} = container = Containers.get_container!(id, current_user)
container =
Containers.get_container!(id, current_user)
|> Repo.preload([:tags], force: true)
ammo_groups = Ammo.list_ammo_groups_for_container(container, current_user, show_used) ammo_groups = Ammo.list_ammo_groups_for_container(container, current_user, show_used)
original_counts = ammo_groups |> Ammo.get_original_counts(current_user)
cprs = ammo_groups |> Ammo.get_cprs(current_user)
last_used_dates = ammo_groups |> ActivityLog.get_last_used_dates(current_user)
page_title = page_title =
case live_action do case live_action do
@ -114,7 +109,16 @@ defmodule CanneryWeb.ContainerLive.Show do
:edit_tags -> gettext("Edit %{name} tags", name: container_name) :edit_tags -> gettext("Edit %{name} tags", name: container_name)
end end
socket |> assign(container: container, ammo_groups: ammo_groups, page_title: page_title) socket
|> assign(
container: container,
round_count: Ammo.get_round_count_for_container!(container, current_user),
ammo_groups: ammo_groups,
original_counts: original_counts,
cprs: cprs,
last_used_dates: last_used_dates,
page_title: page_title
)
end end
@spec render_container(Socket.t()) :: Socket.t() @spec render_container(Socket.t()) :: Socket.t()

View File

@ -20,21 +20,18 @@
<%= unless @ammo_groups |> Enum.empty?() do %> <%= unless @ammo_groups |> Enum.empty?() do %>
<span class="rounded-lg title text-lg"> <span class="rounded-lg title text-lg">
<%= if @show_used do %>
<%= gettext("Total packs:") %>
<% else %>
<%= gettext("Packs:") %> <%= gettext("Packs:") %>
<% end %> <%= @ammo_groups |> Enum.reject(fn %{count: count} -> count in [0, nil] end) |> Enum.count() %>
</span>
<span :if={@show_used} class="rounded-lg title text-lg">
<%= gettext("Total packs:") %>
<%= Enum.count(@ammo_groups) %> <%= Enum.count(@ammo_groups) %>
</span> </span>
<span class="rounded-lg title text-lg"> <span class="rounded-lg title text-lg">
<%= if @show_used do %>
<%= gettext("Total rounds:") %>
<% else %>
<%= gettext("Rounds:") %> <%= gettext("Rounds:") %>
<% end %> <%= @round_count %>
<%= @container |> Containers.get_container_rounds!() %>
</span> </span>
<% end %> <% end %>
@ -42,7 +39,7 @@
<.link <.link
patch={Routes.container_show_path(Endpoint, :edit, @container)} patch={Routes.container_show_path(Endpoint, :edit, @container)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa="edit" aria-label={dgettext("actions", "Edit %{container_name}", container_name: @container.name)}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -54,7 +51,9 @@
data-confirm={ data-confirm={
dgettext("prompts", "Are you sure you want to delete %{name}?", name: @container.name) dgettext("prompts", "Are you sure you want to delete %{name}?", name: @container.name)
} }
data-qa="delete" aria-label={
dgettext("actions", "Delete %{container_name}", container_name: @container.name)
}
> >
<i class="fa-fw fa-lg fas fa-trash"></i> <i class="fa-fw fa-lg fas fa-trash"></i>
</.link> </.link>
@ -128,17 +127,23 @@
</.live_component> </.live_component>
<% else %> <% else %>
<div class="flex flex-wrap justify-center items-stretch"> <div class="flex flex-wrap justify-center items-stretch">
<.ammo_group_card :for={ammo_group <- @ammo_groups} ammo_group={ammo_group} /> <.ammo_group_card
:for={%{id: ammo_group_id} = ammo_group <- @ammo_groups}
ammo_group={ammo_group}
original_count={Map.fetch!(@original_counts, ammo_group_id)}
cpr={Map.get(@cprs, ammo_group_id)}
last_used_date={Map.get(@last_used_dates, ammo_group_id)}
current_user={@current_user}
/>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>
</div> </div>
<.modal <%= case @live_action do %>
:if={@live_action == :edit} <% :edit -> %>
return_to={Routes.container_show_path(Endpoint, :show, @container)} <.modal return_to={Routes.container_show_path(Endpoint, :show, @container)}>
>
<.live_component <.live_component
module={CanneryWeb.ContainerLive.FormComponent} module={CanneryWeb.ContainerLive.FormComponent}
id={@container.id} id={@container.id}
@ -148,12 +153,9 @@
return_to={Routes.container_show_path(Endpoint, :show, @container)} return_to={Routes.container_show_path(Endpoint, :show, @container)}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :edit_tags -> %>
<.modal <.modal return_to={Routes.container_show_path(Endpoint, :show, @container)}>
:if={@live_action == :edit_tags}
return_to={Routes.container_show_path(Endpoint, :show, @container)}
>
<.live_component <.live_component
module={CanneryWeb.ContainerLive.EditTagsComponent} module={CanneryWeb.ContainerLive.EditTagsComponent}
id={@container.id} id={@container.id}
@ -161,6 +163,9 @@
action={@live_action} action={@live_action}
container={@container} container={@container}
return_to={Routes.container_show_path(Endpoint, :show, @container)} return_to={Routes.container_show_path(Endpoint, :show, @container)}
current_path={Routes.container_show_path(Endpoint, :edit_tags, @container)}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% _ -> %>
<% end %>

View File

@ -12,7 +12,6 @@ defmodule CanneryWeb.HomeLive do
@impl true @impl true
def mount(_params, _session, socket) do def mount(_params, _session, socket) do
admins = Accounts.list_users_by_role(:admin) admins = Accounts.list_users_by_role(:admin)
socket = socket |> assign(page_title: gettext("Home"), admins: admins, version: @version) {:ok, socket |> assign(page_title: gettext("Home"), admins: admins, version: @version)}
{:ok, socket}
end end
end end

View File

@ -17,8 +17,7 @@
<hr class="hr" /> <hr class="hr" />
<ul class="flex flex-col space-y-4 text-center"> <ul class="flex flex-col space-y-4 text-center">
<li class="flex flex-col justify-center items-center <li class="flex flex-col justify-center items-center space-y-2">
space-y-2">
<b class="whitespace-nowrap"> <b class="whitespace-nowrap">
<%= gettext("Easy to Use:") %> <%= gettext("Easy to Use:") %>
</b> </b>
@ -37,8 +36,7 @@
<%= gettext("Your data stays with you, period") %> <%= gettext("Your data stays with you, period") %>
</p> </p>
</li> </li>
<li class="flex flex-col justify-center items-center <li class="flex flex-col justify-center items-center space-y-2">
space-y-2">
<b class="whitespace-nowrap"> <b class="whitespace-nowrap">
<%= gettext("Simple:") %> <%= gettext("Simple:") %>
</b> </b>
@ -66,9 +64,13 @@
</.link> </.link>
<% else %> <% else %>
<div class="flex flex-wrap justify-center space-x-2"> <div class="flex flex-wrap justify-center space-x-2">
<a :for={%{email: email} <- @admins} class="hover:underline" href={"mailto:#{email}"}> <.link
:for={%{email: email} <- @admins}
class="hover:underline"
href={"mailto:#{email}"}
>
<%= email %> <%= email %>
</a> </.link>
</div> </div>
<% end %> <% end %>
</p> </p>
@ -77,9 +79,9 @@
<li class="flex flex-row justify-center space-x-2"> <li class="flex flex-row justify-center space-x-2">
<b><%= gettext("Registration:") %></b> <b><%= gettext("Registration:") %></b>
<p> <p>
<%= case Application.get_env(:cannery, Cannery.Accounts)[:registration] do <%= case Accounts.registration_mode() do
"public" -> gettext("Public Signups") :public -> gettext("Public Signups")
_ -> gettext("Invite Only") :invite_only -> gettext("Invite Only")
end %> end %>
</p> </p>
</li> </li>

View File

@ -4,7 +4,6 @@ defmodule CanneryWeb.InviteLive.Index do
""" """
use CanneryWeb, :live_view use CanneryWeb, :live_view
import CanneryWeb.Components.{InviteCard, UserCard}
alias Cannery.Accounts alias Cannery.Accounts
alias Cannery.Accounts.{Invite, Invites} alias Cannery.Accounts.{Invite, Invites}
alias CanneryWeb.HomeLive alias CanneryWeb.HomeLive
@ -30,8 +29,8 @@ defmodule CanneryWeb.InviteLive.Index do
end end
defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"id" => id}) do defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit, %{"id" => id}) do
socket invite = Invites.get_invite!(id, current_user)
|> assign(page_title: gettext("Edit Invite"), invite: Invites.get_invite!(id, current_user)) socket |> assign(page_title: gettext("Edit Invite"), invite: invite)
end end
defp apply_action(socket, :new, _params) do defp apply_action(socket, :new, _params) do
@ -123,22 +122,17 @@ defmodule CanneryWeb.InviteLive.Index do
{:noreply, socket} {:noreply, socket}
end end
@impl true
def handle_event("copy_to_clipboard", _params, socket) do def handle_event("copy_to_clipboard", _params, socket) do
prompt = dgettext("prompts", "Copied to clipboard") {:noreply, socket |> put_flash(:info, dgettext("prompts", "Copied to clipboard"))}
{:noreply, socket |> put_flash(:info, prompt)}
end end
@impl true
def handle_event( def handle_event(
"delete_user", "delete_user",
%{"id" => id}, %{"id" => id},
%{assigns: %{current_user: current_user}} = socket %{assigns: %{current_user: current_user}} = socket
) do ) do
%{email: user_email} = Accounts.get_user!(id) |> Accounts.delete_user!(current_user) %{email: user_email} = Accounts.get_user!(id) |> Accounts.delete_user!(current_user)
prompt = dgettext("prompts", "%{user_email} deleted succesfully", user_email: user_email) prompt = dgettext("prompts", "%{user_email} deleted succesfully", user_email: user_email)
{:noreply, socket |> put_flash(:info, prompt) |> display_invites()} {:noreply, socket |> put_flash(:info, prompt) |> display_invites()}
end end
@ -151,7 +145,8 @@ defmodule CanneryWeb.InviteLive.Index do
|> Map.get(:admin, []) |> Map.get(:admin, [])
|> Enum.reject(fn %{id: user_id} -> user_id == current_user.id end) |> Enum.reject(fn %{id: user_id} -> user_id == current_user.id end)
use_counts = invites |> Invites.get_use_counts(current_user)
users = all_users |> Map.get(:user, []) users = all_users |> Map.get(:user, [])
socket |> assign(invites: invites, admins: admins, users: users) socket |> assign(invites: invites, use_counts: use_counts, admins: admins, users: users)
end end
end end

View File

@ -19,13 +19,21 @@
<% end %> <% end %>
<div class="flex flex-col justify-center items-stretch space-y-4"> <div class="flex flex-col justify-center items-stretch space-y-4">
<.invite_card :for={invite <- @invites} invite={invite} current_user={@current_user}> <.invite_card
:for={invite <- @invites}
invite={invite}
current_user={@current_user}
use_count={Map.get(@use_counts, invite.id)}
>
<:code_actions> <:code_actions>
<form phx-submit="copy_to_clipboard"> <form phx-submit="copy_to_clipboard">
<button <button
type="submit" type="submit"
class="mx-2 my-1 btn btn-primary" class="mx-2 my-1 btn btn-primary"
phx-click={JS.dispatch("cannery:clipcopy", to: "#code-#{invite.id}")} phx-click={JS.dispatch("cannery:clipcopy", to: "#code-#{invite.id}")}
aria-label={
dgettext("actions", "Copy invite link for %{invite_name}", invite_name: invite.name)
}
> >
<%= dgettext("actions", "Copy to clipboard") %> <%= dgettext("actions", "Copy to clipboard") %>
</button> </button>
@ -34,7 +42,9 @@
<.link <.link
patch={Routes.invite_index_path(Endpoint, :edit, invite)} patch={Routes.invite_index_path(Endpoint, :edit, invite)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"edit-#{invite.id}"} aria-label={
dgettext("actions", "Edit invite for %{invite_name}", invite_name: invite.name)
}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -49,21 +59,23 @@
invite_name: invite.name invite_name: invite.name
) )
} }
data-qa={"delete-#{invite.id}"} aria-label={
dgettext("actions", "Delete invite for %{invite_name}", invite_name: invite.name)
}
> >
<i class="fa-fw fa-lg fas fa-trash"></i> <i class="fa-fw fa-lg fas fa-trash"></i>
</.link> </.link>
<a <.link
href="#" href="#"
class="btn btn-primary" class="btn btn-primary"
phx-click={if invite.disabled_at, do: "enable_invite", else: "disable_invite"} phx-click={if invite.disabled_at, do: "enable_invite", else: "disable_invite"}
phx-value-id={invite.id} phx-value-id={invite.id}
> >
<%= if invite.disabled_at, do: gettext("Enable"), else: gettext("Disable") %> <%= if invite.disabled_at, do: gettext("Enable"), else: gettext("Disable") %>
</a> </.link>
<a <.link
:if={invite.disabled_at |> is_nil() and not (invite.uses_left |> is_nil())} :if={invite.disabled_at |> is_nil() and not (invite.uses_left |> is_nil())}
href="#" href="#"
class="btn btn-primary" class="btn btn-primary"
@ -76,7 +88,7 @@
} }
> >
<%= dgettext("actions", "Set Unlimited") %> <%= dgettext("actions", "Set Unlimited") %>
</a> </.link>
</.invite_card> </.invite_card>
</div> </div>

View File

@ -1,128 +0,0 @@
defmodule CanneryWeb.LiveHelpers do
@moduledoc """
Contains common helper functions for liveviews
"""
import Phoenix.Component
alias Phoenix.LiveView.JS
@doc """
Renders a live component inside a modal.
The rendered modal receives a `:return_to` option to properly update
the URL when the modal is closed.
## Examples
<.modal return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}>
<.live_component
module={<%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent}
id={@<%= schema.singular %>.id || :new}
title={@page_title}
action={@live_action}
return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}
<%= schema.singular %>: @<%= schema.singular %>
/>
</.modal>
"""
def modal(assigns) do
~H"""
<.link
patch={@return_to}
id="modal-bg"
class="fade-in fixed z-10 left-0 top-0
w-full h-full overflow-hidden
p-8 flex flex-col justify-center items-center cursor-auto"
style="background-color: rgba(0,0,0,0.4);"
phx-remove={hide_modal()}
>
<span class="hidden"></span>
</.link>
<div
id="modal"
class="fixed z-10 left-0 top-0 pointer-events-none
w-full h-full overflow-hidden
p-4 sm:p-8 flex flex-col justify-center items-center"
>
<div
id="modal-content"
class="fade-in-scale w-full max-w-3xl relative
pointer-events-auto overflow-hidden
px-8 py-4 sm:py-8
flex flex-col justify-start items-center
bg-white border-2 rounded-lg"
>
<.link
patch={@return_to}
id="close"
class="absolute top-8 right-10
text-gray-500 hover:text-gray-800
transition-all duration-500 ease-in-out"
phx-remove={hide_modal()}
>
<i class="fa-fw fa-lg fas fa-times"></i>
</.link>
<div class="overflow-x-hidden overflow-y-auto w-full p-8 flex flex-col space-y-4 justify-start items-center">
<%= render_slot(@inner_block) %>
</div>
</div>
</div>
"""
end
defp hide_modal(js \\ %JS{}) do
js
|> JS.hide(to: "#modal", transition: "fade-out")
|> JS.hide(to: "#modal-bg", transition: "fade-out")
|> JS.hide(to: "#modal-content", transition: "fade-out-scale")
end
@doc """
A toggle button element that can be directed to a liveview or a
live_component's `handle_event/3`.
## Examples
<.toggle_button action="my_liveview_action" value={@some_value}>
<span>Toggle me!</span>
</.toggle_button>
<.toggle_button action="my_live_component_action" target={@myself} value={@some_value}>
<span>Whatever you want</span>
</.toggle_button>
"""
def toggle_button(assigns) do
assigns = assigns |> assign_new(:id, fn -> assigns.action end)
~H"""
<label for={@id} class="inline-flex relative items-center cursor-pointer">
<input
id={@id}
type="checkbox"
value={@value}
checked={@value}
class="sr-only peer"
data-qa={@id}
{
if assigns |> Map.has_key?(:target),
do: %{"phx-click": @action, "phx-value-value": @value, "phx-target": @target},
else: %{"phx-click": @action, "phx-value-value": @value}
}
/>
<div class="w-11 h-6 bg-gray-300 rounded-full peer
peer-focus:ring-4 peer-focus:ring-teal-300 dark:peer-focus:ring-teal-800
peer-checked:bg-gray-600
peer-checked:after:translate-x-full peer-checked:after:border-white
after:content-[''] after:absolute after:top-1 after:left-[2px] after:bg-white after:border-gray-300
after:border after:rounded-full after:h-5 after:w-5
after:transition-all after:duration-250 after:ease-in-out
transition-colors duration-250 ease-in-out">
</div>
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
<%= render_slot(@inner_block) %>
</span>
</label>
"""
end
end

View File

@ -5,8 +5,12 @@ defmodule CanneryWeb.RangeLive.FormComponent do
use CanneryWeb, :live_component use CanneryWeb, :live_component
alias Cannery.{Accounts.User, ActivityLog, ActivityLog.ShotGroup, Ammo, Ammo.AmmoGroup} alias Cannery.{Accounts.User, ActivityLog, ActivityLog.ShotGroup, Ammo, Ammo.AmmoGroup}
alias Ecto.Changeset
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@impl true
def mount(socket), do: {:ok, socket |> assign(:ammo_group, nil)}
@impl true @impl true
@spec update( @spec update(
%{ %{
@ -19,28 +23,23 @@ defmodule CanneryWeb.RangeLive.FormComponent do
) :: {:ok, Socket.t()} ) :: {:ok, Socket.t()}
def update( def update(
%{ %{
shot_group: %ShotGroup{ammo_group_id: ammo_group_id} = shot_group, shot_group: %ShotGroup{ammo_group_id: ammo_group_id},
current_user: current_user current_user: current_user
} = assigns, } = assigns,
socket socket
) do )
changeset = shot_group |> ShotGroup.update_changeset(current_user, %{}) when is_binary(ammo_group_id) do
ammo_group = Ammo.get_ammo_group!(ammo_group_id, current_user) ammo_group = Ammo.get_ammo_group!(ammo_group_id, current_user)
{:ok, socket |> assign(assigns) |> assign(ammo_group: ammo_group, changeset: changeset)} {:ok, socket |> assign(assigns) |> assign(:ammo_group, ammo_group) |> assign_changeset(%{})}
end
def update(%{shot_group: %ShotGroup{}} = assigns, socket) do
{:ok, socket |> assign(assigns) |> assign_changeset(%{})}
end end
@impl true @impl true
def handle_event( def handle_event("validate", %{"shot_group" => shot_group_params}, socket) do
"validate", {:noreply, socket |> assign_changeset(shot_group_params, :validate)}
%{"shot_group" => shot_group_params},
%{assigns: %{current_user: current_user, shot_group: shot_group}} = socket
) do
changeset =
shot_group
|> ShotGroup.update_changeset(current_user, shot_group_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :changeset, changeset)}
end end
def handle_event( def handle_event(
@ -61,4 +60,37 @@ defmodule CanneryWeb.RangeLive.FormComponent do
{:noreply, socket} {:noreply, socket}
end end
defp assign_changeset(
%{
assigns: %{
action: live_action,
current_user: user,
ammo_group: ammo_group,
shot_group: shot_group
}
} = socket,
shot_group_params,
action \\ nil
) do
default_action =
case live_action do
:add_shot_group -> :insert
editing when editing in [:edit, :edit_shot_group] -> :update
end
changeset =
case default_action do
:insert -> shot_group |> ShotGroup.create_changeset(user, ammo_group, shot_group_params)
:update -> shot_group |> ShotGroup.update_changeset(user, shot_group_params)
end
changeset =
case changeset |> Changeset.apply_action(action || default_action) do
{:ok, _data} -> changeset
{:error, changeset} -> changeset
end
socket |> assign(:changeset, changeset)
end
end end

View File

@ -29,8 +29,11 @@
<%= label(f, :notes, gettext("Notes"), class: "title text-lg text-primary-600") %> <%= label(f, :notes, gettext("Notes"), class: "title text-lg text-primary-600") %>
<%= textarea(f, :notes, <%= textarea(f, :notes,
id: "shot-group-form-notes",
class: "input input-primary col-span-2", class: "input input-primary col-span-2",
phx_hook: "MaintainAttrs" placeholder: gettext("Really great weather"),
phx_hook: "MaintainAttrs",
phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :notes, "col-span-3") %> <%= error_tag(f, :notes, "col-span-3") %>

View File

@ -4,8 +4,7 @@ defmodule CanneryWeb.RangeLive.Index do
""" """
use CanneryWeb, :live_view use CanneryWeb, :live_view
import CanneryWeb.Components.AmmoGroupCard alias Cannery.{ActivityLog, ActivityLog.ShotGroup, Ammo}
alias Cannery.{ActivityLog, ActivityLog.ShotGroup, Ammo, Repo}
alias CanneryWeb.Endpoint alias CanneryWeb.Endpoint
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@ -81,7 +80,6 @@ defmodule CanneryWeb.RangeLive.Index do
{:noreply, socket |> put_flash(:info, prompt) |> display_shot_groups()} {:noreply, socket |> put_flash(:info, prompt) |> display_shot_groups()}
end end
@impl true
def handle_event( def handle_event(
"toggle_staged", "toggle_staged",
%{"ammo_group_id" => ammo_group_id}, %{"ammo_group_id" => ammo_group_id},
@ -96,7 +94,6 @@ defmodule CanneryWeb.RangeLive.Index do
{:noreply, socket |> put_flash(:info, prompt) |> display_shot_groups()} {:noreply, socket |> put_flash(:info, prompt) |> display_shot_groups()}
end end
@impl true
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.range_index_path(Endpoint, :index))} {:noreply, socket |> push_patch(to: Routes.range_index_path(Endpoint, :index))}
end end
@ -107,16 +104,19 @@ defmodule CanneryWeb.RangeLive.Index do
@spec display_shot_groups(Socket.t()) :: Socket.t() @spec display_shot_groups(Socket.t()) :: Socket.t()
defp display_shot_groups(%{assigns: %{search: search, current_user: current_user}} = socket) do defp display_shot_groups(%{assigns: %{search: search, current_user: current_user}} = socket) do
shot_groups = shot_groups = ActivityLog.list_shot_groups(search, current_user)
ActivityLog.list_shot_groups(search, current_user)
|> Repo.preload(ammo_group: :ammo_type)
ammo_groups = Ammo.list_staged_ammo_groups(current_user) ammo_groups = Ammo.list_staged_ammo_groups(current_user)
chart_data = shot_groups |> get_chart_data_for_shot_group() chart_data = shot_groups |> get_chart_data_for_shot_group()
original_counts = ammo_groups |> Ammo.get_original_counts(current_user)
cprs = ammo_groups |> Ammo.get_cprs(current_user)
last_used_dates = ammo_groups |> ActivityLog.get_last_used_dates(current_user)
socket socket
|> assign( |> assign(
ammo_groups: ammo_groups, ammo_groups: ammo_groups,
original_counts: original_counts,
cprs: cprs,
last_used_dates: last_used_dates,
chart_data: chart_data, chart_data: chart_data,
shot_groups: shot_groups shot_groups: shot_groups
) )
@ -125,7 +125,6 @@ defmodule CanneryWeb.RangeLive.Index do
@spec get_chart_data_for_shot_group([ShotGroup.t()]) :: [map()] @spec get_chart_data_for_shot_group([ShotGroup.t()]) :: [map()]
defp get_chart_data_for_shot_group(shot_groups) do defp get_chart_data_for_shot_group(shot_groups) do
shot_groups shot_groups
|> Repo.preload(ammo_group: :ammo_type)
|> Enum.group_by(fn %{date: date} -> date end, fn %{count: count} -> count end) |> Enum.group_by(fn %{date: date} -> date end, fn %{count: count} -> count end)
|> Enum.map(fn {date, rounds} -> |> Enum.map(fn {date, rounds} ->
sum = Enum.sum(rounds) sum = Enum.sum(rounds)

View File

@ -18,7 +18,14 @@
</.link> </.link>
<div class="w-full flex flex-row flex-wrap justify-center items-stretch"> <div class="w-full flex flex-row flex-wrap justify-center items-stretch">
<.ammo_group_card :for={ammo_group <- @ammo_groups} ammo_group={ammo_group}> <.ammo_group_card
:for={%{id: ammo_group_id} = ammo_group <- @ammo_groups}
ammo_group={ammo_group}
original_count={Map.fetch!(@original_counts, ammo_group_id)}
cpr={Map.get(@cprs, ammo_group_id)}
last_used_date={Map.get(@last_used_dates, ammo_group_id)}
current_user={@current_user}
>
<button <button
type="button" type="button"
class="btn btn-primary" class="btn btn-primary"
@ -70,11 +77,11 @@
<div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl"> <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
<.form <.form
:let={f} :let={f}
for={:search} for={%{}}
as={:search}
phx-change="search" phx-change="search"
phx-submit="search" phx-submit="search"
class="grow self-stretch flex flex-col items-stretch" class="grow self-stretch flex flex-col items-stretch"
data-qa="shot_group_search"
> >
<%= text_input(f, :search_term, <%= text_input(f, :search_term,
class: "input input-primary", class: "input input-primary",
@ -102,7 +109,11 @@
<.link <.link
patch={Routes.range_index_path(Endpoint, :edit, shot_group)} patch={Routes.range_index_path(Endpoint, :edit, shot_group)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"edit-#{shot_group.id}"} aria-label={
dgettext("actions", "Edit shot record of %{shot_group_count} shots",
shot_group_count: shot_group.count
)
}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -115,7 +126,11 @@
data-confirm={ data-confirm={
dgettext("prompts", "Are you sure you want to delete this shot record?") dgettext("prompts", "Are you sure you want to delete this shot record?")
} }
data-qa={"delete-#{shot_group.id}"} aria-label={
dgettext("actions", "Delete shot record of %{shot_group_count} shots",
shot_group_count: shot_group.count
)
}
> >
<i class="fa-fw fa-lg fas fa-trash"></i> <i class="fa-fw fa-lg fas fa-trash"></i>
</.link> </.link>
@ -126,7 +141,9 @@
<% end %> <% end %>
</div> </div>
<.modal :if={@live_action == :edit} return_to={Routes.range_index_path(Endpoint, :index)}> <%= case @live_action do %>
<% :edit -> %>
<.modal return_to={Routes.range_index_path(Endpoint, :index)}>
<.live_component <.live_component
module={CanneryWeb.RangeLive.FormComponent} module={CanneryWeb.RangeLive.FormComponent}
id={@shot_group.id} id={@shot_group.id}
@ -136,12 +153,9 @@
return_to={Routes.range_index_path(Endpoint, :index)} return_to={Routes.range_index_path(Endpoint, :index)}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :add_shot_group -> %>
<.modal <.modal return_to={Routes.range_index_path(Endpoint, :index)}>
:if={@live_action == :add_shot_group}
return_to={Routes.range_index_path(Endpoint, :index)}
>
<.live_component <.live_component
module={CanneryWeb.Components.AddShotGroupComponent} module={CanneryWeb.Components.AddShotGroupComponent}
id={:new} id={:new}
@ -151,4 +165,6 @@
return_to={Routes.range_index_path(Endpoint, :index)} return_to={Routes.range_index_path(Endpoint, :index)}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% _ -> %>
<% end %>

View File

@ -1,11 +1,10 @@
defmodule CanneryWeb.TagLive.FormComponent do defmodule CanneryWeb.TagLive.FormComponent do
@moduledoc """ @moduledoc """
Livecomponent that can update or create an Cannery.Tags.Tag Livecomponent that can update or create an Cannery.Containers.Tag
""" """
use CanneryWeb, :live_component use CanneryWeb, :live_component
alias Cannery.Tags alias Cannery.{Accounts.User, Containers, Containers.Tag}
alias Cannery.{Accounts.User, Tags.Tag}
alias Ecto.Changeset alias Ecto.Changeset
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@ -56,7 +55,7 @@ defmodule CanneryWeb.TagLive.FormComponent do
tag_params tag_params
) do ) do
socket = socket =
case Tags.update_tag(tag, tag_params, current_user) do case Containers.update_tag(tag, tag_params, current_user) do
{:ok, %{name: tag_name}} -> {:ok, %{name: tag_name}} ->
prompt = dgettext("prompts", "%{name} updated successfully", name: tag_name) prompt = dgettext("prompts", "%{name} updated successfully", name: tag_name)
socket |> put_flash(:info, prompt) |> push_navigate(to: return_to) socket |> put_flash(:info, prompt) |> push_navigate(to: return_to)
@ -74,7 +73,7 @@ defmodule CanneryWeb.TagLive.FormComponent do
tag_params tag_params
) do ) do
socket = socket =
case Tags.create_tag(tag_params, current_user) do case Containers.create_tag(tag_params, current_user) do
{:ok, %{name: tag_name}} -> {:ok, %{name: tag_name}} ->
prompt = dgettext("prompts", "%{name} created successfully", name: tag_name) prompt = dgettext("prompts", "%{name} created successfully", name: tag_name)
socket |> put_flash(:info, prompt) |> push_navigate(to: return_to) socket |> put_flash(:info, prompt) |> push_navigate(to: return_to)

View File

@ -1,11 +1,10 @@
defmodule CanneryWeb.TagLive.Index do defmodule CanneryWeb.TagLive.Index do
@moduledoc """ @moduledoc """
Liveview to show a Cannery.Tags.Tag index Liveview to show a Cannery.Containers.Tag index
""" """
use CanneryWeb, :live_view use CanneryWeb, :live_view
import CanneryWeb.Components.TagCard alias Cannery.{Containers, Containers.Tag}
alias Cannery.{Tags, Tags.Tag}
alias CanneryWeb.ViewHelpers alias CanneryWeb.ViewHelpers
@impl true @impl true
@ -26,7 +25,7 @@ defmodule CanneryWeb.TagLive.Index do
socket socket
|> assign( |> assign(
page_title: gettext("Edit Tag"), page_title: gettext("Edit Tag"),
tag: Tags.get_tag!(id, current_user) tag: Containers.get_tag!(id, current_user)
) )
end end
@ -60,12 +59,13 @@ defmodule CanneryWeb.TagLive.Index do
@impl true @impl true
def handle_event("delete", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do def handle_event("delete", %{"id" => id}, %{assigns: %{current_user: current_user}} = socket) do
%{name: tag_name} = Tags.get_tag!(id, current_user) |> Tags.delete_tag!(current_user) %{name: tag_name} =
Containers.get_tag!(id, current_user) |> Containers.delete_tag!(current_user)
prompt = dgettext("prompts", "%{name} deleted succesfully", name: tag_name) prompt = dgettext("prompts", "%{name} deleted succesfully", name: tag_name)
{:noreply, socket |> put_flash(:info, prompt) |> display_tags()} {:noreply, socket |> put_flash(:info, prompt) |> display_tags()}
end end
@impl true
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.tag_index_path(Endpoint, :index))} {:noreply, socket |> push_patch(to: Routes.tag_index_path(Endpoint, :index))}
end end
@ -75,6 +75,6 @@ defmodule CanneryWeb.TagLive.Index do
end end
defp display_tags(%{assigns: %{search: search, current_user: current_user}} = socket) do defp display_tags(%{assigns: %{search: search, current_user: current_user}} = socket) do
socket |> assign(tags: Tags.list_tags(search, current_user)) socket |> assign(tags: Containers.list_tags(search, current_user))
end end
end end

View File

@ -23,11 +23,11 @@
<div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl"> <div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
<.form <.form
:let={f} :let={f}
for={:search} for={%{}}
as={:search}
phx-change="search" phx-change="search"
phx-submit="search" phx-submit="search"
class="grow self-stretch flex flex-col items-stretch" class="grow self-stretch flex flex-col items-stretch"
data-qa="tag_search"
> >
<%= text_input(f, :search_term, <%= text_input(f, :search_term,
class: "input input-primary", class: "input input-primary",
@ -49,7 +49,7 @@
<.link <.link
patch={Routes.tag_index_path(Endpoint, :edit, tag)} patch={Routes.tag_index_path(Endpoint, :edit, tag)}
class="text-primary-600 link" class="text-primary-600 link"
data-qa={"edit-#{tag.id}"} aria-label={dgettext("actions", "Edit %{tag_name}", tag_name: tag.name)}
> >
<i class="fa-fw fa-lg fas fa-edit"></i> <i class="fa-fw fa-lg fas fa-edit"></i>
</.link> </.link>
@ -62,7 +62,7 @@
data-confirm={ data-confirm={
dgettext("prompts", "Are you sure you want to delete %{name}?", name: tag.name) dgettext("prompts", "Are you sure you want to delete %{name}?", name: tag.name)
} }
data-qa={"delete-#{tag.id}"} aria-label={dgettext("actions", "Delete %{tag_name}", tag_name: tag.name)}
> >
<i class="fa-fw fa-lg fas fa-trash"></i> <i class="fa-fw fa-lg fas fa-trash"></i>
</.link> </.link>

View File

@ -24,9 +24,12 @@
<hr class="w-full hr" /> <hr class="w-full hr" />
<a href={Routes.live_path(Endpoint, HomeLive)} class="link title text-primary-600 text-lg"> <.link
href={Routes.live_path(Endpoint, HomeLive)}
class="link title text-primary-600 text-lg"
>
<%= dgettext("errors", "Go back home") %> <%= dgettext("errors", "Go back home") %>
</a> </.link>
</div> </div>
</div> </div>
</body> </body>

View File

@ -5,7 +5,8 @@
<.form <.form
:let={f} :let={f}
for={:user} for={%{}}
as={:user}
action={Routes.user_confirmation_path(@conn, :create)} action={Routes.user_confirmation_path(@conn, :create)}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >

View File

@ -5,7 +5,8 @@
<.form <.form
:let={f} :let={f}
for={:user} for={%{}}
as={:user}
action={Routes.user_reset_password_path(@conn, :create)} action={Routes.user_reset_password_path(@conn, :create)}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >

View File

@ -1,6 +1,5 @@
defmodule CanneryWeb.ErrorView do defmodule CanneryWeb.ErrorView do
use CanneryWeb, :view use CanneryWeb, :view
import CanneryWeb.Components.Topbar
alias CanneryWeb.HomeLive alias CanneryWeb.HomeLive
def template_not_found(error_path, _assigns) do def template_not_found(error_path, _assigns) do

View File

@ -1,6 +1,5 @@
defmodule CanneryWeb.LayoutView do defmodule CanneryWeb.LayoutView do
use CanneryWeb, :view use CanneryWeb, :view
import CanneryWeb.Components.Topbar
alias CanneryWeb.HomeLive alias CanneryWeb.HomeLive
# Phoenix LiveDashboard is available only in development by default, # Phoenix LiveDashboard is available only in development by default,

View File

@ -7,61 +7,6 @@ defmodule CanneryWeb.ViewHelpers do
use Phoenix.Component use Phoenix.Component
@doc """
Phoenix.Component for a <time> element that renders the naivedatetime in the
user's local timezone with Alpine.js
"""
attr :datetime, :any, required: true, doc: "A `DateTime` struct or nil"
def datetime(assigns) do
~H"""
<time
:if={@datetime}
datetime={cast_datetime(@datetime)}
x-data={"{
datetime:
Intl.DateTimeFormat([], {dateStyle: 'short', timeStyle: 'long'})
.format(new Date(\"#{cast_datetime(@datetime)}\"))
}"}
x-text="datetime"
>
<%= cast_datetime(@datetime) %>
</time>
"""
end
@spec cast_datetime(NaiveDateTime.t() | nil) :: String.t()
defp cast_datetime(%NaiveDateTime{} = datetime) do
datetime |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_iso8601(:extended)
end
defp cast_datetime(_datetime), do: ""
@doc """
Phoenix.Component for a <date> element that renders the Date in the user's
local timezone with Alpine.js
"""
attr :date, :any, required: true, doc: "A `Date` struct or nil"
def date(assigns) do
~H"""
<time
:if={@date}
datetime={@date |> Date.to_iso8601(:extended)}
x-data={"{
date:
Intl.DateTimeFormat([], {timeZone: 'Etc/UTC', dateStyle: 'short'})
.format(new Date(\"#{@date |> Date.to_iso8601(:extended)}\"))
}"}
x-text="date"
>
<%= @date |> Date.to_iso8601(:extended) %>
</time>
"""
end
@doc """ @doc """
Displays emoji as text emoji if SHIBAO_MODE is set to true :) Displays emoji as text emoji if SHIBAO_MODE is set to true :)
""" """
@ -87,23 +32,6 @@ defmodule CanneryWeb.ViewHelpers do
"data:image/png;base64," <> img_data "data:image/png;base64," <> img_data
end end
@doc """
Creates a downloadable QR Code element
"""
attr :content, :string, required: true
attr :filename, :string, default: "qrcode", doc: "filename without .png extension"
attr :image_class, :string, default: "w-64 h-max"
attr :width, :integer, default: 384, doc: "width of png to generate"
def qr_code(assigns) do
~H"""
<a href={qr_code_image(@content)} download={@filename <> ".png"}>
<img class={@image_class} alt={@filename} src={qr_code_image(@content)} />
</a>
"""
end
@doc """ @doc """
Get a random color in `#ffffff` hex format Get a random color in `#ffffff` hex format

View File

@ -4,7 +4,7 @@ defmodule Cannery.MixProject do
def project do def project do
[ [
app: :cannery, app: :cannery,
version: "0.8.3", version: "0.8.4",
elixir: "1.14.1", elixir: "1.14.1",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(), compilers: Mix.compilers(),
@ -47,13 +47,13 @@ defmodule Cannery.MixProject do
# Type `mix help deps` for examples and options. # Type `mix help deps` for examples and options.
defp deps do defp deps do
[ [
{:bcrypt_elixir, "~> 2.0"}, {:bcrypt_elixir, "~> 3.0"},
{:phoenix, "~> 1.6.0"}, {:phoenix, "~> 1.6.0"},
{:phoenix_ecto, "~> 4.4"}, {:phoenix_ecto, "~> 4.4"},
{:phoenix_html, "~> 3.0"}, {:phoenix_html, "~> 3.0"},
{:phoenix_live_reload, "~> 1.2", only: :dev}, {:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 0.18.0"}, {:phoenix_live_view, "~> 0.18.0"},
{:phoenix_view, "~> 1.1"}, {:phoenix_view, "~> 2.0"},
{:phoenix_live_dashboard, "~> 0.6"}, {:phoenix_live_dashboard, "~> 0.6"},
{:ecto_sql, "~> 3.6"}, {:ecto_sql, "~> 3.6"},
{:postgrex, ">= 0.0.0"}, {:postgrex, ">= 0.0.0"},

View File

@ -1,5 +1,5 @@
%{ %{
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
@ -11,38 +11,39 @@
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"},
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"},
"ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"}, "ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"}, "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"},
"ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"},
"elixir_make": {:hex, :elixir_make, "0.7.3", "c37fdae1b52d2cc51069713a58c2314877c1ad40800a57efb213f77b078a460d", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "24ada3e3996adbed1fa024ca14995ef2ba3d0d17b678b0f3f2b1f66e6ce2b274"}, "elixir_make": {:hex, :elixir_make, "0.7.5", "784cc00f5fa24239067cc04d449437dcc5f59353c44eb08f188b2b146568738a", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "c3d63e8d5c92fa3880d89ecd41de59473fa2e83eeb68148155e25e8b95aa2887"},
"eqrcode": {:hex, :eqrcode, "0.1.10", "6294fece9d68ad64eef1c3c92cf111cfd6469f4fbf230a2d4cc905a682178f3f", [:mix], [], "hexpm", "da30e373c36a0fd37ab6f58664b16029919896d6c45a68a95cc4d713e81076f1"}, "eqrcode": {:hex, :eqrcode, "0.1.10", "6294fece9d68ad64eef1c3c92cf111cfd6469f4fbf230a2d4cc905a682178f3f", [:mix], [], "hexpm", "da30e373c36a0fd37ab6f58664b16029919896d6c45a68a95cc4d713e81076f1"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, "ex_doc": {:hex, :ex_doc, "0.29.2", "dfa97532ba66910b2a3016a4bbd796f41a86fc71dd5227e96f4c8581fdf0fdf0", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "6b5d7139eda18a753e3250e27e4a929f8d2c880dd0d460cb9986305dea3e03af"},
"expo": {:hex, :expo, "0.3.0", "13127c1d5f653b2927f2616a4c9ace5ae372efd67c7c2693b87fd0fdc30c6feb", [:mix], [], "hexpm", "fb3cd4bf012a77bc1608915497dae2ff684a06f0fa633c7afa90c4d72b881823"}, "expo": {:hex, :expo, "0.4.0", "bbe4bf455e2eb2ebd2f1e7d83530ce50fb9990eb88fc47855c515bfdf1c6626f", [:mix], [], "hexpm", "a8ed1683ec8b7c7fa53fd7a41b2c6935f539168a6bb0616d7fd6b58a36f3abf2"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"floki": {:hex, :floki, "0.34.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"}, "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"}, "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.22.0", "a25d71ec21b1848957d9207b81fd61cb25161688d282d58bdafef74c2270bdc4", [:mix], [{:expo, "~> 0.3.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "cb0675141576f73720c8e49b4f0fd3f2c69f0cd8c218202724d4aebab8c70ace"}, "gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"oban": {:hex, :oban, "2.13.6", "a0cb1bce3bd393770512231fb5a3695fa19fd3af10d7575bf73f837aee7abf43", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c1c5eb16f377b3cbbf2ea14be24d20e3d91285af9d1ac86260b7c2af5464887"}, "oban": {:hex, :oban, "2.14.2", "ae925d9a33e110addaa59ff7ec1b2fd84270ac7eb00fbb4b4a179d74c407bba3", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "32bf30127c8c44ac42f05f229a50fadc2177b3e799c29499f5daf90d5e5b5d3c"},
"phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"}, "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"}, "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.1", "b0bf8f3348dec4910907a2ad1453e642f6fe4d444376c1c9b26222d63c73cf97", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "b6c5d744bf4b40692b1b361d3608bdfd05aeab83e17c7bc217d730f007f31abf"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.2", "635cf07de947235deb030cd6b776c71a3b790ab04cebf526aa8c879fe17c7784", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "da287a77327e996cc166e4c440c3ad5ab33ccdb151b91c793209b39ebbce5b75"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.1.0", "f8e4780705c9f254cc853f7a40e25f7198ba4d91102bcfad2226669b69766b35", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "aa82f10afd9a4b6080fdf3274dbb9432b25b210d42b4b6b55308f6e59cd87c3d"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"},
"phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"}, "swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"},

View File

@ -66,11 +66,11 @@ msgstr ""
msgid "Invite someone new!" msgid "Invite someone new!"
msgstr "" msgstr ""
#: lib/cannery_web/components/topbar.ex:137 #: lib/cannery_web/components/core_components/topbar.html.heex:122
#: lib/cannery_web/templates/user_confirmation/new.html.heex:31 #: lib/cannery_web/templates/user_confirmation/new.html.heex:32
#: lib/cannery_web/templates/user_registration/new.html.heex:44 #: lib/cannery_web/templates/user_registration/new.html.heex:44
#: lib/cannery_web/templates/user_reset_password/edit.html.heex:45 #: lib/cannery_web/templates/user_reset_password/edit.html.heex:45
#: lib/cannery_web/templates/user_reset_password/new.html.heex:31 #: lib/cannery_web/templates/user_reset_password/new.html.heex:32
#: lib/cannery_web/templates/user_session/new.html.heex:3 #: lib/cannery_web/templates/user_session/new.html.heex:3
#: lib/cannery_web/templates/user_session/new.html.heex:28 #: lib/cannery_web/templates/user_session/new.html.heex:28
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -97,19 +97,19 @@ msgstr ""
msgid "New Tag" msgid "New Tag"
msgstr "" msgstr ""
#: lib/cannery_web/components/topbar.ex:129 #: lib/cannery_web/components/core_components/topbar.html.heex:114
#: lib/cannery_web/templates/user_confirmation/new.html.heex:28 #: lib/cannery_web/templates/user_confirmation/new.html.heex:29
#: lib/cannery_web/templates/user_registration/new.html.heex:3 #: lib/cannery_web/templates/user_registration/new.html.heex:3
#: lib/cannery_web/templates/user_registration/new.html.heex:37 #: lib/cannery_web/templates/user_registration/new.html.heex:37
#: lib/cannery_web/templates/user_reset_password/edit.html.heex:42 #: lib/cannery_web/templates/user_reset_password/edit.html.heex:42
#: lib/cannery_web/templates/user_reset_password/new.html.heex:28 #: lib/cannery_web/templates/user_reset_password/new.html.heex:29
#: lib/cannery_web/templates/user_session/new.html.heex:39 #: lib/cannery_web/templates/user_session/new.html.heex:39
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Register" msgid "Register"
msgstr "" msgstr ""
#: lib/cannery_web/templates/user_confirmation/new.html.heex:3 #: lib/cannery_web/templates/user_confirmation/new.html.heex:3
#: lib/cannery_web/templates/user_confirmation/new.html.heex:15 #: lib/cannery_web/templates/user_confirmation/new.html.heex:16
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Resend confirmation instructions" msgid "Resend confirmation instructions"
msgstr "" msgstr ""
@ -120,28 +120,28 @@ msgstr ""
msgid "Reset password" msgid "Reset password"
msgstr "" msgstr ""
#: lib/cannery_web/components/add_shot_group_component.html.heex:54 #: lib/cannery_web/components/add_shot_group_component.html.heex:56
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:82 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:84
#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:157 #: lib/cannery_web/live/ammo_type_live/form_component.html.heex:159
#: lib/cannery_web/live/container_live/form_component.html.heex:51 #: lib/cannery_web/live/container_live/form_component.html.heex:55
#: lib/cannery_web/live/invite_live/form_component.html.heex:32 #: lib/cannery_web/live/invite_live/form_component.html.heex:32
#: lib/cannery_web/live/range_live/form_component.html.heex:41 #: lib/cannery_web/live/range_live/form_component.html.heex:44
#: lib/cannery_web/live/tag_live/form_component.html.heex:37 #: lib/cannery_web/live/tag_live/form_component.html.heex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Save" msgid "Save"
msgstr "" msgstr ""
#: lib/cannery_web/templates/user_reset_password/new.html.heex:15 #: lib/cannery_web/templates/user_reset_password/new.html.heex:16
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Send instructions to reset password" msgid "Send instructions to reset password"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/show.html.heex:76 #: lib/cannery_web/live/container_live/show.html.heex:75
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Why not add one?" msgid "Why not add one?"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/edit_tags_component.html.heex:50 #: lib/cannery_web/live/container_live/edit_tags_component.html.heex:51
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Add" msgid "Add"
msgstr "" msgstr ""
@ -156,9 +156,9 @@ msgstr ""
msgid "Why not get some ready to shoot?" msgid "Why not get some ready to shoot?"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:101 #: lib/cannery_web/live/ammo_group_live/index.html.heex:103
#: lib/cannery_web/live/ammo_group_live/show.html.heex:101 #: lib/cannery_web/live/ammo_group_live/show.html.heex:103
#: lib/cannery_web/live/range_live/index.html.heex:38 #: lib/cannery_web/live/range_live/index.html.heex:45
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Record shots" msgid "Record shots"
msgstr "" msgstr ""
@ -168,17 +168,12 @@ msgstr ""
msgid "Add another container!" msgid "Add another container!"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.html.heex:94
#, elixir-autogen, elixir-format
msgid "Move containers"
msgstr ""
#: lib/cannery_web/components/move_ammo_group_component.ex:126 #: lib/cannery_web/components/move_ammo_group_component.ex:126
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select" msgid "Select"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:30 #: lib/cannery_web/live/invite_live/index.html.heex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Copy to clipboard" msgid "Copy to clipboard"
msgstr "" msgstr ""
@ -188,7 +183,7 @@ msgstr ""
msgid "add a container first" msgid "add a container first"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:75 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:77
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Create" msgid "Create"
msgstr "" msgstr ""
@ -203,7 +198,7 @@ msgstr ""
msgid "Change language" msgid "Change language"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.html.heex:60 #: lib/cannery_web/live/ammo_group_live/show.html.heex:55
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "View in Catalog" msgid "View in Catalog"
msgstr "" msgstr ""
@ -214,23 +209,25 @@ msgid "add an ammo type first"
msgstr "" msgstr ""
#: lib/cannery_web/components/move_ammo_group_component.ex:80 #: lib/cannery_web/components/move_ammo_group_component.ex:80
#: lib/cannery_web/live/ammo_group_live/index.html.heex:120
#: lib/cannery_web/live/ammo_group_live/show.html.heex:96
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Move ammo" msgid "Move ammo"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:78 #: lib/cannery_web/live/invite_live/index.html.heex:90
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Set Unlimited" msgid "Set Unlimited"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.html.heex:86 #: lib/cannery_web/live/ammo_group_live/show.html.heex:89
#: lib/cannery_web/live/range_live/index.html.heex:31 #: lib/cannery_web/live/range_live/index.html.heex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Stage for range" msgid "Stage for range"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.html.heex:85 #: lib/cannery_web/live/ammo_group_live/show.html.heex:88
#: lib/cannery_web/live/range_live/index.html.heex:30 #: lib/cannery_web/live/range_live/index.html.heex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Unstage from range" msgid "Unstage from range"
msgstr "" msgstr ""
@ -239,3 +236,124 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Export Data as JSON" msgid "Export Data as JSON"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Clone %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:87
#: lib/cannery_web/live/container_live/index.html.heex:143
#, elixir-autogen, elixir-format
msgid "Clone %{container_name}"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:35
#, elixir-autogen, elixir-format
msgid "Copy invite link for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:103
#: lib/cannery_web/live/ammo_type_live/show.html.heex:36
#, elixir-autogen, elixir-format
msgid "Delete %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:102
#: lib/cannery_web/live/container_live/index.html.heex:158
#: lib/cannery_web/live/container_live/show.html.heex:55
#, elixir-autogen, elixir-format
msgid "Delete %{container_name}"
msgstr ""
#: lib/cannery_web/live/tag_live/index.html.heex:65
#, elixir-autogen, elixir-format
msgid "Delete %{tag_name}"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:63
#, elixir-autogen, elixir-format
msgid "Delete invite for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.ex:161
#: lib/cannery_web/live/range_live/index.html.heex:130
#, elixir-autogen, elixir-format
msgid "Delete shot record of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:74
#: lib/cannery_web/live/ammo_type_live/show.html.heex:19
#, elixir-autogen, elixir-format
msgid "Edit %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:77
#: lib/cannery_web/live/container_live/index.html.heex:133
#: lib/cannery_web/live/container_live/show.html.heex:42
#, elixir-autogen, elixir-format
msgid "Edit %{container_name}"
msgstr ""
#: lib/cannery_web/live/tag_live/index.html.heex:52
#, elixir-autogen, elixir-format
msgid "Edit %{tag_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:142
#: lib/cannery_web/live/ammo_group_live/show.html.heex:62
#, elixir-autogen, elixir-format
msgid "Edit ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:46
#, elixir-autogen, elixir-format
msgid "Edit invite for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.ex:146
#, elixir-autogen, elixir-format
msgid "Edit shot group of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/range_live/index.html.heex:113
#, elixir-autogen, elixir-format
msgid "Edit shot record of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:96
#, elixir-autogen, elixir-format
msgid "Stage"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:65
#: lib/cannery_web/live/container_live/index.html.heex:122
#, elixir-autogen, elixir-format
msgid "Tag %{container_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:95
#, elixir-autogen, elixir-format
msgid "Unstage"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:64
#, elixir-autogen, elixir-format
msgid "View %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:154
#, elixir-autogen, elixir-format
msgid "Clone ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:169
#: lib/cannery_web/live/ammo_group_live/show.html.heex:76
#, elixir-autogen, elixir-format
msgid "Delete ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:130
#, elixir-autogen, elixir-format
msgid "View ammo group of %{ammo_group_count} bullets"
msgstr ""

View File

@ -79,11 +79,11 @@ msgstr "Passwort vergessen?"
msgid "Invite someone new!" msgid "Invite someone new!"
msgstr "Laden Sie jemanden ein!" msgstr "Laden Sie jemanden ein!"
#: lib/cannery_web/components/topbar.ex:137 #: lib/cannery_web/components/core_components/topbar.html.heex:122
#: lib/cannery_web/templates/user_confirmation/new.html.heex:31 #: lib/cannery_web/templates/user_confirmation/new.html.heex:32
#: lib/cannery_web/templates/user_registration/new.html.heex:44 #: lib/cannery_web/templates/user_registration/new.html.heex:44
#: lib/cannery_web/templates/user_reset_password/edit.html.heex:45 #: lib/cannery_web/templates/user_reset_password/edit.html.heex:45
#: lib/cannery_web/templates/user_reset_password/new.html.heex:31 #: lib/cannery_web/templates/user_reset_password/new.html.heex:32
#: lib/cannery_web/templates/user_session/new.html.heex:3 #: lib/cannery_web/templates/user_session/new.html.heex:3
#: lib/cannery_web/templates/user_session/new.html.heex:28 #: lib/cannery_web/templates/user_session/new.html.heex:28
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -110,19 +110,19 @@ msgstr "Neuer Behälter"
msgid "New Tag" msgid "New Tag"
msgstr "Neuer Tag" msgstr "Neuer Tag"
#: lib/cannery_web/components/topbar.ex:129 #: lib/cannery_web/components/core_components/topbar.html.heex:114
#: lib/cannery_web/templates/user_confirmation/new.html.heex:28 #: lib/cannery_web/templates/user_confirmation/new.html.heex:29
#: lib/cannery_web/templates/user_registration/new.html.heex:3 #: lib/cannery_web/templates/user_registration/new.html.heex:3
#: lib/cannery_web/templates/user_registration/new.html.heex:37 #: lib/cannery_web/templates/user_registration/new.html.heex:37
#: lib/cannery_web/templates/user_reset_password/edit.html.heex:42 #: lib/cannery_web/templates/user_reset_password/edit.html.heex:42
#: lib/cannery_web/templates/user_reset_password/new.html.heex:28 #: lib/cannery_web/templates/user_reset_password/new.html.heex:29
#: lib/cannery_web/templates/user_session/new.html.heex:39 #: lib/cannery_web/templates/user_session/new.html.heex:39
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Register" msgid "Register"
msgstr "Registrieren" msgstr "Registrieren"
#: lib/cannery_web/templates/user_confirmation/new.html.heex:3 #: lib/cannery_web/templates/user_confirmation/new.html.heex:3
#: lib/cannery_web/templates/user_confirmation/new.html.heex:15 #: lib/cannery_web/templates/user_confirmation/new.html.heex:16
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Resend confirmation instructions" msgid "Resend confirmation instructions"
msgstr "Bestätigungsmail erneut senden" msgstr "Bestätigungsmail erneut senden"
@ -133,28 +133,28 @@ msgstr "Bestätigungsmail erneut senden"
msgid "Reset password" msgid "Reset password"
msgstr "Passwort zurücksetzen" msgstr "Passwort zurücksetzen"
#: lib/cannery_web/components/add_shot_group_component.html.heex:54 #: lib/cannery_web/components/add_shot_group_component.html.heex:56
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:82 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:84
#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:157 #: lib/cannery_web/live/ammo_type_live/form_component.html.heex:159
#: lib/cannery_web/live/container_live/form_component.html.heex:51 #: lib/cannery_web/live/container_live/form_component.html.heex:55
#: lib/cannery_web/live/invite_live/form_component.html.heex:32 #: lib/cannery_web/live/invite_live/form_component.html.heex:32
#: lib/cannery_web/live/range_live/form_component.html.heex:41 #: lib/cannery_web/live/range_live/form_component.html.heex:44
#: lib/cannery_web/live/tag_live/form_component.html.heex:37 #: lib/cannery_web/live/tag_live/form_component.html.heex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
#: lib/cannery_web/templates/user_reset_password/new.html.heex:15 #: lib/cannery_web/templates/user_reset_password/new.html.heex:16
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Send instructions to reset password" msgid "Send instructions to reset password"
msgstr "Anleitung zum Passwort zurücksetzen zusenden" msgstr "Anleitung zum Passwort zurücksetzen zusenden"
#: lib/cannery_web/live/container_live/show.html.heex:76 #: lib/cannery_web/live/container_live/show.html.heex:75
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Why not add one?" msgid "Why not add one?"
msgstr "Warum fügen Sie keine hinzu?" msgstr "Warum fügen Sie keine hinzu?"
#: lib/cannery_web/live/container_live/edit_tags_component.html.heex:50 #: lib/cannery_web/live/container_live/edit_tags_component.html.heex:51
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Add" msgid "Add"
msgstr "Hinzufügen" msgstr "Hinzufügen"
@ -169,9 +169,9 @@ msgstr "Munition markieren"
msgid "Why not get some ready to shoot?" msgid "Why not get some ready to shoot?"
msgstr "Warum nicht einige für den Schießstand auswählen?" msgstr "Warum nicht einige für den Schießstand auswählen?"
#: lib/cannery_web/live/ammo_group_live/index.html.heex:101 #: lib/cannery_web/live/ammo_group_live/index.html.heex:103
#: lib/cannery_web/live/ammo_group_live/show.html.heex:101 #: lib/cannery_web/live/ammo_group_live/show.html.heex:103
#: lib/cannery_web/live/range_live/index.html.heex:38 #: lib/cannery_web/live/range_live/index.html.heex:45
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Record shots" msgid "Record shots"
msgstr "Schüsse dokumentieren" msgstr "Schüsse dokumentieren"
@ -181,17 +181,12 @@ msgstr "Schüsse dokumentieren"
msgid "Add another container!" msgid "Add another container!"
msgstr "Einen weiteren Behälter hinzufügen!" msgstr "Einen weiteren Behälter hinzufügen!"
#: lib/cannery_web/live/ammo_group_live/show.html.heex:94
#, elixir-autogen, elixir-format
msgid "Move containers"
msgstr "Behälter verschieben"
#: lib/cannery_web/components/move_ammo_group_component.ex:126 #: lib/cannery_web/components/move_ammo_group_component.ex:126
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select" msgid "Select"
msgstr "Markieren" msgstr "Markieren"
#: lib/cannery_web/live/invite_live/index.html.heex:30 #: lib/cannery_web/live/invite_live/index.html.heex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Copy to clipboard" msgid "Copy to clipboard"
msgstr "In die Zwischenablage kopieren" msgstr "In die Zwischenablage kopieren"
@ -201,7 +196,7 @@ msgstr "In die Zwischenablage kopieren"
msgid "add a container first" msgid "add a container first"
msgstr "Zuerst einen Behälter hinzufügen" msgstr "Zuerst einen Behälter hinzufügen"
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:75 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:77
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Create" msgid "Create"
msgstr "Erstellen" msgstr "Erstellen"
@ -216,7 +211,7 @@ msgstr "Sprache wechseln"
msgid "Change language" msgid "Change language"
msgstr "Sprache wechseln" msgstr "Sprache wechseln"
#: lib/cannery_web/live/ammo_group_live/show.html.heex:60 #: lib/cannery_web/live/ammo_group_live/show.html.heex:55
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "View in Catalog" msgid "View in Catalog"
msgstr "" msgstr ""
@ -227,23 +222,25 @@ msgid "add an ammo type first"
msgstr "" msgstr ""
#: lib/cannery_web/components/move_ammo_group_component.ex:80 #: lib/cannery_web/components/move_ammo_group_component.ex:80
#: lib/cannery_web/live/ammo_group_live/index.html.heex:120
#: lib/cannery_web/live/ammo_group_live/show.html.heex:96
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Move ammo" msgid "Move ammo"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:78 #: lib/cannery_web/live/invite_live/index.html.heex:90
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Set Unlimited" msgid "Set Unlimited"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.html.heex:86 #: lib/cannery_web/live/ammo_group_live/show.html.heex:89
#: lib/cannery_web/live/range_live/index.html.heex:31 #: lib/cannery_web/live/range_live/index.html.heex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Stage for range" msgid "Stage for range"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.html.heex:85 #: lib/cannery_web/live/ammo_group_live/show.html.heex:88
#: lib/cannery_web/live/range_live/index.html.heex:30 #: lib/cannery_web/live/range_live/index.html.heex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Unstage from range" msgid "Unstage from range"
msgstr "" msgstr ""
@ -252,3 +249,124 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Export Data as JSON" msgid "Export Data as JSON"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Clone %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:87
#: lib/cannery_web/live/container_live/index.html.heex:143
#, elixir-autogen, elixir-format
msgid "Clone %{container_name}"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:35
#, elixir-autogen, elixir-format
msgid "Copy invite link for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:103
#: lib/cannery_web/live/ammo_type_live/show.html.heex:36
#, elixir-autogen, elixir-format
msgid "Delete %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:102
#: lib/cannery_web/live/container_live/index.html.heex:158
#: lib/cannery_web/live/container_live/show.html.heex:55
#, elixir-autogen, elixir-format
msgid "Delete %{container_name}"
msgstr ""
#: lib/cannery_web/live/tag_live/index.html.heex:65
#, elixir-autogen, elixir-format
msgid "Delete %{tag_name}"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:63
#, elixir-autogen, elixir-format
msgid "Delete invite for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.ex:161
#: lib/cannery_web/live/range_live/index.html.heex:130
#, elixir-autogen, elixir-format
msgid "Delete shot record of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:74
#: lib/cannery_web/live/ammo_type_live/show.html.heex:19
#, elixir-autogen, elixir-format
msgid "Edit %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:77
#: lib/cannery_web/live/container_live/index.html.heex:133
#: lib/cannery_web/live/container_live/show.html.heex:42
#, elixir-autogen, elixir-format
msgid "Edit %{container_name}"
msgstr ""
#: lib/cannery_web/live/tag_live/index.html.heex:52
#, elixir-autogen, elixir-format
msgid "Edit %{tag_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:142
#: lib/cannery_web/live/ammo_group_live/show.html.heex:62
#, elixir-autogen, elixir-format
msgid "Edit ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:46
#, elixir-autogen, elixir-format
msgid "Edit invite for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.ex:146
#, elixir-autogen, elixir-format
msgid "Edit shot group of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/range_live/index.html.heex:113
#, elixir-autogen, elixir-format
msgid "Edit shot record of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:96
#, elixir-autogen, elixir-format, fuzzy
msgid "Stage"
msgstr "Munition markieren"
#: lib/cannery_web/live/container_live/index.html.heex:65
#: lib/cannery_web/live/container_live/index.html.heex:122
#, elixir-autogen, elixir-format
msgid "Tag %{container_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:95
#, elixir-autogen, elixir-format
msgid "Unstage"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:64
#, elixir-autogen, elixir-format
msgid "View %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:154
#, elixir-autogen, elixir-format, fuzzy
msgid "Clone ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:169
#: lib/cannery_web/live/ammo_group_live/show.html.heex:76
#, elixir-autogen, elixir-format, fuzzy
msgid "Delete ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:130
#, elixir-autogen, elixir-format, fuzzy
msgid "View ammo group of %{ammo_group_count} bullets"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -23,18 +23,18 @@ msgstr ""
## Run "mix gettext.extract" to bring this file up to ## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here has no ## date. Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead. ## effect: edit them in PO (.po) files instead.
#: lib/cannery/containers.ex:179 #: lib/cannery/containers.ex:200
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Container must be empty before deleting" msgid "Container must be empty before deleting"
msgstr "Behälter muss vor dem Löschen leer sein" msgstr "Behälter muss vor dem Löschen leer sein"
#: lib/cannery_web/live/container_live/index.ex:92 #: lib/cannery_web/live/container_live/index.ex:86
#: lib/cannery_web/live/container_live/show.ex:73 #: lib/cannery_web/live/container_live/show.ex:71
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not delete %{name}: %{error}" msgid "Could not delete %{name}: %{error}"
msgstr "Konnte %{name} nicht löschen: %{error}" msgstr "Konnte %{name} nicht löschen: %{error}"
#: lib/cannery_web/live/container_live/index.ex:80 #: lib/cannery_web/live/container_live/index.ex:74
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not find that container" msgid "Could not find that container"
msgstr "Konnte Behälter nicht finden" msgstr "Konnte Behälter nicht finden"
@ -49,12 +49,12 @@ msgstr "Mailadressenänderungs-Link ist ungültig oder abgelaufen."
msgid "Error" msgid "Error"
msgstr "Fehler" msgstr "Fehler"
#: lib/cannery_web/templates/error/error.html.heex:28 #: lib/cannery_web/templates/error/error.html.heex:31
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Go back home" msgid "Go back home"
msgstr "Zur Hauptseite zurückkehren" msgstr "Zur Hauptseite zurückkehren"
#: lib/cannery_web/views/error_view.ex:11 #: lib/cannery_web/views/error_view.ex:10
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Internal Server Error" msgid "Internal Server Error"
msgstr "Interner Serverfehler" msgstr "Interner Serverfehler"
@ -64,7 +64,7 @@ msgstr "Interner Serverfehler"
msgid "Invalid email or password" msgid "Invalid email or password"
msgstr "Ungültige Mailadresse oder Passwort" msgstr "Ungültige Mailadresse oder Passwort"
#: lib/cannery_web/views/error_view.ex:9 #: lib/cannery_web/views/error_view.ex:8
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Not found" msgid "Not found"
msgstr "Nicht gefunden" msgstr "Nicht gefunden"
@ -83,15 +83,15 @@ msgstr "Oops, etwas ist schiefgegangen. Bitte beachten Sie den Fehler unten."
msgid "Reset password link is invalid or it has expired." msgid "Reset password link is invalid or it has expired."
msgstr "Link zum Passwort zurücksetzen ist ungültig oder abgelaufen." msgstr "Link zum Passwort zurücksetzen ist ungültig oder abgelaufen."
#: lib/cannery_web/controllers/user_registration_controller.ex:22 #: lib/cannery_web/controllers/user_registration_controller.ex:23
#: lib/cannery_web/controllers/user_registration_controller.ex:51 #: lib/cannery_web/controllers/user_registration_controller.ex:52
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Sorry, public registration is disabled" msgid "Sorry, public registration is disabled"
msgstr "Entschuldigung, aber öffentliche Registrierung ist deaktiviert" msgstr "Entschuldigung, aber öffentliche Registrierung ist deaktiviert"
#: lib/cannery_web/controllers/user_registration_controller.ex:12 #: lib/cannery_web/controllers/user_registration_controller.ex:13
#: lib/cannery_web/controllers/user_registration_controller.ex:41 #: lib/cannery_web/controllers/user_registration_controller.ex:42
#: lib/cannery_web/controllers/user_registration_controller.ex:70 #: lib/cannery_web/controllers/user_registration_controller.ex:71
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Sorry, this invite was not found or expired" msgid "Sorry, this invite was not found or expired"
msgstr "" msgstr ""
@ -102,7 +102,7 @@ msgstr ""
msgid "Unable to delete user" msgid "Unable to delete user"
msgstr "Dieser Nutzer konnte nicht gelöscht werden" msgstr "Dieser Nutzer konnte nicht gelöscht werden"
#: lib/cannery_web/views/error_view.ex:10 #: lib/cannery_web/views/error_view.ex:9
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Unauthorized" msgid "Unauthorized"
msgstr "Unbefugt" msgstr "Unbefugt"
@ -112,7 +112,7 @@ msgstr "Unbefugt"
msgid "User confirmation link is invalid or it has expired." msgid "User confirmation link is invalid or it has expired."
msgstr "Nutzerkonto Bestätigungslink ist ungültig oder abgelaufen." msgstr "Nutzerkonto Bestätigungslink ist ungültig oder abgelaufen."
#: lib/cannery_web/live/invite_live/index.ex:19 #: lib/cannery_web/live/invite_live/index.ex:18
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You are not authorized to view this page" msgid "You are not authorized to view this page"
msgstr "Sie sind nicht berechtigt, diese Seite aufzurufen" msgstr "Sie sind nicht berechtigt, diese Seite aufzurufen"
@ -142,27 +142,16 @@ msgstr "ist nicht gültig"
msgid "must have the @ sign and no spaces" msgid "must have the @ sign and no spaces"
msgstr "Muss ein @ Zeichen und keine Leerzeichen haben" msgstr "Muss ein @ Zeichen und keine Leerzeichen haben"
#: lib/cannery/tags.ex:66 #: lib/cannery_web/live/container_live/show.ex:46
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tag not found" msgid "Tag not found"
msgstr "Tag nicht gefunden" msgstr "Tag nicht gefunden"
#: lib/cannery_web/live/container_live/edit_tags_component.ex:30 #: lib/cannery_web/live/container_live/edit_tags_component.ex:46
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tag could not be added" msgid "Tag could not be added"
msgstr "Tag konnte nicht hinzugefügt werden" msgstr "Tag konnte nicht hinzugefügt werden"
#: lib/cannery/activity_log/shot_group.ex:122
#, elixir-autogen, elixir-format
msgid "Count must be at least 1"
msgstr "Anzahl muss mindestens 1 sein"
#: lib/cannery/activity_log/shot_group.ex:82
#: lib/cannery/activity_log/shot_group.ex:118
#, elixir-autogen, elixir-format
msgid "Count must be less than %{count}"
msgstr "Anzahl muss weniger als %{count} betragen"
#: lib/cannery_web/controllers/user_auth.ex:39 #: lib/cannery_web/controllers/user_auth.ex:39
#: lib/cannery_web/controllers/user_auth.ex:161 #: lib/cannery_web/controllers/user_auth.ex:161
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -171,39 +160,59 @@ msgstr ""
"Sie müssen ihr Nutzerkonto bestätigen und einloggen, um diese Seite " "Sie müssen ihr Nutzerkonto bestätigen und einloggen, um diese Seite "
"anzuzeigen." "anzuzeigen."
#: lib/cannery_web/live/container_live/edit_tags_component.ex:52 #: lib/cannery_web/live/container_live/edit_tags_component.ex:73
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tag could not be removed" msgid "Tag could not be removed"
msgstr "Tag konnte nicht gelöscht werden" msgstr "Tag konnte nicht gelöscht werden"
#: lib/cannery_web/live/ammo_group_live/form_component.ex:157 #: lib/cannery_web/live/ammo_group_live/form_component.ex:160
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not parse number of copies" msgid "Could not parse number of copies"
msgstr "Konnte die Anzahl der Kopien nicht verstehen" msgstr "Konnte die Anzahl der Kopien nicht verstehen"
#: lib/cannery_web/live/ammo_group_live/form_component.ex:142 #: lib/cannery_web/live/ammo_group_live/form_component.ex:150
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}"
msgstr "" msgstr ""
"Ungültige Nummer an Kopien. Muss zwischen 1 and %{max} liegen. War " "Ungültige Nummer an Kopien. Muss zwischen 1 and %{max} liegen. War "
"%{multiplier}" "%{multiplier}"
#: lib/cannery/ammo.ex:686 #: lib/cannery/ammo.ex:1015
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid multiplier" msgid "Invalid multiplier"
msgstr "" msgstr ""
#: lib/cannery/ammo/ammo_group.ex:97 #: lib/cannery/ammo/ammo_group.ex:92
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Please select an ammo type and container" msgid "Please select an ammo type and container"
msgstr "" msgstr ""
#: lib/cannery_web/live/range_live/index.html.heex:67 #: lib/cannery_web/live/range_live/index.html.heex:74
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Your browser does not support the canvas element." msgid "Your browser does not support the canvas element."
msgstr "" msgstr ""
#: lib/cannery/activity_log/shot_group.ex:77 #: lib/cannery/activity_log/shot_group.ex:72
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Please select a valid user and ammo pack" msgid "Please select a valid user and ammo pack"
msgstr "" msgstr ""
#: lib/cannery/activity_log/shot_group.ex:86
#, elixir-autogen, elixir-format
msgid "Ammo left can be at most %{count} rounds"
msgstr ""
#: lib/cannery/activity_log/shot_group.ex:82
#, elixir-autogen, elixir-format
msgid "Ammo left must be at least 0"
msgstr ""
#: lib/cannery/activity_log/shot_group.ex:119
#, elixir-autogen, elixir-format, fuzzy
msgid "Count can be at most %{count} shots"
msgstr "Anzahl muss weniger als %{count} betragen"
#: lib/cannery/activity_log/shot_group.ex:78
#, elixir-autogen, elixir-format
msgid "can't be blank"
msgstr ""

View File

@ -23,31 +23,31 @@ msgstr ""
## Run "mix gettext.extract" to bring this file up to ## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here has no ## date. Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead. ## effect: edit them in PO (.po) files instead.
#: lib/cannery_web/live/ammo_type_live/form_component.ex:86 #: lib/cannery_web/live/ammo_type_live/form_component.ex:89
#: lib/cannery_web/live/container_live/form_component.ex:89 #: lib/cannery_web/live/container_live/form_component.ex:89
#: lib/cannery_web/live/invite_live/form_component.ex:80 #: lib/cannery_web/live/invite_live/form_component.ex:80
#: lib/cannery_web/live/tag_live/form_component.ex:79 #: lib/cannery_web/live/tag_live/form_component.ex:78
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} created successfully" msgid "%{name} created successfully"
msgstr "%{name} erfolgreich erstellt" msgstr "%{name} erfolgreich erstellt"
#: lib/cannery_web/live/ammo_type_live/index.ex:73 #: lib/cannery_web/live/ammo_type_live/index.ex:72
#: lib/cannery_web/live/ammo_type_live/show.ex:55 #: lib/cannery_web/live/ammo_type_live/show.ex:54
#: lib/cannery_web/live/tag_live/index.ex:64 #: lib/cannery_web/live/tag_live/index.ex:65
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} deleted succesfully" msgid "%{name} deleted succesfully"
msgstr "%{name} erfolgreich gelöscht" msgstr "%{name} erfolgreich gelöscht"
#: lib/cannery_web/live/container_live/index.ex:85 #: lib/cannery_web/live/container_live/index.ex:79
#: lib/cannery_web/live/container_live/show.ex:63 #: lib/cannery_web/live/container_live/show.ex:61
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} has been deleted" msgid "%{name} has been deleted"
msgstr "%{name} wurde gelöscht" msgstr "%{name} wurde gelöscht"
#: lib/cannery_web/live/ammo_type_live/form_component.ex:67 #: lib/cannery_web/live/ammo_type_live/form_component.ex:70
#: lib/cannery_web/live/container_live/form_component.ex:70 #: lib/cannery_web/live/container_live/form_component.ex:70
#: lib/cannery_web/live/invite_live/form_component.ex:62 #: lib/cannery_web/live/invite_live/form_component.ex:62
#: lib/cannery_web/live/tag_live/form_component.ex:61 #: lib/cannery_web/live/tag_live/form_component.ex:60
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} updated successfully" msgid "%{name} updated successfully"
msgstr "%{name} erfolgreich aktualisiert" msgstr "%{name} erfolgreich aktualisiert"
@ -57,24 +57,24 @@ msgstr "%{name} erfolgreich aktualisiert"
msgid "A link to confirm your email change has been sent to the new address." msgid "A link to confirm your email change has been sent to the new address."
msgstr "Eine Mail zum Bestätigen ihre Mailadresse wurde Ihnen zugesandt." msgstr "Eine Mail zum Bestätigen ihre Mailadresse wurde Ihnen zugesandt."
#: lib/cannery_web/live/invite_live/index.html.heex:98 #: lib/cannery_web/live/invite_live/index.html.heex:110
#: lib/cannery_web/live/invite_live/index.html.heex:126 #: lib/cannery_web/live/invite_live/index.html.heex:138
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to delete %{email}? This action is permanent!" msgid "Are you sure you want to delete %{email}? This action is permanent!"
msgstr "" msgstr ""
"Sind Sie sicher, dass sie %{email} löschen möchten? Dies kann nicht " "Sind Sie sicher, dass sie %{email} löschen möchten? Dies kann nicht "
"zurückgenommen werden!" "zurückgenommen werden!"
#: lib/cannery_web/live/container_live/index.html.heex:92 #: lib/cannery_web/live/container_live/index.html.heex:99
#: lib/cannery_web/live/container_live/index.html.heex:135 #: lib/cannery_web/live/container_live/index.html.heex:155
#: lib/cannery_web/live/container_live/show.html.heex:55 #: lib/cannery_web/live/container_live/show.html.heex:52
#: lib/cannery_web/live/tag_live/index.html.heex:63 #: lib/cannery_web/live/tag_live/index.html.heex:63
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to delete %{name}?" msgid "Are you sure you want to delete %{name}?"
msgstr "Sind Sie sicher, dass sie %{name} löschen möchten?" msgstr "Sind Sie sicher, dass sie %{name} löschen möchten?"
#: lib/cannery_web/live/ammo_group_live/index.html.heex:153 #: lib/cannery_web/live/ammo_group_live/index.html.heex:167
#: lib/cannery_web/live/ammo_group_live/show.html.heex:75 #: lib/cannery_web/live/ammo_group_live/show.html.heex:74
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to delete this ammo?" msgid "Are you sure you want to delete this ammo?"
msgstr "Sind Sie sicher, dass sie diese Munition löschen möchten?" msgstr "Sind Sie sicher, dass sie diese Munition löschen möchten?"
@ -84,7 +84,7 @@ msgstr "Sind Sie sicher, dass sie diese Munition löschen möchten?"
msgid "Are you sure you want to delete your account?" msgid "Are you sure you want to delete your account?"
msgstr "Sind Sie sicher, dass sie Ihren Account löschen möchten?" msgstr "Sind Sie sicher, dass sie Ihren Account löschen möchten?"
#: lib/cannery_web/components/topbar.ex:104 #: lib/cannery_web/components/core_components/topbar.html.heex:89
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to log out?" msgid "Are you sure you want to log out?"
msgstr "Wirklich ausloggen?" msgstr "Wirklich ausloggen?"
@ -123,17 +123,17 @@ msgstr "Passwort erfolgreich zurückgesetzt."
msgid "Password updated successfully." msgid "Password updated successfully."
msgstr "Passwort erfolgreich geändert." msgstr "Passwort erfolgreich geändert."
#: lib/cannery_web/controllers/user_registration_controller.ex:65 #: lib/cannery_web/controllers/user_registration_controller.ex:66
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Please check your email to verify your account" msgid "Please check your email to verify your account"
msgstr "Bitte überprüfen Sie ihre Mailbox und bestätigen Sie das Nutzerkonto" msgstr "Bitte überprüfen Sie ihre Mailbox und bestätigen Sie das Nutzerkonto"
#: lib/cannery_web/components/add_shot_group_component.html.heex:56 #: lib/cannery_web/components/add_shot_group_component.html.heex:58
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:83 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:85
#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:158 #: lib/cannery_web/live/ammo_type_live/form_component.html.heex:160
#: lib/cannery_web/live/container_live/form_component.html.heex:53 #: lib/cannery_web/live/container_live/form_component.html.heex:57
#: lib/cannery_web/live/invite_live/form_component.html.heex:34 #: lib/cannery_web/live/invite_live/form_component.html.heex:34
#: lib/cannery_web/live/range_live/form_component.html.heex:43 #: lib/cannery_web/live/range_live/form_component.html.heex:46
#: lib/cannery_web/live/tag_live/form_component.html.heex:39 #: lib/cannery_web/live/tag_live/form_component.html.heex:39
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Saving..." msgid "Saving..."
@ -151,44 +151,44 @@ msgstr ""
"Sind Sie sicher, dass sie %{tag_name} Tag von %{container_name} entfernen " "Sind Sie sicher, dass sie %{tag_name} Tag von %{container_name} entfernen "
"wollen?" "wollen?"
#: lib/cannery_web/live/container_live/edit_tags_component.ex:36 #: lib/cannery_web/live/container_live/edit_tags_component.ex:51
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} added successfully" msgid "%{name} added successfully"
msgstr "%{name} erfolgreich hinzugefügt" msgstr "%{name} erfolgreich hinzugefügt"
#: lib/cannery_web/live/container_live/show.ex:39 #: lib/cannery_web/live/container_live/show.ex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{tag_name} has been removed from %{container_name}" msgid "%{tag_name} has been removed from %{container_name}"
msgstr "%{tag_name} wurde von %{container_name} entfernt" msgstr "%{tag_name} wurde von %{container_name} entfernt"
#: lib/cannery_web/live/container_live/edit_tags_component.html.heex:52 #: lib/cannery_web/live/container_live/edit_tags_component.html.heex:53
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Adding..." msgid "Adding..."
msgstr "Füge hinzu..." msgstr "Füge hinzu..."
#: lib/cannery_web/components/add_shot_group_component.ex:56 #: lib/cannery_web/components/add_shot_group_component.ex:60
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Shots recorded successfully" msgid "Shots recorded successfully"
msgstr "Schüsse erfolgreich dokumentiert" msgstr "Schüsse erfolgreich dokumentiert"
#: lib/cannery_web/live/range_live/index.html.heex:27 #: lib/cannery_web/live/range_live/index.html.heex:34
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to unstage this ammo?" msgid "Are you sure you want to unstage this ammo?"
msgstr "Sind sie sicher, dass Sie diese Munition demarkieren möchten?" msgstr "Sind sie sicher, dass Sie diese Munition demarkieren möchten?"
#: lib/cannery_web/live/ammo_group_live/show.ex:142 #: lib/cannery_web/live/ammo_group_live/show.ex:159
#: lib/cannery_web/live/range_live/index.html.heex:116 #: lib/cannery_web/live/range_live/index.html.heex:127
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to delete this shot record?" msgid "Are you sure you want to delete this shot record?"
msgstr "Sind sie sicher, dass sie die Schießkladde löschen möchten?" msgstr "Sind sie sicher, dass sie die Schießkladde löschen möchten?"
#: lib/cannery_web/live/ammo_group_live/show.ex:83 #: lib/cannery_web/live/ammo_group_live/show.ex:81
#: lib/cannery_web/live/range_live/index.ex:80 #: lib/cannery_web/live/range_live/index.ex:79
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Shot records deleted succesfully" msgid "Shot records deleted succesfully"
msgstr "Schießkladde erfolgreich gelöscht" msgstr "Schießkladde erfolgreich gelöscht"
#: lib/cannery_web/live/range_live/form_component.ex:55 #: lib/cannery_web/live/range_live/form_component.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Shot records updated successfully" msgid "Shot records updated successfully"
msgstr "Schießkladde erfolgreich aktualisiert" msgstr "Schießkladde erfolgreich aktualisiert"
@ -198,17 +198,17 @@ msgstr "Schießkladde erfolgreich aktualisiert"
msgid "%{email} confirmed successfully." msgid "%{email} confirmed successfully."
msgstr "%{email} erfolgreich bestätigt." msgstr "%{email} erfolgreich bestätigt."
#: lib/cannery_web/components/move_ammo_group_component.ex:53 #: lib/cannery_web/components/move_ammo_group_component.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Ammo moved to %{name} successfully" msgid "Ammo moved to %{name} successfully"
msgstr "Munition erfolgreich zu %{name} verschoben" msgstr "Munition erfolgreich zu %{name} verschoben"
#: lib/cannery_web/live/invite_live/index.ex:128 #: lib/cannery_web/live/invite_live/index.ex:126
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "Der Zwischenablage hinzugefügt" msgstr "Der Zwischenablage hinzugefügt"
#: lib/cannery_web/live/container_live/edit_tags_component.ex:58 #: lib/cannery_web/live/container_live/edit_tags_component.ex:78
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} removed successfully" msgid "%{name} removed successfully"
msgstr "%{name} erfolgreich entfernt" msgstr "%{name} erfolgreich entfernt"
@ -219,7 +219,7 @@ msgstr "%{name} erfolgreich entfernt"
msgid "You'll need to" msgid "You'll need to"
msgstr "Sie müssen" msgstr "Sie müssen"
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:76 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:78
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Creating..." msgid "Creating..."
msgstr "Erstellen..." msgstr "Erstellen..."
@ -240,65 +240,65 @@ msgstr "Spracheinstellung gespeichert."
msgid "Ammo deleted succesfully" msgid "Ammo deleted succesfully"
msgstr "Munitionsgruppe erfolgreich gelöscht" msgstr "Munitionsgruppe erfolgreich gelöscht"
#: lib/cannery_web/live/range_live/index.ex:95 #: lib/cannery_web/live/range_live/index.ex:93
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Ammo unstaged succesfully" msgid "Ammo unstaged succesfully"
msgstr "Munition erfolgreich demarkiert" msgstr "Munition erfolgreich demarkiert"
#: lib/cannery_web/live/ammo_group_live/form_component.ex:118 #: lib/cannery_web/live/ammo_group_live/form_component.ex:126
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Ammo updated successfully" msgid "Ammo updated successfully"
msgstr "Munitionsgruppe erfolgreich aktualisiert" msgstr "Munitionsgruppe erfolgreich aktualisiert"
#: lib/cannery_web/live/ammo_group_live/form_component.ex:178 #: lib/cannery_web/live/ammo_group_live/form_component.ex:185
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Ammo added successfully" msgid "Ammo added successfully"
msgid_plural "Ammo added successfully" msgid_plural "Ammo added successfully"
msgstr[0] "Munitionsgruppe erfolgreich aktualisiert" msgstr[0] "Munitionsgruppe erfolgreich aktualisiert"
msgstr[1] "Munitionsgruppe erfolgreich aktualisiert" msgstr[1] "Munitionsgruppe erfolgreich aktualisiert"
#: lib/cannery_web/live/ammo_type_live/index.html.heex:90 #: lib/cannery_web/live/ammo_type_live/index.html.heex:96
#: lib/cannery_web/live/ammo_type_live/show.html.heex:29 #: lib/cannery_web/live/ammo_type_live/show.html.heex:29
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Are you sure you want to delete %{name}? This will delete all %{name} type ammo as well!" msgid "Are you sure you want to delete %{name}? This will delete all %{name} type ammo as well!"
msgstr "Sind Sie sicher, dass sie %{name} löschen möchten?" msgstr "Sind Sie sicher, dass sie %{name} löschen möchten?"
#: lib/cannery_web/live/home_live.html.heex:65 #: lib/cannery_web/live/home_live.html.heex:63
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Register to setup Cannery" msgid "Register to setup Cannery"
msgstr "Registrieren Sie sich, um %{name} zu bearbeiten" msgstr "Registrieren Sie sich, um %{name} zu bearbeiten"
#: lib/cannery_web/live/invite_live/index.ex:54 #: lib/cannery_web/live/invite_live/index.ex:53
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{invite_name} deleted succesfully" msgid "%{invite_name} deleted succesfully"
msgstr "%{name} erfolgreich gelöscht" msgstr "%{name} erfolgreich gelöscht"
#: lib/cannery_web/live/invite_live/index.ex:115 #: lib/cannery_web/live/invite_live/index.ex:114
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{invite_name} disabled succesfully" msgid "%{invite_name} disabled succesfully"
msgstr "%{name} erfolgreich deaktiviert" msgstr "%{name} erfolgreich deaktiviert"
#: lib/cannery_web/live/invite_live/index.ex:91 #: lib/cannery_web/live/invite_live/index.ex:90
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{invite_name} enabled succesfully" msgid "%{invite_name} enabled succesfully"
msgstr "%{name} erfolgreich aktiviert" msgstr "%{name} erfolgreich aktiviert"
#: lib/cannery_web/live/invite_live/index.ex:69 #: lib/cannery_web/live/invite_live/index.ex:68
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{invite_name} updated succesfully" msgid "%{invite_name} updated succesfully"
msgstr "%{name} erfolgreich aktualisiert" msgstr "%{name} erfolgreich aktualisiert"
#: lib/cannery_web/live/invite_live/index.ex:140 #: lib/cannery_web/live/invite_live/index.ex:135
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{user_email} deleted succesfully" msgid "%{user_email} deleted succesfully"
msgstr "%{name} erfolgreich gelöscht" msgstr "%{name} erfolgreich gelöscht"
#: lib/cannery_web/live/invite_live/index.html.heex:48 #: lib/cannery_web/live/invite_live/index.html.heex:58
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Are you sure you want to delete the invite for %{invite_name}?" msgid "Are you sure you want to delete the invite for %{invite_name}?"
msgstr "Sind Sie sicher, dass sie die Einladung für %{name} löschen möchten?" msgstr "Sind Sie sicher, dass sie die Einladung für %{name} löschen möchten?"
#: lib/cannery_web/live/invite_live/index.html.heex:73 #: lib/cannery_web/live/invite_live/index.html.heex:85
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Are you sure you want to make %{invite_name} unlimited?" msgid "Are you sure you want to make %{invite_name} unlimited?"
msgstr "Sind Sie sicher, dass sie %{name} auf unbegrenzt setzen möchten?" msgstr "Sind Sie sicher, dass sie %{name} auf unbegrenzt setzen möchten?"

File diff suppressed because it is too large Load Diff

View File

@ -66,11 +66,11 @@ msgstr ""
msgid "Invite someone new!" msgid "Invite someone new!"
msgstr "" msgstr ""
#: lib/cannery_web/components/topbar.ex:137 #: lib/cannery_web/components/core_components/topbar.html.heex:122
#: lib/cannery_web/templates/user_confirmation/new.html.heex:31 #: lib/cannery_web/templates/user_confirmation/new.html.heex:32
#: lib/cannery_web/templates/user_registration/new.html.heex:44 #: lib/cannery_web/templates/user_registration/new.html.heex:44
#: lib/cannery_web/templates/user_reset_password/edit.html.heex:45 #: lib/cannery_web/templates/user_reset_password/edit.html.heex:45
#: lib/cannery_web/templates/user_reset_password/new.html.heex:31 #: lib/cannery_web/templates/user_reset_password/new.html.heex:32
#: lib/cannery_web/templates/user_session/new.html.heex:3 #: lib/cannery_web/templates/user_session/new.html.heex:3
#: lib/cannery_web/templates/user_session/new.html.heex:28 #: lib/cannery_web/templates/user_session/new.html.heex:28
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -97,19 +97,19 @@ msgstr ""
msgid "New Tag" msgid "New Tag"
msgstr "" msgstr ""
#: lib/cannery_web/components/topbar.ex:129 #: lib/cannery_web/components/core_components/topbar.html.heex:114
#: lib/cannery_web/templates/user_confirmation/new.html.heex:28 #: lib/cannery_web/templates/user_confirmation/new.html.heex:29
#: lib/cannery_web/templates/user_registration/new.html.heex:3 #: lib/cannery_web/templates/user_registration/new.html.heex:3
#: lib/cannery_web/templates/user_registration/new.html.heex:37 #: lib/cannery_web/templates/user_registration/new.html.heex:37
#: lib/cannery_web/templates/user_reset_password/edit.html.heex:42 #: lib/cannery_web/templates/user_reset_password/edit.html.heex:42
#: lib/cannery_web/templates/user_reset_password/new.html.heex:28 #: lib/cannery_web/templates/user_reset_password/new.html.heex:29
#: lib/cannery_web/templates/user_session/new.html.heex:39 #: lib/cannery_web/templates/user_session/new.html.heex:39
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Register" msgid "Register"
msgstr "" msgstr ""
#: lib/cannery_web/templates/user_confirmation/new.html.heex:3 #: lib/cannery_web/templates/user_confirmation/new.html.heex:3
#: lib/cannery_web/templates/user_confirmation/new.html.heex:15 #: lib/cannery_web/templates/user_confirmation/new.html.heex:16
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Resend confirmation instructions" msgid "Resend confirmation instructions"
msgstr "" msgstr ""
@ -120,28 +120,28 @@ msgstr ""
msgid "Reset password" msgid "Reset password"
msgstr "" msgstr ""
#: lib/cannery_web/components/add_shot_group_component.html.heex:54 #: lib/cannery_web/components/add_shot_group_component.html.heex:56
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:82 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:84
#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:157 #: lib/cannery_web/live/ammo_type_live/form_component.html.heex:159
#: lib/cannery_web/live/container_live/form_component.html.heex:51 #: lib/cannery_web/live/container_live/form_component.html.heex:55
#: lib/cannery_web/live/invite_live/form_component.html.heex:32 #: lib/cannery_web/live/invite_live/form_component.html.heex:32
#: lib/cannery_web/live/range_live/form_component.html.heex:41 #: lib/cannery_web/live/range_live/form_component.html.heex:44
#: lib/cannery_web/live/tag_live/form_component.html.heex:37 #: lib/cannery_web/live/tag_live/form_component.html.heex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Save" msgid "Save"
msgstr "" msgstr ""
#: lib/cannery_web/templates/user_reset_password/new.html.heex:15 #: lib/cannery_web/templates/user_reset_password/new.html.heex:16
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Send instructions to reset password" msgid "Send instructions to reset password"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/show.html.heex:76 #: lib/cannery_web/live/container_live/show.html.heex:75
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Why not add one?" msgid "Why not add one?"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/edit_tags_component.html.heex:50 #: lib/cannery_web/live/container_live/edit_tags_component.html.heex:51
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Add" msgid "Add"
msgstr "" msgstr ""
@ -156,9 +156,9 @@ msgstr ""
msgid "Why not get some ready to shoot?" msgid "Why not get some ready to shoot?"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:101 #: lib/cannery_web/live/ammo_group_live/index.html.heex:103
#: lib/cannery_web/live/ammo_group_live/show.html.heex:101 #: lib/cannery_web/live/ammo_group_live/show.html.heex:103
#: lib/cannery_web/live/range_live/index.html.heex:38 #: lib/cannery_web/live/range_live/index.html.heex:45
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Record shots" msgid "Record shots"
msgstr "" msgstr ""
@ -168,17 +168,12 @@ msgstr ""
msgid "Add another container!" msgid "Add another container!"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.html.heex:94
#, elixir-autogen, elixir-format
msgid "Move containers"
msgstr ""
#: lib/cannery_web/components/move_ammo_group_component.ex:126 #: lib/cannery_web/components/move_ammo_group_component.ex:126
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select" msgid "Select"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:30 #: lib/cannery_web/live/invite_live/index.html.heex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Copy to clipboard" msgid "Copy to clipboard"
msgstr "" msgstr ""
@ -188,7 +183,7 @@ msgstr ""
msgid "add a container first" msgid "add a container first"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:75 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:77
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Create" msgid "Create"
msgstr "" msgstr ""
@ -203,7 +198,7 @@ msgstr ""
msgid "Change language" msgid "Change language"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.html.heex:60 #: lib/cannery_web/live/ammo_group_live/show.html.heex:55
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "View in Catalog" msgid "View in Catalog"
msgstr "" msgstr ""
@ -214,23 +209,25 @@ msgid "add an ammo type first"
msgstr "" msgstr ""
#: lib/cannery_web/components/move_ammo_group_component.ex:80 #: lib/cannery_web/components/move_ammo_group_component.ex:80
#: lib/cannery_web/live/ammo_group_live/index.html.heex:120
#: lib/cannery_web/live/ammo_group_live/show.html.heex:96
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Move ammo" msgid "Move ammo"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:78 #: lib/cannery_web/live/invite_live/index.html.heex:90
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Set Unlimited" msgid "Set Unlimited"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.html.heex:86 #: lib/cannery_web/live/ammo_group_live/show.html.heex:89
#: lib/cannery_web/live/range_live/index.html.heex:31 #: lib/cannery_web/live/range_live/index.html.heex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Stage for range" msgid "Stage for range"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.html.heex:85 #: lib/cannery_web/live/ammo_group_live/show.html.heex:88
#: lib/cannery_web/live/range_live/index.html.heex:30 #: lib/cannery_web/live/range_live/index.html.heex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Unstage from range" msgid "Unstage from range"
msgstr "" msgstr ""
@ -239,3 +236,124 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Export Data as JSON" msgid "Export Data as JSON"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Clone %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:87
#: lib/cannery_web/live/container_live/index.html.heex:143
#, elixir-autogen, elixir-format
msgid "Clone %{container_name}"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:35
#, elixir-autogen, elixir-format
msgid "Copy invite link for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:103
#: lib/cannery_web/live/ammo_type_live/show.html.heex:36
#, elixir-autogen, elixir-format
msgid "Delete %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:102
#: lib/cannery_web/live/container_live/index.html.heex:158
#: lib/cannery_web/live/container_live/show.html.heex:55
#, elixir-autogen, elixir-format
msgid "Delete %{container_name}"
msgstr ""
#: lib/cannery_web/live/tag_live/index.html.heex:65
#, elixir-autogen, elixir-format
msgid "Delete %{tag_name}"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:63
#, elixir-autogen, elixir-format
msgid "Delete invite for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.ex:161
#: lib/cannery_web/live/range_live/index.html.heex:130
#, elixir-autogen, elixir-format
msgid "Delete shot record of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:74
#: lib/cannery_web/live/ammo_type_live/show.html.heex:19
#, elixir-autogen, elixir-format
msgid "Edit %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:77
#: lib/cannery_web/live/container_live/index.html.heex:133
#: lib/cannery_web/live/container_live/show.html.heex:42
#, elixir-autogen, elixir-format
msgid "Edit %{container_name}"
msgstr ""
#: lib/cannery_web/live/tag_live/index.html.heex:52
#, elixir-autogen, elixir-format
msgid "Edit %{tag_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:142
#: lib/cannery_web/live/ammo_group_live/show.html.heex:62
#, elixir-autogen, elixir-format
msgid "Edit ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:46
#, elixir-autogen, elixir-format
msgid "Edit invite for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.ex:146
#, elixir-autogen, elixir-format
msgid "Edit shot group of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/range_live/index.html.heex:113
#, elixir-autogen, elixir-format
msgid "Edit shot record of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:96
#, elixir-autogen, elixir-format, fuzzy
msgid "Stage"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:65
#: lib/cannery_web/live/container_live/index.html.heex:122
#, elixir-autogen, elixir-format
msgid "Tag %{container_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:95
#, elixir-autogen, elixir-format
msgid "Unstage"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:64
#, elixir-autogen, elixir-format
msgid "View %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:154
#, elixir-autogen, elixir-format, fuzzy
msgid "Clone ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:169
#: lib/cannery_web/live/ammo_group_live/show.html.heex:76
#, elixir-autogen, elixir-format, fuzzy
msgid "Delete ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:130
#, elixir-autogen, elixir-format, fuzzy
msgid "View ammo group of %{ammo_group_count} bullets"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -10,18 +10,18 @@ msgid ""
msgstr "" msgstr ""
"Language: en\n" "Language: en\n"
#: lib/cannery/containers.ex:179 #: lib/cannery/containers.ex:200
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Container must be empty before deleting" msgid "Container must be empty before deleting"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/index.ex:92 #: lib/cannery_web/live/container_live/index.ex:86
#: lib/cannery_web/live/container_live/show.ex:73 #: lib/cannery_web/live/container_live/show.ex:71
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not delete %{name}: %{error}" msgid "Could not delete %{name}: %{error}"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/index.ex:80 #: lib/cannery_web/live/container_live/index.ex:74
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not find that container" msgid "Could not find that container"
msgstr "" msgstr ""
@ -36,12 +36,12 @@ msgstr ""
msgid "Error" msgid "Error"
msgstr "" msgstr ""
#: lib/cannery_web/templates/error/error.html.heex:28 #: lib/cannery_web/templates/error/error.html.heex:31
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Go back home" msgid "Go back home"
msgstr "" msgstr ""
#: lib/cannery_web/views/error_view.ex:11 #: lib/cannery_web/views/error_view.ex:10
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Internal Server Error" msgid "Internal Server Error"
msgstr "" msgstr ""
@ -51,7 +51,7 @@ msgstr ""
msgid "Invalid email or password" msgid "Invalid email or password"
msgstr "" msgstr ""
#: lib/cannery_web/views/error_view.ex:9 #: lib/cannery_web/views/error_view.ex:8
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Not found" msgid "Not found"
msgstr "" msgstr ""
@ -70,15 +70,15 @@ msgstr ""
msgid "Reset password link is invalid or it has expired." msgid "Reset password link is invalid or it has expired."
msgstr "" msgstr ""
#: lib/cannery_web/controllers/user_registration_controller.ex:22 #: lib/cannery_web/controllers/user_registration_controller.ex:23
#: lib/cannery_web/controllers/user_registration_controller.ex:51 #: lib/cannery_web/controllers/user_registration_controller.ex:52
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Sorry, public registration is disabled" msgid "Sorry, public registration is disabled"
msgstr "" msgstr ""
#: lib/cannery_web/controllers/user_registration_controller.ex:12 #: lib/cannery_web/controllers/user_registration_controller.ex:13
#: lib/cannery_web/controllers/user_registration_controller.ex:41 #: lib/cannery_web/controllers/user_registration_controller.ex:42
#: lib/cannery_web/controllers/user_registration_controller.ex:70 #: lib/cannery_web/controllers/user_registration_controller.ex:71
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Sorry, this invite was not found or expired" msgid "Sorry, this invite was not found or expired"
msgstr "" msgstr ""
@ -88,7 +88,7 @@ msgstr ""
msgid "Unable to delete user" msgid "Unable to delete user"
msgstr "" msgstr ""
#: lib/cannery_web/views/error_view.ex:10 #: lib/cannery_web/views/error_view.ex:9
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Unauthorized" msgid "Unauthorized"
msgstr "" msgstr ""
@ -98,7 +98,7 @@ msgstr ""
msgid "User confirmation link is invalid or it has expired." msgid "User confirmation link is invalid or it has expired."
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.ex:19 #: lib/cannery_web/live/invite_live/index.ex:18
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You are not authorized to view this page" msgid "You are not authorized to view this page"
msgstr "" msgstr ""
@ -129,64 +129,73 @@ msgstr ""
msgid "must have the @ sign and no spaces" msgid "must have the @ sign and no spaces"
msgstr "" msgstr ""
#: lib/cannery/tags.ex:66 #: lib/cannery_web/live/container_live/show.ex:46
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tag not found" msgid "Tag not found"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/edit_tags_component.ex:30 #: lib/cannery_web/live/container_live/edit_tags_component.ex:46
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tag could not be added" msgid "Tag could not be added"
msgstr "" msgstr ""
#: lib/cannery/activity_log/shot_group.ex:122
#, elixir-autogen, elixir-format
msgid "Count must be at least 1"
msgstr ""
#: lib/cannery/activity_log/shot_group.ex:82
#: lib/cannery/activity_log/shot_group.ex:118
#, elixir-autogen, elixir-format
msgid "Count must be less than %{count}"
msgstr ""
#: lib/cannery_web/controllers/user_auth.ex:39 #: lib/cannery_web/controllers/user_auth.ex:39
#: lib/cannery_web/controllers/user_auth.ex:161 #: lib/cannery_web/controllers/user_auth.ex:161
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You must confirm your account and log in to access this page." msgid "You must confirm your account and log in to access this page."
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/edit_tags_component.ex:52 #: lib/cannery_web/live/container_live/edit_tags_component.ex:73
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Tag could not be removed" msgid "Tag could not be removed"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/form_component.ex:157 #: lib/cannery_web/live/ammo_group_live/form_component.ex:160
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not parse number of copies" msgid "Could not parse number of copies"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/form_component.ex:142 #: lib/cannery_web/live/ammo_group_live/form_component.ex:150
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}"
msgstr "" msgstr ""
#: lib/cannery/ammo.ex:686 #: lib/cannery/ammo.ex:1015
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid multiplier" msgid "Invalid multiplier"
msgstr "" msgstr ""
#: lib/cannery/ammo/ammo_group.ex:97 #: lib/cannery/ammo/ammo_group.ex:92
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Please select an ammo type and container" msgid "Please select an ammo type and container"
msgstr "" msgstr ""
#: lib/cannery_web/live/range_live/index.html.heex:67 #: lib/cannery_web/live/range_live/index.html.heex:74
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Your browser does not support the canvas element." msgid "Your browser does not support the canvas element."
msgstr "" msgstr ""
#: lib/cannery/activity_log/shot_group.ex:77 #: lib/cannery/activity_log/shot_group.ex:72
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Please select a valid user and ammo pack" msgid "Please select a valid user and ammo pack"
msgstr "" msgstr ""
#: lib/cannery/activity_log/shot_group.ex:86
#, elixir-autogen, elixir-format
msgid "Ammo left can be at most %{count} rounds"
msgstr ""
#: lib/cannery/activity_log/shot_group.ex:82
#, elixir-autogen, elixir-format
msgid "Ammo left must be at least 0"
msgstr ""
#: lib/cannery/activity_log/shot_group.ex:119
#, elixir-autogen, elixir-format, fuzzy
msgid "Count can be at most %{count} shots"
msgstr ""
#: lib/cannery/activity_log/shot_group.ex:78
#, elixir-autogen, elixir-format
msgid "can't be blank"
msgstr ""

View File

@ -10,31 +10,31 @@ msgid ""
msgstr "" msgstr ""
"Language: en\n" "Language: en\n"
#: lib/cannery_web/live/ammo_type_live/form_component.ex:86 #: lib/cannery_web/live/ammo_type_live/form_component.ex:89
#: lib/cannery_web/live/container_live/form_component.ex:89 #: lib/cannery_web/live/container_live/form_component.ex:89
#: lib/cannery_web/live/invite_live/form_component.ex:80 #: lib/cannery_web/live/invite_live/form_component.ex:80
#: lib/cannery_web/live/tag_live/form_component.ex:79 #: lib/cannery_web/live/tag_live/form_component.ex:78
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} created successfully" msgid "%{name} created successfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.ex:73 #: lib/cannery_web/live/ammo_type_live/index.ex:72
#: lib/cannery_web/live/ammo_type_live/show.ex:55 #: lib/cannery_web/live/ammo_type_live/show.ex:54
#: lib/cannery_web/live/tag_live/index.ex:64 #: lib/cannery_web/live/tag_live/index.ex:65
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} deleted succesfully" msgid "%{name} deleted succesfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/index.ex:85 #: lib/cannery_web/live/container_live/index.ex:79
#: lib/cannery_web/live/container_live/show.ex:63 #: lib/cannery_web/live/container_live/show.ex:61
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} has been deleted" msgid "%{name} has been deleted"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_type_live/form_component.ex:67 #: lib/cannery_web/live/ammo_type_live/form_component.ex:70
#: lib/cannery_web/live/container_live/form_component.ex:70 #: lib/cannery_web/live/container_live/form_component.ex:70
#: lib/cannery_web/live/invite_live/form_component.ex:62 #: lib/cannery_web/live/invite_live/form_component.ex:62
#: lib/cannery_web/live/tag_live/form_component.ex:61 #: lib/cannery_web/live/tag_live/form_component.ex:60
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} updated successfully" msgid "%{name} updated successfully"
msgstr "" msgstr ""
@ -44,22 +44,22 @@ msgstr ""
msgid "A link to confirm your email change has been sent to the new address." msgid "A link to confirm your email change has been sent to the new address."
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:98 #: lib/cannery_web/live/invite_live/index.html.heex:110
#: lib/cannery_web/live/invite_live/index.html.heex:126 #: lib/cannery_web/live/invite_live/index.html.heex:138
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to delete %{email}? This action is permanent!" msgid "Are you sure you want to delete %{email}? This action is permanent!"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:92 #: lib/cannery_web/live/container_live/index.html.heex:99
#: lib/cannery_web/live/container_live/index.html.heex:135 #: lib/cannery_web/live/container_live/index.html.heex:155
#: lib/cannery_web/live/container_live/show.html.heex:55 #: lib/cannery_web/live/container_live/show.html.heex:52
#: lib/cannery_web/live/tag_live/index.html.heex:63 #: lib/cannery_web/live/tag_live/index.html.heex:63
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to delete %{name}?" msgid "Are you sure you want to delete %{name}?"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:153 #: lib/cannery_web/live/ammo_group_live/index.html.heex:167
#: lib/cannery_web/live/ammo_group_live/show.html.heex:75 #: lib/cannery_web/live/ammo_group_live/show.html.heex:74
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to delete this ammo?" msgid "Are you sure you want to delete this ammo?"
msgstr "" msgstr ""
@ -69,7 +69,7 @@ msgstr ""
msgid "Are you sure you want to delete your account?" msgid "Are you sure you want to delete your account?"
msgstr "" msgstr ""
#: lib/cannery_web/components/topbar.ex:104 #: lib/cannery_web/components/core_components/topbar.html.heex:89
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to log out?" msgid "Are you sure you want to log out?"
msgstr "" msgstr ""
@ -104,17 +104,17 @@ msgstr ""
msgid "Password updated successfully." msgid "Password updated successfully."
msgstr "" msgstr ""
#: lib/cannery_web/controllers/user_registration_controller.ex:65 #: lib/cannery_web/controllers/user_registration_controller.ex:66
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Please check your email to verify your account" msgid "Please check your email to verify your account"
msgstr "" msgstr ""
#: lib/cannery_web/components/add_shot_group_component.html.heex:56 #: lib/cannery_web/components/add_shot_group_component.html.heex:58
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:83 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:85
#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:158 #: lib/cannery_web/live/ammo_type_live/form_component.html.heex:160
#: lib/cannery_web/live/container_live/form_component.html.heex:53 #: lib/cannery_web/live/container_live/form_component.html.heex:57
#: lib/cannery_web/live/invite_live/form_component.html.heex:34 #: lib/cannery_web/live/invite_live/form_component.html.heex:34
#: lib/cannery_web/live/range_live/form_component.html.heex:43 #: lib/cannery_web/live/range_live/form_component.html.heex:46
#: lib/cannery_web/live/tag_live/form_component.html.heex:39 #: lib/cannery_web/live/tag_live/form_component.html.heex:39
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Saving..." msgid "Saving..."
@ -130,44 +130,44 @@ msgstr ""
msgid "Are you sure you want to remove the %{tag_name} tag from %{container_name}?" msgid "Are you sure you want to remove the %{tag_name} tag from %{container_name}?"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/edit_tags_component.ex:36 #: lib/cannery_web/live/container_live/edit_tags_component.ex:51
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{name} added successfully" msgid "%{name} added successfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/show.ex:39 #: lib/cannery_web/live/container_live/show.ex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{tag_name} has been removed from %{container_name}" msgid "%{tag_name} has been removed from %{container_name}"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/edit_tags_component.html.heex:52 #: lib/cannery_web/live/container_live/edit_tags_component.html.heex:53
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Adding..." msgid "Adding..."
msgstr "" msgstr ""
#: lib/cannery_web/components/add_shot_group_component.ex:56 #: lib/cannery_web/components/add_shot_group_component.ex:60
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Shots recorded successfully" msgid "Shots recorded successfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/range_live/index.html.heex:27 #: lib/cannery_web/live/range_live/index.html.heex:34
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to unstage this ammo?" msgid "Are you sure you want to unstage this ammo?"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.ex:142 #: lib/cannery_web/live/ammo_group_live/show.ex:159
#: lib/cannery_web/live/range_live/index.html.heex:116 #: lib/cannery_web/live/range_live/index.html.heex:127
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Are you sure you want to delete this shot record?" msgid "Are you sure you want to delete this shot record?"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.ex:83 #: lib/cannery_web/live/ammo_group_live/show.ex:81
#: lib/cannery_web/live/range_live/index.ex:80 #: lib/cannery_web/live/range_live/index.ex:79
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Shot records deleted succesfully" msgid "Shot records deleted succesfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/range_live/form_component.ex:55 #: lib/cannery_web/live/range_live/form_component.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Shot records updated successfully" msgid "Shot records updated successfully"
msgstr "" msgstr ""
@ -177,17 +177,17 @@ msgstr ""
msgid "%{email} confirmed successfully." msgid "%{email} confirmed successfully."
msgstr "" msgstr ""
#: lib/cannery_web/components/move_ammo_group_component.ex:53 #: lib/cannery_web/components/move_ammo_group_component.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Ammo moved to %{name} successfully" msgid "Ammo moved to %{name} successfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.ex:128 #: lib/cannery_web/live/invite_live/index.ex:126
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/edit_tags_component.ex:58 #: lib/cannery_web/live/container_live/edit_tags_component.ex:78
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{name} removed successfully" msgid "%{name} removed successfully"
msgstr "" msgstr ""
@ -198,7 +198,7 @@ msgstr ""
msgid "You'll need to" msgid "You'll need to"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:76 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:78
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Creating..." msgid "Creating..."
msgstr "" msgstr ""
@ -219,65 +219,65 @@ msgstr ""
msgid "Ammo deleted succesfully" msgid "Ammo deleted succesfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/range_live/index.ex:95 #: lib/cannery_web/live/range_live/index.ex:93
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Ammo unstaged succesfully" msgid "Ammo unstaged succesfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/form_component.ex:118 #: lib/cannery_web/live/ammo_group_live/form_component.ex:126
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Ammo updated successfully" msgid "Ammo updated successfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/form_component.ex:178 #: lib/cannery_web/live/ammo_group_live/form_component.ex:185
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Ammo added successfully" msgid "Ammo added successfully"
msgid_plural "Ammo added successfully" msgid_plural "Ammo added successfully"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:90 #: lib/cannery_web/live/ammo_type_live/index.html.heex:96
#: lib/cannery_web/live/ammo_type_live/show.html.heex:29 #: lib/cannery_web/live/ammo_type_live/show.html.heex:29
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Are you sure you want to delete %{name}? This will delete all %{name} type ammo as well!" msgid "Are you sure you want to delete %{name}? This will delete all %{name} type ammo as well!"
msgstr "" msgstr ""
#: lib/cannery_web/live/home_live.html.heex:65 #: lib/cannery_web/live/home_live.html.heex:63
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Register to setup Cannery" msgid "Register to setup Cannery"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.ex:54 #: lib/cannery_web/live/invite_live/index.ex:53
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{invite_name} deleted succesfully" msgid "%{invite_name} deleted succesfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.ex:115 #: lib/cannery_web/live/invite_live/index.ex:114
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{invite_name} disabled succesfully" msgid "%{invite_name} disabled succesfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.ex:91 #: lib/cannery_web/live/invite_live/index.ex:90
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{invite_name} enabled succesfully" msgid "%{invite_name} enabled succesfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.ex:69 #: lib/cannery_web/live/invite_live/index.ex:68
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{invite_name} updated succesfully" msgid "%{invite_name} updated succesfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.ex:140 #: lib/cannery_web/live/invite_live/index.ex:135
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "%{user_email} deleted succesfully" msgid "%{user_email} deleted succesfully"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:48 #: lib/cannery_web/live/invite_live/index.html.heex:58
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Are you sure you want to delete the invite for %{invite_name}?" msgid "Are you sure you want to delete the invite for %{invite_name}?"
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:73 #: lib/cannery_web/live/invite_live/index.html.heex:85
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Are you sure you want to make %{invite_name} unlimited?" msgid "Are you sure you want to make %{invite_name} unlimited?"
msgstr "" msgstr ""

View File

@ -10,18 +10,18 @@
msgid "" msgid ""
msgstr "" msgstr ""
#: lib/cannery/containers.ex:179 #: lib/cannery/containers.ex:200
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Container must be empty before deleting" msgid "Container must be empty before deleting"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/index.ex:92 #: lib/cannery_web/live/container_live/index.ex:86
#: lib/cannery_web/live/container_live/show.ex:73 #: lib/cannery_web/live/container_live/show.ex:71
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not delete %{name}: %{error}" msgid "Could not delete %{name}: %{error}"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/index.ex:80 #: lib/cannery_web/live/container_live/index.ex:74
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not find that container" msgid "Could not find that container"
msgstr "" msgstr ""
@ -36,12 +36,12 @@ msgstr ""
msgid "Error" msgid "Error"
msgstr "" msgstr ""
#: lib/cannery_web/templates/error/error.html.heex:28 #: lib/cannery_web/templates/error/error.html.heex:31
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Go back home" msgid "Go back home"
msgstr "" msgstr ""
#: lib/cannery_web/views/error_view.ex:11 #: lib/cannery_web/views/error_view.ex:10
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Internal Server Error" msgid "Internal Server Error"
msgstr "" msgstr ""
@ -51,7 +51,7 @@ msgstr ""
msgid "Invalid email or password" msgid "Invalid email or password"
msgstr "" msgstr ""
#: lib/cannery_web/views/error_view.ex:9 #: lib/cannery_web/views/error_view.ex:8
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Not found" msgid "Not found"
msgstr "" msgstr ""
@ -70,15 +70,15 @@ msgstr ""
msgid "Reset password link is invalid or it has expired." msgid "Reset password link is invalid or it has expired."
msgstr "" msgstr ""
#: lib/cannery_web/controllers/user_registration_controller.ex:22 #: lib/cannery_web/controllers/user_registration_controller.ex:23
#: lib/cannery_web/controllers/user_registration_controller.ex:51 #: lib/cannery_web/controllers/user_registration_controller.ex:52
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Sorry, public registration is disabled" msgid "Sorry, public registration is disabled"
msgstr "" msgstr ""
#: lib/cannery_web/controllers/user_registration_controller.ex:12 #: lib/cannery_web/controllers/user_registration_controller.ex:13
#: lib/cannery_web/controllers/user_registration_controller.ex:41 #: lib/cannery_web/controllers/user_registration_controller.ex:42
#: lib/cannery_web/controllers/user_registration_controller.ex:70 #: lib/cannery_web/controllers/user_registration_controller.ex:71
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Sorry, this invite was not found or expired" msgid "Sorry, this invite was not found or expired"
msgstr "" msgstr ""
@ -88,7 +88,7 @@ msgstr ""
msgid "Unable to delete user" msgid "Unable to delete user"
msgstr "" msgstr ""
#: lib/cannery_web/views/error_view.ex:10 #: lib/cannery_web/views/error_view.ex:9
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Unauthorized" msgid "Unauthorized"
msgstr "" msgstr ""
@ -98,7 +98,7 @@ msgstr ""
msgid "User confirmation link is invalid or it has expired." msgid "User confirmation link is invalid or it has expired."
msgstr "" msgstr ""
#: lib/cannery_web/live/invite_live/index.ex:19 #: lib/cannery_web/live/invite_live/index.ex:18
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You are not authorized to view this page" msgid "You are not authorized to view this page"
msgstr "" msgstr ""
@ -128,64 +128,73 @@ msgstr ""
msgid "must have the @ sign and no spaces" msgid "must have the @ sign and no spaces"
msgstr "" msgstr ""
#: lib/cannery/tags.ex:66 #: lib/cannery_web/live/container_live/show.ex:46
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tag not found" msgid "Tag not found"
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/edit_tags_component.ex:30 #: lib/cannery_web/live/container_live/edit_tags_component.ex:46
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tag could not be added" msgid "Tag could not be added"
msgstr "" msgstr ""
#: lib/cannery/activity_log/shot_group.ex:122
#, elixir-autogen, elixir-format
msgid "Count must be at least 1"
msgstr ""
#: lib/cannery/activity_log/shot_group.ex:82
#: lib/cannery/activity_log/shot_group.ex:118
#, elixir-autogen, elixir-format
msgid "Count must be less than %{count}"
msgstr ""
#: lib/cannery_web/controllers/user_auth.ex:39 #: lib/cannery_web/controllers/user_auth.ex:39
#: lib/cannery_web/controllers/user_auth.ex:161 #: lib/cannery_web/controllers/user_auth.ex:161
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You must confirm your account and log in to access this page." msgid "You must confirm your account and log in to access this page."
msgstr "" msgstr ""
#: lib/cannery_web/live/container_live/edit_tags_component.ex:52 #: lib/cannery_web/live/container_live/edit_tags_component.ex:73
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tag could not be removed" msgid "Tag could not be removed"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/form_component.ex:157 #: lib/cannery_web/live/ammo_group_live/form_component.ex:160
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not parse number of copies" msgid "Could not parse number of copies"
msgstr "" msgstr ""
#: lib/cannery_web/live/ammo_group_live/form_component.ex:142 #: lib/cannery_web/live/ammo_group_live/form_component.ex:150
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}"
msgstr "" msgstr ""
#: lib/cannery/ammo.ex:686 #: lib/cannery/ammo.ex:1015
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid multiplier" msgid "Invalid multiplier"
msgstr "" msgstr ""
#: lib/cannery/ammo/ammo_group.ex:97 #: lib/cannery/ammo/ammo_group.ex:92
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Please select an ammo type and container" msgid "Please select an ammo type and container"
msgstr "" msgstr ""
#: lib/cannery_web/live/range_live/index.html.heex:67 #: lib/cannery_web/live/range_live/index.html.heex:74
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Your browser does not support the canvas element." msgid "Your browser does not support the canvas element."
msgstr "" msgstr ""
#: lib/cannery/activity_log/shot_group.ex:77 #: lib/cannery/activity_log/shot_group.ex:72
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Please select a valid user and ammo pack" msgid "Please select a valid user and ammo pack"
msgstr "" msgstr ""
#: lib/cannery/activity_log/shot_group.ex:86
#, elixir-autogen, elixir-format
msgid "Ammo left can be at most %{count} rounds"
msgstr ""
#: lib/cannery/activity_log/shot_group.ex:82
#, elixir-autogen, elixir-format
msgid "Ammo left must be at least 0"
msgstr ""
#: lib/cannery/activity_log/shot_group.ex:119
#, elixir-autogen, elixir-format
msgid "Count can be at most %{count} shots"
msgstr ""
#: lib/cannery/activity_log/shot_group.ex:78
#, elixir-autogen, elixir-format
msgid "can't be blank"
msgstr ""

View File

@ -79,11 +79,11 @@ msgstr "¿Has olvidado tu contraseña?"
msgid "Invite someone new!" msgid "Invite someone new!"
msgstr "¡Invita a alguien nuevo!" msgstr "¡Invita a alguien nuevo!"
#: lib/cannery_web/components/topbar.ex:137 #: lib/cannery_web/components/core_components/topbar.html.heex:122
#: lib/cannery_web/templates/user_confirmation/new.html.heex:31 #: lib/cannery_web/templates/user_confirmation/new.html.heex:32
#: lib/cannery_web/templates/user_registration/new.html.heex:44 #: lib/cannery_web/templates/user_registration/new.html.heex:44
#: lib/cannery_web/templates/user_reset_password/edit.html.heex:45 #: lib/cannery_web/templates/user_reset_password/edit.html.heex:45
#: lib/cannery_web/templates/user_reset_password/new.html.heex:31 #: lib/cannery_web/templates/user_reset_password/new.html.heex:32
#: lib/cannery_web/templates/user_session/new.html.heex:3 #: lib/cannery_web/templates/user_session/new.html.heex:3
#: lib/cannery_web/templates/user_session/new.html.heex:28 #: lib/cannery_web/templates/user_session/new.html.heex:28
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -110,19 +110,19 @@ msgstr "Nuevo Contenedor"
msgid "New Tag" msgid "New Tag"
msgstr "Nueva Etiqueta" msgstr "Nueva Etiqueta"
#: lib/cannery_web/components/topbar.ex:129 #: lib/cannery_web/components/core_components/topbar.html.heex:114
#: lib/cannery_web/templates/user_confirmation/new.html.heex:28 #: lib/cannery_web/templates/user_confirmation/new.html.heex:29
#: lib/cannery_web/templates/user_registration/new.html.heex:3 #: lib/cannery_web/templates/user_registration/new.html.heex:3
#: lib/cannery_web/templates/user_registration/new.html.heex:37 #: lib/cannery_web/templates/user_registration/new.html.heex:37
#: lib/cannery_web/templates/user_reset_password/edit.html.heex:42 #: lib/cannery_web/templates/user_reset_password/edit.html.heex:42
#: lib/cannery_web/templates/user_reset_password/new.html.heex:28 #: lib/cannery_web/templates/user_reset_password/new.html.heex:29
#: lib/cannery_web/templates/user_session/new.html.heex:39 #: lib/cannery_web/templates/user_session/new.html.heex:39
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Register" msgid "Register"
msgstr "Registrarse" msgstr "Registrarse"
#: lib/cannery_web/templates/user_confirmation/new.html.heex:3 #: lib/cannery_web/templates/user_confirmation/new.html.heex:3
#: lib/cannery_web/templates/user_confirmation/new.html.heex:15 #: lib/cannery_web/templates/user_confirmation/new.html.heex:16
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Resend confirmation instructions" msgid "Resend confirmation instructions"
msgstr "Reenviar instrucciones de confirmación" msgstr "Reenviar instrucciones de confirmación"
@ -133,28 +133,28 @@ msgstr "Reenviar instrucciones de confirmación"
msgid "Reset password" msgid "Reset password"
msgstr "Resetear contraseña" msgstr "Resetear contraseña"
#: lib/cannery_web/components/add_shot_group_component.html.heex:54 #: lib/cannery_web/components/add_shot_group_component.html.heex:56
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:82 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:84
#: lib/cannery_web/live/ammo_type_live/form_component.html.heex:157 #: lib/cannery_web/live/ammo_type_live/form_component.html.heex:159
#: lib/cannery_web/live/container_live/form_component.html.heex:51 #: lib/cannery_web/live/container_live/form_component.html.heex:55
#: lib/cannery_web/live/invite_live/form_component.html.heex:32 #: lib/cannery_web/live/invite_live/form_component.html.heex:32
#: lib/cannery_web/live/range_live/form_component.html.heex:41 #: lib/cannery_web/live/range_live/form_component.html.heex:44
#: lib/cannery_web/live/tag_live/form_component.html.heex:37 #: lib/cannery_web/live/tag_live/form_component.html.heex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Save" msgid "Save"
msgstr "Guardar" msgstr "Guardar"
#: lib/cannery_web/templates/user_reset_password/new.html.heex:15 #: lib/cannery_web/templates/user_reset_password/new.html.heex:16
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Send instructions to reset password" msgid "Send instructions to reset password"
msgstr "Enviar instrucciones para reestablecer contraseña" msgstr "Enviar instrucciones para reestablecer contraseña"
#: lib/cannery_web/live/container_live/show.html.heex:76 #: lib/cannery_web/live/container_live/show.html.heex:75
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Why not add one?" msgid "Why not add one?"
msgstr "¿Por qué no añadir una?" msgstr "¿Por qué no añadir una?"
#: lib/cannery_web/live/container_live/edit_tags_component.html.heex:50 #: lib/cannery_web/live/container_live/edit_tags_component.html.heex:51
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Add" msgid "Add"
msgstr "Añadir" msgstr "Añadir"
@ -169,9 +169,9 @@ msgstr "Preparar munición"
msgid "Why not get some ready to shoot?" msgid "Why not get some ready to shoot?"
msgstr "¿Por qué no preparar parte para disparar?" msgstr "¿Por qué no preparar parte para disparar?"
#: lib/cannery_web/live/ammo_group_live/index.html.heex:101 #: lib/cannery_web/live/ammo_group_live/index.html.heex:103
#: lib/cannery_web/live/ammo_group_live/show.html.heex:101 #: lib/cannery_web/live/ammo_group_live/show.html.heex:103
#: lib/cannery_web/live/range_live/index.html.heex:38 #: lib/cannery_web/live/range_live/index.html.heex:45
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Record shots" msgid "Record shots"
msgstr "Tiros récord" msgstr "Tiros récord"
@ -181,17 +181,12 @@ msgstr "Tiros récord"
msgid "Add another container!" msgid "Add another container!"
msgstr "¡Añade otro contenedor!" msgstr "¡Añade otro contenedor!"
#: lib/cannery_web/live/ammo_group_live/show.html.heex:94
#, elixir-autogen, elixir-format
msgid "Move containers"
msgstr "Mover contenedores"
#: lib/cannery_web/components/move_ammo_group_component.ex:126 #: lib/cannery_web/components/move_ammo_group_component.ex:126
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select" msgid "Select"
msgstr "Seleccionar" msgstr "Seleccionar"
#: lib/cannery_web/live/invite_live/index.html.heex:30 #: lib/cannery_web/live/invite_live/index.html.heex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Copy to clipboard" msgid "Copy to clipboard"
msgstr "Copiar al portapapeles" msgstr "Copiar al portapapeles"
@ -201,7 +196,7 @@ msgstr "Copiar al portapapeles"
msgid "add a container first" msgid "add a container first"
msgstr "añade primero un contenedor" msgstr "añade primero un contenedor"
#: lib/cannery_web/live/ammo_group_live/form_component.html.heex:75 #: lib/cannery_web/live/ammo_group_live/form_component.html.heex:77
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Create" msgid "Create"
msgstr "Crear" msgstr "Crear"
@ -216,7 +211,7 @@ msgstr "Cambiar Lenguaje"
msgid "Change language" msgid "Change language"
msgstr "Cambiar lenguaje" msgstr "Cambiar lenguaje"
#: lib/cannery_web/live/ammo_group_live/show.html.heex:60 #: lib/cannery_web/live/ammo_group_live/show.html.heex:55
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "View in Catalog" msgid "View in Catalog"
msgstr "Ver en Catalogo" msgstr "Ver en Catalogo"
@ -227,23 +222,25 @@ msgid "add an ammo type first"
msgstr "añade primero un tipo de munición" msgstr "añade primero un tipo de munición"
#: lib/cannery_web/components/move_ammo_group_component.ex:80 #: lib/cannery_web/components/move_ammo_group_component.ex:80
#: lib/cannery_web/live/ammo_group_live/index.html.heex:120
#: lib/cannery_web/live/ammo_group_live/show.html.heex:96
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Move ammo" msgid "Move ammo"
msgstr "Mover munición" msgstr "Mover munición"
#: lib/cannery_web/live/invite_live/index.html.heex:78 #: lib/cannery_web/live/invite_live/index.html.heex:90
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Set Unlimited" msgid "Set Unlimited"
msgstr "Activar ilimitados" msgstr "Activar ilimitados"
#: lib/cannery_web/live/ammo_group_live/show.html.heex:86 #: lib/cannery_web/live/ammo_group_live/show.html.heex:89
#: lib/cannery_web/live/range_live/index.html.heex:31 #: lib/cannery_web/live/range_live/index.html.heex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Stage for range" msgid "Stage for range"
msgstr "Preparar para el campo de tiro" msgstr "Preparar para el campo de tiro"
#: lib/cannery_web/live/ammo_group_live/show.html.heex:85 #: lib/cannery_web/live/ammo_group_live/show.html.heex:88
#: lib/cannery_web/live/range_live/index.html.heex:30 #: lib/cannery_web/live/range_live/index.html.heex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Unstage from range" msgid "Unstage from range"
msgstr "Desmontar del campo de tiro" msgstr "Desmontar del campo de tiro"
@ -252,3 +249,124 @@ msgstr "Desmontar del campo de tiro"
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Export Data as JSON" msgid "Export Data as JSON"
msgstr "Exportar datos como JSON" msgstr "Exportar datos como JSON"
#: lib/cannery_web/live/ammo_type_live/index.html.heex:84
#, elixir-autogen, elixir-format
msgid "Clone %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:87
#: lib/cannery_web/live/container_live/index.html.heex:143
#, elixir-autogen, elixir-format
msgid "Clone %{container_name}"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:35
#, elixir-autogen, elixir-format
msgid "Copy invite link for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:103
#: lib/cannery_web/live/ammo_type_live/show.html.heex:36
#, elixir-autogen, elixir-format
msgid "Delete %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:102
#: lib/cannery_web/live/container_live/index.html.heex:158
#: lib/cannery_web/live/container_live/show.html.heex:55
#, elixir-autogen, elixir-format
msgid "Delete %{container_name}"
msgstr ""
#: lib/cannery_web/live/tag_live/index.html.heex:65
#, elixir-autogen, elixir-format
msgid "Delete %{tag_name}"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:63
#, elixir-autogen, elixir-format
msgid "Delete invite for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.ex:161
#: lib/cannery_web/live/range_live/index.html.heex:130
#, elixir-autogen, elixir-format
msgid "Delete shot record of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:74
#: lib/cannery_web/live/ammo_type_live/show.html.heex:19
#, elixir-autogen, elixir-format
msgid "Edit %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/container_live/index.html.heex:77
#: lib/cannery_web/live/container_live/index.html.heex:133
#: lib/cannery_web/live/container_live/show.html.heex:42
#, elixir-autogen, elixir-format
msgid "Edit %{container_name}"
msgstr ""
#: lib/cannery_web/live/tag_live/index.html.heex:52
#, elixir-autogen, elixir-format
msgid "Edit %{tag_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:142
#: lib/cannery_web/live/ammo_group_live/show.html.heex:62
#, elixir-autogen, elixir-format
msgid "Edit ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/invite_live/index.html.heex:46
#, elixir-autogen, elixir-format
msgid "Edit invite for %{invite_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/show.ex:146
#, elixir-autogen, elixir-format
msgid "Edit shot group of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/range_live/index.html.heex:113
#, elixir-autogen, elixir-format
msgid "Edit shot record of %{shot_group_count} shots"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:96
#, elixir-autogen, elixir-format, fuzzy
msgid "Stage"
msgstr "Preparar munición"
#: lib/cannery_web/live/container_live/index.html.heex:65
#: lib/cannery_web/live/container_live/index.html.heex:122
#, elixir-autogen, elixir-format
msgid "Tag %{container_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:95
#, elixir-autogen, elixir-format
msgid "Unstage"
msgstr ""
#: lib/cannery_web/live/ammo_type_live/index.html.heex:64
#, elixir-autogen, elixir-format
msgid "View %{ammo_type_name}"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:154
#, elixir-autogen, elixir-format, fuzzy
msgid "Clone ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:169
#: lib/cannery_web/live/ammo_group_live/show.html.heex:76
#, elixir-autogen, elixir-format, fuzzy
msgid "Delete ammo group of %{ammo_group_count} bullets"
msgstr ""
#: lib/cannery_web/live/ammo_group_live/index.html.heex:130
#, elixir-autogen, elixir-format, fuzzy
msgid "View ammo group of %{ammo_group_count} bullets"
msgstr ""

Some files were not shown because too many files have changed in this diff Show More