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 based on actual attribute definitions (allow_nil? false) - Edit member field properties (expandable form like custom fields) - Updates Settings.member_field_visibility """ use MvWeb, :live_component alias Ash.Resource.Info alias Mv.Membership alias Mv.Membership.Helpers.VisibilityConfig alias MvWeb.Helpers.FieldTypeFormatter alias MvWeb.Translations.MemberFields @impl true def render(assigns) do assigns = assigns |> assign(:member_fields, get_member_fields_with_visibility(assigns.settings)) |> assign(:required?, &required?/1) ~H"""

{gettext( "These fields are neccessary for MILA to handle member identification and payment calculations in the future. Thus you cannot delete these fields but hide them in the member overview." )}

<%!-- Show form when editing --%>
<.live_component module={MvWeb.MemberFieldLive.FormComponent} id={@form_id} member_field={@editing_member_field} settings={@settings} on_save={ fn member_field, action -> send(self(), {:member_field_saved, member_field, action}) end } on_cancel={fn -> send_update(__MODULE__, id: @id, show_form: false) end} />
<%!-- Hide table when form is visible --%> <.table :if={!@show_form} id="member_fields" rows={@member_fields} > <:col :let={{_field_name, field_data}} label={gettext("Name")}> {MemberFields.label(field_data.field)} <:col :let={{_field_name, field_data}} label={gettext("Value Type")}> {format_value_type(field_data.field)} <:col :let={{_field_name, field_data}} label={gettext("Description")}> {field_data.description || ""} <:col :let={{_field_name, field_data}} label={gettext("Required")} class="max-w-[9.375rem] text-center" > {gettext("Required")} {gettext("Optional")} <:col :let={{_field_name, field_data}} label={gettext("Show in overview")} class="max-w-[9.375rem] text-center" > {gettext("Yes")} {gettext("No")} <:action :let={{_field_name, field_data}}> <.link phx-click="edit_member_field" phx-value-field={Atom.to_string(field_data.field)} phx-target={@myself} > {gettext("Edit")}
""" end @impl true def update(assigns, socket) do # Track previous show_form state to detect when form is closed previous_show_form = Map.get(socket.assigns, :show_form, false) # If show_form is explicitly provided in assigns, reset editing state socket = if Map.has_key?(assigns, :show_form) and assigns.show_form == false do socket |> assign(:editing_member_field, nil) |> assign(:form_id, "member-field-form-new") else socket end # Detect when form is closed (show_form changes from true to false) new_show_form = Map.get(assigns, :show_form, false) if previous_show_form and not new_show_form do send(self(), {:editing_section_changed, nil}) end {:ok, socket |> assign(assigns) |> assign_new(:settings, fn -> get_settings() end) |> assign_new(:show_form, fn -> false end) |> assign_new(:form_id, fn -> "member-field-form-new" end) |> assign_new(:editing_member_field, fn -> nil end)} end @impl true def handle_event("edit_member_field", %{"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 field_atom = String.to_existing_atom(field_string) # Only send event if form was not already open if not socket.assigns[:show_form] do send(self(), {:editing_section_changed, :member_fields}) end {:noreply, socket |> assign(:show_form, true) |> assign(:editing_member_field, field_atom) |> assign(:form_id, "member-field-form-#{field_string}")} 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 = VisibilityConfig.normalize(visibility_config) Enum.map(member_fields, fn field -> show_in_overview = Map.get(normalized_config, field, true) attribute = Info.attribute(Mv.Membership.Member, field) %{ field: field, show_in_overview: show_in_overview, value_type: (attribute && attribute.type) || :string, description: nil } end) |> Enum.map(fn field_data -> {Atom.to_string(field_data.field), field_data} end) end defp format_value_type(field) when is_atom(field) do case Info.attribute(Mv.Membership.Member, field) do nil -> FieldTypeFormatter.format(:string) attribute -> FieldTypeFormatter.format(attribute.type) end end # Check if a field is required by checking the actual attribute definition defp required?(field) when is_atom(field) do case Info.attribute(Mv.Membership.Member, field) do nil -> false attribute -> not attribute.allow_nil? end end defp required?(_), do: false end