defmodule MvWeb.CustomFieldLive.Index do @moduledoc """ LiveView for managing custom field definitions (admin). ## Features - List all custom fields - Display type information (name, value type, description) - Show immutable and required flags - Create new custom fields - Edit existing custom fields - Delete custom fields with confirmation (cascades to all custom field values) ## Displayed Information - Name: Unique identifier for the custom field - Value type: Data type constraint (string, integer, boolean, date, email) - Description: Human-readable explanation - Immutable: Whether custom field values can be changed after creation - Required: Whether all members must have this custom field (future feature) ## Events - `prepare_delete` - Opens deletion confirmation modal with member count - `confirm_delete` - Executes deletion after slug verification - `cancel_delete` - Cancels deletion and closes modal - `update_slug_confirmation` - Updates slug input state ## Security Custom field management is restricted to admin users. Deletion requires entering the custom field's slug to prevent accidental deletions. """ use MvWeb, :live_view @impl true def render(assigns) do ~H""" <.header> Listing Custom fields <:actions> <.button variant="primary" navigate={~p"/custom_fields/new"}> <.icon name="hero-plus" /> New Custom field <.table id="custom_fields" rows={@streams.custom_fields} row_click={fn {_id, custom_field} -> JS.navigate(~p"/custom_fields/#{custom_field}") end} > <:col :let={{_id, custom_field}} label="Name">{custom_field.name} <:col :let={{_id, custom_field}} label="Description">{custom_field.description} <:action :let={{_id, custom_field}}>
<.link navigate={~p"/custom_fields/#{custom_field}"}>Show
<.link navigate={~p"/custom_fields/#{custom_field}/edit"}>Edit <:action :let={{_id, custom_field}}> <.link phx-click={JS.push("prepare_delete", value: %{id: custom_field.id})}> Delete <%!-- Delete Confirmation Modal --%>
""" end @impl true def mount(_params, _session, socket) do {:ok, socket |> assign(:page_title, "Listing Custom fields") |> assign(:show_delete_modal, false) |> assign(:custom_field_to_delete, nil) |> assign(:slug_confirmation, "") |> stream(:custom_fields, Ash.read!(Mv.Membership.CustomField))} end @impl true def handle_event("prepare_delete", %{"id" => id}, socket) do custom_field = Ash.get!(Mv.Membership.CustomField, id, load: [:assigned_members_count]) {:noreply, socket |> assign(:custom_field_to_delete, custom_field) |> assign(:show_delete_modal, true) |> assign(:slug_confirmation, "")} end @impl true def handle_event("update_slug_confirmation", %{"slug" => slug}, socket) do {:noreply, assign(socket, :slug_confirmation, slug)} end @impl true def handle_event("confirm_delete", _params, socket) do custom_field = socket.assigns.custom_field_to_delete if socket.assigns.slug_confirmation == custom_field.slug do # Delete the custom field (CASCADE will handle custom field values) case Ash.destroy(custom_field) do :ok -> {:noreply, socket |> put_flash(:info, "Custom field deleted successfully") |> assign(:show_delete_modal, false) |> assign(:custom_field_to_delete, nil) |> assign(:slug_confirmation, "") |> stream_delete(:custom_fields, custom_field)} {:error, error} -> {:noreply, socket |> put_flash(:error, "Failed to delete custom field: #{inspect(error)}")} end else {:noreply, socket |> put_flash(:error, "Slug does not match. Deletion cancelled.")} end end @impl true def handle_event("cancel_delete", _params, socket) do {:noreply, socket |> assign(:show_delete_modal, false) |> assign(:custom_field_to_delete, nil) |> assign(:slug_confirmation, "")} end end