formatting
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
carla 2025-11-27 14:10:27 +01:00
parent c974be9ee2
commit 631cf23a0f
6 changed files with 148 additions and 68 deletions

View file

@ -373,6 +373,7 @@ defmodule MvWeb.CoreComponents do
>
{if dyn_col[:render] do
rendered = dyn_col[:render].(@row_item.(row))
if rendered == "" do
""
else

View file

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

View file

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

View file

@ -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()

View file

@ -171,4 +171,3 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
assert html =~ "Value3"
end
end

View file

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