formatting and refactor member fields constant
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
7f0da693ee
commit
d039e4bb7d
6 changed files with 150 additions and 104 deletions
|
|
@ -29,11 +29,18 @@ defmodule MvWeb.MemberLive.Index do
|
|||
require Ash.Query
|
||||
import Ash.Expr
|
||||
|
||||
alias Mv.Membership
|
||||
alias MvWeb.MemberLive.Index.Formatter
|
||||
|
||||
# Prefix used in sort field names for custom fields (e.g., "custom_field_<id>")
|
||||
@custom_field_prefix "custom_field_"
|
||||
|
||||
# Member fields that are loaded for the overview
|
||||
# Uses constants from Mv.Constants to ensure consistency
|
||||
# Note: :id is always included for member identification
|
||||
# All member fields are loaded, but visibility is controlled via settings
|
||||
@overview_fields [:id | Mv.Constants.member_fields()]
|
||||
|
||||
@doc """
|
||||
Initializes the LiveView state.
|
||||
|
||||
|
|
@ -52,6 +59,14 @@ defmodule MvWeb.MemberLive.Index do
|
|||
|> Ash.Query.sort(name: :asc)
|
||||
|> Ash.read!()
|
||||
|
||||
# Load settings once to avoid N+1 queries
|
||||
settings =
|
||||
case Membership.get_settings() do
|
||||
{:ok, s} -> s
|
||||
# Fallback if settings can't be loaded
|
||||
{:error, _} -> %{member_field_visibility: %{}}
|
||||
end
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:page_title, gettext("Members"))
|
||||
|
|
@ -59,9 +74,10 @@ defmodule MvWeb.MemberLive.Index do
|
|||
|> assign_new(:sort_field, fn -> :first_name end)
|
||||
|> assign_new(:sort_order, fn -> :asc end)
|
||||
|> assign(:selected_members, [])
|
||||
|> assign(:settings, settings)
|
||||
|> assign(:custom_fields_visible, custom_fields_visible)
|
||||
|> assign(:member_field_configurations, get_member_field_configurations())
|
||||
|> assign(:member_fields_visible, get_visible_member_fields())
|
||||
|> assign(:member_field_configurations, get_member_field_configurations(settings))
|
||||
|> assign(:member_fields_visible, get_visible_member_fields(settings))
|
||||
|
||||
# We call handle params to use the query from the URL
|
||||
{:ok, socket}
|
||||
|
|
@ -315,18 +331,7 @@ defmodule MvWeb.MemberLive.Index do
|
|||
query =
|
||||
Mv.Membership.Member
|
||||
|> Ash.Query.new()
|
||||
|> Ash.Query.select([
|
||||
:id,
|
||||
:first_name,
|
||||
:last_name,
|
||||
:email,
|
||||
:street,
|
||||
:house_number,
|
||||
:postal_code,
|
||||
:city,
|
||||
:phone_number,
|
||||
:join_date
|
||||
])
|
||||
|> Ash.Query.select(@overview_fields)
|
||||
|
||||
# Load custom field values for visible custom fields
|
||||
custom_field_ids_list = Enum.map(socket.assigns.custom_fields_visible, & &1.id)
|
||||
|
|
@ -435,18 +440,13 @@ defmodule MvWeb.MemberLive.Index do
|
|||
defp maybe_sort(query, _, _, _), do: {query, false}
|
||||
|
||||
# Validate that a field is sortable
|
||||
# Uses member fields from constants, but excludes fields that don't make sense to sort
|
||||
# (e.g., :notes is too long, :paid is boolean and not very useful for sorting)
|
||||
defp valid_sort_field?(field) when is_atom(field) do
|
||||
valid_fields = [
|
||||
:first_name,
|
||||
:last_name,
|
||||
:email,
|
||||
:street,
|
||||
:house_number,
|
||||
:postal_code,
|
||||
:city,
|
||||
:phone_number,
|
||||
:join_date
|
||||
]
|
||||
# All member fields are sortable, but we exclude some that don't make sense
|
||||
# :id is not in member_fields, but we don't want to sort by it anyway
|
||||
non_sortable_fields = [:notes, :paid]
|
||||
valid_fields = Mv.Constants.member_fields() -- non_sortable_fields
|
||||
|
||||
field in valid_fields or custom_field_sort?(field)
|
||||
end
|
||||
|
|
@ -742,6 +742,12 @@ defmodule MvWeb.MemberLive.Index do
|
|||
# and their show_in_overview values (true or false). Fields not configured in settings
|
||||
# default to true.
|
||||
#
|
||||
# Performance: This function uses the already-loaded settings to avoid N+1 queries.
|
||||
# Settings should be loaded once in mount/3 and passed to this function.
|
||||
#
|
||||
# Parameters:
|
||||
# - `settings` - The settings struct loaded from the database
|
||||
#
|
||||
# Returns a map: %{field_name => show_in_overview}
|
||||
#
|
||||
# This can be used for:
|
||||
|
|
@ -750,12 +756,16 @@ defmodule MvWeb.MemberLive.Index do
|
|||
# - Dynamic field management
|
||||
#
|
||||
# Fields are read from the global Constants module.
|
||||
defp get_member_field_configurations do
|
||||
@spec get_member_field_configurations(map()) :: %{atom() => boolean()}
|
||||
defp get_member_field_configurations(settings) do
|
||||
# Get all eligible fields from the global constants
|
||||
all_fields = Mv.Constants.member_fields()
|
||||
|
||||
# Normalize visibility config (JSONB may return string keys)
|
||||
visibility_config = normalize_visibility_config(settings.member_field_visibility || %{})
|
||||
|
||||
Enum.reduce(all_fields, %{}, fn field, acc ->
|
||||
show_in_overview = Mv.Membership.Member.show_in_overview?(field)
|
||||
show_in_overview = Map.get(visibility_config, field, true)
|
||||
Map.put(acc, field, show_in_overview)
|
||||
end)
|
||||
end
|
||||
|
|
@ -764,10 +774,38 @@ defmodule MvWeb.MemberLive.Index do
|
|||
#
|
||||
# Filters the member field configurations to return only fields with show_in_overview: true.
|
||||
#
|
||||
# Parameters:
|
||||
# - `settings` - The settings struct loaded from the database
|
||||
#
|
||||
# Returns a list of atoms representing visible member field names.
|
||||
defp get_visible_member_fields do
|
||||
get_member_field_configurations()
|
||||
@spec get_visible_member_fields(map()) :: [atom()]
|
||||
defp get_visible_member_fields(settings) do
|
||||
get_member_field_configurations(settings)
|
||||
|> Enum.filter(fn {_field, show_in_overview} -> show_in_overview end)
|
||||
|> Enum.map(fn {field, _show_in_overview} -> field end)
|
||||
end
|
||||
|
||||
# Normalizes visibility config map keys from strings to atoms.
|
||||
# JSONB in PostgreSQL converts atom keys to string keys when storing.
|
||||
# This is a local helper to avoid N+1 queries by reusing the normalization logic.
|
||||
defp normalize_visibility_config(config) when is_map(config) do
|
||||
Enum.reduce(config, %{}, fn
|
||||
{key, value}, acc when is_atom(key) ->
|
||||
Map.put(acc, key, value)
|
||||
|
||||
{key, value}, acc when is_binary(key) ->
|
||||
try do
|
||||
atom_key = String.to_existing_atom(key)
|
||||
Map.put(acc, atom_key, value)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
acc
|
||||
end
|
||||
|
||||
_, acc ->
|
||||
acc
|
||||
end)
|
||||
end
|
||||
|
||||
defp normalize_visibility_config(_), do: %{}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -69,7 +69,10 @@
|
|||
>
|
||||
{member.first_name} {member.last_name}
|
||||
</:col>
|
||||
<:col :if={:email in @member_fields_visible} :let={member} label={
|
||||
<:col
|
||||
:let={member}
|
||||
:if={:email in @member_fields_visible}
|
||||
label={
|
||||
~H"""
|
||||
<.live_component
|
||||
module={MvWeb.Components.SortHeaderComponent}
|
||||
|
|
@ -80,10 +83,14 @@
|
|||
sort_order={@sort_order}
|
||||
/>
|
||||
"""
|
||||
}>
|
||||
}
|
||||
>
|
||||
{member.email}
|
||||
</:col>
|
||||
<:col :if={:street in @member_fields_visible} :let={member} label={
|
||||
<:col
|
||||
:let={member}
|
||||
:if={:street in @member_fields_visible}
|
||||
label={
|
||||
~H"""
|
||||
<.live_component
|
||||
module={MvWeb.Components.SortHeaderComponent}
|
||||
|
|
@ -94,10 +101,14 @@
|
|||
sort_order={@sort_order}
|
||||
/>
|
||||
"""
|
||||
}>
|
||||
}
|
||||
>
|
||||
{member.street}
|
||||
</:col>
|
||||
<:col :if={:house_number in @member_fields_visible} :let={member} label={
|
||||
<:col
|
||||
:let={member}
|
||||
:if={:house_number in @member_fields_visible}
|
||||
label={
|
||||
~H"""
|
||||
<.live_component
|
||||
module={MvWeb.Components.SortHeaderComponent}
|
||||
|
|
@ -108,10 +119,14 @@
|
|||
sort_order={@sort_order}
|
||||
/>
|
||||
"""
|
||||
}>
|
||||
}
|
||||
>
|
||||
{member.house_number}
|
||||
</:col>
|
||||
<:col :if={:postal_code in @member_fields_visible} :let={member} label={
|
||||
<:col
|
||||
:let={member}
|
||||
:if={:postal_code in @member_fields_visible}
|
||||
label={
|
||||
~H"""
|
||||
<.live_component
|
||||
module={MvWeb.Components.SortHeaderComponent}
|
||||
|
|
@ -122,10 +137,14 @@
|
|||
sort_order={@sort_order}
|
||||
/>
|
||||
"""
|
||||
}>
|
||||
}
|
||||
>
|
||||
{member.postal_code}
|
||||
</:col>
|
||||
<:col :if={:city in @member_fields_visible} :let={member} label={
|
||||
<:col
|
||||
:let={member}
|
||||
:if={:city in @member_fields_visible}
|
||||
label={
|
||||
~H"""
|
||||
<.live_component
|
||||
module={MvWeb.Components.SortHeaderComponent}
|
||||
|
|
@ -136,10 +155,14 @@
|
|||
sort_order={@sort_order}
|
||||
/>
|
||||
"""
|
||||
}>
|
||||
}
|
||||
>
|
||||
{member.city}
|
||||
</:col>
|
||||
<:col :if={:phone_number in @member_fields_visible} :let={member} label={
|
||||
<:col
|
||||
:let={member}
|
||||
:if={:phone_number in @member_fields_visible}
|
||||
label={
|
||||
~H"""
|
||||
<.live_component
|
||||
module={MvWeb.Components.SortHeaderComponent}
|
||||
|
|
@ -150,10 +173,14 @@
|
|||
sort_order={@sort_order}
|
||||
/>
|
||||
"""
|
||||
}>
|
||||
}
|
||||
>
|
||||
{member.phone_number}
|
||||
</:col>
|
||||
<:col :if={:join_date in @member_fields_visible} :let={member} label={
|
||||
<:col
|
||||
:let={member}
|
||||
:if={:join_date in @member_fields_visible}
|
||||
label={
|
||||
~H"""
|
||||
<.live_component
|
||||
module={MvWeb.Components.SortHeaderComponent}
|
||||
|
|
@ -164,7 +191,8 @@
|
|||
sort_order={@sort_order}
|
||||
/>
|
||||
"""
|
||||
}>
|
||||
}
|
||||
>
|
||||
{member.join_date}
|
||||
</:col>
|
||||
<:action :let={member}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue