From 8da22b3d8835754d77b3eb8bd998d5272b21cb17 Mon Sep 17 00:00:00 2001 From: Moritz Date: Mon, 9 Mar 2026 14:28:50 +0100 Subject: [PATCH] Apply review feedback and fix Credo in fee type filter - Index: use FilterParams and constants; fix parse recursion; validate fee type/group IDs; OR semantics for :in; build_query_params/reset_all_filters map-based API; alias order (Credo); Map.take list deprecation fix - MemberFilterComponent: use FilterParams and constants; fee_type_filter_part helper (Credo nesting); in_not_in_filter_label_class; reset_all_filters map; button label for :not_in and combined filter count; fieldset borders - Gettext: Fee types, filter count plural, 'without %{name}' (en/de) --- .../components/member_filter_component.ex | 170 ++++++----- lib/mv_web/live/member_live/index.ex | 264 ++++++++---------- priv/gettext/de/LC_MESSAGES/default.po | 16 +- priv/gettext/default.pot | 12 + priv/gettext/en/LC_MESSAGES/default.po | 16 +- 5 files changed, 233 insertions(+), 245 deletions(-) diff --git a/lib/mv_web/live/components/member_filter_component.ex b/lib/mv_web/live/components/member_filter_component.ex index 56c5666..ddd3538 100644 --- a/lib/mv_web/live/components/member_filter_component.ex +++ b/lib/mv_web/live/components/member_filter_component.ex @@ -34,8 +34,10 @@ defmodule MvWeb.Components.MemberFilterComponent do """ use MvWeb, :live_component - @group_filter_prefix "group_" - @fee_type_filter_prefix "fee_type_" + alias MvWeb.MemberLive.Index.FilterParams + + @group_filter_prefix Mv.Constants.group_filter_prefix() + @fee_type_filter_prefix Mv.Constants.fee_type_filter_prefix() @impl true def mount(socket) do @@ -201,7 +203,7 @@ defmodule MvWeb.Components.MemberFilterComponent do
{group.name} @@ -268,7 +270,7 @@ defmodule MvWeb.Components.MemberFilterComponent do
{fee_type.name} @@ -335,7 +337,7 @@ defmodule MvWeb.Components.MemberFilterComponent do
{custom_field.name} @@ -436,10 +438,10 @@ defmodule MvWeb.Components.MemberFilterComponent do payment_filter = parse_payment_filter(params) group_filters_parsed = - parse_prefix_filters(params, @group_filter_prefix, &parse_group_filter_value/1) + parse_prefix_filters(params, @group_filter_prefix, &FilterParams.parse_in_not_in_value/1) fee_type_filters_parsed = - parse_prefix_filters(params, @fee_type_filter_prefix, &parse_fee_type_filter_value/1) + parse_prefix_filters(params, @fee_type_filter_prefix, &FilterParams.parse_in_not_in_value/1) custom_boolean_filters_parsed = parse_custom_boolean_filters(params) @@ -455,7 +457,16 @@ defmodule MvWeb.Components.MemberFilterComponent do def handle_event("reset_filters", _params, socket) do # Send single message to reset all filters at once (performance optimization) # This avoids N×2 load_members() calls when resetting multiple filters - send(self(), {:reset_all_filters, nil, %{}, %{}, %{}}) + send( + self(), + {:reset_all_filters, + %{ + cycle_status_filter: nil, + boolean_filters: %{}, + group_filters: %{}, + fee_type_filters: %{} + }} + ) # Close dropdown after reset {:noreply, assign(socket, :open, false)} @@ -467,14 +478,6 @@ defmodule MvWeb.Components.MemberFilterComponent do defp parse_tri_state("all"), do: nil defp parse_tri_state(_), do: nil - defp parse_group_filter_value("in"), do: :in - defp parse_group_filter_value("not_in"), do: :not_in - defp parse_group_filter_value(_), do: nil - - defp parse_fee_type_filter_value("in"), do: :in - defp parse_fee_type_filter_value("not_in"), do: :not_in - defp parse_fee_type_filter_value(_), do: nil - defp parse_payment_filter(params) do case Map.get(params, "payment_filter") do "paid" -> :paid @@ -550,24 +553,53 @@ defmodule MvWeb.Components.MemberFilterComponent do boolean_custom_fields, boolean_filters ) do - cond do - cycle_status_filter -> - payment_filter_label(cycle_status_filter) + active_count = + count_active_filter_categories( + cycle_status_filter, + group_filters, + fee_type_filters, + boolean_filters + ) - map_size(group_filters) > 0 -> - group_filters_label(groups, group_filters) + if active_count >= 2 do + ngettext("%{count} filter active", "%{count} filters active", active_count, + count: active_count + ) + else + cond do + cycle_status_filter -> + payment_filter_label(cycle_status_filter) - map_size(fee_type_filters) > 0 -> - fee_type_filters_label(fee_types, fee_type_filters) + map_size(group_filters) > 0 -> + group_filters_label(groups, group_filters) - map_size(boolean_filters) > 0 -> - boolean_filter_label(boolean_custom_fields, boolean_filters) + map_size(fee_type_filters) > 0 -> + fee_type_filters_label(fee_types, fee_type_filters) - true -> - gettext("Apply filters") + map_size(boolean_filters) > 0 -> + boolean_filter_label(boolean_custom_fields, boolean_filters) + + true -> + gettext("Apply filters") + end end end + defp count_active_filter_categories( + cycle_status_filter, + group_filters, + fee_type_filters, + boolean_filters + ) do + [ + cycle_status_filter, + map_size(group_filters) > 0, + map_size(fee_type_filters) > 0, + map_size(boolean_filters) > 0 + ] + |> Enum.count(& &1) + end + defp group_filters_label(_groups, group_filters) when map_size(group_filters) == 0, do: gettext("All") @@ -589,15 +621,22 @@ defmodule MvWeb.Components.MemberFilterComponent do defp fee_type_filters_label(fee_types, fee_type_filters) do fee_types_by_id = Map.new(fee_types, fn ft -> {to_string(ft.id), ft.name} end) - names = + parts = fee_type_filters - |> Enum.map(fn {fee_type_id_str, _} -> Map.get(fee_types_by_id, fee_type_id_str) end) + |> Enum.map(fn {fee_type_id_str, value} -> + fee_type_filter_part(Map.get(fee_types_by_id, fee_type_id_str), value) + end) |> Enum.reject(&is_nil/1) - label = Enum.join(names, ", ") + label = Enum.join(parts, ", ") truncate_label(label, 30) end + defp fee_type_filter_part(nil, _value), do: nil + + defp fee_type_filter_part(name, :not_in), do: gettext("without %{name}", name: name) + defp fee_type_filter_part(name, _), do: name + # Get payment filter label defp payment_filter_label(nil), do: gettext("All") defp payment_filter_label(:paid), do: gettext("Paid") @@ -671,70 +710,27 @@ defmodule MvWeb.Components.MemberFilterComponent do end end - # Get CSS classes for per-group filter label based on current state - defp group_filter_label_class(group_filters, group_id, expected_value) do + # Shared CSS classes for in/not_in filter labels (groups and fee types) + defp in_not_in_filter_label_class(filters, id, expected_value) do base_classes = "join-item btn btn-sm" - current_value = Map.get(group_filters, to_string(group_id)) + current_value = Map.get(filters, to_string(id)) is_active = current_value == expected_value - cond do - expected_value == nil -> - if is_active do - "#{base_classes} btn-active" - else - "#{base_classes} btn" - end - - expected_value == :in -> - if is_active do - "#{base_classes} btn-success btn-active" - else - "#{base_classes} btn" - end - - expected_value == :not_in -> - if is_active do - "#{base_classes} btn-error btn-active" - else - "#{base_classes} btn" - end - - true -> - "#{base_classes} btn-outline" + case {expected_value, is_active} do + {_, false} -> "#{base_classes} btn" + {nil, true} -> "#{base_classes} btn-active" + {:in, true} -> "#{base_classes} btn-success btn-active" + {:not_in, true} -> "#{base_classes} btn-error btn-active" + _ -> "#{base_classes} btn-outline" end end - # Get CSS classes for per-fee-type filter label based on current state + defp group_filter_label_class(group_filters, group_id, expected_value) do + in_not_in_filter_label_class(group_filters, group_id, expected_value) + end + defp fee_type_filter_label_class(fee_type_filters, fee_type_id, expected_value) do - base_classes = "join-item btn btn-sm" - current_value = Map.get(fee_type_filters, to_string(fee_type_id)) - is_active = current_value == expected_value - - cond do - expected_value == nil -> - if is_active do - "#{base_classes} btn-active" - else - "#{base_classes} btn" - end - - expected_value == :in -> - if is_active do - "#{base_classes} btn-success btn-active" - else - "#{base_classes} btn" - end - - expected_value == :not_in -> - if is_active do - "#{base_classes} btn-error btn-active" - else - "#{base_classes} btn" - end - - true -> - "#{base_classes} btn-outline" - end + in_not_in_filter_label_class(fee_type_filters, fee_type_id, expected_value) end # Get CSS classes for boolean filter label based on current state diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index c745a3d..e2e037d 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -38,13 +38,14 @@ defmodule MvWeb.MemberLive.Index do alias MvWeb.Helpers.DateFormatter alias MvWeb.MemberLive.Index.FieldSelection alias MvWeb.MemberLive.Index.FieldVisibility + alias MvWeb.MemberLive.Index.FilterParams alias MvWeb.MemberLive.Index.Formatter alias MvWeb.MemberLive.Index.MembershipFeeStatus @custom_field_prefix Mv.Constants.custom_field_prefix() @boolean_filter_prefix Mv.Constants.boolean_filter_prefix() - @group_filter_prefix "group_" - @fee_type_filter_prefix "fee_type_" + @group_filter_prefix Mv.Constants.group_filter_prefix() + @fee_type_filter_prefix Mv.Constants.fee_type_filter_prefix() # Maximum number of boolean custom field filters allowed per request (DoS protection) @max_boolean_filters Mv.Constants.max_boolean_filters() @@ -222,16 +223,7 @@ defmodule MvWeb.MemberLive.Index do |> update_selection_assigns() query_params = - build_query_params( - socket.assigns.query, - socket.assigns.sort_field, - socket.assigns.sort_order, - socket.assigns.cycle_status_filter, - socket.assigns[:group_filters], - new_show_current, - socket.assigns.boolean_custom_field_filters, - socket.assigns[:fee_type_filters] - ) + build_query_params(opts_for_query_params(socket, %{show_current_cycle: new_show_current})) |> maybe_add_field_selection( socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false @@ -306,14 +298,10 @@ defmodule MvWeb.MemberLive.Index do # URL sync - push_patch happens synchronously in the event handler query_params = build_query_params( - socket.assigns.query, - export_sort_field(socket.assigns.sort_field), - export_sort_order(socket.assigns.sort_order), - socket.assigns.cycle_status_filter, - socket.assigns[:group_filters], - socket.assigns.show_current_cycle, - socket.assigns.boolean_custom_field_filters, - socket.assigns[:fee_type_filters] + opts_for_query_params(socket, %{ + sort_field: export_sort_field(socket.assigns.sort_field), + sort_order: export_sort_order(socket.assigns.sort_order) + }) ) |> maybe_add_field_selection( socket.assigns[:user_field_selection], @@ -345,16 +333,7 @@ defmodule MvWeb.MemberLive.Index do |> update_selection_assigns() query_params = - build_query_params( - q, - socket.assigns.sort_field, - socket.assigns.sort_order, - socket.assigns.cycle_status_filter, - socket.assigns[:group_filters], - socket.assigns.show_current_cycle, - socket.assigns.boolean_custom_field_filters, - socket.assigns[:fee_type_filters] - ) + build_query_params(opts_for_query_params(socket, %{query: q})) |> maybe_add_field_selection( socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false @@ -374,16 +353,7 @@ defmodule MvWeb.MemberLive.Index do |> update_selection_assigns() query_params = - build_query_params( - socket.assigns.query, - socket.assigns.sort_field, - socket.assigns.sort_order, - filter, - socket.assigns[:group_filters], - socket.assigns.show_current_cycle, - socket.assigns.boolean_custom_field_filters, - socket.assigns[:fee_type_filters] - ) + build_query_params(opts_for_query_params(socket, %{cycle_status_filter: filter})) |> maybe_add_field_selection( socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false @@ -409,16 +379,7 @@ defmodule MvWeb.MemberLive.Index do |> update_selection_assigns() query_params = - build_query_params( - socket.assigns.query, - socket.assigns.sort_field, - socket.assigns.sort_order, - socket.assigns.cycle_status_filter, - socket.assigns[:group_filters], - socket.assigns.show_current_cycle, - updated_filters, - socket.assigns[:fee_type_filters] - ) + build_query_params(opts_for_query_params(socket, %{boolean_filters: updated_filters})) |> maybe_add_field_selection( socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false @@ -446,16 +407,7 @@ defmodule MvWeb.MemberLive.Index do |> update_selection_assigns() query_params = - build_query_params( - socket.assigns.query, - socket.assigns.sort_field, - socket.assigns.sort_order, - socket.assigns.cycle_status_filter, - group_filters, - socket.assigns.show_current_cycle, - socket.assigns.boolean_custom_field_filters, - socket.assigns[:fee_type_filters] - ) + build_query_params(opts_for_query_params(socket, %{group_filters: group_filters})) |> maybe_add_field_selection( socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false @@ -483,16 +435,7 @@ defmodule MvWeb.MemberLive.Index do |> update_selection_assigns() query_params = - build_query_params( - socket.assigns.query, - socket.assigns.sort_field, - socket.assigns.sort_order, - socket.assigns.cycle_status_filter, - socket.assigns[:group_filters], - socket.assigns.show_current_cycle, - socket.assigns.boolean_custom_field_filters, - fee_type_filters - ) + build_query_params(opts_for_query_params(socket, %{fee_type_filters: fee_type_filters})) |> maybe_add_field_selection( socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false @@ -502,9 +445,18 @@ defmodule MvWeb.MemberLive.Index do {:noreply, push_patch(socket, to: new_path, replace: true)} end - @impl true + # Backward compatibility: tuple form delegates to map form def handle_info({:reset_all_filters, cycle_status_filter, boolean_filters}, socket) do - handle_info({:reset_all_filters, cycle_status_filter, boolean_filters, %{}, %{}}, socket) + handle_info( + {:reset_all_filters, + %{ + cycle_status_filter: cycle_status_filter, + boolean_filters: boolean_filters, + group_filters: %{}, + fee_type_filters: %{} + }}, + socket + ) end def handle_info( @@ -512,7 +464,13 @@ defmodule MvWeb.MemberLive.Index do socket ) do handle_info( - {:reset_all_filters, cycle_status_filter, boolean_filters, group_filters, %{}}, + {:reset_all_filters, + %{ + cycle_status_filter: cycle_status_filter, + boolean_filters: boolean_filters, + group_filters: group_filters, + fee_type_filters: %{} + }}, socket ) end @@ -522,26 +480,30 @@ defmodule MvWeb.MemberLive.Index do fee_type_filters}, socket ) do + handle_info( + {:reset_all_filters, + %{ + cycle_status_filter: cycle_status_filter, + boolean_filters: boolean_filters, + group_filters: group_filters, + fee_type_filters: fee_type_filters + }}, + socket + ) + end + + def handle_info({:reset_all_filters, %{} = opts}, socket) do socket = socket - |> assign(:cycle_status_filter, cycle_status_filter) - |> assign(:group_filters, group_filters) - |> assign(:fee_type_filters, fee_type_filters) - |> assign(:boolean_custom_field_filters, boolean_filters) + |> assign(:cycle_status_filter, Map.get(opts, :cycle_status_filter)) + |> assign(:group_filters, Map.get(opts, :group_filters, %{})) + |> assign(:fee_type_filters, Map.get(opts, :fee_type_filters, %{})) + |> assign(:boolean_custom_field_filters, Map.get(opts, :boolean_filters, %{})) |> load_members() |> update_selection_assigns() query_params = - build_query_params( - socket.assigns.query, - socket.assigns.sort_field, - socket.assigns.sort_order, - cycle_status_filter, - socket.assigns[:group_filters], - socket.assigns.show_current_cycle, - boolean_filters, - fee_type_filters - ) + build_query_params(opts_for_query_params(socket)) |> maybe_add_field_selection( socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false @@ -801,16 +763,7 @@ defmodule MvWeb.MemberLive.Index do defp push_field_selection_url(socket) do query_params = - build_query_params( - socket.assigns.query, - socket.assigns.sort_field, - socket.assigns.sort_order, - socket.assigns.cycle_status_filter, - socket.assigns[:group_filters], - socket.assigns.show_current_cycle, - socket.assigns.boolean_custom_field_filters, - socket.assigns[:fee_type_filters] - ) + build_query_params(opts_for_query_params(socket)) |> maybe_add_field_selection(socket.assigns[:user_field_selection], true) new_path = ~p"/members?#{query_params}" @@ -821,22 +774,27 @@ defmodule MvWeb.MemberLive.Index do assign(socket, :user_field_selection, selection) end - defp build_query_params( - query, - sort_field, - sort_order, - cycle_status_filter, - group_filters, - show_current_cycle, - boolean_filters, - fee_type_filters - ) do - base_params = build_base_params(query, sort_field, sort_order) - base_params = add_cycle_status_filter(base_params, cycle_status_filter) - base_params = add_group_filters(base_params, group_filters) - base_params = add_fee_type_filters(base_params, fee_type_filters || %{}) - base_params = add_show_current_cycle(base_params, show_current_cycle) - add_boolean_filters(base_params, boolean_filters) + defp build_query_params(opts) when is_map(opts) do + base_params = build_base_params(opts.query, opts.sort_field, opts.sort_order) + base_params = add_cycle_status_filter(base_params, opts.cycle_status_filter) + base_params = add_group_filters(base_params, opts.group_filters || %{}) + base_params = add_fee_type_filters(base_params, opts.fee_type_filters || %{}) + base_params = add_show_current_cycle(base_params, opts.show_current_cycle) + add_boolean_filters(base_params, opts.boolean_filters || %{}) + end + + defp opts_for_query_params(socket, overrides \\ %{}) do + %{ + query: socket.assigns.query, + sort_field: socket.assigns.sort_field, + sort_order: socket.assigns.sort_order, + cycle_status_filter: socket.assigns.cycle_status_filter, + group_filters: socket.assigns[:group_filters] || %{}, + show_current_cycle: socket.assigns.show_current_cycle, + boolean_filters: socket.assigns.boolean_custom_field_filters || %{}, + fee_type_filters: socket.assigns[:fee_type_filters] || %{} + } + |> Map.merge(overrides) end defp add_fee_type_filters(params, fee_type_filters) do @@ -1146,7 +1104,8 @@ defmodule MvWeb.MemberLive.Index do defp apply_one_group_filter(query, _, _), do: query - # Multiple fee type filters combine with AND: member must match all selected fee type conditions. + # Fee type filters: :in selections combine with OR (member has any of the selected types); + # :not_in selections combine with AND (member must not have type A and not have type B). defp apply_fee_type_filters(query, fee_type_filters, _fee_types) when fee_type_filters == %{}, do: query @@ -1157,27 +1116,28 @@ defmodule MvWeb.MemberLive.Index do |> Enum.reject(&is_nil/1) |> MapSet.new() - Enum.reduce(fee_type_filters, query, fn {fee_type_id_str, value}, q -> - member? = MapSet.member?(valid_ids, fee_type_id_str) + {in_id_strs, not_in_filters} = + fee_type_filters + |> Enum.filter(fn {id_str, _} -> MapSet.member?(valid_ids, id_str) end) + |> Enum.split_with(fn {_, value} -> value == :in end) - if member? do - apply_one_fee_type_filter(q, fee_type_id_str, value) - else - q - end - end) - end + in_uuids = + in_id_strs + |> Enum.map(fn {id_str, _} -> id_str end) + |> Enum.map(&Ecto.UUID.cast/1) + |> Enum.filter(&match?({:ok, _}, &1)) + |> Enum.map(fn {:ok, uuid} -> uuid end) - defp apply_one_fee_type_filter(query, _fee_type_id_str, nil), do: query - - defp apply_one_fee_type_filter(query, fee_type_id_str, :in) do - case Ecto.UUID.cast(fee_type_id_str) do - {:ok, fee_type_uuid} -> - Ash.Query.filter(query, expr(membership_fee_type_id == ^fee_type_uuid)) - - _ -> + query = + if in_uuids == [] do query - end + else + Ash.Query.filter(query, expr(membership_fee_type_id in ^in_uuids)) + end + + Enum.reduce(not_in_filters, query, fn {fee_type_id_str, _}, q -> + apply_one_fee_type_filter(q, fee_type_id_str, :not_in) + end) end defp apply_one_fee_type_filter(query, fee_type_id_str, :not_in) do @@ -1523,7 +1483,14 @@ defmodule MvWeb.MemberLive.Index do add_group_filter_entry(acc, key, value_str, prefix_len) end) - assign(socket, :group_filters, filters) + valid_group_ids = + socket.assigns.groups + |> Enum.map(&normalize_uuid_string(to_string(&1.id))) + |> Enum.reject(&is_nil/1) + |> MapSet.new() + |> MapSet.to_list() + + assign(socket, :group_filters, Map.take(filters, valid_group_ids)) end defp maybe_update_group_filters(socket, _), do: socket @@ -1544,7 +1511,14 @@ defmodule MvWeb.MemberLive.Index do add_fee_type_filter_entry(acc, key, value_str, prefix_len) end) - assign(socket, :fee_type_filters, filters) + valid_fee_type_ids = + socket.assigns.fee_types + |> Enum.map(&normalize_uuid_string(to_string(&1.id))) + |> Enum.reject(&is_nil/1) + |> MapSet.new() + |> MapSet.to_list() + + assign(socket, :fee_type_filters, Map.take(filters, valid_fee_type_ids)) end defp maybe_update_fee_type_filters(socket, _), do: socket @@ -1556,7 +1530,7 @@ defmodule MvWeb.MemberLive.Index do valid_id? = fee_type_id_str && String.length(fee_type_id_str) <= @max_uuid_length if valid_id? do - case parse_fee_type_filter_value(value_str) do + case FilterParams.parse_in_not_in_value(value_str) do nil -> acc value -> Map.put(acc, fee_type_id_str, value) end @@ -1565,15 +1539,6 @@ defmodule MvWeb.MemberLive.Index do end end - defp parse_fee_type_filter_value("in"), do: :in - defp parse_fee_type_filter_value("not_in"), do: :not_in - - defp parse_fee_type_filter_value(val) when is_binary(val) do - parse_fee_type_filter_value(String.trim(val)) - end - - defp parse_fee_type_filter_value(_), do: nil - defp add_group_filter_entry(acc, key, value_str, prefix_len) do key_str = to_string(key) raw_id = String.slice(key_str, prefix_len, String.length(key_str) - prefix_len) @@ -1581,7 +1546,7 @@ defmodule MvWeb.MemberLive.Index do valid_id? = group_id_str && String.length(group_id_str) <= @max_uuid_length if valid_id? do - case parse_group_filter_value(value_str) do + case FilterParams.parse_in_not_in_value(value_str) do nil -> acc value -> Map.put(acc, group_id_str, value) end @@ -1600,15 +1565,6 @@ defmodule MvWeb.MemberLive.Index do defp normalize_uuid_string(_), do: nil - defp parse_group_filter_value("in"), do: :in - defp parse_group_filter_value("not_in"), do: :not_in - - defp parse_group_filter_value(val) when is_binary(val) do - parse_group_filter_value(String.trim(val)) - end - - defp parse_group_filter_value(_), do: nil - defp determine_cycle_status_filter("paid"), do: :paid defp determine_cycle_status_filter("unpaid"), do: :unpaid defp determine_cycle_status_filter(_), do: nil diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 0f07068..ec26b39 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -3234,6 +3234,18 @@ msgid "Include joining cycle: When active, members pay from their joining cycle; msgstr "Beitrittszyklus einbeziehen: Aktiv = Zahlung ab Beitrittszyklus; inaktiv = ab dem nächsten vollen Zyklus." #: lib/mv_web/live/components/member_filter_component.ex -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Fee types" -msgstr "Beitragsart" +msgstr "Beitragsarten" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "%{count} filter active" +msgid_plural "%{count} filters active" +msgstr[0] "%{count} Filter aktiv" +msgstr[1] "%{count} Filter aktiv" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "without %{name}" +msgstr "ohne %{name}" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 3ca889b..d5efdd8 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -3237,3 +3237,15 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Fee types" msgstr "" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "%{count} filter active" +msgid_plural "%{count} filters active" +msgstr[0] "" +msgstr[1] "" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "without %{name}" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 6c6069a..9a76cc8 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -3234,6 +3234,18 @@ msgid "Include joining cycle: When active, members pay from their joining cycle; msgstr "Include joining cycle: When active, members pay from their joining cycle; when inactive, from the next full cycle." #: lib/mv_web/live/components/member_filter_component.ex -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Fee types" -msgstr "" +msgstr "Fee types" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "%{count} filter active" +msgid_plural "%{count} filters active" +msgstr[0] "%{count} filter active" +msgstr[1] "%{count} filters active" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "without %{name}" +msgstr "without %{name}"