Implements exporting groups closes #428 #435

Merged
carla merged 6 commits from feature/428_export_groups into main 2026-02-23 16:25:42 +01:00
6 changed files with 66 additions and 22 deletions
Showing only changes of commit dbdac5870a - Show all commits

View file

@ -682,6 +682,18 @@ 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 +952,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,7 +1057,9 @@ 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)
@ -1086,13 +1101,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 +1270,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,18 +1285,28 @@ defmodule MvWeb.MemberLive.Index do
defp determine_field(default, nil), do: default
defp determine_field(default, sf) when is_binary(sf) do
# 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)
end
end
defp determine_field(default, sf) when is_atom(sf) do
# 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,9 +1654,13 @@ defmodule MvWeb.MemberLive.Index do
FieldVisibility.computed_member_fields()
|> Enum.filter(&(&1 in member_fields_computed))
# Groups is always included in export if it's visible in the table
# (groups column is always shown in the table, so we always include it in export)
member_fields_with_groups = ordered_member_fields_db ++ ["groups"]
# 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 =
@ -1632,7 +1670,8 @@ defmodule MvWeb.MemberLive.Index do
%{
selected_ids: socket.assigns.selected_members |> MapSet.to_list(),
member_fields: Enum.map(member_fields_with_groups, fn
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),

View file

@ -331,6 +331,7 @@
</:col>
<:col
:let={member}
:if={:groups in @member_fields_visible}
label={
~H"""
<.live_component

View file

@ -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} ->

View file

@ -2205,6 +2205,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"

View file

@ -2206,6 +2206,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 ""

View file

@ -2206,6 +2206,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 ""