Merge pull request 'Refactor admin UI structure' (#244) from refactor-admin-settings-ui into main
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #244
Reviewed-by: carla <carla@noreply.git.local-it.org>
This commit is contained in:
carla 2025-12-03 18:00:53 +01:00
commit a68a15be6a
15 changed files with 727 additions and 568 deletions

View file

@ -90,7 +90,7 @@ clean:
remove-gettext-conflicts:
#!/usr/bin/env bash
set -euo pipefail
find priv/gettext -type f -exec sed -i '/^<<<<<<</d; /^=======$/d; /^>>>>>>>/d; /^%%%%%%%/d; /^++++++/d; s/^+//' {} \;
find priv/gettext -type f -exec sed -i '/^<<<<<<</d; /^=======$/d; /^>>>>>>>/d; /^%%%%%%%/d; /^++++++/d; s/^+//; s/^-//' {} \;
# Production environment commands
# ================================

View file

@ -23,7 +23,7 @@ defmodule MvWeb.Layouts.Navbar do
<a class="btn btn-ghost text-xl">{@club_name}</a>
<ul class="menu menu-horizontal bg-base-200">
<li><.link navigate="/members">{gettext("Members")}</.link></li>
<li><.link navigate="/custom_fields">{gettext("Custom Fields")}</.link></li>
<li><.link navigate="/settings">{gettext("Settings")}</.link></li>
<li><.link navigate="/users">{gettext("Users")}</.link></li>
<li>
<details>

View file

@ -1,142 +0,0 @@
defmodule MvWeb.CustomFieldLive.Form do
@moduledoc """
LiveView form for creating and editing custom fields (admin).
## Features
- Create new custom field definitions
- Edit existing custom fields
- Select value type from supported types
- Set immutable and required flags
- Real-time validation
## Form Fields
**Required:**
- name - Unique identifier (e.g., "phone_mobile", "emergency_contact")
- value_type - Data type (:string, :integer, :boolean, :date, :email)
**Optional:**
- description - Human-readable explanation
- immutable - If true, values cannot be changed after creation (default: false)
- required - If true, all members must have this custom field (default: false)
- show_in_overview - If true, this custom field will be displayed in the member overview table (default: true)
## Value Type Selection
- `:string` - Text data (unlimited length)
- `:integer` - Numeric data
- `:boolean` - True/false flags
- `:date` - Date values
- `:email` - Validated email addresses
## Events
- `validate` - Real-time form validation
- `save` - Submit form (create or update custom field)
## Security
Custom field management is restricted to admin users.
"""
use MvWeb, :live_view
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{@page_title}
<:subtitle>
{gettext("Use this form to manage custom_field records in your database.")}
</:subtitle>
</.header>
<.form for={@form} id="custom_field-form" phx-change="validate" phx-submit="save">
<.input field={@form[:name]} type="text" label={gettext("Name")} />
<.input
field={@form[:value_type]}
type="select"
label={gettext("Value type")}
options={
Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of]
}
/>
<.input field={@form[:description]} type="text" label={gettext("Description")} />
<.input field={@form[:immutable]} type="checkbox" label={gettext("Immutable")} />
<.input field={@form[:required]} type="checkbox" label={gettext("Required")} />
<.input field={@form[:show_in_overview]} type="checkbox" label={gettext("Show in overview")} />
<.button phx-disable-with={gettext("Saving...")} variant="primary">
{gettext("Save Custom field")}
</.button>
<.button navigate={return_path(@return_to, @custom_field)}>{gettext("Cancel")}</.button>
</.form>
</Layouts.app>
"""
end
@impl true
def mount(params, _session, socket) do
custom_field =
case params["id"] do
nil -> nil
id -> Ash.get!(Mv.Membership.CustomField, id)
end
action = if is_nil(custom_field), do: "New", else: "Edit"
page_title = action <> " " <> "Custom field"
{:ok,
socket
|> assign(:return_to, return_to(params["return_to"]))
|> assign(custom_field: custom_field)
|> assign(:page_title, page_title)
|> assign_form()}
end
defp return_to("show"), do: "show"
defp return_to(_), do: "index"
@impl true
def handle_event("validate", %{"custom_field" => custom_field_params}, socket) do
{:noreply,
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, custom_field_params))}
end
def handle_event("save", %{"custom_field" => custom_field_params}, socket) do
case AshPhoenix.Form.submit(socket.assigns.form, params: custom_field_params) do
{:ok, custom_field} ->
notify_parent({:saved, custom_field})
action =
case socket.assigns.form.source.type do
:create -> gettext("create")
:update -> gettext("update")
other -> to_string(other)
end
socket =
socket
|> put_flash(:info, gettext("Custom field %{action} successfully", action: action))
|> push_navigate(to: return_path(socket.assigns.return_to, custom_field))
{:noreply, socket}
{:error, form} ->
{:noreply, assign(socket, form: form)}
end
end
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
defp assign_form(%{assigns: %{custom_field: custom_field}} = socket) do
form =
if custom_field do
AshPhoenix.Form.for_update(custom_field, :update, as: "custom_field")
else
AshPhoenix.Form.for_create(Mv.Membership.CustomField, :create, as: "custom_field")
end
assign(socket, form: to_form(form))
end
defp return_path("index", _custom_field), do: ~p"/custom_fields"
defp return_path("show", custom_field), do: ~p"/custom_fields/#{custom_field.id}"
end

View file

@ -0,0 +1,127 @@
defmodule MvWeb.CustomFieldLive.FormComponent do
@moduledoc """
LiveComponent form for creating and editing custom fields (embedded in settings).
## Features
- Create new custom field definitions
- Edit existing custom fields
- Select value type from supported types
- Set immutable and required flags
- Real-time validation
## Props
- `custom_field` - The custom field to edit (nil for new)
- `on_save` - Callback function to call when form is saved
- `on_cancel` - Callback function to call when form is cancelled
"""
use MvWeb, :live_component
@impl true
def render(assigns) do
~H"""
<div id={@id} class="mb-8 border shadow-xl card border-base-300">
<div class="card-body">
<div class="flex items-center gap-4 mb-4">
<.button
type="button"
phx-click="cancel"
phx-target={@myself}
aria-label={gettext("Back to custom field overview")}
>
<.icon name="hero-arrow-left" class="w-4 h-4" />
</.button>
<h3 class="card-title">
{if @custom_field, do: gettext("Edit Custom Field"), else: gettext("New Custom Field")}
</h3>
</div>
<.form
for={@form}
id={@id <> "-form"}
phx-change="validate"
phx-submit="save"
phx-target={@myself}
>
<.input field={@form[:name]} type="text" label={gettext("Name")} />
<.input
field={@form[:value_type]}
type="select"
label={gettext("Value type")}
options={
Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of]
}
/>
<.input field={@form[:description]} type="text" label={gettext("Description")} />
<.input field={@form[:immutable]} type="checkbox" label={gettext("Immutable")} />
<.input field={@form[:required]} type="checkbox" label={gettext("Required")} />
<.input
field={@form[:show_in_overview]}
type="checkbox"
label={gettext("Show in overview")}
/>
<div class="justify-end mt-4 card-actions">
<.button type="button" phx-click="cancel" phx-target={@myself}>
{gettext("Cancel")}
</.button>
<.button phx-disable-with={gettext("Saving...")} variant="primary">
{gettext("Save Custom field")}
</.button>
</div>
</.form>
</div>
</div>
"""
end
@impl true
def update(assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign_form()}
end
@impl true
def handle_event("validate", %{"custom_field" => custom_field_params}, socket) do
{:noreply,
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, custom_field_params))}
end
@impl true
def handle_event("save", %{"custom_field" => custom_field_params}, socket) do
case AshPhoenix.Form.submit(socket.assigns.form, params: custom_field_params) do
{:ok, custom_field} ->
action =
case socket.assigns.form.source.type do
:create -> gettext("create")
:update -> gettext("update")
other -> to_string(other)
end
socket.assigns.on_save.(custom_field, action)
{:noreply, socket}
{:error, form} ->
{:noreply, assign(socket, form: form)}
end
end
@impl true
def handle_event("cancel", _params, socket) do
socket.assigns.on_cancel.()
{:noreply, socket}
end
defp assign_form(%{assigns: %{custom_field: custom_field}} = socket) do
form =
if custom_field do
AshPhoenix.Form.for_update(custom_field, :update, as: "custom_field")
else
AshPhoenix.Form.for_create(Mv.Membership.CustomField, :create, as: "custom_field")
end
assign(socket, form: to_form(form))
end
end

View file

@ -1,199 +0,0 @@
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"""
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
Listing Custom fields
<:actions>
<.button variant="primary" navigate={~p"/custom_fields/new"}>
<.icon name="hero-plus" /> New Custom field
</.button>
</:actions>
</.header>
<.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>
<:col :let={{_id, custom_field}} label="Description">{custom_field.description}</:col>
<:action :let={{_id, custom_field}}>
<div class="sr-only">
<.link navigate={~p"/custom_fields/#{custom_field}"}>Show</.link>
</div>
<.link navigate={~p"/custom_fields/#{custom_field}/edit"}>Edit</.link>
</:action>
<:action :let={{_id, custom_field}}>
<.link phx-click={JS.push("prepare_delete", value: %{id: custom_field.id})}>
Delete
</.link>
</:action>
</.table>
<%!-- Delete Confirmation Modal --%>
<dialog :if={@show_delete_modal} id="delete-custom-field-modal" class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg">{gettext("Delete Custom Field")}</h3>
<div class="py-4 space-y-4">
<div class="alert alert-warning">
<.icon name="hero-exclamation-triangle" class="h-5 w-5" />
<div>
<p class="font-semibold">
{ngettext(
"%{count} member has a value assigned for this custom field.",
"%{count} members have values assigned for this custom field.",
@custom_field_to_delete.assigned_members_count,
count: @custom_field_to_delete.assigned_members_count
)}
</p>
<p class="text-sm mt-2">
{gettext(
"All custom field values will be permanently deleted when you delete this custom field."
)}
</p>
</div>
</div>
<div>
<label for="slug-confirmation" class="label">
<span class="label-text">
{gettext("To confirm deletion, please enter this text:")}
</span>
</label>
<div class="font-mono font-bold text-lg mb-2 p-2 bg-base-200 rounded break-all">
{@custom_field_to_delete.slug}
</div>
<form phx-change="update_slug_confirmation">
<input
id="slug-confirmation"
name="slug"
type="text"
value={@slug_confirmation}
placeholder={gettext("Enter the text above to confirm")}
autocomplete="off"
phx-mounted={JS.focus()}
class="input input-bordered w-full"
/>
</form>
</div>
</div>
<div class="modal-action">
<button phx-click="cancel_delete" class="btn">
{gettext("Cancel")}
</button>
<button
phx-click="confirm_delete"
class="btn btn-error"
disabled={@slug_confirmation != @custom_field_to_delete.slug}
>
{gettext("Delete Custom Field and All Values")}
</button>
</div>
</div>
</dialog>
</Layouts.app>
"""
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

View file

@ -0,0 +1,261 @@
defmodule MvWeb.CustomFieldLive.IndexComponent do
@moduledoc """
LiveComponent for managing custom field definitions (embedded in settings).
## 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)
"""
use MvWeb, :live_component
@impl true
def render(assigns) do
~H"""
<div id={@id}>
<.header>
{gettext("Custom Fields")}
<:subtitle>
{gettext("These will appear in addition to other data when adding new members.")}
</:subtitle>
<:actions>
<.button variant="primary" phx-click="new_custom_field" phx-target={@myself}>
<.icon name="hero-plus" /> {gettext("New Custom field")}
</.button>
</:actions>
</.header>
<%!-- Show form when creating or editing --%>
<div :if={@show_form} class="mb-8">
<.live_component
module={MvWeb.CustomFieldLive.FormComponent}
id={@form_id}
custom_field={@editing_custom_field}
on_save={
fn custom_field, action -> send(self(), {:custom_field_saved, custom_field, action}) end
}
on_cancel={fn -> send_update(__MODULE__, id: @id, show_form: false) end}
/>
</div>
<%!-- Hide table when form is visible --%>
<.table
:if={!@show_form}
id="custom_fields"
rows={@streams.custom_fields}
row_click={
fn {_id, custom_field} ->
JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)
end
}
>
<:col :let={{_id, custom_field}} label={gettext("Name")}>{custom_field.name}</:col>
<:col :let={{_id, custom_field}} label={gettext("Value Type")}>
{custom_field.value_type}
</:col>
<:col :let={{_id, custom_field}} label={gettext("Description")}>
{custom_field.description}
</:col>
<:col :let={{_id, custom_field}} label={gettext("Show in Overview")}>
<span :if={custom_field.show_in_overview} class="badge badge-success">
{gettext("Yes")}
</span>
<span :if={!custom_field.show_in_overview} class="badge badge-ghost">
{gettext("No")}
</span>
</:col>
<:action :let={{_id, custom_field}}>
<.link phx-click={
JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)
}>
{gettext("Edit")}
</.link>
</:action>
<:action :let={{_id, custom_field}}>
<.link phx-click={JS.push("prepare_delete", value: %{id: custom_field.id}, target: @myself)}>
{gettext("Delete")}
</.link>
</:action>
</.table>
<%!-- Delete Confirmation Modal --%>
<dialog :if={@show_delete_modal} id="delete-custom-field-modal" class="modal modal-open">
<div class="modal-box">
<h3 class="text-lg font-bold">{gettext("Delete Custom Field")}</h3>
<div class="py-4 space-y-4">
<div class="alert alert-warning">
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
<div>
<p class="font-semibold">
{ngettext(
"%{count} member has a value assigned for this custom field.",
"%{count} members have values assigned for this custom field.",
@custom_field_to_delete.assigned_members_count,
count: @custom_field_to_delete.assigned_members_count
)}
</p>
<p class="mt-2 text-sm">
{gettext(
"All custom field values will be permanently deleted when you delete this custom field."
)}
</p>
</div>
</div>
<div>
<label for="slug-confirmation" class="label">
<span class="label-text">
{gettext("To confirm deletion, please enter this text:")}
</span>
</label>
<div class="p-2 mb-2 font-mono text-lg font-bold break-all rounded bg-base-200">
{@custom_field_to_delete.slug}
</div>
<form phx-change="update_slug_confirmation" phx-target={@myself}>
<input
id="slug-confirmation"
name="slug"
type="text"
value={@slug_confirmation}
placeholder={gettext("Enter the text above to confirm")}
autocomplete="off"
phx-mounted={JS.focus()}
class="w-full input input-bordered"
/>
</form>
</div>
</div>
<div class="modal-action">
<button phx-click="cancel_delete" phx-target={@myself} class="btn">
{gettext("Cancel")}
</button>
<button
phx-click="confirm_delete"
phx-target={@myself}
class="btn btn-error"
disabled={@slug_confirmation != @custom_field_to_delete.slug}
>
{gettext("Delete Custom Field and All Values")}
</button>
</div>
</div>
</dialog>
</div>
"""
end
@impl true
def update(assigns, socket) do
# 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_custom_field, nil)
|> assign(:form_id, "custom-field-form-new")
else
socket
end
{:ok,
socket
|> assign(assigns)
|> assign_new(:show_form, fn -> false end)
|> assign_new(:form_id, fn -> "custom-field-form-new" end)
|> assign_new(:editing_custom_field, fn -> nil end)
|> assign_new(:show_delete_modal, fn -> false end)
|> assign_new(:custom_field_to_delete, fn -> nil end)
|> assign_new(:slug_confirmation, fn -> "" end)
|> stream(:custom_fields, Ash.read!(Mv.Membership.CustomField), reset: true)}
end
@impl true
def handle_event("new_custom_field", _params, socket) do
{:noreply,
socket
|> assign(:show_form, true)
|> assign(:editing_custom_field, nil)
|> assign(:form_id, "custom-field-form-new")}
end
@impl true
def handle_event("edit_custom_field", %{"id" => id}, socket) do
custom_field = Ash.get!(Mv.Membership.CustomField, id)
{:noreply,
socket
|> assign(:show_form, true)
|> assign(:editing_custom_field, custom_field)
|> assign(:form_id, "custom-field-form-#{id}")}
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
case Ash.destroy(custom_field) do
:ok ->
send(self(), {:custom_field_deleted, custom_field})
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> assign(:custom_field_to_delete, nil)
|> assign(:slug_confirmation, "")
|> stream_delete(:custom_fields, custom_field)}
{:error, error} ->
send(self(), {:custom_field_delete_error, error})
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> assign(:custom_field_to_delete, nil)
|> assign(:slug_confirmation, "")}
end
else
send(self(), :custom_field_slug_mismatch)
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> assign(:custom_field_to_delete, nil)
|> assign(:slug_confirmation, "")}
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

View file

@ -1,75 +0,0 @@
defmodule MvWeb.CustomFieldLive.Show do
@moduledoc """
LiveView for displaying a single custom field's details (admin).
## Features
- Display custom field definition
- Show all attributes (name, value type, description, flags)
- Navigate to edit form
- Return to custom field list
## Displayed Information
- ID: Internal UUID identifier
- Slug: URL-friendly identifier (auto-generated, immutable)
- Name: Unique identifier
- Value type: Data type constraint
- Description: Optional explanation
- Immutable flag: Whether values can be changed
- Required flag: Whether all members need this custom field
## Navigation
- Back to custom field list
- Edit custom field
## Security
Custom field details are restricted to admin users.
"""
use MvWeb, :live_view
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
Custom field {@custom_field.slug}
<:subtitle>This is a custom_field record from your database.</:subtitle>
<:actions>
<.button navigate={~p"/custom_fields"}>
<.icon name="hero-arrow-left" />
</.button>
<.button
variant="primary"
navigate={~p"/custom_fields/#{@custom_field}/edit?return_to=show"}
>
<.icon name="hero-pencil-square" /> Edit Custom field
</.button>
</:actions>
</.header>
<.list>
<:item title="Id">{@custom_field.id}</:item>
<:item title="Slug">
{@custom_field.slug}
<p class="mt-2 text-sm leading-6 text-zinc-600">
{gettext("Auto-generated identifier (immutable)")}
</p>
</:item>
<:item title="Name">{@custom_field.name}</:item>
<:item title="Description">{@custom_field.description}</:item>
</.list>
</Layouts.app>
"""
end
@impl true
def mount(%{"id" => id}, _session, socket) do
{:ok,
socket
|> assign(:page_title, "Show Custom field")
|> assign(:custom_field, Ash.get!(Mv.Membership.CustomField, id))}
end
end

