From 82bd5732768b92ab875ccf868a91ed3a388b967d Mon Sep 17 00:00:00 2001 From: carla Date: Thu, 27 Nov 2025 14:10:27 +0100 Subject: [PATCH] formatting --- lib/mv_web/components/core_components.ex | 1 + lib/mv_web/live/member_live/index.ex | 179 ++++++++++++------ ...index_custom_fields_accessibility_test.exs | 8 +- .../index_custom_fields_display_test.exs | 2 +- .../index_custom_fields_edge_cases_test.exs | 1 - .../index_custom_fields_sorting_test.exs | 25 ++- 6 files changed, 148 insertions(+), 68 deletions(-) diff --git a/lib/mv_web/components/core_components.ex b/lib/mv_web/components/core_components.ex index 24e5ffe..b8fe0fc 100644 --- a/lib/mv_web/components/core_components.ex +++ b/lib/mv_web/components/core_components.ex @@ -373,6 +373,7 @@ defmodule MvWeb.CoreComponents do > {if dyn_col[:render] do rendered = dyn_col[:render].(@row_item.(row)) + if rendered == "" do "" else diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index 4a134f2..419df17 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -205,7 +205,9 @@ defmodule MvWeb.MemberLive.Index do custom_field: custom_field, render: fn member -> case get_custom_field_value(member, custom_field) do - nil -> "" + nil -> + "" + cfv -> formatted = Formatter.format_custom_field_value(cfv.value, custom_field) if formatted == "", do: "", else: formatted @@ -335,7 +337,13 @@ defmodule MvWeb.MemberLive.Index do # Apply sorting based on current socket state # For custom fields, we sort after loading - {query, sort_after_load} = maybe_sort(query, socket.assigns.sort_field, socket.assigns.sort_order, socket.assigns.custom_fields_visible) + {query, sort_after_load} = + maybe_sort( + query, + socket.assigns.sort_field, + socket.assigns.sort_order, + socket.assigns.custom_fields_visible + ) # Note: Using Ash.read! - errors will be handled by Phoenix LiveView # This is appropriate for data loading in LiveViews @@ -346,24 +354,21 @@ defmodule MvWeb.MemberLive.Index do # For large datasets (>1000 members), this could be optimized by filtering # at the database level, but requires more complex Ash queries. custom_field_ids = MapSet.new(Enum.map(socket.assigns.custom_fields_visible, & &1.id)) - members = Enum.map(members, fn member -> - # Only filter if custom_field_values is loaded (is a list, not Ash.NotLoaded) - if is_list(member.custom_field_values) do - filtered_values = Enum.filter(member.custom_field_values, fn cfv -> - cfv.custom_field_id in custom_field_ids - end) - %{member | custom_field_values: filtered_values} - else - member - end - end) + + members = filter_member_custom_field_values(members, custom_field_ids) # Sort in memory if needed (for custom fields) - members = if sort_after_load do - sort_members_in_memory(members, socket.assigns.sort_field, socket.assigns.sort_order, socket.assigns.custom_fields_visible) - else - members - end + members = + if sort_after_load do + sort_members_in_memory( + members, + socket.assigns.sort_field, + socket.assigns.sort_order, + socket.assigns.custom_fields_visible + ) + else + members + end assign(socket, :members, members) end @@ -381,6 +386,28 @@ defmodule MvWeb.MemberLive.Index do |> Ash.Query.load(custom_field_values: [custom_field: [:id, :name, :value_type]]) end + # Filters custom field values to only visible ones for all members + defp filter_member_custom_field_values(members, custom_field_ids) do + Enum.map(members, fn member -> + filter_single_member_custom_field_values(member, custom_field_ids) + end) + end + + # Filters custom field values for a single member + defp filter_single_member_custom_field_values(member, _custom_field_ids) + when not is_list(member.custom_field_values) do + member + end + + defp filter_single_member_custom_field_values(member, custom_field_ids) do + filtered_values = + Enum.filter(member.custom_field_values, fn cfv -> + cfv.custom_field_id in custom_field_ids + end) + + %{member | custom_field_values: filtered_values} + end + # ------------------------------------------------------------- # Helper Functions # ------------------------------------------------------------- @@ -508,45 +535,76 @@ defmodule MvWeb.MemberLive.Index do members id_str -> - # Find the custom field by matching the ID string - custom_field = - Enum.find(custom_fields, fn cf -> - to_string(cf.id) == id_str - end) + sort_members_by_custom_field(members, id_str, order, custom_fields) + end + end - case custom_field do - nil -> - members + # Sorts members by a specific custom field ID + defp sort_members_by_custom_field(members, id_str, order, custom_fields) do + custom_field = find_custom_field_by_id(custom_fields, id_str) - cf -> - # Split members into those with values and those without (NULL/empty) - {members_with_values, members_without_values} = - Enum.split_with(members, fn member -> - case get_custom_field_value(member, cf) do - nil -> false - cfv -> - extracted = extract_sort_value(cfv.value, cf.value_type) - not is_empty_value(extracted, cf.value_type) - end - end) + case custom_field do + nil -> + members - # Sort members with values - sorted_with_values = Enum.sort_by(members_with_values, fn member -> - cfv = get_custom_field_value(member, cf) - extracted = extract_sort_value(cfv.value, cf.value_type) - normalize_sort_value(extracted, order) - end) + cf -> + sort_members_with_custom_field(members, cf, order) + end + end - # For DESC, reverse only the members with values - sorted_with_values = if order == :desc do - Enum.reverse(sorted_with_values) - else - sorted_with_values - end + # Finds a custom field by matching its ID string + defp find_custom_field_by_id(custom_fields, id_str) do + Enum.find(custom_fields, fn cf -> + to_string(cf.id) == id_str + end) + end - # Combine: sorted values first, then NULL/empty values at the end - sorted_with_values ++ members_without_values - end + # Sorts members that have a specific custom field + defp sort_members_with_custom_field(members, custom_field, order) do + # Split members into those with values and those without (NULL/empty) + {members_with_values, members_without_values} = + split_members_by_value_presence(members, custom_field) + + # Sort members with values + sorted_with_values = sort_members_with_values(members_with_values, custom_field, order) + + # Combine: sorted values first, then NULL/empty values at the end + sorted_with_values ++ members_without_values + end + + # Splits members into those with values and those without + defp split_members_by_value_presence(members, custom_field) do + Enum.split_with(members, fn member -> + has_non_empty_value?(member, custom_field) + end) + end + + # Checks if a member has a non-empty value for the custom field + defp has_non_empty_value?(member, custom_field) do + case get_custom_field_value(member, custom_field) do + nil -> + false + + cfv -> + extracted = extract_sort_value(cfv.value, custom_field.value_type) + not empty_value?(extracted, custom_field.value_type) + end + end + + # Sorts members that have values for the custom field + defp sort_members_with_values(members_with_values, custom_field, order) do + sorted = + Enum.sort_by(members_with_values, fn member -> + cfv = get_custom_field_value(member, custom_field) + extracted = extract_sort_value(cfv.value, custom_field.value_type) + normalize_sort_value(extracted, order) + end) + + # For DESC, reverse only the members with values + if order == :desc do + Enum.reverse(sorted) + else + sorted end end @@ -569,20 +627,21 @@ defmodule MvWeb.MemberLive.Index do defp extract_sort_value(value, _type), do: to_string(value) # Check if a value is considered empty (NULL or empty string) - defp is_empty_value(value, :string) when is_binary(value) do + defp empty_value?(value, :string) when is_binary(value) do String.trim(value) == "" end - defp is_empty_value(value, :email) when is_binary(value) do + + defp empty_value?(value, :email) when is_binary(value) do String.trim(value) == "" end - defp is_empty_value(_value, _type), do: false + + defp empty_value?(_value, _type), do: false # Normalize sort value for DESC order # For DESC, we sort ascending first, then reverse the list # This function is kept for consistency but doesn't need to invert values defp normalize_sort_value(value, _order), do: value - # Updates sort field and order from URL parameters if present. # # Validates the sort field and order, falling back to defaults if invalid. @@ -678,13 +737,17 @@ defmodule MvWeb.MemberLive.Index do # get_custom_field_value(member, non_existent_field) -> nil def get_custom_field_value(member, custom_field) do case member.custom_field_values do - nil -> nil + nil -> + nil + values when is_list(values) -> Enum.find(values, fn cfv -> cfv.custom_field_id == custom_field.id or (cfv.custom_field && cfv.custom_field.id == custom_field.id) end) - _ -> nil + + _ -> + nil end end end diff --git a/test/mv_web/member_live/index_custom_fields_accessibility_test.exs b/test/mv_web/member_live/index_custom_fields_accessibility_test.exs index e4d174f..cfe3145 100644 --- a/test/mv_web/member_live/index_custom_fields_accessibility_test.exs +++ b/test/mv_web/member_live/index_custom_fields_accessibility_test.exs @@ -67,7 +67,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do field: field } do conn = conn_with_oidc_user(conn) - {:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=asc") + + {:ok, view, _html} = + live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=asc") html = render(view) @@ -80,7 +82,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do field: field } do conn = conn_with_oidc_user(conn) - {:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc") + + {:ok, view, _html} = + live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc") html = render(view) diff --git a/test/mv_web/member_live/index_custom_fields_display_test.exs b/test/mv_web/member_live/index_custom_fields_display_test.exs index 7788c60..25aefe5 100644 --- a/test/mv_web/member_live/index_custom_fields_display_test.exs +++ b/test/mv_web/member_live/index_custom_fields_display_test.exs @@ -105,7 +105,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do |> Ash.Changeset.for_create(:create, %{ member_id: member1.id, custom_field_id: field_show_integer.id, - value: %{"_union_type" => "integer", "_union_value" => 12345} + value: %{"_union_type" => "integer", "_union_value" => 12_345} }) |> Ash.create() diff --git a/test/mv_web/member_live/index_custom_fields_edge_cases_test.exs b/test/mv_web/member_live/index_custom_fields_edge_cases_test.exs index 9d44c40..d526556 100644 --- a/test/mv_web/member_live/index_custom_fields_edge_cases_test.exs +++ b/test/mv_web/member_live/index_custom_fields_edge_cases_test.exs @@ -171,4 +171,3 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do assert html =~ "Value3" end end - diff --git a/test/mv_web/member_live/index_custom_fields_sorting_test.exs b/test/mv_web/member_live/index_custom_fields_sorting_test.exs index e1c99b2..21b0c9f 100644 --- a/test/mv_web/member_live/index_custom_fields_sorting_test.exs +++ b/test/mv_web/member_live/index_custom_fields_sorting_test.exs @@ -174,7 +174,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do test "sorting by custom field works with URL parameters", %{conn: conn, field_string: field} do conn = conn_with_oidc_user(conn) - {:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc") + + {:ok, view, _html} = + live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc") # Check that the sort state is correctly applied assert has_element?(view, "[data-testid='custom_field_#{field.id}'][aria-label='descending']") @@ -186,14 +188,19 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do field_integer: field_integer } do conn = conn_with_oidc_user(conn) - {:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field_string.id}&sort_order=desc") + + {:ok, view, _html} = + live(conn, "/members?query=&sort_field=custom_field_#{field_string.id}&sort_order=desc") # Click on a different custom field column view |> element("[data-testid='custom_field_#{field_integer.id}']") |> render_click() - assert_patch(view, "/members?query=&sort_field=custom_field_#{field_integer.id}&sort_order=asc") + assert_patch( + view, + "/members?query=&sort_field=custom_field_#{field_integer.id}&sort_order=asc" + ) end test "clicking regular column after custom field column works", %{ @@ -201,7 +208,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do field_string: field } do conn = conn_with_oidc_user(conn) - {:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc") + + {:ok, view, _html} = + live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc") # Click on email column view @@ -305,7 +314,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do |> Ash.create() conn = conn_with_oidc_user(conn) - {:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=asc") + + {:ok, view, _html} = + live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=asc") html = render(view) @@ -414,7 +425,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do |> Ash.create() conn = conn_with_oidc_user(conn) - {:ok, view, _html} = live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc") + + {:ok, view, _html} = + live(conn, "/members?query=&sort_field=custom_field_#{field.id}&sort_order=desc") html = render(view)