feat: add membership fee status to columns and dropdown

This commit is contained in:
carla 2026-02-09 13:34:38 +01:00
parent 36e57b24be
commit e1266944b1
7 changed files with 725 additions and 514 deletions

View file

@ -100,7 +100,6 @@ defmodule MvWeb.MemberLive.Index do
all_available_fields =
all_custom_fields
|> FieldVisibility.get_all_available_fields()
|> dedupe_available_fields()
initial_selection =
FieldVisibility.merge_with_global_settings(
@ -124,6 +123,7 @@ defmodule MvWeb.MemberLive.Index do
|> assign(:boolean_custom_fields, boolean_custom_fields)
|> assign(:all_available_fields, all_available_fields)
|> assign(:user_field_selection, initial_selection)
|> assign(:fields_in_url?, false)
|> assign(
:member_fields_visible,
FieldVisibility.get_visible_member_fields(initial_selection)
@ -245,6 +245,7 @@ defmodule MvWeb.MemberLive.Index do
new_show_current,
socket.assigns.boolean_custom_field_filters
)
|> maybe_add_field_selection(socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false)
new_path = ~p"/members?#{query_params}"
@ -351,7 +352,7 @@ defmodule MvWeb.MemberLive.Index do
socket.assigns.show_current_cycle,
socket.assigns.boolean_custom_field_filters
)
|> maybe_add_field_selection(socket.assigns[:user_field_selection])
|> maybe_add_field_selection(socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false)
{:noreply, push_patch(socket, to: ~p"/members?#{query_params}", replace: true)}
end
@ -373,6 +374,7 @@ defmodule MvWeb.MemberLive.Index do
socket.assigns.show_current_cycle,
socket.assigns.boolean_custom_field_filters
)
|> maybe_add_field_selection(socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false)
new_path = ~p"/members?#{query_params}"
@ -396,6 +398,7 @@ defmodule MvWeb.MemberLive.Index do
socket.assigns.show_current_cycle,
socket.assigns.boolean_custom_field_filters
)
|> maybe_add_field_selection(socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false)
new_path = ~p"/members?#{query_params}"
{:noreply, push_patch(socket, to: new_path, replace: true)}
@ -425,6 +428,7 @@ defmodule MvWeb.MemberLive.Index do
socket.assigns.show_current_cycle,
updated_filters
)
|> maybe_add_field_selection(socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false)
new_path = ~p"/members?#{query_params}"
{:noreply, push_patch(socket, to: new_path, replace: true)}
@ -448,6 +452,7 @@ defmodule MvWeb.MemberLive.Index do
socket.assigns.show_current_cycle,
boolean_filters
)
|> maybe_add_field_selection(socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false)
new_path = ~p"/members?#{query_params}"
{:noreply, push_patch(socket, to: new_path, replace: true)}
@ -537,6 +542,12 @@ defmodule MvWeb.MemberLive.Index do
@impl true
def handle_params(params, _url, socket) do
prev_sig = build_signature(socket)
fields_in_url? =
case Map.get(params, "fields") do
v when is_binary(v) and v != "" -> true
_ -> false
end
url_selection = FieldSelection.parse_from_url(params)
merged_selection =
@ -572,6 +583,7 @@ defmodule MvWeb.MemberLive.Index do
|> maybe_update_cycle_status_filter(params)
|> maybe_update_boolean_filters(params)
|> maybe_update_show_current_cycle(params)
|> assign(:fields_in_url?, fields_in_url?)
|> assign(:query, params["query"])
|> assign(:user_field_selection, final_selection)
|> assign(:member_fields_visible, visible_member_fields)
@ -674,37 +686,18 @@ defmodule MvWeb.MemberLive.Index do
defp to_sort_id(field) when is_atom(field), do: :"sort_#{field}"
defp push_sort_url(socket, field, order) do
field_str =
if is_atom(field) do
Atom.to_string(field)
else
field
end
query_params =
build_query_params(
socket.assigns.query,
field_str,
Atom.to_string(order),
socket.assigns.cycle_status_filter,
socket.assigns.show_current_cycle,
socket.assigns.boolean_custom_field_filters
)
new_path = ~p"/members?#{query_params}"
{:noreply, push_patch(socket, to: new_path, replace: true)}
end
defp maybe_add_field_selection(params, nil), do: params
defp maybe_add_field_selection(params, selection) when is_map(selection) do
# Only keep `fields` in the URL when it was already present (bookmark/share),
# OR when we intentionally push it via push_field_selection_url/1.
defp maybe_add_field_selection(params, selection, true) when is_map(selection) do
fields_param = FieldSelection.to_url_param(selection)
if fields_param != "", do: Map.put(params, "fields", fields_param), else: params
cond do
fields_param == "" -> Map.delete(params, "fields")
true -> Map.put(params, "fields", fields_param)
end
end
defp maybe_add_field_selection(params, _), do: params
defp maybe_add_field_selection(params, _selection, _include?), do: params
defp push_field_selection_url(socket) do
query_params =
@ -716,7 +709,7 @@ defmodule MvWeb.MemberLive.Index do
socket.assigns.show_current_cycle,
socket.assigns.boolean_custom_field_filters
)
|> maybe_add_field_selection(socket.assigns[:user_field_selection])
|> maybe_add_field_selection(socket.assigns[:user_field_selection], true)
new_path = ~p"/members?#{query_params}"
push_patch(socket, to: new_path, replace: true)
@ -1398,6 +1391,12 @@ defmodule MvWeb.MemberLive.Index do
member_fields: Enum.map(ordered_member_fields_db, &Atom.to_string/1),
computed_fields: Enum.map(ordered_computed_fields, &Atom.to_string/1),
custom_field_ids: ordered_custom_field_ids,
column_order:
export_column_order(
ordered_member_fields_db,
ordered_computed_fields,
ordered_custom_field_ids
),
query: socket.assigns[:query] || nil,
sort_field: export_sort_field(socket.assigns[:sort_field]),
sort_order: export_sort_order(socket.assigns[:sort_order]),
@ -1420,25 +1419,33 @@ defmodule MvWeb.MemberLive.Index do
defp export_sort_order(:asc), do: "asc"
defp export_sort_order(:desc), do: "desc"
defp export_sort_order(o) when is_binary(o), do: o
# Build a single ordered list that matches the table order:
# - DB fields in Mv.Constants.member_fields() order (already pre-filtered as ordered_member_fields_db)
# - computed fields inserted at the correct position (membership_fee_status after membership_fee_start_date)
# - custom fields appended in the same order as table (already ordered_custom_field_ids)
defp export_column_order(
ordered_member_fields_db,
ordered_computed_fields,
ordered_custom_field_ids
) do
db_strings = Enum.map(ordered_member_fields_db, &Atom.to_string/1)
computed_strings = Enum.map(ordered_computed_fields, &Atom.to_string/1)
# -------------------------------------------------------------
# Internal utility: dedupe dropdown fields defensively
# -------------------------------------------------------------
# Place membership_fee_status right after membership_fee_start_date if present in export
db_with_computed =
Enum.flat_map(db_strings, fn f ->
if f == "membership_fee_start_date" and "membership_fee_status" in computed_strings do
[f, "membership_fee_status"]
else
[f]
end
end)
defp dedupe_available_fields(fields) when is_list(fields) do
Enum.uniq_by(fields, fn item ->
cond do
is_map(item) ->
Map.get(item, :key) || Map.get(item, :id) || Map.get(item, :field) || item
# Any remaining computed fields not inserted above (future-proof)
remaining_computed =
computed_strings
|> Enum.reject(&(&1 in db_with_computed))
is_tuple(item) and tuple_size(item) >= 1 ->
elem(item, 0)
true ->
item
end
end)
db_with_computed ++ remaining_computed ++ ordered_custom_field_ids
end
defp dedupe_available_fields(other), do: other
end