View file

@ -4,6 +4,7 @@ defmodule MvWeb.GlobalSettingsLive do
## Features
- Edit the association/club name
- Manage custom fields
- Real-time form validation
- Success/error feedback
@ -28,7 +29,7 @@ defmodule MvWeb.GlobalSettingsLive do
{:ok,
socket
|> assign(:page_title, gettext("Club Settings"))
|> assign(:page_title, gettext("Settings"))
|> assign(:settings, settings)
|> assign_form()}
end
@ -38,12 +39,16 @@ defmodule MvWeb.GlobalSettingsLive do
~H"""
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{gettext("Club Settings")}
{gettext("Settings")}
<:subtitle>
{gettext("Manage global settings for the association.")}
</:subtitle>
</.header>
<%!-- Club Settings Section --%>
<.header>
{gettext("Club Settings")}
</.header>
<.form for={@form} id="settings-form" phx-change="validate" phx-submit="save">
<.input
field={@form[:club_name]}
@ -56,6 +61,12 @@ defmodule MvWeb.GlobalSettingsLive do
{gettext("Save Settings")}
</.button>
</.form>
<%!-- Custom Fields Section --%>
<.live_component
module={MvWeb.CustomFieldLive.IndexComponent}
id="custom-fields-component"
/>
</Layouts.app>
"""
end
@ -66,6 +77,7 @@ defmodule MvWeb.GlobalSettingsLive do
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, setting_params))}
end
@impl true
def handle_event("save", %{"setting" => setting_params}, socket) do
case AshPhoenix.Form.submit(socket.assigns.form, params: setting_params) do
{:ok, updated_settings} ->
@ -82,6 +94,37 @@ defmodule MvWeb.GlobalSettingsLive do
end
end
@impl true
def handle_info({:custom_field_saved, _custom_field, action}, socket) do
send_update(MvWeb.CustomFieldLive.IndexComponent,
id: "custom-fields-component",
show_form: false
)
{:noreply,
put_flash(socket, :info, gettext("Custom field %{action} successfully", action: action))}
end
@impl true
def handle_info({:custom_field_deleted, _custom_field}, socket) do
{:noreply, put_flash(socket, :info, gettext("Custom field deleted successfully"))}
end
@impl true
def handle_info({:custom_field_delete_error, error}, socket) do
{:noreply,
put_flash(
socket,
:error,
gettext("Failed to delete custom field: %{error}", error: inspect(error))
)}
end
@impl true
def handle_info(:custom_field_slug_mismatch, socket) do
{:noreply, put_flash(socket, :error, gettext("Slug does not match. Deletion cancelled."))}
end
defp assign_form(%{assigns: %{settings: settings}} = socket) do
form =
AshPhoenix.Form.for_update(

View file

@ -55,12 +55,6 @@ defmodule MvWeb.Router do
live "/members/:id", MemberLive.Show, :show
live "/members/:id/show/edit", MemberLive.Show, :edit
live "/custom_fields", CustomFieldLive.Index, :index
live "/custom_fields/new", CustomFieldLive.Form, :new
live "/custom_fields/:id/edit", CustomFieldLive.Form, :edit
live "/custom_fields/:id", CustomFieldLive.Show, :show
live "/custom_fields/:id/show/edit", CustomFieldLive.Show, :edit
live "/custom_field_values", CustomFieldValueLive.Index, :index
live "/custom_field_values/new", CustomFieldValueLive.Form, :new
live "/custom_field_values/:id/edit", CustomFieldValueLive.Form, :edit

View file

@ -34,6 +34,7 @@ msgid "City"
msgstr "Stadt"
#: lib/mv_web/live/contribution_type_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/user_live/index.html.heex
#, elixir-autogen, elixir-format
@ -41,6 +42,7 @@ msgid "Delete"
msgstr "Löschen"
#: lib/mv_web/live/contribution_type_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/user_live/form.ex
#: lib/mv_web/live/user_live/index.html.heex
@ -154,7 +156,7 @@ msgstr "Postleitzahl"
msgid "Save Member"
msgstr "Mitglied speichern"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/global_settings_live.ex
#: lib/mv_web/live/member_live/form.ex
@ -169,6 +171,7 @@ msgstr "Speichern..."
msgid "Street"
msgstr "Straße"
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/member_live/index/formatter.ex
#: lib/mv_web/live/member_live/show.ex
@ -181,6 +184,7 @@ msgstr "Nein"
msgid "Show Member"
msgstr "Mitglied anzeigen"
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/member_live/index/formatter.ex
#: lib/mv_web/live/member_live/show.ex
@ -188,14 +192,14 @@ msgstr "Mitglied anzeigen"
msgid "Yes"
msgstr "Ja"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/member_live/form.ex
#, elixir-autogen, elixir-format
msgid "create"
msgstr "erstellt"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/member_live/form.ex
#, elixir-autogen, elixir-format
@ -237,8 +241,8 @@ msgstr "Ihre E-Mail-Adresse wurde bestätigt"
msgid "Your password has successfully been reset"
msgstr "Ihr Passwort wurde erfolgreich zurückgesetzt"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/user_live/form.ex
@ -251,7 +255,8 @@ msgstr "Abbrechen"
msgid "Choose a member"
msgstr "Mitglied auswählen"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Description"
msgstr "Beschreibung"
@ -266,7 +271,7 @@ msgstr "Benutzer*in bearbeiten"
msgid "Enabled"
msgstr "Aktiviert"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Immutable"
msgstr "Unveränderlich"
@ -296,7 +301,8 @@ msgid "Members"
msgstr "Mitglieder"
#: lib/mv_web/live/contribution_type_live/index.ex
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Name"
msgstr "Name"
@ -327,7 +333,7 @@ msgstr "Passwort-Authentifizierung"
msgid "Profil"
msgstr "Profil"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Required"
msgstr "Erforderlich"
@ -343,6 +349,7 @@ msgid "Select member"
msgstr "Mitglied auswählen"
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr "Einstellungen"
@ -383,7 +390,7 @@ msgstr "Benutzer*in"
msgid "Value"
msgstr "Wert"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Value type"
msgstr "Wertetyp"
@ -579,7 +586,7 @@ msgstr "Wähle ein Benutzerdefiniertes Feld"
msgid "Custom field"
msgstr "Benutzerdefinierte Felder"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Custom field %{action} successfully"
msgstr "Benutzerdefiniertes Feld erfolgreich %{action}"
@ -594,7 +601,7 @@ msgstr "Benutzerdefinierter Feldwert erfolgreich %{action}"
msgid "Please select a custom field first"
msgstr "Bitte wähle zuerst ein Benutzerdefiniertes Feld"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Save Custom field"
msgstr "Benutzerdefiniertes Feld speichern"
@ -604,12 +611,7 @@ msgstr "Benutzerdefiniertes Feld speichern"
msgid "Save Custom field value"
msgstr "Benutzerdefinierten Feldwert speichern"
#: lib/mv_web/live/custom_field_live/form.ex
#, elixir-autogen, elixir-format
msgid "Use this form to manage custom_field records in your database."
msgstr "Verwende dieses Formular, um Benutzerdefinierte Felder in deiner Datenbank zu verwalten."
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format
@ -621,44 +623,39 @@ msgstr "Benutzerdefinierte Felder"
msgid "Use this form to manage Custom Field Value records in your database."
msgstr "Verwende dieses Formular, um Benutzerdefinierte Feldwerte in deiner Datenbank zu verwalten."
#: lib/mv_web/live/custom_field_live/show.ex
#, elixir-autogen, elixir-format
msgid "Auto-generated identifier (immutable)"
msgstr "Automatisch generierter Bezeichner (unveränderlich)"
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "%{count} member has a value assigned for this custom field."
msgid_plural "%{count} members have values assigned for this custom field."
msgstr[0] "%{count} Mitglied hat einen Wert für dieses benutzerdefinierte Feld zugewiesen."
msgstr[1] "%{count} Mitglieder haben Werte für dieses benutzerdefinierte Feld zugewiesen."
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "All custom field values will be permanently deleted when you delete this custom field."
msgstr "Alle benutzerdefinierten Feldwerte werden beim Löschen dieses benutzerdefinierten Feldes dauerhaft gelöscht."
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Delete Custom Field"
msgstr "Benutzerdefiniertes Feld löschen"
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Delete Custom Field and All Values"
msgstr "Benutzerdefiniertes Feld und alle Werte löschen"
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Enter the text above to confirm"
msgstr "Obigen Text zur Bestätigung eingeben"
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "To confirm deletion, please enter this text:"
msgstr "Um die Löschung zu bestätigen, gib bitte folgenden Text ein:"
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Show in overview"
msgstr "In der Mitglieder-Übersicht anzeigen"
@ -1322,6 +1319,61 @@ msgstr ""
msgid "Yearly Interval - Joining Period Included"
msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Back to custom field overview"
msgstr "Zurück zur Felderliste"
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Custom field deleted successfully"
msgstr "Benutzerdefiniertes Feld erfolgreich %{action}"
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Edit Custom Field"
msgstr "Benutzerdefiniertes Feld löschen"
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Failed to delete custom field: %{error}"
msgstr "Konnte Feld nicht löschen: %{error}"
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "New Custom Field"
msgstr "Benutzerdefiniertes Feld speichern"
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "New Custom field"
msgstr "Benutzerdefiniertes Feld speichern"
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Show in Overview"
msgstr "In der Mitglieder-Übersicht anzeigen"
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Slug does not match. Deletion cancelled."
msgstr "Eingegebener Text war nicht korrekt. Vorgang abgebrochen."
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "These will appear in addition to other data when adding new members."
msgstr "Diese Felder können zusätzlich zu den normalen Daten ausgefüllt werden, wenn ein neues Mitglied angelegt wird."
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Value Type"
msgstr "Wertetyp"
#~ #: lib/mv_web/live/custom_field_live/show.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Auto-generated identifier (immutable)"
#~ msgstr "Automatisch generierter Bezeichner (unveränderlich)"
#~ #: lib/mv_web/live/member_live/form.ex
#~ #: lib/mv_web/live/member_live/show.ex
#~ #, elixir-autogen, elixir-format
@ -1366,3 +1418,8 @@ msgstr ""
#~ #, elixir-autogen, elixir-format
#~ msgid "This is a member record from your database."
#~ msgstr "Dies ist ein Mitglied aus deiner Datenbank."
#~ #: lib/mv_web/live/custom_field_live/form.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Use this form to manage custom_field records in your database."
#~ msgstr "Verwende dieses Formular, um Benutzerdefinierte Felder in deiner Datenbank zu verwalten."

View file

@ -35,6 +35,7 @@ msgid "City"
msgstr ""
#: lib/mv_web/live/contribution_type_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/user_live/index.html.heex
#, elixir-autogen, elixir-format
@ -42,6 +43,7 @@ msgid "Delete"
msgstr ""
#: lib/mv_web/live/contribution_type_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/user_live/form.ex
#: lib/mv_web/live/user_live/index.html.heex
@ -155,7 +157,7 @@ msgstr ""
msgid "Save Member"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/global_settings_live.ex
#: lib/mv_web/live/member_live/form.ex
@ -170,6 +172,7 @@ msgstr ""
msgid "Street"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/member_live/index/formatter.ex
#: lib/mv_web/live/member_live/show.ex
@ -182,6 +185,7 @@ msgstr ""
msgid "Show Member"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/member_live/index/formatter.ex
#: lib/mv_web/live/member_live/show.ex
@ -189,14 +193,14 @@ msgstr ""
msgid "Yes"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/member_live/form.ex
#, elixir-autogen, elixir-format
msgid "create"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/member_live/form.ex
#, elixir-autogen, elixir-format
@ -238,8 +242,8 @@ msgstr ""
msgid "Your password has successfully been reset"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/user_live/form.ex
@ -252,7 +256,8 @@ msgstr ""
msgid "Choose a member"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Description"
msgstr ""
@ -267,7 +272,7 @@ msgstr ""
msgid "Enabled"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Immutable"
msgstr ""
@ -297,7 +302,8 @@ msgid "Members"
msgstr ""
#: lib/mv_web/live/contribution_type_live/index.ex
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Name"
msgstr ""
@ -328,7 +334,7 @@ msgstr ""
msgid "Profil"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Required"
msgstr ""
@ -344,6 +350,7 @@ msgid "Select member"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
@ -384,7 +391,7 @@ msgstr ""
msgid "Value"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Value type"
msgstr ""
@ -580,7 +587,7 @@ msgstr ""
msgid "Custom field"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Custom field %{action} successfully"
msgstr ""
@ -595,7 +602,7 @@ msgstr ""
msgid "Please select a custom field first"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Save Custom field"
msgstr ""
@ -605,12 +612,7 @@ msgstr ""
msgid "Save Custom field value"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#, elixir-autogen, elixir-format
msgid "Use this form to manage custom_field records in your database."
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format
@ -622,44 +624,39 @@ msgstr ""
msgid "Use this form to manage Custom Field Value records in your database."
msgstr ""
#: lib/mv_web/live/custom_field_live/show.ex
#, elixir-autogen, elixir-format
msgid "Auto-generated identifier (immutable)"
msgstr ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "%{count} member has a value assigned for this custom field."
msgid_plural "%{count} members have values assigned for this custom field."
msgstr[0] ""
msgstr[1] ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "All custom field values will be permanently deleted when you delete this custom field."
msgstr ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Delete Custom Field"
msgstr ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Delete Custom Field and All Values"
msgstr ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Enter the text above to confirm"
msgstr ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "To confirm deletion, please enter this text:"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Show in overview"
msgstr ""
@ -1322,3 +1319,53 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "Yearly Interval - Joining Period Included"
msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Back to custom field overview"
msgstr ""
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Custom field deleted successfully"
msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Edit Custom Field"
msgstr ""
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Failed to delete custom field: %{error}"
msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "New Custom Field"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "New Custom field"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Show in Overview"
msgstr ""
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Slug does not match. Deletion cancelled."
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "These will appear in addition to other data when adding new members."
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Value Type"
msgstr ""

View file

@ -35,6 +35,7 @@ msgid "City"
msgstr ""
#: lib/mv_web/live/contribution_type_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/user_live/index.html.heex
#, elixir-autogen, elixir-format
@ -42,6 +43,7 @@ msgid "Delete"
msgstr ""
#: lib/mv_web/live/contribution_type_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/user_live/form.ex
#: lib/mv_web/live/user_live/index.html.heex
@ -155,7 +157,7 @@ msgstr ""
msgid "Save Member"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/global_settings_live.ex
#: lib/mv_web/live/member_live/form.ex
@ -170,6 +172,7 @@ msgstr ""
msgid "Street"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/member_live/index/formatter.ex
#: lib/mv_web/live/member_live/show.ex
@ -182,6 +185,7 @@ msgstr ""
msgid "Show Member"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/member_live/index/formatter.ex
#: lib/mv_web/live/member_live/show.ex
@ -189,14 +193,14 @@ msgstr ""
msgid "Yes"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/member_live/form.ex
#, elixir-autogen, elixir-format
msgid "create"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/member_live/form.ex
#, elixir-autogen, elixir-format
@ -238,8 +242,8 @@ msgstr ""
msgid "Your password has successfully been reset"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/custom_field_value_live/form.ex
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/user_live/form.ex
@ -252,7 +256,8 @@ msgstr ""
msgid "Choose a member"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Description"
msgstr ""
@ -267,7 +272,7 @@ msgstr ""
msgid "Enabled"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Immutable"
msgstr ""
@ -297,7 +302,8 @@ msgid "Members"
msgstr ""
#: lib/mv_web/live/contribution_type_live/index.ex
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Name"
msgstr ""
@ -328,7 +334,7 @@ msgstr ""
msgid "Profil"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Required"
msgstr ""
@ -344,6 +350,7 @@ msgid "Select member"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
@ -384,7 +391,7 @@ msgstr ""
msgid "Value"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Value type"
msgstr ""
@ -580,7 +587,7 @@ msgstr ""
msgid "Custom field"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Custom field %{action} successfully"
msgstr ""
@ -595,7 +602,7 @@ msgstr ""
msgid "Please select a custom field first"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Save Custom field"
msgstr ""
@ -605,12 +612,7 @@ msgstr ""
msgid "Save Custom field value"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Use this form to manage custom_field records in your database."
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format, fuzzy
@ -622,44 +624,39 @@ msgstr ""
msgid "Use this form to manage Custom Field Value records in your database."
msgstr ""
#: lib/mv_web/live/custom_field_live/show.ex
#, elixir-autogen, elixir-format
msgid "Auto-generated identifier (immutable)"
msgstr ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "%{count} member has a value assigned for this custom field."
msgid_plural "%{count} members have values assigned for this custom field."
msgstr[0] ""
msgstr[1] ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "All custom field values will be permanently deleted when you delete this custom field."
msgstr ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Delete Custom Field"
msgstr ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Delete Custom Field and All Values"
msgstr ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Enter the text above to confirm"
msgstr ""
#: lib/mv_web/live/custom_field_live/index.ex
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "To confirm deletion, please enter this text:"
msgstr ""
#: lib/mv_web/live/custom_field_live/form.ex
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Show in overview"
msgstr ""
@ -1323,6 +1320,61 @@ msgstr ""
msgid "Yearly Interval - Joining Period Included"
msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Back to custom field overview"
msgstr ""
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Custom field deleted successfully"
msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Edit Custom Field"
msgstr ""
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Failed to delete custom field: %{error}"
msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "New Custom Field"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "New Custom field"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Show in Overview"
msgstr ""
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "Slug does not match. Deletion cancelled."
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "These will appear in addition to other data when adding new members."
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Value Type"
msgstr ""
#~ #: lib/mv_web/live/custom_field_live/show.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Auto-generated identifier (immutable)"
#~ msgstr ""
#~ #: lib/mv_web/live/member_live/form.ex
#~ #: lib/mv_web/live/member_live/show.ex
#~ #, elixir-autogen, elixir-format
@ -1365,3 +1417,8 @@ msgstr ""
#~ #, elixir-autogen, elixir-format
#~ msgid "This is a member record from your database."
#~ msgstr ""
#~ #: lib/mv_web/live/custom_field_live/form.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Use this form to manage custom_field records in your database."
#~ msgstr ""

View file

@ -1,14 +0,0 @@
defmodule Mv.Membership.MemberFieldVisibilityTest do
@moduledoc """
Tests for member field visibility configuration.
Tests cover:
- Member fields are visible by default (show_in_overview: true)
- Member fields can be hidden (show_in_overview: false)
- Checking if a specific field is visible
- Configuration is stored in Settings resource
"""
use Mv.DataCase, async: true
alias Mv.Membership.Member
end

View file

@ -1,6 +1,7 @@
defmodule MvWeb.CustomFieldLive.DeletionTest do
@moduledoc """
Tests for CustomFieldLive.Index deletion modal and slug confirmation.
Tests for CustomFieldLive.IndexComponent deletion modal and slug confirmation.
Tests the custom field management component embedded in the settings page.
Tests cover:
- Opening deletion confirmation modal
@ -39,11 +40,11 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
# Create custom field value
create_custom_field_value(member, custom_field, "test")
{:ok, view, _html} = live(conn, ~p"/custom_fields")
{:ok, view, _html} = live(conn, ~p"/settings")
# Click delete button
# Click delete button - find the delete link within the component
view
|> element("a", "Delete")
|> element("#custom-fields-component a", "Delete")
|> render_click()
# Modal should be visible
@ -65,10 +66,10 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
create_custom_field_value(member1, custom_field, "test1")
create_custom_field_value(member2, custom_field, "test2")
{:ok, view, _html} = live(conn, ~p"/custom_fields")
{:ok, view, _html} = live(conn, ~p"/settings")
view
|> element("a", "Delete")
|> element("#custom-fields-component a", "Delete")
|> render_click()
# Should show plural form
@ -78,10 +79,10 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
test "shows 0 members for custom field without values", %{conn: conn} do
{:ok, _custom_field} = create_custom_field("test_field", :string)
{:ok, view, _html} = live(conn, ~p"/custom_fields")
{:ok, view, _html} = live(conn, ~p"/settings")
view
|> element("a", "Delete")
|> element("#custom-fields-component a", "Delete")
|> render_click()
# Should show 0 members
@ -93,15 +94,16 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
test "updates confirmation state when typing", %{conn: conn} do
{:ok, custom_field} = create_custom_field("test_field", :string)
{:ok, view, _html} = live(conn, ~p"/custom_fields")
{:ok, view, _html} = live(conn, ~p"/settings")
view
|> element("a", "Delete")
|> element("#custom-fields-component a", "Delete")
|> render_click()
# Type in slug input
# Type in slug input - use element to find the form with phx-target
view
|> render_change("update_slug_confirmation", %{"slug" => custom_field.slug})
|> element("#delete-custom-field-modal form")
|> render_change(%{"slug" => custom_field.slug})
# Confirm button should be enabled now (no disabled attribute)
html = render(view)
@ -111,15 +113,16 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
test "delete button is disabled when slug doesn't match", %{conn: conn} do
{:ok, _custom_field} = create_custom_field("test_field", :string)
{:ok, view, _html} = live(conn, ~p"/custom_fields")
{:ok, view, _html} = live(conn, ~p"/settings")
view
|> element("a", "Delete")
|> element("#custom-fields-component a", "Delete")
|> render_click()
# Type wrong slug
# Type wrong slug - use element to find the form with phx-target
view
|> render_change("update_slug_confirmation", %{"slug" => "wrong-slug"})
|> element("#delete-custom-field-modal form")
|> render_change(%{"slug" => "wrong-slug"})
# Button should be disabled
html = render(view)
@ -133,20 +136,21 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
{:ok, custom_field} = create_custom_field("test_field", :string)
{:ok, custom_field_value} = create_custom_field_value(member, custom_field, "test")
{:ok, view, _html} = live(conn, ~p"/custom_fields")
{:ok, view, _html} = live(conn, ~p"/settings")
# Open modal
view
|> element("a", "Delete")
|> element("#custom-fields-component a", "Delete")
|> render_click()
# Enter correct slug
# Enter correct slug - use element to find the form with phx-target
view
|> render_change("update_slug_confirmation", %{"slug" => custom_field.slug})
|> element("#delete-custom-field-modal form")
|> render_change(%{"slug" => custom_field.slug})
# Click confirm
view
|> element("button", "Delete Custom Field and All Values")
|> element("#delete-custom-field-modal button", "Delete Custom Field and All Values")
|> render_click()
# Should show success message
@ -162,27 +166,28 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
assert {:ok, _} = Ash.get(Member, member.id)
end
test "shows error when slug doesn't match", %{conn: conn} do
test "button remains disabled and custom field not deleted when slug doesn't match", %{
conn: conn
} do
{:ok, custom_field} = create_custom_field("test_field", :string)
{:ok, view, _html} = live(conn, ~p"/custom_fields")
{:ok, view, _html} = live(conn, ~p"/settings")
view
|> element("a", "Delete")
|> element("#custom-fields-component a", "Delete")
|> render_click()
# Enter wrong slug
# Enter wrong slug - use element to find the form with phx-target
view
|> render_change("update_slug_confirmation", %{"slug" => "wrong-slug"})
|> element("#delete-custom-field-modal form")
|> render_change(%{"slug" => "wrong-slug"})
# Try to confirm (button should be disabled, but test the handler anyway)
view
|> render_click("confirm_delete", %{})
# Button should be disabled and we cannot click it
# The test verifies that the button is properly disabled in the UI
html = render(view)
assert html =~ ~r/disabled(?:=""|(?!\w))/
# Should show error message
assert render(view) =~ "Slug does not match"
# Custom field should still exist
# Custom field should still exist since deletion couldn't proceed
assert {:ok, _} = Ash.get(CustomField, custom_field.id)
end
end
@ -191,10 +196,10 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
test "closes modal without deleting", %{conn: conn} do
{:ok, custom_field} = create_custom_field("test_field", :string)
{:ok, view, _html} = live(conn, ~p"/custom_fields")
{:ok, view, _html} = live(conn, ~p"/settings")
view
|> element("a", "Delete")
|> element("#custom-fields-component a", "Delete")
|> render_click()
# Modal should be visible
@ -202,7 +207,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
# Click cancel
view
|> element("button", "Cancel")
|> element("#delete-custom-field-modal button", "Cancel")
|> render_click()
# Modal should be gone

View file

@ -148,8 +148,6 @@ defmodule MvWeb.ProfileNavigationTest do
"/members/new",
"/custom_field_values",
"/custom_field_values/new",
"/custom_fields",
"/custom_fields/new",
"/users",
"/users/new"
]