Implements exporting groups closes #428 #435
11 changed files with 253 additions and 49 deletions
|
|
@ -16,7 +16,7 @@ defmodule Mv.Membership.MemberExport do
|
|||
alias MvWeb.MemberLive.Index.MembershipFeeStatus
|
||||
|
||||
@member_fields_allowlist (Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)) ++
|
||||
["membership_fee_status"]
|
||||
["membership_fee_status", "groups"]
|
||||
@computed_export_fields ["membership_fee_status"]
|
||||
@computed_insert_after "membership_fee_start_date"
|
||||
@custom_field_prefix Mv.Constants.custom_field_prefix()
|
||||
|
|
@ -323,10 +323,14 @@ defmodule Mv.Membership.MemberExport do
|
|||
|> Enum.filter(&(&1 in @domain_member_field_strings))
|
||||
|> order_member_fields_like_table()
|
||||
|
||||
# final member_fields list (used for column specs order): table order + computed inserted
|
||||
# Separate groups from other fields (groups is handled as a special field, not a member field)
|
||||
groups_field = if "groups" in member_fields, do: ["groups"], else: []
|
||||
|
||||
# final member_fields list (used for column specs order): table order + computed inserted + groups
|
||||
ordered_member_fields =
|
||||
selectable_member_fields
|
||||
|> insert_computed_fields_like_table(computed_fields)
|
||||
|> then(fn fields -> fields ++ groups_field end)
|
||||
|
||||
%{
|
||||
selected_ids: filter_valid_uuids(extract_list(params, "selected_ids")),
|
||||
|
|
|
|||
|
|
@ -132,12 +132,15 @@ defmodule Mv.Membership.MemberExport.Build do
|
|||
parsed.computed_fields != [] or
|
||||
"membership_fee_status" in parsed.member_fields
|
||||
|
||||
need_groups = "groups" in parsed.member_fields
|
||||
|
||||
query =
|
||||
Member
|
||||
|> Ash.Query.new()
|
||||
|> Ash.Query.select(select_fields)
|
||||
|> load_custom_field_values_query(custom_field_ids_union)
|
||||
|> maybe_load_cycles(need_cycles, parsed.show_current_cycle)
|
||||
|> maybe_load_groups(need_groups)
|
||||
|
||||
query =
|
||||
if parsed.selected_ids != [] do
|
||||
|
|
@ -241,16 +244,22 @@ defmodule Mv.Membership.MemberExport.Build do
|
|||
defp maybe_sort(query, _field, nil), do: {query, false}
|
||||
|
||||
defp maybe_sort(query, field, order) when is_binary(field) do
|
||||
if custom_field_sort?(field) do
|
||||
{query, true}
|
||||
else
|
||||
field_atom = String.to_existing_atom(field)
|
||||
cond do
|
||||
field == "groups" ->
|
||||
# Groups sort → in-memory nach dem Read (wie Tabelle)
|
||||
{query, true}
|
||||
|
||||
if field_atom in (Mv.Constants.member_fields() -- [:notes]) do
|
||||
{Ash.Query.sort(query, [{field_atom, String.to_existing_atom(order)}]), false}
|
||||
else
|
||||
{query, false}
|
||||
end
|
||||
custom_field_sort?(field) ->
|
||||
{query, true}
|
||||
|
||||
true ->
|
||||
field_atom = String.to_existing_atom(field)
|
||||
|
||||
if field_atom in (Mv.Constants.member_fields() -- [:notes]) do
|
||||
{Ash.Query.sort(query, [{field_atom, String.to_existing_atom(order)}]), false}
|
||||
else
|
||||
{query, false}
|
||||
end
|
||||
end
|
||||
rescue
|
||||
ArgumentError -> {query, false}
|
||||
|
|
@ -260,11 +269,25 @@ defmodule Mv.Membership.MemberExport.Build do
|
|||
do: []
|
||||
|
||||
defp sort_members_by_custom_field(members, field, order, custom_fields) when is_binary(field) do
|
||||
if field == "groups" do
|
||||
sort_members_by_groups_export(members, order)
|
||||
else
|
||||
sort_by_custom_field_value(members, field, order, custom_fields)
|
||||
end
|
||||
end
|
||||
|
||||
defp sort_by_custom_field_value(members, field, order, custom_fields) do
|
||||
id_str = String.trim_leading(field, @custom_field_prefix)
|
||||
custom_field = Enum.find(custom_fields, fn cf -> to_string(cf.id) == id_str end)
|
||||
|
||||
if is_nil(custom_field), do: members
|
||||
if is_nil(custom_field) do
|
||||
members
|
||||
else
|
||||
sort_members_with_custom_field(members, custom_field, order)
|
||||
end
|
||||
end
|
||||
|
||||
defp sort_members_with_custom_field(members, custom_field, order) do
|
||||
key_fn = fn member ->
|
||||
cfv = find_cfv(member, custom_field)
|
||||
raw = if cfv, do: cfv.value, else: nil
|
||||
|
|
@ -277,6 +300,26 @@ defmodule Mv.Membership.MemberExport.Build do
|
|||
|> Enum.map(fn {m, _} -> m end)
|
||||
end
|
||||
|
||||
defp sort_members_by_groups_export(members, order) do
|
||||
# Members with groups first, then by first group name alphabetically (min = first by sort order)
|
||||
# Match table behavior from MvWeb.MemberLive.Index.sort_members_by_groups/2
|
||||
first_group_name = fn member ->
|
||||
(member.groups || [])
|
||||
|> Enum.map(& &1.name)
|
||||
|> Enum.min(fn -> nil end)
|
||||
end
|
||||
|
||||
members
|
||||
|> Enum.sort_by(fn member ->
|
||||
name = first_group_name.(member)
|
||||
# Nil (no groups) sorts last in asc, first in desc
|
||||
{name == nil, name || ""}
|
||||
end)
|
||||
|> then(fn list ->
|
||||
if order == "desc", do: Enum.reverse(list), else: list
|
||||
end)
|
||||
end
|
||||
|
||||
defp find_cfv(member, custom_field) do
|
||||
(member.custom_field_values || [])
|
||||
|> Enum.find(fn cfv ->
|
||||
|
|
@ -294,6 +337,13 @@ defmodule Mv.Membership.MemberExport.Build do
|
|||
MembershipFeeStatus.load_cycles_for_members(query, show_current)
|
||||
end
|
||||
|
||||
defp maybe_load_groups(query, false), do: query
|
||||
|
||||
defp maybe_load_groups(query, true) do
|
||||
# Load groups with id and name only (for export formatting)
|
||||
Ash.Query.load(query, groups: [:id, :name])
|
||||
end
|
||||
|
||||
defp apply_cycle_status_filter(members, nil, _show_current), do: members
|
||||
|
||||
defp apply_cycle_status_filter(members, status, show_current) when status in [:paid, :unpaid] do
|
||||
|
|
@ -343,6 +393,19 @@ defmodule Mv.Membership.MemberExport.Build do
|
|||
}
|
||||
end)
|
||||
|
||||
groups_col =
|
||||
if "groups" in parsed.member_fields do
|
||||
[
|
||||
%{
|
||||
key: :groups,
|
||||
kind: :groups,
|
||||
label: label_fn.(:groups)
|
||||
}
|
||||
]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
custom_cols =
|
||||
parsed.custom_field_ids
|
||||
|> Enum.map(fn id ->
|
||||
|
|
@ -361,7 +424,7 @@ defmodule Mv.Membership.MemberExport.Build do
|
|||
end)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|
||||
member_cols ++ computed_cols ++ custom_cols
|
||||
member_cols ++ computed_cols ++ groups_col ++ custom_cols
|
||||
end
|
||||
|
||||
defp build_rows(members, columns, custom_fields_by_id) do
|
||||
|
|
@ -391,6 +454,11 @@ defmodule Mv.Membership.MemberExport.Build do
|
|||
if is_binary(value), do: value, else: ""
|
||||
end
|
||||
|
||||
defp cell_value(member, %{kind: :groups, key: :groups}, _custom_fields_by_id) do
|
||||
groups = Map.get(member, :groups) || []
|
||||
format_groups(groups)
|
||||
end
|
||||
|
||||
defp key_to_atom(k) when is_atom(k), do: k
|
||||
|
||||
defp key_to_atom(k) when is_binary(k) do
|
||||
|
|
@ -424,6 +492,15 @@ defmodule Mv.Membership.MemberExport.Build do
|
|||
defp format_member_value(%NaiveDateTime{} = dt), do: NaiveDateTime.to_iso8601(dt)
|
||||
defp format_member_value(value), do: to_string(value)
|
||||
|
||||
defp format_groups([]), do: ""
|
||||
|
||||
defp format_groups(groups) when is_list(groups) do
|
||||
groups
|
||||
|> Enum.map(fn group -> Map.get(group, :name) || "" end)
|
||||
|> Enum.reject(&(&1 == ""))
|
||||
|> Enum.join(", ")
|
||||
end
|
||||
|
||||
defp build_meta(members) do
|
||||
%{
|
||||
generated_at: DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@ defmodule Mv.Membership.MembersCSV do
|
|||
if is_binary(value), do: value, else: ""
|
||||
end
|
||||
|
||||
defp cell_value(member, %{kind: :groups, key: :groups}) do
|
||||
groups = Map.get(member, :groups) || []
|
||||
format_groups(groups)
|
||||
end
|
||||
|
||||
defp key_to_atom(k) when is_atom(k), do: k
|
||||
|
||||
defp key_to_atom(k) when is_binary(k) do
|
||||
|
|
@ -97,4 +102,13 @@ defmodule Mv.Membership.MembersCSV do
|
|||
defp format_member_value(%DateTime{} = dt), do: DateTime.to_iso8601(dt)
|
||||
defp format_member_value(%NaiveDateTime{} = dt), do: NaiveDateTime.to_iso8601(dt)
|
||||
defp format_member_value(value), do: to_string(value)
|
||||
|
||||
defp format_groups([]), do: ""
|
||||
|
||||
defp format_groups(groups) when is_list(groups) do
|
||||
groups
|
||||
|> Enum.map(fn group -> Map.get(group, :name) || "" end)
|
||||
|> Enum.reject(&(&1 == ""))
|
||||
|> Enum.join(", ")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ defmodule MvWeb.MemberExportController do
|
|||
alias MvWeb.MemberLive.Index.MembershipFeeStatus
|
||||
use Gettext, backend: MvWeb.Gettext
|
||||
|
||||
@member_fields_allowlist Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)
|
||||
@member_fields_allowlist (Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)) ++
|
||||
["groups"]
|
||||
@computed_export_fields ["membership_fee_status"]
|
||||
@custom_field_prefix Mv.Constants.custom_field_prefix()
|
||||
|
||||
|
|
@ -83,6 +84,7 @@ defmodule MvWeb.MemberExportController do
|
|||
domain_fields = Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)
|
||||
selectable = Enum.filter(member_fields, fn f -> f in domain_fields end)
|
||||
computed = Enum.filter(member_fields, fn f -> f in @computed_export_fields end)
|
||||
# "groups" is neither a domain field nor a computed field, it's handled separately
|
||||
{selectable, computed}
|
||||
end
|
||||
|
||||
|
|
@ -235,12 +237,15 @@ defmodule MvWeb.MemberExportController do
|
|||
need_cycles =
|
||||
parsed.computed_fields != [] and "membership_fee_status" in parsed.computed_fields
|
||||
|
||||
need_groups = "groups" in parsed.member_fields
|
||||
|
||||
query =
|
||||
Member
|
||||
|> Ash.Query.new()
|
||||
|> Ash.Query.select(select_fields)
|
||||
|> load_custom_field_values_query(parsed.custom_field_ids)
|
||||
|> maybe_load_cycles(need_cycles, parsed.show_current_cycle)
|
||||
|> maybe_load_groups(need_groups)
|
||||
|
||||
query =
|
||||
if parsed.selected_ids != [] do
|
||||
|
|
@ -284,6 +289,13 @@ defmodule MvWeb.MemberExportController do
|
|||
MembershipFeeStatus.load_cycles_for_members(query, show_current)
|
||||
end
|
||||
|
||||
defp maybe_load_groups(query, false), do: query
|
||||
|
||||
defp maybe_load_groups(query, true) do
|
||||
# Load groups with id and name only (for export formatting)
|
||||
Ash.Query.load(query, groups: [:id, :name])
|
||||
end
|
||||
|
||||
# Adds computed field values to members (e.g. membership_fee_status)
|
||||
defp add_computed_fields(members, computed_fields, show_current_cycle) do
|
||||
if "membership_fee_status" in computed_fields do
|
||||
|
|
@ -329,17 +341,23 @@ defmodule MvWeb.MemberExportController do
|
|||
defp maybe_sort_export(query, _field, nil), do: {query, false}
|
||||
|
||||
defp maybe_sort_export(query, field, order) when is_binary(field) do
|
||||
if custom_field_sort?(field) do
|
||||
# Custom field sort → in-memory nach dem Read (wie Tabelle)
|
||||
{query, true}
|
||||
else
|
||||
field_atom = String.to_existing_atom(field)
|
||||
cond do
|
||||
field == "groups" ->
|
||||
# Groups sort → in-memory nach dem Read (wie Tabelle)
|
||||
{query, true}
|
||||
|
||||
if field_atom in (Mv.Constants.member_fields() -- [:notes]) do
|
||||
{Ash.Query.sort(query, [{field_atom, String.to_existing_atom(order)}]), false}
|
||||
else
|
||||
{query, false}
|
||||
end
|
||||
custom_field_sort?(field) ->
|
||||
# Custom field sort → in-memory nach dem Read (wie Tabelle)
|
||||
{query, true}
|
||||
|
||||
true ->
|
||||
field_atom = String.to_existing_atom(field)
|
||||
|
||||
if field_atom in (Mv.Constants.member_fields() -- [:notes]) do
|
||||
{Ash.Query.sort(query, [{field_atom, String.to_existing_atom(order)}]), false}
|
||||
else
|
||||
{query, false}
|
||||
end
|
||||
end
|
||||
rescue
|
||||
ArgumentError -> {query, false}
|
||||
|
|
@ -358,6 +376,15 @@ defmodule MvWeb.MemberExportController do
|
|||
defp sort_members_by_custom_field_export(members, field, order, custom_fields)
|
||||
when is_binary(field) do
|
||||
order = order || "asc"
|
||||
|
||||
if field == "groups" do
|
||||
sort_members_by_groups_export(members, order)
|
||||
else
|
||||
sort_by_custom_field_value(members, field, order, custom_fields)
|
||||
end
|
||||
end
|
||||
|
||||
defp sort_by_custom_field_value(members, field, order, custom_fields) do
|
||||
id_str = String.trim_leading(field, @custom_field_prefix)
|
||||
|
||||
custom_field =
|
||||
|
|
@ -387,6 +414,26 @@ defmodule MvWeb.MemberExportController do
|
|||
end
|
||||
end
|
||||
|
||||
defp sort_members_by_groups_export(members, order) do
|
||||
# Members with groups first, then by first group name alphabetically (min = first by sort order)
|
||||
# Match table behavior from MvWeb.MemberLive.Index.sort_members_by_groups/2
|
||||
first_group_name = fn member ->
|
||||
(member.groups || [])
|
||||
|> Enum.map(& &1.name)
|
||||
|> Enum.min(fn -> nil end)
|
||||
end
|
||||
|
||||
members
|
||||
|> Enum.sort_by(fn member ->
|
||||
name = first_group_name.(member)
|
||||
# Nil (no groups) sorts last in asc, first in desc
|
||||
{name == nil, name || ""}
|
||||
end)
|
||||
|> then(fn list ->
|
||||
if order == "desc", do: Enum.reverse(list), else: list
|
||||
end)
|
||||
end
|
||||
|
||||
defp has_non_empty_custom_field_value?(member, custom_field) do
|
||||
case find_cfv(member, custom_field) do
|
||||
nil ->
|
||||
|
|
@ -441,6 +488,19 @@ defmodule MvWeb.MemberExportController do
|
|||
}
|
||||
end)
|
||||
|
||||
groups_col =
|
||||
if "groups" in parsed.member_fields do
|
||||
[
|
||||
%{
|
||||
header: groups_field_header(conn),
|
||||
kind: :groups,
|
||||
key: :groups
|
||||
}
|
||||
]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
custom_cols =
|
||||
parsed.custom_field_ids
|
||||
|> Enum.map(fn id ->
|
||||
|
|
@ -459,7 +519,7 @@ defmodule MvWeb.MemberExportController do
|
|||
end)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|
||||
member_cols ++ computed_cols ++ custom_cols
|
||||
member_cols ++ computed_cols ++ groups_col ++ custom_cols
|
||||
end
|
||||
|
||||
# --- headers: use MemberFields.label for translations ---
|
||||
|
|
@ -499,6 +559,10 @@ defmodule MvWeb.MemberExportController do
|
|||
cf.name
|
||||
end
|
||||
|
||||
defp groups_field_header(_conn) do
|
||||
MemberFields.label(:groups)
|
||||
end
|
||||
|
||||
defp humanize_field(str) do
|
||||
str
|
||||
|> String.replace("_", " ")
|
||||
|
|
|
|||
|
|
@ -682,6 +682,19 @@ defmodule MvWeb.MemberLive.Index do
|
|||
|> update_selection_assigns()
|
||||
end
|
||||
|
||||
# Update sort components after rendering
|
||||
socket =
|
||||
if socket.assigns[:sort_needs_update] do
|
||||
old_field = socket.assigns[:previous_sort_field] || socket.assigns.sort_field
|
||||
|
||||
socket
|
||||
|> update_sort_components(old_field, socket.assigns.sort_field, socket.assigns.sort_order)
|
||||
|> assign(:sort_needs_update, false)
|
||||
|> assign(:previous_sort_field, nil)
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
|
|
@ -940,9 +953,10 @@ defmodule MvWeb.MemberLive.Index do
|
|||
)
|
||||
|
||||
# Sort in memory if needed (custom fields, groups, group_count; computed fields are blocked)
|
||||
# Note: :groups is in computed_member_fields() but can be sorted in-memory, so we only block :membership_fee_status
|
||||
members =
|
||||
if sort_after_load and
|
||||
socket.assigns.sort_field not in FieldVisibility.computed_member_fields() do
|
||||
socket.assigns.sort_field != :membership_fee_status do
|
||||
sort_members_in_memory(
|
||||
members,
|
||||
socket.assigns.sort_field,
|
||||
|
|
@ -1044,21 +1058,15 @@ defmodule MvWeb.MemberLive.Index do
|
|||
defp maybe_sort(query, _field, nil, _custom_fields), do: {query, false}
|
||||
|
||||
defp maybe_sort(query, field, order, _custom_fields) do
|
||||
if computed_field?(field) do
|
||||
# :groups is in computed_member_fields() but can be sorted in-memory
|
||||
# Only :membership_fee_status should be blocked from sorting
|
||||
if field == :membership_fee_status or field == "membership_fee_status" do
|
||||
{query, false}
|
||||
else
|
||||
apply_sort_to_query(query, field, order)
|
||||
end
|
||||
end
|
||||
|
||||
defp computed_field?(field) do
|
||||
computed_atoms = FieldVisibility.computed_member_fields()
|
||||
computed_strings = Enum.map(computed_atoms, &Atom.to_string/1)
|
||||
|
||||
(is_atom(field) and field in computed_atoms) or
|
||||
(is_binary(field) and field in computed_strings)
|
||||
end
|
||||
|
||||
defp apply_sort_to_query(query, field, order) do
|
||||
cond do
|
||||
# Groups sort -> after load (in memory)
|
||||
|
|
@ -1086,13 +1094,19 @@ defmodule MvWeb.MemberLive.Index do
|
|||
end
|
||||
|
||||
defp valid_sort_field?(field) when is_atom(field) do
|
||||
if field in FieldVisibility.computed_member_fields(),
|
||||
do: false,
|
||||
else: valid_sort_field_db_or_custom?(field)
|
||||
# :groups is in computed_member_fields() but can be sorted
|
||||
# Only :membership_fee_status should be blocked
|
||||
if field == :membership_fee_status do
|
||||
false
|
||||
else
|
||||
valid_sort_field_db_or_custom?(field)
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_sort_field?(field) when is_binary(field) do
|
||||
if field in Enum.map(FieldVisibility.computed_member_fields(), &Atom.to_string/1) do
|
||||
# "groups" is in computed_member_fields() but can be sorted
|
||||
# Only "membership_fee_status" should be blocked
|
||||
if field == "membership_fee_status" do
|
||||
false
|
||||
else
|
||||
valid_sort_field_db_or_custom?(field)
|
||||
|
|
@ -1249,10 +1263,13 @@ defmodule MvWeb.MemberLive.Index do
|
|||
defp maybe_update_sort(socket, %{"sort_field" => sf, "sort_order" => so}) do
|
||||
field = determine_field(socket.assigns.sort_field, sf)
|
||||
order = determine_order(socket.assigns.sort_order, so)
|
||||
old_field = socket.assigns.sort_field
|
||||
|
||||
socket
|
||||
|> assign(:sort_field, field)
|
||||
|> assign(:sort_order, order)
|
||||
|> assign(:sort_needs_update, old_field != field or socket.assigns.sort_order != order)
|
||||
|> assign(:previous_sort_field, old_field)
|
||||
end
|
||||
|
||||
defp maybe_update_sort(socket, _), do: socket
|
||||
|
|
@ -1261,17 +1278,27 @@ defmodule MvWeb.MemberLive.Index do
|
|||
defp determine_field(default, nil), do: default
|
||||
|
||||
defp determine_field(default, sf) when is_binary(sf) do
|
||||
computed_strings = Enum.map(FieldVisibility.computed_member_fields(), &Atom.to_string/1)
|
||||
# Handle "groups" specially - it's in computed_member_fields() but can be sorted
|
||||
if sf == "groups" do
|
||||
:groups
|
||||
else
|
||||
computed_strings = Enum.map(FieldVisibility.computed_member_fields(), &Atom.to_string/1)
|
||||
|
||||
if sf in computed_strings,
|
||||
do: default,
|
||||
else: determine_field_after_computed_check(default, sf)
|
||||
if sf in computed_strings,
|
||||
do: default,
|
||||
else: determine_field_after_computed_check(default, sf)
|
||||
end
|
||||
end
|
||||
|
||||
defp determine_field(default, sf) when is_atom(sf) do
|
||||
if sf in FieldVisibility.computed_member_fields(),
|
||||
do: default,
|
||||
else: determine_field_after_computed_check(default, sf)
|
||||
# Handle :groups specially - it's in computed_member_fields() but can be sorted
|
||||
if sf == :groups do
|
||||
:groups
|
||||
else
|
||||
if sf in FieldVisibility.computed_member_fields(),
|
||||
do: default,
|
||||
else: determine_field_after_computed_check(default, sf)
|
||||
end
|
||||
end
|
||||
|
||||
defp determine_field(default, _), do: default
|
||||
|
|
@ -1620,6 +1647,14 @@ defmodule MvWeb.MemberLive.Index do
|
|||
FieldVisibility.computed_member_fields()
|
||||
|> Enum.filter(&(&1 in member_fields_computed))
|
||||
|
||||
# Include groups in export only if it's visible in the table
|
||||
member_fields_with_groups =
|
||||
if :groups in socket.assigns[:member_fields_visible] do
|
||||
ordered_member_fields_db ++ ["groups"]
|
||||
else
|
||||
ordered_member_fields_db
|
||||
end
|
||||
|
||||
# Order custom fields like the table (same as dynamic_cols / all_custom_fields order)
|
||||
ordered_custom_field_ids =
|
||||
socket.assigns.all_custom_fields
|
||||
|
|
@ -1628,7 +1663,11 @@ defmodule MvWeb.MemberLive.Index do
|
|||
|
||||
%{
|
||||
selected_ids: socket.assigns.selected_members |> MapSet.to_list(),
|
||||
member_fields: Enum.map(ordered_member_fields_db, &Atom.to_string/1),
|
||||
member_fields:
|
||||
Enum.map(member_fields_with_groups, fn
|
||||
f when is_atom(f) -> Atom.to_string(f)
|
||||
f when is_binary(f) -> f
|
||||
end),
|
||||
computed_fields: Enum.map(ordered_computed_fields, &Atom.to_string/1),
|
||||
custom_field_ids: ordered_custom_field_ids,
|
||||
column_order:
|
||||
|
|
|
|||
|
|
@ -331,6 +331,7 @@
|
|||
</:col>
|
||||
<:col
|
||||
:let={member}
|
||||
:if={:groups in @member_fields_visible}
|
||||
label={
|
||||
~H"""
|
||||
<.live_component
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ defmodule MvWeb.MemberLive.Index.FieldVisibility do
|
|||
alias Mv.Membership.Helpers.VisibilityConfig
|
||||
|
||||
# Single UI key for "Membership Fee Status"; only this appears in the dropdown.
|
||||
@pseudo_member_fields [:membership_fee_status]
|
||||
# Groups is also a pseudo field (not a DB attribute, but displayed in the table).
|
||||
@pseudo_member_fields [:membership_fee_status, :groups]
|
||||
|
||||
# Export/API may accept this as alias; must not appear in the UI options list.
|
||||
@export_only_alias :payment_status
|
||||
|
|
@ -201,7 +202,7 @@ defmodule MvWeb.MemberLive.Index.FieldVisibility do
|
|||
"""
|
||||
@spec get_visible_member_fields_computed(%{String.t() => boolean()}) :: [atom()]
|
||||
def get_visible_member_fields_computed(field_selection) when is_map(field_selection) do
|
||||
computed_set = MapSet.new(@pseudo_member_fields)
|
||||
computed_set = MapSet.new([:membership_fee_status])
|
||||
|
||||
field_selection
|
||||
|> Enum.filter(fn {field_string, visible} ->
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ defmodule MvWeb.Translations.MemberFields do
|
|||
def label(:postal_code), do: gettext("Postal Code")
|
||||
def label(:membership_fee_start_date), do: gettext("Membership Fee Start Date")
|
||||
def label(:membership_fee_status), do: gettext("Membership Fee Status")
|
||||
def label(:groups), do: gettext("Groups")
|
||||
|
||||
# Fallback for unknown fields
|
||||
def label(field) do
|
||||
|
|
|
|||
|
|
@ -2218,6 +2218,7 @@ msgstr "Gruppe erfolgreich gespeichert."
|
|||
#: lib/mv_web/live/group_live/index.ex
|
||||
#: lib/mv_web/live/member_live/index.html.heex
|
||||
#: lib/mv_web/live/member_live/show.ex
|
||||
#: lib/mv_web/translations/member_fields.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Groups"
|
||||
msgstr "Gruppen"
|
||||
|
|
|
|||
|
|
@ -2219,6 +2219,7 @@ msgstr ""
|
|||
#: lib/mv_web/live/group_live/index.ex
|
||||
#: lib/mv_web/live/member_live/index.html.heex
|
||||
#: lib/mv_web/live/member_live/show.ex
|
||||
#: lib/mv_web/translations/member_fields.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Groups"
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -2219,6 +2219,7 @@ msgstr ""
|
|||
#: lib/mv_web/live/group_live/index.ex
|
||||
#: lib/mv_web/live/member_live/index.html.heex
|
||||
#: lib/mv_web/live/member_live/show.ex
|
||||
#: lib/mv_web/translations/member_fields.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Groups"
|
||||
msgstr ""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue