WIP: Implements settings for member fields closes #223 #300
2 changed files with 239 additions and 0 deletions
|
|
@ -62,6 +62,12 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
</.button>
|
||||
</.form>
|
||||
</.form_section>
|
||||
<%!-- Memberdata Section --%>
|
||||
<.live_component
|
||||
module={MvWeb.MemberFieldLive.IndexComponent}
|
||||
id="member-fields-component"
|
||||
settings={@settings}
|
||||
/>
|
||||
<%!-- Custom Fields Section --%>
|
||||
<.live_component
|
||||
module={MvWeb.CustomFieldLive.IndexComponent}
|
||||
|
|
@ -125,6 +131,33 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
{:noreply, put_flash(socket, :error, gettext("Slug does not match. Deletion cancelled."))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:member_field_visibility_updated}, socket) do
|
||||
# Reload settings to get updated member_field_visibility
|
||||
{:ok, updated_settings} = Membership.get_settings()
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:settings, updated_settings)
|
||||
|> put_flash(:info, gettext("Member field visibility updated successfully"))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:member_field_visibility_error, error}, socket) do
|
||||
error_message =
|
||||
case error do
|
||||
%Ash.Error.Invalid{} = invalid_error ->
|
||||
gettext("Failed to update member field visibility: %{error}",
|
||||
error: Ash.ErrorKind.message(invalid_error)
|
||||
)
|
||||
|
||||
error ->
|
||||
gettext("Failed to update member field visibility: %{error}", error: inspect(error))
|
||||
end
|
||||
|
||||
{:noreply, put_flash(socket, :error, error_message)}
|
||||
end
|
||||
|
||||
defp assign_form(%{assigns: %{settings: settings}} = socket) do
|
||||
form =
|
||||
AshPhoenix.Form.for_update(
|
||||
|
|
|
|||
206
lib/mv_web/live/member_field_live/index_component.ex
Normal file
206
lib/mv_web/live/member_field_live/index_component.ex
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
defmodule MvWeb.MemberFieldLive.IndexComponent do
|
||||
@moduledoc """
|
||||
LiveComponent for managing member field visibility in overview (embedded in settings).
|
||||
|
||||
## Features
|
||||
- List all member fields from Mv.Constants.member_fields()
|
||||
- Display show_in_overview status as badge (Yes/No)
|
||||
- Display required status for required fields (first_name, last_name, email)
|
||||
- Toggle show_in_overview flag for each field
|
||||
- Updates Settings.member_field_visibility
|
||||
"""
|
||||
use MvWeb, :live_component
|
||||
|
||||
alias Mv.Membership
|
||||
|
||||
@required_fields [:first_name, :last_name, :email]
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:member_fields, get_member_fields_with_visibility(assigns.settings))
|
||||
|> assign(:required?, &required?/1)
|
||||
|
||||
~H"""
|
||||
<div id={@id}>
|
||||
<.form_section title={gettext("Memberdata")}>
|
||||
<p class="text-sm text-base-content/70 mb-4">
|
||||
{gettext("These fields are neccessary for MILA to handle member identification and payment calculations in the future. This you cannot delete these fields but hide them in the member overview.")}
|
||||
</p>
|
||||
|
||||
<.table id="member_fields" rows={@member_fields}>
|
||||
<:col :let={{_field_name, field_data}} label={gettext("Field Name")}>
|
||||
{format_field_name(field_data.field)}
|
||||
</:col>
|
||||
|
||||
<:col
|
||||
:let={{_field_name, field_data}}
|
||||
label={gettext("Required")}
|
||||
class="max-w-[9.375rem] text-center"
|
||||
>
|
||||
<span
|
||||
:if={@required?.(field_data.field)}
|
||||
class="text-base-content font-semibold"
|
||||
>
|
||||
{gettext("Required")}
|
||||
</span>
|
||||
<span :if={!@required?.(field_data.field)} class="text-base-content/70">
|
||||
{gettext("Optional")}
|
||||
</span>
|
||||
</:col>
|
||||
|
||||
<:col
|
||||
:let={{_field_name, field_data}}
|
||||
label={gettext("Show in overview")}
|
||||
class="max-w-[9.375rem] text-center"
|
||||
>
|
||||
<span :if={field_data.show_in_overview} class="badge badge-success">
|
||||
{gettext("Yes")}
|
||||
</span>
|
||||
<span :if={!field_data.show_in_overview} class="badge badge-ghost">
|
||||
{gettext("No")}
|
||||
</span>
|
||||
</:col>
|
||||
|
||||
<:action :let={{_field_name, field_data}}>
|
||||
<button
|
||||
id={"member-field-#{field_data.field}-toggle"}
|
||||
phx-click="toggle_field_visibility"
|
||||
phx-keydown="toggle_field_visibility"
|
||||
phx-key="Enter"
|
||||
phx-target={@myself}
|
||||
phx-value-field={field_data.field}
|
||||
class="btn btn-sm btn-secondary"
|
||||
aria-label={
|
||||
if field_data.show_in_overview,
|
||||
do:
|
||||
gettext("Hide %{field} in overview", field: format_field_name(field_data.field)),
|
||||
else:
|
||||
gettext("Show %{field} in overview", field: format_field_name(field_data.field))
|
||||
}
|
||||
aria-pressed={to_string(field_data.show_in_overview)}
|
||||
>
|
||||
{if field_data.show_in_overview, do: gettext("Hide"), else: gettext("Show")}
|
||||
</button>
|
||||
</:action>
|
||||
</.table>
|
||||
</.form_section>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign_new(:settings, fn -> get_settings() end)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("toggle_field_visibility", %{"field" => field_string}, socket) do
|
||||
# Validate that the field is a valid member field before converting to atom
|
||||
valid_fields = Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)
|
||||
|
||||
if field_string in valid_fields do
|
||||
{:ok, settings} = Membership.get_settings()
|
||||
|
||||
# Get current visibility config
|
||||
current_visibility = settings.member_field_visibility || %{}
|
||||
# Normalize keys to strings
|
||||
normalized_visibility =
|
||||
Enum.reduce(current_visibility, %{}, fn
|
||||
{key, value}, acc when is_atom(key) ->
|
||||
Map.put(acc, Atom.to_string(key), value)
|
||||
|
||||
{key, value}, acc when is_binary(key) ->
|
||||
Map.put(acc, key, value)
|
||||
end)
|
||||
|
||||
# Toggle the field visibility
|
||||
current_value = Map.get(normalized_visibility, field_string, true)
|
||||
new_value = !current_value
|
||||
updated_visibility = Map.put(normalized_visibility, field_string, new_value)
|
||||
|
||||
# Update settings
|
||||
case Membership.update_member_field_visibility(settings, updated_visibility) do
|
||||
{:ok, updated_settings} ->
|
||||
# Send message to parent LiveView
|
||||
send(self(), {:member_field_visibility_updated})
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:settings, updated_settings)}
|
||||
|
||||
{:error, error} ->
|
||||
# Send error message to parent LiveView for user feedback
|
||||
send(self(), {:member_field_visibility_error, error})
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
# Helper functions
|
||||
|
||||
defp get_settings do
|
||||
case Membership.get_settings() do
|
||||
{:ok, settings} ->
|
||||
settings
|
||||
|
||||
{:error, _} ->
|
||||
# Return a minimal struct-like map for fallback
|
||||
# This is only used for initial rendering, actual settings will be loaded properly
|
||||
%{member_field_visibility: %{}}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_member_fields_with_visibility(settings) do
|
||||
member_fields = Mv.Constants.member_fields()
|
||||
visibility_config = settings.member_field_visibility || %{}
|
||||
|
||||
# Normalize visibility config keys to atoms
|
||||
normalized_config = normalize_visibility_config(visibility_config)
|
||||
|
||||
Enum.map(member_fields, fn field ->
|
||||
show_in_overview = Map.get(normalized_config, field, true)
|
||||
|
||||
{Atom.to_string(field), %{field: field, show_in_overview: show_in_overview}}
|
||||
end)
|
||||
end
|
||||
|
||||
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: %{}
|
||||
|
||||
defp required?(field) when field in @required_fields, do: true
|
||||
defp required?(_), do: false
|
||||
|
||||
defp format_field_name(field) when is_atom(field) do
|
||||
field
|
||||
|> Atom.to_string()
|
||||
|> String.replace("_", " ")
|
||||
|> String.split()
|
||||
|> Enum.map_join(" ", &String.capitalize/1)
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue