From 3b6c0b9fc95a8ddb1ddcea170ac43988af09bfe6 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 24 Feb 2026 09:15:04 +0100 Subject: [PATCH] fix: sort Fee Type by name in LiveView and exports Use Ash related-field sort (membership_fee_type.name) instead of membership_fee_type_id so column order is alphabetical. Load membership_fee_type when sorting by it even if column is hidden. In-memory re-sort (Build) uses loaded fee type name. Co-authored-by: Cursor --- lib/mv/membership/member_export/build.ex | 34 ++++++++++++------- .../controllers/member_export_controller.ex | 7 ++-- lib/mv_web/live/member_live/index.ex | 26 +++++++++++--- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/lib/mv/membership/member_export/build.ex b/lib/mv/membership/member_export/build.ex index 8a5aa60..9a1c03a 100644 --- a/lib/mv/membership/member_export/build.ex +++ b/lib/mv/membership/member_export/build.ex @@ -133,7 +133,10 @@ defmodule Mv.Membership.MemberExport.Build do "membership_fee_status" in parsed.member_fields need_groups = "groups" in parsed.member_fields - need_membership_fee_type = "membership_fee_type" in parsed.member_fields + + need_membership_fee_type = + "membership_fee_type" in parsed.member_fields or + parsed.sort_field == "membership_fee_type" query = Member @@ -199,10 +202,9 @@ defmodule Mv.Membership.MemberExport.Build do field_atom = String.to_existing_atom(field) if field_atom in Mv.Constants.member_fields() or field_atom == :membership_fee_type do - sort_field = - if field_atom == :membership_fee_type, do: :membership_fee_type_id, else: field_atom - - sort_by_field(members, sort_field, order) + key_fn = sort_key_fn_for_field(field_atom) + compare_fn = build_compare_fn(order) + Enum.sort_by(members, key_fn, compare_fn) else members end @@ -212,13 +214,17 @@ defmodule Mv.Membership.MemberExport.Build do defp sort_members_in_memory(members, _field, _order), do: members - defp sort_by_field(members, field_atom, order) do - key_fn = fn member -> Map.get(member, field_atom) end - compare_fn = build_compare_fn(order) - - Enum.sort_by(members, key_fn, compare_fn) + defp sort_key_fn_for_field(:membership_fee_type) do + fn member -> + case Map.get(member, :membership_fee_type) do + nil -> nil + rel -> Map.get(rel, :name) + end + end end + defp sort_key_fn_for_field(field_atom), do: fn member -> Map.get(member, field_atom) end + defp build_compare_fn("asc"), do: fn a, b -> a <= b end defp build_compare_fn("desc"), do: fn a, b -> b <= a end defp build_compare_fn(_), do: fn _a, _b -> true end @@ -261,7 +267,7 @@ defmodule Mv.Membership.MemberExport.Build do defp apply_fee_type_sort(query, order) do order_atom = if order == "desc", do: :desc, else: :asc - {Ash.Query.sort(query, membership_fee_type_id: order_atom), false} + {Ash.Query.sort(query, [{"membership_fee_type.name", order_atom}]), false} end defp apply_standard_member_sort(query, field, order) do @@ -275,9 +281,11 @@ defmodule Mv.Membership.MemberExport.Build do order_atom = if order == "desc", do: :desc, else: :asc sort_field = - if field_atom == :membership_fee_type, do: :membership_fee_type_id, else: field_atom + if field_atom == :membership_fee_type, + do: {"membership_fee_type.name", order_atom}, + else: {field_atom, order_atom} - {Ash.Query.sort(query, [{sort_field, order_atom}]), false} + {Ash.Query.sort(query, [sort_field]), false} else {query, false} end diff --git a/lib/mv_web/controllers/member_export_controller.ex b/lib/mv_web/controllers/member_export_controller.ex index b5386a9..715f86a 100644 --- a/lib/mv_web/controllers/member_export_controller.ex +++ b/lib/mv_web/controllers/member_export_controller.ex @@ -238,7 +238,10 @@ defmodule MvWeb.MemberExportController do parsed.computed_fields != [] and "membership_fee_status" in parsed.computed_fields need_groups = "groups" in parsed.member_fields - need_membership_fee_type = "membership_fee_type" in parsed.member_fields + + need_membership_fee_type = + "membership_fee_type" in parsed.member_fields or + parsed.sort_field == "membership_fee_type" query = Member @@ -368,7 +371,7 @@ defmodule MvWeb.MemberExportController do defp apply_membership_fee_type_sort_export(query, order) do order_atom = if order == "desc", do: :desc, else: :asc - {Ash.Query.sort(query, membership_fee_type_id: order_atom), false} + {Ash.Query.sort(query, [{"membership_fee_type.name", order_atom}]), false} end defp apply_member_field_sort_export(query, field, order) do diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index 889a818..8fb50b9 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -965,9 +965,10 @@ defmodule MvWeb.MemberLive.Index do query = Ash.Query.load(query, groups: [:id, :name, :slug]) - # Load membership_fee_type when the column is visible + # Load membership_fee_type when the column is visible or when sorting by it query = - if :membership_fee_type in socket.assigns.member_fields_visible do + if :membership_fee_type in socket.assigns.member_fields_visible or + socket.assigns.sort_field in [:membership_fee_type, "membership_fee_type"] do Ash.Query.load(query, membership_fee_type: [:id, :name]) else query @@ -1133,9 +1134,9 @@ defmodule MvWeb.MemberLive.Index do field in [:groups, "groups"] -> {query, true} - # Membership fee type sort -> by FK at DB + # Membership fee type sort -> by related name at DB field in [:membership_fee_type, "membership_fee_type"] -> - {Ash.Query.sort(query, membership_fee_type_id: order), false} + {Ash.Query.sort(query, [{"membership_fee_type.name", order}]), false} # Custom field sort -> after load custom_field_sort?(field) -> @@ -1777,6 +1778,15 @@ defmodule MvWeb.MemberLive.Index do end end) + # If fee type is visible but start_date was not in the list, append it + with_extras = + if :membership_fee_type in (member_fields_visible || []) and + :membership_fee_type not in with_extras do + with_extras ++ [:membership_fee_type] + else + with_extras + end + if :groups in (member_fields_visible || []), do: with_extras ++ [:groups], else: with_extras end @@ -1815,6 +1825,14 @@ defmodule MvWeb.MemberLive.Index do &expand_db_string_for_export(&1, membership_fee_type_visible, computed_strings) ) + # If fee type is visible but start_date was not in the list, append it before computed/groups + db_with_extras = + if membership_fee_type_visible and "membership_fee_type" not in db_with_extras do + db_with_extras ++ ["membership_fee_type"] + else + db_with_extras + end + # Any remaining computed fields not inserted above (future-proof) remaining_computed = computed_strings