formatting
This commit is contained in:
parent
e7c4a4f62f
commit
82bd573276
6 changed files with 148 additions and 68 deletions
|
|
@ -373,6 +373,7 @@ defmodule MvWeb.CoreComponents do
|
||||||
>
|
>
|
||||||
{if dyn_col[:render] do
|
{if dyn_col[:render] do
|
||||||
rendered = dyn_col[:render].(@row_item.(row))
|
rendered = dyn_col[:render].(@row_item.(row))
|
||||||
|
|
||||||
if rendered == "" do
|
if rendered == "" do
|
||||||
""
|
""
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,9 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
custom_field: custom_field,
|
custom_field: custom_field,
|
||||||
render: fn member ->
|
render: fn member ->
|
||||||
case get_custom_field_value(member, custom_field) do
|
case get_custom_field_value(member, custom_field) do
|
||||||
nil -> ""
|
nil ->
|
||||||
|
""
|
||||||
|
|
||||||
cfv ->
|
cfv ->
|
||||||
formatted = Formatter.format_custom_field_value(cfv.value, custom_field)
|
formatted = Formatter.format_custom_field_value(cfv.value, custom_field)
|
||||||
if formatted == "", do: "", else: formatted
|
if formatted == "", do: "", else: formatted
|
||||||
|
|
@ -335,7 +337,13 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
|
|
||||||
# Apply sorting based on current socket state
|
# Apply sorting based on current socket state
|
||||||
# For custom fields, we sort after loading
|
# 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
|
# Note: Using Ash.read! - errors will be handled by Phoenix LiveView
|
||||||
# This is appropriate for data loading in LiveViews
|
# This is appropriate for data loading in LiveViews
|
||||||
|
|
@ -346,21 +354,18 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
# For large datasets (>1000 members), this could be optimized by filtering
|
# For large datasets (>1000 members), this could be optimized by filtering
|
||||||
# at the database level, but requires more complex Ash queries.
|
# at the database level, but requires more complex Ash queries.
|
||||||
custom_field_ids = MapSet.new(Enum.map(socket.assigns.custom_fields_visible, & &1.id))
|
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)
|
members = filter_member_custom_field_values(members, custom_field_ids)
|
||||||
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)
|
|
||||||
|
|
||||||
# Sort in memory if needed (for custom fields)
|
# Sort in memory if needed (for custom fields)
|
||||||
members = if sort_after_load do
|
members =
|
||||||
sort_members_in_memory(members, socket.assigns.sort_field, socket.assigns.sort_order, socket.assigns.custom_fields_visible)
|
if sort_after_load do
|
||||||
|
sort_members_in_memory(
|
||||||
|
members,
|
||||||
|
socket.assigns.sort_field,
|
||||||
|
socket.assigns.sort_order,
|
||||||
|
socket.assigns.custom_fields_visible
|
||||||
|
)
|
||||||
else
|
else
|
||||||
members
|
members
|
||||||
end
|
end
|
||||||
|
|
@ -381,6 +386,28 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
|> Ash.Query.load(custom_field_values: [custom_field: [:id, :name, :value_type]])
|
|> Ash.Query.load(custom_field_values: [custom_field: [:id, :name, :value_type]])
|
||||||
end
|
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
|
# Helper Functions
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
|
|
@ -508,45 +535,76 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
members
|
members
|
||||||
|
|
||||||
id_str ->
|
id_str ->
|
||||||
# Find the custom field by matching the ID string
|
sort_members_by_custom_field(members, id_str, order, custom_fields)
|
||||||
custom_field =
|
end
|
||||||
Enum.find(custom_fields, fn cf ->
|
end
|
||||||
to_string(cf.id) == id_str
|
|
||||||
end)
|
# 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)
|
||||||
|
|
||||||
case custom_field do
|
case custom_field do
|
||||||
nil ->
|
nil ->
|
||||||
members
|
members
|
||||||
|
|
||||||
cf ->
|
cf ->
|
||||||
|
sort_members_with_custom_field(members, cf, order)
|
||||||
|
end
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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)
|
# Split members into those with values and those without (NULL/empty)
|
||||||
{members_with_values, members_without_values} =
|
{members_with_values, members_without_values} =
|
||||||
Enum.split_with(members, fn member ->
|
split_members_by_value_presence(members, custom_field)
|
||||||
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)
|
|
||||||
|
|
||||||
# Sort members with values
|
# Sort members with values
|
||||||
sorted_with_values = Enum.sort_by(members_with_values, fn member ->
|
sorted_with_values = sort_members_with_values(members_with_values, custom_field, order)
|
||||||
cfv = get_custom_field_value(member, cf)
|
|
||||||
extracted = extract_sort_value(cfv.value, cf.value_type)
|
|
||||||
normalize_sort_value(extracted, order)
|
|
||||||
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
|
|
||||||
|
|
||||||
# Combine: sorted values first, then NULL/empty values at the end
|
# Combine: sorted values first, then NULL/empty values at the end
|
||||||
sorted_with_values ++ members_without_values
|
sorted_with_values ++ members_without_values
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -569,20 +627,21 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
defp extract_sort_value(value, _type), do: to_string(value)
|
defp extract_sort_value(value, _type), do: to_string(value)
|
||||||
|
|
||||||
# Check if a value is considered empty (NULL or empty string)
|
# 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) == ""
|
String.trim(value) == ""
|
||||||
end
|
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) == ""
|
String.trim(value) == ""
|
||||||
end
|
end
|
||||||
defp is_empty_value(_value, _type), do: false
|
|
||||||
|
defp empty_value?(_value, _type), do: false
|
||||||
|
|
||||||
# Normalize sort value for DESC order
|
# Normalize sort value for DESC order
|
||||||
# For DESC, we sort ascending first, then reverse the list
|
# For DESC, we sort ascending first, then reverse the list
|
||||||
# This function is kept for consistency but doesn't need to invert values
|
# This function is kept for consistency but doesn't need to invert values
|
||||||
defp normalize_sort_value(value, _order), do: value
|
defp normalize_sort_value(value, _order), do: value
|
||||||
|
|
||||||
|
|
||||||
# Updates sort field and order from URL parameters if present.
|
# Updates sort field and order from URL parameters if present.
|
||||||
#
|
#
|
||||||
# Validates the sort field and order, falling back to defaults if invalid.
|
# 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
|
# get_custom_field_value(member, non_existent_field) -> nil
|
||||||
def get_custom_field_value(member, custom_field) do
|
def get_custom_field_value(member, custom_field) do
|
||||||
case member.custom_field_values do
|
case member.custom_field_values do
|
||||||
nil -> nil
|
nil ->
|
||||||
|
nil
|
||||||
|
|
||||||
values when is_list(values) ->
|
values when is_list(values) ->
|
||||||
Enum.find(values, fn cfv ->
|
Enum.find(values, fn cfv ->
|
||||||
cfv.custom_field_id == custom_field.id or
|
cfv.custom_field_id == custom_field.id or
|
||||||
(cfv.custom_field && cfv.custom_field.id == custom_field.id)
|
(cfv.custom_field && cfv.custom_field.id == custom_field.id)
|
||||||
end)
|
end)
|
||||||
_ -> nil
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do
|
||||||
field: field
|
field: field
|
||||||
} do
|
} do
|
||||||
conn = conn_with_oidc_user(conn)
|
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)
|
html = render(view)
|
||||||
|
|
||||||
|
|
@ -80,7 +82,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do
|
||||||
field: field
|
field: field
|
||||||
} do
|
} do
|
||||||
conn = conn_with_oidc_user(conn)
|
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)
|
html = render(view)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
|
||||||
|> Ash.Changeset.for_create(:create, %{
|
|> Ash.Changeset.for_create(:create, %{
|
||||||
member_id: member1.id,
|
member_id: member1.id,
|
||||||
custom_field_id: field_show_integer.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()
|
|> Ash.create()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -171,4 +171,3 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
|
||||||
assert html =~ "Value3"
|
assert html =~ "Value3"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
||||||
|
|
||||||
test "sorting by custom field works with URL parameters", %{conn: conn, field_string: field} do
|
test "sorting by custom field works with URL parameters", %{conn: conn, field_string: field} do
|
||||||
conn = conn_with_oidc_user(conn)
|
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
|
# Check that the sort state is correctly applied
|
||||||
assert has_element?(view, "[data-testid='custom_field_#{field.id}'][aria-label='descending']")
|
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
|
field_integer: field_integer
|
||||||
} do
|
} do
|
||||||
conn = conn_with_oidc_user(conn)
|
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
|
# Click on a different custom field column
|
||||||
view
|
view
|
||||||
|> element("[data-testid='custom_field_#{field_integer.id}']")
|
|> element("[data-testid='custom_field_#{field_integer.id}']")
|
||||||
|> render_click()
|
|> 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
|
end
|
||||||
|
|
||||||
test "clicking regular column after custom field column works", %{
|
test "clicking regular column after custom field column works", %{
|
||||||
|
|
@ -201,7 +208,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
||||||
field_string: field
|
field_string: field
|
||||||
} do
|
} do
|
||||||
conn = conn_with_oidc_user(conn)
|
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
|
# Click on email column
|
||||||
view
|
view
|
||||||
|
|
@ -305,7 +314,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
||||||
|> Ash.create()
|
|> Ash.create()
|
||||||
|
|
||||||
conn = conn_with_oidc_user(conn)
|
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)
|
html = render(view)
|
||||||
|
|
||||||
|
|
@ -414,7 +425,9 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
|
||||||
|> Ash.create()
|
|> Ash.create()
|
||||||
|
|
||||||
conn = conn_with_oidc_user(conn)
|
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)
|
html = render(view)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue