235 lines
7.6 KiB
Elixir
235 lines
7.6 KiB
Elixir
defmodule MvWeb.MemberLive.Index.FieldVisibility do
|
|
@moduledoc """
|
|
Manages field visibility by merging user-specific selection with global settings.
|
|
|
|
This module handles:
|
|
- Getting all available fields (member fields + custom fields)
|
|
- Merging user selection with global settings (user selection takes priority)
|
|
- Falling back to global settings when no user selection exists
|
|
- Converting between different field name formats (atoms vs strings)
|
|
|
|
## Field Naming Convention
|
|
|
|
- **Member Fields**: Atoms (e.g., `:first_name`, `:email`)
|
|
- **Custom Fields**: Strings with format `"custom_field_<id>"` (e.g., `"custom_field_abc-123"`)
|
|
|
|
## Priority Order
|
|
|
|
1. User-specific selection (from URL/Session/Cookie)
|
|
2. Global settings (from database)
|
|
3. Default (all fields visible)
|
|
"""
|
|
|
|
@doc """
|
|
Gets all available fields for selection.
|
|
|
|
Returns a list of field identifiers:
|
|
- Member fields as atoms (e.g., `:first_name`, `:email`)
|
|
- Custom fields as strings (e.g., `"custom_field_abc-123"`)
|
|
|
|
## Parameters
|
|
|
|
- `custom_fields` - List of CustomField resources that are available
|
|
|
|
## Returns
|
|
|
|
List of field identifiers (atoms and strings)
|
|
"""
|
|
@spec get_all_available_fields([struct()]) :: [atom() | String.t()]
|
|
def get_all_available_fields(custom_fields) do
|
|
member_fields = Mv.Constants.member_fields()
|
|
custom_field_names = Enum.map(custom_fields, &"custom_field_#{&1.id}")
|
|
|
|
member_fields ++ custom_field_names
|
|
end
|
|
|
|
@doc """
|
|
Merges user field selection with global settings.
|
|
|
|
User selection takes priority over global settings. If a field is not in the
|
|
user selection, the global setting is used. If a field is not in global settings,
|
|
it defaults to `true` (visible).
|
|
|
|
## Parameters
|
|
|
|
- `user_selection` - Map of field names (strings) to boolean visibility
|
|
- `global_settings` - Settings struct with `member_field_visibility` field
|
|
- `custom_fields` - List of CustomField resources
|
|
|
|
## Returns
|
|
|
|
Map of field names (strings) to boolean visibility values
|
|
|
|
## Examples
|
|
|
|
iex> user_selection = %{"first_name" => false}
|
|
iex> settings = %{member_field_visibility: %{first_name: true, email: true}}
|
|
iex> merge_with_global_settings(user_selection, settings, [])
|
|
%{"first_name" => false, "email" => true} # User selection overrides global
|
|
"""
|
|
@spec merge_with_global_settings(
|
|
%{String.t() => boolean()},
|
|
map(),
|
|
[struct()]
|
|
) :: %{String.t() => boolean()}
|
|
def merge_with_global_settings(user_selection, global_settings, custom_fields) do
|
|
all_fields = get_all_available_fields(custom_fields)
|
|
global_visibility = get_global_visibility_map(global_settings, custom_fields)
|
|
|
|
Enum.reduce(all_fields, %{}, fn field, acc ->
|
|
field_string = field_to_string(field)
|
|
|
|
visibility =
|
|
case Map.get(user_selection, field_string) do
|
|
nil -> Map.get(global_visibility, field_string, true)
|
|
user_value -> user_value
|
|
end
|
|
|
|
Map.put(acc, field_string, visibility)
|
|
end)
|
|
end
|
|
|
|
@doc """
|
|
Gets the list of visible fields from a field selection map.
|
|
|
|
Returns only fields where visibility is `true`.
|
|
|
|
## Parameters
|
|
|
|
- `field_selection` - Map of field names to boolean visibility
|
|
|
|
## Returns
|
|
|
|
List of field identifiers (atoms for member fields, strings for custom fields)
|
|
|
|
## Examples
|
|
|
|
iex> selection = %{"first_name" => true, "email" => false, "street" => true}
|
|
iex> get_visible_fields(selection)
|
|
[:first_name, :street]
|
|
"""
|
|
@spec get_visible_fields(%{String.t() => boolean()}) :: [atom() | String.t()]
|
|
def get_visible_fields(field_selection) when is_map(field_selection) do
|
|
field_selection
|
|
|> Enum.filter(fn {_field, visible} -> visible end)
|
|
|> Enum.map(fn {field_string, _visible} -> to_field_identifier(field_string) end)
|
|
end
|
|
|
|
def get_visible_fields(_), do: []
|
|
|
|
@doc """
|
|
Gets visible member fields from field selection.
|
|
|
|
Returns only member fields (atoms) that are visible.
|
|
|
|
## Examples
|
|
|
|
iex> selection = %{"first_name" => true, "email" => true, "custom_field_123" => true}
|
|
iex> get_visible_member_fields(selection)
|
|
[:first_name, :email]
|
|
"""
|
|
@spec get_visible_member_fields(%{String.t() => boolean()}) :: [atom()]
|
|
def get_visible_member_fields(field_selection) when is_map(field_selection) do
|
|
member_fields = Mv.Constants.member_fields()
|
|
|
|
field_selection
|
|
|> Enum.filter(fn {field_string, visible} ->
|
|
field_atom = to_field_identifier(field_string)
|
|
visible && field_atom in member_fields
|
|
end)
|
|
|> Enum.map(fn {field_string, _visible} -> to_field_identifier(field_string) end)
|
|
end
|
|
|
|
def get_visible_member_fields(_), do: []
|
|
|
|
@doc """
|
|
Gets visible custom fields from field selection.
|
|
|
|
Returns only custom field identifiers (strings) that are visible.
|
|
|
|
## Examples
|
|
|
|
iex> selection = %{"first_name" => true, "custom_field_123" => true, "custom_field_456" => false}
|
|
iex> get_visible_custom_fields(selection)
|
|
["custom_field_123"]
|
|
"""
|
|
@spec get_visible_custom_fields(%{String.t() => boolean()}) :: [String.t()]
|
|
def get_visible_custom_fields(field_selection) when is_map(field_selection) do
|
|
field_selection
|
|
|> Enum.filter(fn {field_string, visible} ->
|
|
visible && String.starts_with?(field_string, "custom_field_")
|
|
end)
|
|
|> Enum.map(fn {field_string, _visible} -> field_string end)
|
|
end
|
|
|
|
def get_visible_custom_fields(_), do: []
|
|
|
|
# Gets global visibility map from settings
|
|
defp get_global_visibility_map(settings, custom_fields) do
|
|
member_visibility = get_member_field_visibility_from_settings(settings)
|
|
custom_field_visibility = get_custom_field_visibility(custom_fields)
|
|
|
|
Map.merge(member_visibility, custom_field_visibility)
|
|
end
|
|
|
|
# Gets member field visibility from settings
|
|
defp get_member_field_visibility_from_settings(settings) do
|
|
visibility_config =
|
|
normalize_visibility_config(Map.get(settings, :member_field_visibility, %{}))
|
|
|
|
member_fields = Mv.Constants.member_fields()
|
|
|
|
Enum.reduce(member_fields, %{}, fn field, acc ->
|
|
field_string = Atom.to_string(field)
|
|
show_in_overview = Map.get(visibility_config, field, true)
|
|
Map.put(acc, field_string, show_in_overview)
|
|
end)
|
|
end
|
|
|
|
# Gets custom field visibility (all custom fields with show_in_overview=true are visible)
|
|
defp get_custom_field_visibility(custom_fields) do
|
|
Enum.reduce(custom_fields, %{}, fn custom_field, acc ->
|
|
field_string = "custom_field_#{custom_field.id}"
|
|
visible = Map.get(custom_field, :show_in_overview, true)
|
|
Map.put(acc, field_string, visible)
|
|
end)
|
|
end
|
|
|
|
# Normalizes visibility config map keys from strings to atoms
|
|
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: %{}
|
|
|
|
# Converts field string to atom (for member fields) or keeps as string (for custom fields)
|
|
defp to_field_identifier(field_string) when is_binary(field_string) do
|
|
if String.starts_with?(field_string, "custom_field_") do
|
|
field_string
|
|
else
|
|
try do
|
|
String.to_existing_atom(field_string)
|
|
rescue
|
|
ArgumentError -> field_string
|
|
end
|
|
end
|
|
end
|
|
|
|
# Converts field identifier to string
|
|
defp field_to_string(field) when is_atom(field), do: Atom.to_string(field)
|
|
defp field_to_string(field) when is_binary(field), do: field
|
|
end
|