feat(members): show and sort by Fee Type in member overview
Load membership_fee_type when column visible; sort by membership_fee_type_id; add table column with SortHeader and fee type name.
This commit is contained in:
parent
b7ef69813b
commit
68ceaced0c
2 changed files with 89 additions and 22 deletions
|
|
@ -913,6 +913,14 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
query =
|
query =
|
||||||
Ash.Query.load(query, groups: [:id, :name, :slug])
|
Ash.Query.load(query, groups: [:id, :name, :slug])
|
||||||
|
|
||||||
|
# Load membership_fee_type when the column is visible
|
||||||
|
query =
|
||||||
|
if :membership_fee_type in socket.assigns.member_fields_visible do
|
||||||
|
Ash.Query.load(query, membership_fee_type: [:id, :name])
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
|
||||||
query = apply_search_filter(query, search_query)
|
query = apply_search_filter(query, search_query)
|
||||||
|
|
||||||
query = apply_group_filters(query, socket.assigns[:group_filters], socket.assigns[:groups])
|
query = apply_group_filters(query, socket.assigns[:group_filters], socket.assigns[:groups])
|
||||||
|
|
@ -1073,6 +1081,10 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
field in [:groups, "groups"] ->
|
field in [:groups, "groups"] ->
|
||||||
{query, true}
|
{query, true}
|
||||||
|
|
||||||
|
# Membership fee type sort -> by FK at DB
|
||||||
|
field in [:membership_fee_type, "membership_fee_type"] ->
|
||||||
|
{Ash.Query.sort(query, membership_fee_type_id: order), false}
|
||||||
|
|
||||||
# Custom field sort -> after load
|
# Custom field sort -> after load
|
||||||
custom_field_sort?(field) ->
|
custom_field_sort?(field) ->
|
||||||
{query, true}
|
{query, true}
|
||||||
|
|
@ -1118,11 +1130,16 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
defp valid_sort_field_db_or_custom?(field) when is_atom(field) do
|
defp valid_sort_field_db_or_custom?(field) when is_atom(field) do
|
||||||
non_sortable_fields = [:notes]
|
non_sortable_fields = [:notes]
|
||||||
valid_fields = Mv.Constants.member_fields() -- non_sortable_fields
|
valid_fields = Mv.Constants.member_fields() -- non_sortable_fields
|
||||||
field in valid_fields or custom_field_sort?(field) or field == :groups
|
field in valid_fields or custom_field_sort?(field) or field in [:groups, :membership_fee_type]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp valid_sort_field_db_or_custom?(field) when is_binary(field) do
|
defp valid_sort_field_db_or_custom?(field) when is_binary(field) do
|
||||||
normalized = if field == "groups", do: :groups, else: safe_member_field_atom_only(field)
|
normalized =
|
||||||
|
cond do
|
||||||
|
field == "groups" -> :groups
|
||||||
|
field == "membership_fee_type" -> :membership_fee_type
|
||||||
|
true -> safe_member_field_atom_only(field)
|
||||||
|
end
|
||||||
|
|
||||||
(normalized != nil and valid_sort_field_db_or_custom?(normalized)) or
|
(normalized != nil and valid_sort_field_db_or_custom?(normalized)) or
|
||||||
custom_field_sort?(field)
|
custom_field_sort?(field)
|
||||||
|
|
@ -1647,13 +1664,11 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
FieldVisibility.computed_member_fields()
|
FieldVisibility.computed_member_fields()
|
||||||
|> Enum.filter(&(&1 in member_fields_computed))
|
|> Enum.filter(&(&1 in member_fields_computed))
|
||||||
|
|
||||||
# Include groups in export only if it's visible in the table
|
|
||||||
member_fields_with_groups =
|
member_fields_with_groups =
|
||||||
if :groups in socket.assigns[:member_fields_visible] do
|
build_export_member_fields_list(
|
||||||
ordered_member_fields_db ++ ["groups"]
|
ordered_member_fields_db,
|
||||||
else
|
socket.assigns[:member_fields_visible]
|
||||||
ordered_member_fields_db
|
)
|
||||||
end
|
|
||||||
|
|
||||||
# Order custom fields like the table (same as dynamic_cols / all_custom_fields order)
|
# Order custom fields like the table (same as dynamic_cols / all_custom_fields order)
|
||||||
ordered_custom_field_ids =
|
ordered_custom_field_ids =
|
||||||
|
|
@ -1674,7 +1689,9 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
export_column_order(
|
export_column_order(
|
||||||
ordered_member_fields_db,
|
ordered_member_fields_db,
|
||||||
ordered_computed_fields,
|
ordered_computed_fields,
|
||||||
ordered_custom_field_ids
|
ordered_custom_field_ids,
|
||||||
|
:membership_fee_type in socket.assigns[:member_fields_visible],
|
||||||
|
:groups in socket.assigns[:member_fields_visible]
|
||||||
),
|
),
|
||||||
query: socket.assigns[:query] || nil,
|
query: socket.assigns[:query] || nil,
|
||||||
sort_field: export_sort_field(socket.assigns[:sort_field]),
|
sort_field: export_sort_field(socket.assigns[:sort_field]),
|
||||||
|
|
@ -1685,6 +1702,32 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp expand_db_string_for_export(f, membership_fee_type_visible, computed_strings) do
|
||||||
|
if f == "membership_fee_start_date" do
|
||||||
|
extra =
|
||||||
|
if(membership_fee_type_visible, do: ["membership_fee_type"], else: []) ++
|
||||||
|
if "membership_fee_status" in computed_strings, do: ["membership_fee_status"], else: []
|
||||||
|
|
||||||
|
[f] ++ extra
|
||||||
|
else
|
||||||
|
[f]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_export_member_fields_list(ordered_db, member_fields_visible) do
|
||||||
|
with_extras =
|
||||||
|
Enum.flat_map(ordered_db, fn f ->
|
||||||
|
if f == :membership_fee_start_date and
|
||||||
|
:membership_fee_type in (member_fields_visible || []) do
|
||||||
|
[f, :membership_fee_type]
|
||||||
|
else
|
||||||
|
[f]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if :groups in (member_fields_visible || []), do: with_extras ++ [:groups], else: with_extras
|
||||||
|
end
|
||||||
|
|
||||||
defp export_cycle_status_filter(nil), do: nil
|
defp export_cycle_status_filter(nil), do: nil
|
||||||
defp export_cycle_status_filter(:paid), do: "paid"
|
defp export_cycle_status_filter(:paid), do: "paid"
|
||||||
defp export_cycle_status_filter(:unpaid), do: "unpaid"
|
defp export_cycle_status_filter(:unpaid), do: "unpaid"
|
||||||
|
|
@ -1700,31 +1743,33 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
defp export_sort_order(o) when is_binary(o), do: o
|
defp export_sort_order(o) when is_binary(o), do: o
|
||||||
# Build a single ordered list that matches the table order:
|
# 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)
|
# - 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)
|
# - membership_fee_type and membership_fee_status inserted after membership_fee_start_date when visible
|
||||||
|
# - groups appended before custom fields when visible
|
||||||
# - custom fields appended in the same order as table (already ordered_custom_field_ids)
|
# - custom fields appended in the same order as table (already ordered_custom_field_ids)
|
||||||
defp export_column_order(
|
defp export_column_order(
|
||||||
ordered_member_fields_db,
|
ordered_member_fields_db,
|
||||||
ordered_computed_fields,
|
ordered_computed_fields,
|
||||||
ordered_custom_field_ids
|
ordered_custom_field_ids,
|
||||||
|
membership_fee_type_visible,
|
||||||
|
groups_visible
|
||||||
) do
|
) do
|
||||||
db_strings = Enum.map(ordered_member_fields_db, &Atom.to_string/1)
|
db_strings = Enum.map(ordered_member_fields_db, &Atom.to_string/1)
|
||||||
computed_strings = Enum.map(ordered_computed_fields, &Atom.to_string/1)
|
computed_strings = Enum.map(ordered_computed_fields, &Atom.to_string/1)
|
||||||
|
|
||||||
# Place membership_fee_status right after membership_fee_start_date if present in export
|
# Place membership_fee_type and membership_fee_status after membership_fee_start_date when present
|
||||||
db_with_computed =
|
db_with_extras =
|
||||||
Enum.flat_map(db_strings, fn f ->
|
Enum.flat_map(
|
||||||
if f == "membership_fee_start_date" and "membership_fee_status" in computed_strings do
|
db_strings,
|
||||||
[f, "membership_fee_status"]
|
&expand_db_string_for_export(&1, membership_fee_type_visible, computed_strings)
|
||||||
else
|
)
|
||||||
[f]
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
# Any remaining computed fields not inserted above (future-proof)
|
# Any remaining computed fields not inserted above (future-proof)
|
||||||
remaining_computed =
|
remaining_computed =
|
||||||
computed_strings
|
computed_strings
|
||||||
|> Enum.reject(&(&1 in db_with_computed))
|
|> Enum.reject(&(&1 in db_with_extras))
|
||||||
|
|
||||||
db_with_computed ++ remaining_computed ++ ordered_custom_field_ids
|
result = db_with_extras ++ remaining_computed
|
||||||
|
result = if groups_visible, do: result ++ ["groups"], else: result
|
||||||
|
result ++ ordered_custom_field_ids
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,28 @@
|
||||||
>
|
>
|
||||||
{MvWeb.MemberLive.Index.format_date(member.membership_fee_start_date)}
|
{MvWeb.MemberLive.Index.format_date(member.membership_fee_start_date)}
|
||||||
</:col>
|
</:col>
|
||||||
|
<:col
|
||||||
|
:let={member}
|
||||||
|
:if={:membership_fee_type in @member_fields_visible}
|
||||||
|
label={
|
||||||
|
~H"""
|
||||||
|
<.live_component
|
||||||
|
module={MvWeb.Components.SortHeaderComponent}
|
||||||
|
id={:sort_membership_fee_type}
|
||||||
|
field={:membership_fee_type}
|
||||||
|
label={gettext("Fee Type")}
|
||||||
|
sort_field={@sort_field}
|
||||||
|
sort_order={@sort_order}
|
||||||
|
/>
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<%= if member.membership_fee_type do %>
|
||||||
|
{member.membership_fee_type.name}
|
||||||
|
<% else %>
|
||||||
|
<span class="text-base-content/50">—</span>
|
||||||
|
<% end %>
|
||||||
|
</:col>
|
||||||
<:col
|
<:col
|
||||||
:let={member}
|
:let={member}
|
||||||
:if={:membership_fee_status in @member_fields_visible}
|
:if={:membership_fee_status in @member_fields_visible}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue