WIP: Implements settings for member fields closes #223 #300
10 changed files with 1530 additions and 139 deletions
|
|
@ -17,8 +17,7 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
|
||||||
assigns = assign(assigns, :field_type_label, &MvWeb.Translations.FieldTypes.label/1)
|
assigns = assign(assigns, :field_type_label, &MvWeb.Translations.FieldTypes.label/1)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div id={@id}>
|
<div id={@id} class="mt-8">
|
||||||
<.form_section title={gettext("Custom Fields")}>
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<p class="text-sm text-base-content/70">
|
<p class="text-sm text-base-content/70">
|
||||||
{gettext("These will appear in addition to other data when adding new members.")}
|
{gettext("These will appear in addition to other data when adding new members.")}
|
||||||
|
|
@ -68,6 +67,19 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
|
||||||
{custom_field.description}
|
{custom_field.description}
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
|
<:col
|
||||||
|
:let={{_id, custom_field}}
|
||||||
|
label={gettext("Required")}
|
||||||
|
class="max-w-[9.375rem] text-center"
|
||||||
|
>
|
||||||
|
<span :if={custom_field.required} class="text-base-content font-semibold">
|
||||||
|
{gettext("Required")}
|
||||||
|
</span>
|
||||||
|
<span :if={!custom_field.required} class="text-base-content/70">
|
||||||
|
{gettext("Optional")}
|
||||||
|
</span>
|
||||||
|
</:col>
|
||||||
|
|
||||||
<:col
|
<:col
|
||||||
:let={{_id, custom_field}}
|
:let={{_id, custom_field}}
|
||||||
label={gettext("Show in overview")}
|
label={gettext("Show in overview")}
|
||||||
|
|
@ -90,9 +102,7 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
|
||||||
</:action>
|
</:action>
|
||||||
|
|
||||||
<:action :let={{_id, custom_field}}>
|
<:action :let={{_id, custom_field}}>
|
||||||
<.link phx-click={
|
<.link phx-click={JS.push("prepare_delete", value: %{id: custom_field.id}, target: @myself)}>
|
||||||
JS.push("prepare_delete", value: %{id: custom_field.id}, target: @myself)
|
|
||||||
}>
|
|
||||||
{gettext("Delete")}
|
{gettext("Delete")}
|
||||||
</.link>
|
</.link>
|
||||||
</:action>
|
</:action>
|
||||||
|
|
@ -162,7 +172,6 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
</.form_section>
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,19 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
</.button>
|
</.button>
|
||||||
</.form>
|
</.form>
|
||||||
</.form_section>
|
</.form_section>
|
||||||
|
<%!-- Memberdata Section --%>
|
||||||
|
<.form_section title={gettext("Memberdata")}>
|
||||||
|
<.live_component
|
||||||
|
module={MvWeb.MemberFieldLive.IndexComponent}
|
||||||
|
id="member-fields-component"
|
||||||
|
settings={@settings}
|
||||||
|
/>
|
||||||
<%!-- Custom Fields Section --%>
|
<%!-- Custom Fields Section --%>
|
||||||
<.live_component
|
<.live_component
|
||||||
module={MvWeb.CustomFieldLive.IndexComponent}
|
module={MvWeb.CustomFieldLive.IndexComponent}
|
||||||
id="custom-fields-component"
|
id="custom-fields-component"
|
||||||
/>
|
/>
|
||||||
|
</.form_section>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
@ -125,6 +133,51 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
{:noreply, put_flash(socket, :error, gettext("Slug does not match. Deletion cancelled."))}
|
{:noreply, put_flash(socket, :error, gettext("Slug does not match. Deletion cancelled."))}
|
||||||
end
|
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
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({:member_field_saved, _member_field, action}, socket) do
|
||||||
|
# Reload settings to get updated member_field_visibility
|
||||||
|
{:ok, updated_settings} = Membership.get_settings()
|
||||||
|
|
||||||
|
# Send update to member fields component to close form
|
||||||
|
send_update(MvWeb.MemberFieldLive.IndexComponent,
|
||||||
|
id: "member-fields-component",
|
||||||
|
show_form: false,
|
||||||
|
settings: updated_settings
|
||||||
|
)
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:settings, updated_settings)
|
||||||
|
|> put_flash(:info, gettext("Member field %{action} successfully", action: action))}
|
||||||
|
end
|
||||||
|
|
||||||
defp assign_form(%{assigns: %{settings: settings}} = socket) do
|
defp assign_form(%{assigns: %{settings: settings}} = socket) do
|
||||||
form =
|
form =
|
||||||
AshPhoenix.Form.for_update(
|
AshPhoenix.Form.for_update(
|
||||||
|
|
|
||||||
445
lib/mv_web/live/member_field_live/form_component.ex
Normal file
445
lib/mv_web/live/member_field_live/form_component.ex
Normal file
|
|
@ -0,0 +1,445 @@
|
||||||
|
defmodule MvWeb.MemberFieldLive.FormComponent do
|
||||||
|
@moduledoc """
|
||||||
|
LiveComponent form for editing member field properties (embedded in settings).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Edit member field properties (name, value type, description, immutable, required, show in overview)
|
||||||
|
- Display member field information from Member Resource
|
||||||
|
- Restrict editing for email field (only show_in_overview can be changed)
|
||||||
|
- Real-time validation
|
||||||
|
- Updates Settings.member_field_visibility
|
||||||
|
|
||||||
|
## Props
|
||||||
|
- `member_field` - The member field atom to edit (e.g., :first_name, :email)
|
||||||
|
- `settings` - The current Settings resource
|
||||||
|
- `on_save` - Callback function to call when form is saved
|
||||||
|
- `on_cancel` - Callback function to call when form is cancelled
|
||||||
|
"""
|
||||||
|
use MvWeb, :live_component
|
||||||
|
|
||||||
|
alias Mv.Membership
|
||||||
|
alias MvWeb.Translations.MemberFields
|
||||||
|
alias MvWeb.Translations.FieldTypes
|
||||||
|
|
||||||
|
@required_fields [:first_name, :last_name, :email]
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def render(assigns) do
|
||||||
|
assigns =
|
||||||
|
assigns
|
||||||
|
|> assign(:field_attributes, get_field_attributes(assigns.member_field))
|
||||||
|
|> assign(:is_email_field?, assigns.member_field == :email)
|
||||||
|
|> assign(:field_label, MemberFields.label(assigns.member_field))
|
||||||
|
|
||||||
|
~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 member field overview")}
|
||||||
|
>
|
||||||
|
<.icon name="hero-arrow-left" class="w-4 h-4" />
|
||||||
|
</.button>
|
||||||
|
<h3 class="card-title">
|
||||||
|
{gettext("Edit Field: %{field}", field: @field_label)}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<.form
|
||||||
|
for={@form}
|
||||||
|
id={@id <> "-form"}
|
||||||
|
phx-change="validate"
|
||||||
|
phx-submit="save"
|
||||||
|
phx-target={@myself}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="tooltip tooltip-right"
|
||||||
|
data-tip={gettext("This is a technical field and cannot be changed")}
|
||||||
|
aria-label={gettext("This is a technical field and cannot be changed")}
|
||||||
|
>
|
||||||
|
<fieldset class="mb-2 fieldset">
|
||||||
|
<label>
|
||||||
|
<span class="mb-1 label flex items-center gap-2">
|
||||||
|
{gettext("Name")}
|
||||||
|
<.icon
|
||||||
|
name="hero-information-circle"
|
||||||
|
class="w-4 h-4 text-base-content/60 cursor-help"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name={@form[:name].name}
|
||||||
|
id={@form[:name].id}
|
||||||
|
value={@field_label}
|
||||||
|
disabled
|
||||||
|
readonly
|
||||||
|
class="w-full input"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="tooltip tooltip-right"
|
||||||
|
data-tip={gettext("This is a technical field and cannot be changed")}
|
||||||
|
aria-label={gettext("This is a technical field and cannot be changed")}
|
||||||
|
>
|
||||||
|
<fieldset class="mb-2 fieldset">
|
||||||
|
<label>
|
||||||
|
<span class="mb-1 label flex items-center gap-2">
|
||||||
|
{gettext("Value type")}
|
||||||
|
<.icon
|
||||||
|
name="hero-information-circle"
|
||||||
|
class="w-4 h-4 text-base-content/60 cursor-help"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name={@form[:value_type].name}
|
||||||
|
id={@form[:value_type].id}
|
||||||
|
value={format_value_type(@field_attributes.value_type)}
|
||||||
|
disabled
|
||||||
|
readonly
|
||||||
|
class="w-full input"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
:if={@is_email_field?}
|
||||||
|
class="tooltip tooltip-right"
|
||||||
|
data-tip={gettext("This is a technical field and cannot be changed")}
|
||||||
|
aria-label={gettext("This is a technical field and cannot be changed")}
|
||||||
|
>
|
||||||
|
<fieldset class="mb-2 fieldset">
|
||||||
|
<label>
|
||||||
|
<span class="mb-1 label flex items-center gap-2">
|
||||||
|
{gettext("Description")}
|
||||||
|
<.icon
|
||||||
|
name="hero-information-circle"
|
||||||
|
class="w-4 h-4 text-base-content/60 cursor-help"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name={@form[:description].name}
|
||||||
|
id={@form[:description].id}
|
||||||
|
value={@form[:description].value}
|
||||||
|
disabled
|
||||||
|
readonly
|
||||||
|
class="w-full input"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<.input
|
||||||
|
:if={not @is_email_field?}
|
||||||
|
field={@form[:description]}
|
||||||
|
type="text"
|
||||||
|
label={gettext("Description")}
|
||||||
|
disabled={@is_email_field?}
|
||||||
|
readonly={@is_email_field?}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
:if={@is_email_field?}
|
||||||
|
class="tooltip tooltip-right"
|
||||||
|
data-tip={gettext("This is a technical field and cannot be changed")}
|
||||||
|
aria-label={gettext("This is a technical field and cannot be changed")}
|
||||||
|
>
|
||||||
|
<fieldset class="mb-2 fieldset">
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name={@form[:immutable].name} value="false" disabled />
|
||||||
|
<span class="label flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name={@form[:immutable].name}
|
||||||
|
id={@form[:immutable].id}
|
||||||
|
value="true"
|
||||||
|
checked={@form[:immutable].value}
|
||||||
|
disabled
|
||||||
|
readonly
|
||||||
|
class="checkbox checkbox-sm"
|
||||||
|
/>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
{gettext("Immutable")}
|
||||||
|
<.icon
|
||||||
|
name="hero-information-circle"
|
||||||
|
class="w-4 h-4 text-base-content/60 cursor-help"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<.input
|
||||||
|
:if={not @is_email_field?}
|
||||||
|
field={@form[:immutable]}
|
||||||
|
type="checkbox"
|
||||||
|
label={gettext("Immutable")}
|
||||||
|
disabled={@is_email_field?}
|
||||||
|
readonly={@is_email_field?}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
:if={@is_email_field?}
|
||||||
|
class="tooltip tooltip-right"
|
||||||
|
data-tip={gettext("This is a technical field and cannot be changed")}
|
||||||
|
aria-label={gettext("This is a technical field and cannot be changed")}
|
||||||
|
>
|
||||||
|
<fieldset class="mb-2 fieldset">
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name={@form[:required].name} value="false" disabled />
|
||||||
|
<span class="label flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name={@form[:required].name}
|
||||||
|
id={@form[:required].id}
|
||||||
|
value="true"
|
||||||
|
checked={@form[:required].value}
|
||||||
|
disabled
|
||||||
|
readonly
|
||||||
|
class="checkbox checkbox-sm"
|
||||||
|
/>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
{gettext("Required")}
|
||||||
|
<.icon
|
||||||
|
name="hero-information-circle"
|
||||||
|
class="w-4 h-4 text-base-content/60 cursor-help"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<.input
|
||||||
|
:if={not @is_email_field?}
|
||||||
|
field={@form[:required]}
|
||||||
|
type="checkbox"
|
||||||
|
label={gettext("Required")}
|
||||||
|
disabled={@is_email_field?}
|
||||||
|
readonly={@is_email_field?}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<.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 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", %{"member_field" => member_field_params}, socket) do
|
||||||
|
# For member fields, we only validate show_in_overview
|
||||||
|
# Other fields are read-only or derived from the Member Resource
|
||||||
|
form = socket.assigns.form
|
||||||
|
|
||||||
|
updated_params =
|
||||||
|
member_field_params
|
||||||
|
|> Map.put("show_in_overview", parse_boolean(member_field_params["show_in_overview"]))
|
||||||
|
|> Map.put("name", form.source["name"])
|
||||||
|
|> Map.put("value_type", form.source["value_type"])
|
||||||
|
|> Map.put("description", form.source["description"])
|
||||||
|
|> Map.put("immutable", form.source["immutable"])
|
||||||
|
|> Map.put("required", form.source["required"])
|
||||||
|
|
||||||
|
updated_form =
|
||||||
|
form
|
||||||
|
|> Map.put(:value, updated_params)
|
||||||
|
|> Map.put(:errors, [])
|
||||||
|
|
||||||
|
{:noreply, assign(socket, form: updated_form)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("save", %{"member_field" => member_field_params}, socket) do
|
||||||
|
# Only show_in_overview can be changed for member fields
|
||||||
|
show_in_overview = parse_boolean(member_field_params["show_in_overview"])
|
||||||
|
|
||||||
|
# Get current visibility config and update only the current field
|
||||||
|
current_visibility = socket.assigns.settings.member_field_visibility || %{}
|
||||||
|
field_string = Atom.to_string(socket.assigns.member_field)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Update the specific field
|
||||||
|
updated_visibility = Map.put(normalized_visibility, field_string, show_in_overview)
|
||||||
|
|
||||||
|
# Update settings with new visibility
|
||||||
|
case Membership.update_member_field_visibility(
|
||||||
|
socket.assigns.settings,
|
||||||
|
updated_visibility
|
||||||
|
) do
|
||||||
|
{:ok, _updated_settings} ->
|
||||||
|
socket.assigns.on_save.(socket.assigns.member_field, "update")
|
||||||
|
{:noreply, socket}
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
# Add error to form
|
||||||
|
form =
|
||||||
|
socket.assigns.form
|
||||||
|
|> Map.put(:errors, [
|
||||||
|
%{field: :show_in_overview, message: format_error(error)}
|
||||||
|
])
|
||||||
|
|
||||||
|
{:noreply, assign(socket, form: form)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("cancel", _params, socket) do
|
||||||
|
socket.assigns.on_cancel.()
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
|
||||||
|
defp assign_form(%{assigns: %{member_field: member_field, settings: settings}} = socket) do
|
||||||
|
field_attributes = get_field_attributes(member_field)
|
||||||
|
visibility_config = settings.member_field_visibility || %{}
|
||||||
|
normalized_config = normalize_visibility_config(visibility_config)
|
||||||
|
show_in_overview = Map.get(normalized_config, member_field, true)
|
||||||
|
|
||||||
|
# Create a manual form structure with string keys
|
||||||
|
form_data = %{
|
||||||
|
"name" => MemberFields.label(member_field),
|
||||||
|
"value_type" => format_value_type(field_attributes.value_type),
|
||||||
|
"description" => field_attributes.description || "",
|
||||||
|
"immutable" => field_attributes.immutable,
|
||||||
|
"required" => field_attributes.required,
|
||||||
|
"show_in_overview" => show_in_overview
|
||||||
|
}
|
||||||
|
|
||||||
|
form = to_form(form_data, as: "member_field")
|
||||||
|
|
||||||
|
assign(socket, form: form)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_field_attributes(field) when is_atom(field) do
|
||||||
|
# Get attribute info from Member Resource
|
||||||
|
case Ash.Resource.Info.attribute(Mv.Membership.Member, field) do
|
||||||
|
nil ->
|
||||||
|
# Fallback for fields not in resource (shouldn't happen with Constants)
|
||||||
|
%{
|
||||||
|
value_type: :string,
|
||||||
|
description: nil,
|
||||||
|
immutable: field == :email,
|
||||||
|
required: field in @required_fields
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute ->
|
||||||
|
%{
|
||||||
|
value_type: attribute.type,
|
||||||
|
description: nil,
|
||||||
|
immutable: field == :email,
|
||||||
|
required: not attribute.allow_nil?
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_value_type(type) when is_atom(type) do
|
||||||
|
type_string = to_string(type)
|
||||||
|
|
||||||
|
# Check if it's an Ash type module (e.g., Ash.Type.String or Elixir.Ash.Type.String)
|
||||||
|
if String.contains?(type_string, "Ash.Type.") do
|
||||||
|
# Extract the base type name from Ash type modules
|
||||||
|
# e.g., "Elixir.Ash.Type.String" -> "String" -> :string
|
||||||
|
type_name =
|
||||||
|
type_string
|
||||||
|
|> String.split(".")
|
||||||
|
|> List.last()
|
||||||
|
|> String.downcase()
|
||||||
|
|
||||||
|
try do
|
||||||
|
type_atom = String.to_existing_atom(type_name)
|
||||||
|
FieldTypes.label(type_atom)
|
||||||
|
rescue
|
||||||
|
ArgumentError ->
|
||||||
|
# Fallback if atom doesn't exist
|
||||||
|
FieldTypes.label(:string)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# It's already an atom like :string, :boolean, :date
|
||||||
|
FieldTypes.label(type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_value_type(type) do
|
||||||
|
# Fallback for unknown types
|
||||||
|
to_string(type)
|
||||||
|
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 parse_boolean(value) when is_boolean(value), do: value
|
||||||
|
defp parse_boolean("true"), do: true
|
||||||
|
defp parse_boolean("false"), do: false
|
||||||
|
defp parse_boolean(1), do: true
|
||||||
|
defp parse_boolean(0), do: false
|
||||||
|
defp parse_boolean(_), do: false
|
||||||
|
|
||||||
|
defp format_error(%Ash.Error.Invalid{} = error) do
|
||||||
|
Ash.ErrorKind.message(error)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_error(error) do
|
||||||
|
inspect(error)
|
||||||
|
end
|
||||||
|
end
|
||||||
250
lib/mv_web/live/member_field_live/index_component.ex
Normal file
250
lib/mv_web/live/member_field_live/index_component.ex
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
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)
|
||||||
|
- Edit member field properties (expandable form like custom fields)
|
||||||
|
- Updates Settings.member_field_visibility
|
||||||
|
"""
|
||||||
|
use MvWeb, :live_component
|
||||||
|
|
||||||
|
alias Mv.Membership
|
||||||
|
alias MvWeb.Translations.MemberFields
|
||||||
|
alias MvWeb.Translations.FieldTypes
|
||||||
|
|
||||||
|
@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}>
|
||||||
|
<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. Thus you cannot delete these fields but hide them in the member overview."
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<%!-- Show form when editing --%>
|
||||||
|
<div :if={@show_form} class="mb-8">
|
||||||
|
<.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}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%!-- 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>
|
||||||
|
|
||||||
|
<:col :let={{_field_name, field_data}} label={gettext("Value Type")}>
|
||||||
|
{format_value_type(field_data.field)}
|
||||||
|
</:col>
|
||||||
|
|
||||||
|
<:col :let={{_field_name, field_data}} label={gettext("Description")}>
|
||||||
|
{field_data.description || ""}
|
||||||
|
</: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}}>
|
||||||
|
<.link
|
||||||
|
phx-click="edit_member_field"
|
||||||
|
phx-value-field={Atom.to_string(field_data.field)}
|
||||||
|
phx-target={@myself}
|
||||||
|
>
|
||||||
|
{gettext("Edit")}
|
||||||
|
</.link>
|
||||||
|
</:action>
|
||||||
|
</.table>
|
||||||
|
</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_member_field, nil)
|
||||||
|
|> assign(:form_id, "member-field-form-new")
|
||||||
|
else
|
||||||
|
socket
|
||||||
|
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)
|
||||||
|
|
||||||
|
{: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 = normalize_visibility_config(visibility_config)
|
||||||
|
|
||||||
|
Enum.map(member_fields, fn field ->
|
||||||
|
show_in_overview = Map.get(normalized_config, field, true)
|
||||||
|
attribute = Ash.Resource.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 Ash.Resource.Info.attribute(Mv.Membership.Member, field) do
|
||||||
|
nil -> FieldTypes.label(:string)
|
||||||
|
attribute -> format_value_type(attribute.type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_value_type(type) when is_atom(type) do
|
||||||
|
type_string = to_string(type)
|
||||||
|
|
||||||
|
# Check if it's an Ash type module (e.g., Ash.Type.String or Elixir.Ash.Type.String)
|
||||||
|
if String.contains?(type_string, "Ash.Type.") do
|
||||||
|
# Extract the base type name from Ash type modules
|
||||||
|
# e.g., "Elixir.Ash.Type.String" -> "String" -> :string
|
||||||
|
type_name =
|
||||||
|
type_string
|
||||||
|
|> String.split(".")
|
||||||
|
|> List.last()
|
||||||
|
|> String.downcase()
|
||||||
|
|
||||||
|
try do
|
||||||
|
type_atom = String.to_existing_atom(type_name)
|
||||||
|
FieldTypes.label(type_atom)
|
||||||
|
rescue
|
||||||
|
ArgumentError ->
|
||||||
|
# Fallback if atom doesn't exist
|
||||||
|
FieldTypes.label(:string)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# It's already an atom like :string, :boolean, :date
|
||||||
|
FieldTypes.label(type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_value_type(type) do
|
||||||
|
# Fallback for unknown types
|
||||||
|
to_string(type)
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
@ -44,6 +44,7 @@ msgstr "Löschen"
|
||||||
|
|
||||||
#: lib/mv_web/live/contribution_type_live/index.ex
|
#: lib/mv_web/live/contribution_type_live/index.ex
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#: lib/mv_web/live/user_live/index.html.heex
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
|
@ -169,6 +170,7 @@ msgstr "Mitglied speichern"
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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/custom_field_value_live/form.ex
|
||||||
#: lib/mv_web/live/global_settings_live.ex
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#: lib/mv_web/live/member_live/form.ex
|
#: lib/mv_web/live/member_live/form.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -183,6 +185,7 @@ msgid "Street"
|
||||||
msgstr "Straße"
|
msgstr "Straße"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/member_live/index/formatter.ex
|
#: lib/mv_web/live/member_live/index/formatter.ex
|
||||||
#: lib/mv_web/live/member_live/show.ex
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
|
|
@ -196,6 +199,7 @@ msgid "Show Member"
|
||||||
msgstr "Mitglied anzeigen"
|
msgstr "Mitglied anzeigen"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/member_live/index/formatter.ex
|
#: lib/mv_web/live/member_live/index/formatter.ex
|
||||||
#: lib/mv_web/live/member_live/show.ex
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
|
|
@ -255,6 +259,7 @@ msgstr "Ihr Passwort wurde erfolgreich zurückgesetzt"
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex
|
#: lib/mv_web/live/custom_field_value_live/form.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#: lib/mv_web/live/member_live/form.ex
|
#: lib/mv_web/live/member_live/form.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -268,6 +273,8 @@ msgstr "Mitglied auswählen"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Beschreibung"
|
msgstr "Beschreibung"
|
||||||
|
|
@ -283,6 +290,7 @@ msgid "Enabled"
|
||||||
msgstr "Aktiviert"
|
msgstr "Aktiviert"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.ex
|
#: lib/mv_web/live/custom_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Immutable"
|
msgid "Immutable"
|
||||||
msgstr "Unveränderlich"
|
msgstr "Unveränderlich"
|
||||||
|
|
@ -314,6 +322,8 @@ msgstr "Mitglieder"
|
||||||
#: lib/mv_web/live/contribution_type_live/index.ex
|
#: lib/mv_web/live/contribution_type_live/index.ex
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Name"
|
msgstr "Name"
|
||||||
|
|
@ -345,6 +355,9 @@ msgid "Profil"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Required"
|
msgid "Required"
|
||||||
msgstr "Erforderlich"
|
msgstr "Erforderlich"
|
||||||
|
|
@ -402,6 +415,7 @@ msgid "Value"
|
||||||
msgstr "Wert"
|
msgstr "Wert"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.ex
|
#: lib/mv_web/live/custom_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Value type"
|
msgid "Value type"
|
||||||
msgstr "Wertetyp"
|
msgstr "Wertetyp"
|
||||||
|
|
@ -668,6 +682,8 @@ msgstr "Um die Löschung zu bestätigen, gib bitte folgenden Text ein:"
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Show in overview"
|
msgid "Show in overview"
|
||||||
msgstr "In Übersicht anzeigen"
|
msgstr "In Übersicht anzeigen"
|
||||||
|
|
@ -1409,10 +1425,13 @@ 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."
|
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
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Value Type"
|
msgid "Value Type"
|
||||||
msgstr "Wertetyp"
|
msgstr "Wertetyp"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/translations/field_types.ex
|
#: lib/mv_web/translations/field_types.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
|
|
@ -1438,6 +1457,64 @@ msgstr "Textfeld"
|
||||||
msgid "Yes/No-Selection"
|
msgid "Yes/No-Selection"
|
||||||
msgstr "Ja/Nein-Auswahl"
|
msgstr "Ja/Nein-Auswahl"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Failed to update member field visibility: %{error}"
|
||||||
|
msgstr "Fehler beim anpassen der Sichtbarkeit des Feldes: %{error}"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Member field visibility updated successfully"
|
||||||
|
msgstr "Sichtbarkeit des Feldes erfolgreich aktualisiert."
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Memberdata"
|
||||||
|
msgstr "Mitgliederdaten"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Optional"
|
||||||
|
msgstr "Optional"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "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."
|
||||||
|
msgstr "Diese Datenfelder sind für MILA notwendig um Mitglieder zu identifizieren und zukünftig Beitragszahlungen zu berechnen. Aus diesem Grund können sie nicht gelöscht, aber in der Übersicht ausgeblendet werden."
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Back to member field overview"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Boolean"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Edit Member Field: %{field}"
|
||||||
|
msgstr "Mitglied bearbeiten"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Member field %{action} successfully"
|
||||||
|
msgstr "Mitglied wurde erfolgreich %{action}"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Save Member Field"
|
||||||
|
msgstr "Mitglied speichern"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "String"
|
||||||
|
msgstr "Einstellungen"
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/custom_field_live/show.ex
|
#~ #: lib/mv_web/live/custom_field_live/show.ex
|
||||||
#~ #, elixir-autogen, elixir-format
|
#~ #, elixir-autogen, elixir-format
|
||||||
#~ msgid "Auto-generated identifier (immutable)"
|
#~ msgid "Auto-generated identifier (immutable)"
|
||||||
|
|
@ -1455,11 +1532,26 @@ msgstr "Ja/Nein-Auswahl"
|
||||||
#~ msgid "Custom Field Values"
|
#~ msgid "Custom Field Values"
|
||||||
#~ msgstr "Benutzerdefinierte Feldwerte"
|
#~ msgstr "Benutzerdefinierte Feldwerte"
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Field Name"
|
||||||
|
#~ msgstr "Name des Datenfelds"
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/member_live/form.ex
|
#~ #: lib/mv_web/live/member_live/form.ex
|
||||||
#~ #, elixir-autogen, elixir-format
|
#~ #, elixir-autogen, elixir-format
|
||||||
#~ msgid "Fields marked with an asterisk (*) cannot be empty."
|
#~ msgid "Fields marked with an asterisk (*) cannot be empty."
|
||||||
#~ msgstr "Felder, die mit einem Sternchen (*) markiert sind, dürfen nicht leer bleiben."
|
#~ msgstr "Felder, die mit einem Sternchen (*) markiert sind, dürfen nicht leer bleiben."
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Hide"
|
||||||
|
#~ msgstr "Ausblenden"
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Hide %{field} in overview"
|
||||||
|
#~ msgstr "Verstecke %{field} in der Übersicht"
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/custom_field_live/form.ex
|
#~ #: lib/mv_web/live/custom_field_live/form.ex
|
||||||
#~ #: lib/mv_web/live/user_live/show.ex
|
#~ #: lib/mv_web/live/user_live/show.ex
|
||||||
#~ #, elixir-autogen, elixir-format
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
|
@ -1483,6 +1575,11 @@ msgstr "Ja/Nein-Auswahl"
|
||||||
#~ msgid "OIDC ID"
|
#~ msgid "OIDC ID"
|
||||||
#~ msgstr "OIDC ID"
|
#~ msgstr "OIDC ID"
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Show %{field} in overview"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/custom_field_live/index_component.ex
|
#~ #: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
#~ #, elixir-autogen, elixir-format, fuzzy
|
#~ #, elixir-autogen, elixir-format, fuzzy
|
||||||
#~ msgid "Show in Overview"
|
#~ msgid "Show in Overview"
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/contribution_type_live/index.ex
|
#: lib/mv_web/live/contribution_type_live/index.ex
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#: lib/mv_web/live/user_live/index.html.heex
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
|
@ -170,6 +171,7 @@ msgstr ""
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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/custom_field_value_live/form.ex
|
||||||
#: lib/mv_web/live/global_settings_live.ex
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#: lib/mv_web/live/member_live/form.ex
|
#: lib/mv_web/live/member_live/form.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -184,6 +186,7 @@ msgid "Street"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/member_live/index/formatter.ex
|
#: lib/mv_web/live/member_live/index/formatter.ex
|
||||||
#: lib/mv_web/live/member_live/show.ex
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
|
|
@ -197,6 +200,7 @@ msgid "Show Member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/member_live/index/formatter.ex
|
#: lib/mv_web/live/member_live/index/formatter.ex
|
||||||
#: lib/mv_web/live/member_live/show.ex
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
|
|
@ -256,6 +260,7 @@ msgstr ""
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex
|
#: lib/mv_web/live/custom_field_value_live/form.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#: lib/mv_web/live/member_live/form.ex
|
#: lib/mv_web/live/member_live/form.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -269,6 +274,8 @@ msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -284,6 +291,7 @@ msgid "Enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.ex
|
#: lib/mv_web/live/custom_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Immutable"
|
msgid "Immutable"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -315,6 +323,8 @@ msgstr ""
|
||||||
#: lib/mv_web/live/contribution_type_live/index.ex
|
#: lib/mv_web/live/contribution_type_live/index.ex
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -346,6 +356,9 @@ msgid "Profil"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Required"
|
msgid "Required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -403,6 +416,7 @@ msgid "Value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.ex
|
#: lib/mv_web/live/custom_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Value type"
|
msgid "Value type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -669,6 +683,8 @@ msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Show in overview"
|
msgid "Show in overview"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -1410,10 +1426,13 @@ msgid "These will appear in addition to other data when adding new members."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Value Type"
|
msgid "Value Type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/translations/field_types.ex
|
#: lib/mv_web/translations/field_types.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
|
|
@ -1438,3 +1457,61 @@ msgstr ""
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Yes/No-Selection"
|
msgid "Yes/No-Selection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Failed to update member field visibility: %{error}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Member field visibility updated successfully"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Memberdata"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Optional"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "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."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Back to member field overview"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Boolean"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Edit Member Field: %{field}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Member field %{action} successfully"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Save Member Field"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "String"
|
||||||
|
msgstr ""
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/contribution_type_live/index.ex
|
#: lib/mv_web/live/contribution_type_live/index.ex
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#: lib/mv_web/live/user_live/index.html.heex
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
|
@ -170,6 +171,7 @@ msgstr ""
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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/custom_field_value_live/form.ex
|
||||||
#: lib/mv_web/live/global_settings_live.ex
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#: lib/mv_web/live/member_live/form.ex
|
#: lib/mv_web/live/member_live/form.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -184,6 +186,7 @@ msgid "Street"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/member_live/index/formatter.ex
|
#: lib/mv_web/live/member_live/index/formatter.ex
|
||||||
#: lib/mv_web/live/member_live/show.ex
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
|
|
@ -197,6 +200,7 @@ msgid "Show Member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/live/member_live/index.html.heex
|
#: lib/mv_web/live/member_live/index.html.heex
|
||||||
#: lib/mv_web/live/member_live/index/formatter.ex
|
#: lib/mv_web/live/member_live/index/formatter.ex
|
||||||
#: lib/mv_web/live/member_live/show.ex
|
#: lib/mv_web/live/member_live/show.ex
|
||||||
|
|
@ -256,6 +260,7 @@ msgstr ""
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
#: lib/mv_web/live/custom_field_value_live/form.ex
|
#: lib/mv_web/live/custom_field_value_live/form.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#: lib/mv_web/live/member_live/form.ex
|
#: lib/mv_web/live/member_live/form.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
|
|
@ -269,6 +274,8 @@ msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -284,6 +291,7 @@ msgid "Enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.ex
|
#: lib/mv_web/live/custom_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Immutable"
|
msgid "Immutable"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -315,6 +323,8 @@ msgstr ""
|
||||||
#: lib/mv_web/live/contribution_type_live/index.ex
|
#: lib/mv_web/live/contribution_type_live/index.ex
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -346,6 +356,9 @@ msgid "Profil"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Required"
|
msgid "Required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -403,6 +416,7 @@ msgid "Value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.ex
|
#: lib/mv_web/live/custom_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Value type"
|
msgid "Value type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -669,6 +683,8 @@ msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/form_component.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_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Show in overview"
|
msgid "Show in overview"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -1410,10 +1426,13 @@ msgid "These will appear in addition to other data when adding new members."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/custom_field_live/index_component.ex
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Value Type"
|
msgid "Value Type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
#: lib/mv_web/translations/field_types.ex
|
#: lib/mv_web/translations/field_types.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
|
|
@ -1439,6 +1458,64 @@ msgstr ""
|
||||||
msgid "Yes/No-Selection"
|
msgid "Yes/No-Selection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Failed to update member field visibility: %{error}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Member field visibility updated successfully"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Memberdata"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Optional"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "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."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Back to member field overview"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Boolean"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Edit Member Field: %{field}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/global_settings_live.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Member field %{action} successfully"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Save Member Field"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/member_field_live/form_component.ex
|
||||||
|
#: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "String"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/custom_field_live/show.ex
|
#~ #: lib/mv_web/live/custom_field_live/show.ex
|
||||||
#~ #, elixir-autogen, elixir-format
|
#~ #, elixir-autogen, elixir-format
|
||||||
#~ msgid "Auto-generated identifier (immutable)"
|
#~ msgid "Auto-generated identifier (immutable)"
|
||||||
|
|
@ -1456,11 +1533,26 @@ msgstr ""
|
||||||
#~ msgid "Custom Field Values"
|
#~ msgid "Custom Field Values"
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Field Name"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/member_live/form.ex
|
#~ #: lib/mv_web/live/member_live/form.ex
|
||||||
#~ #, elixir-autogen, elixir-format
|
#~ #, elixir-autogen, elixir-format
|
||||||
#~ msgid "Fields marked with an asterisk (*) cannot be empty."
|
#~ msgid "Fields marked with an asterisk (*) cannot be empty."
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Hide"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Hide %{field} in overview"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/user_live/show.ex
|
#~ #: lib/mv_web/live/user_live/show.ex
|
||||||
#~ #, elixir-autogen, elixir-format
|
#~ #, elixir-autogen, elixir-format
|
||||||
#~ msgid "ID"
|
#~ msgid "ID"
|
||||||
|
|
@ -1482,6 +1574,11 @@ msgstr ""
|
||||||
#~ msgid "OIDC ID"
|
#~ msgid "OIDC ID"
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ #: lib/mv_web/live/member_field_live/index_component.ex
|
||||||
|
#~ #, elixir-autogen, elixir-format
|
||||||
|
#~ msgid "Show %{field} in overview"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
#~ #: lib/mv_web/live/custom_field_live/index_component.ex
|
#~ #: lib/mv_web/live/custom_field_live/index_component.ex
|
||||||
#~ #, elixir-autogen, elixir-format, fuzzy
|
#~ #, elixir-autogen, elixir-format, fuzzy
|
||||||
#~ msgid "Show in Overview"
|
#~ msgid "Show in Overview"
|
||||||
|
|
|
||||||
|
|
@ -64,5 +64,21 @@ defmodule MvWeb.GlobalSettingsLiveTest do
|
||||||
|
|
||||||
assert html =~ "must be present"
|
assert html =~ "must be present"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "displays Memberdata section", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
assert html =~ "Memberdata" or html =~ "Member Data"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "displays flash message after member field visibility update", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# Simulate member field visibility update
|
||||||
|
send(view.pid, {:member_field_visibility_updated})
|
||||||
|
|
||||||
|
# Check for flash message
|
||||||
|
assert render(view) =~ "updated" or render(view) =~ "success"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
190
test/mv_web/live/member_field_live/index_component_test.exs
Normal file
190
test/mv_web/live/member_field_live/index_component_test.exs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
defmodule MvWeb.MemberFieldLive.IndexComponentTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests for MemberFieldLive.IndexComponent.
|
||||||
|
|
||||||
|
Tests cover:
|
||||||
|
- Rendering all member fields from Mv.Constants.member_fields()
|
||||||
|
- Displaying show_in_overview status as badge (Yes/No)
|
||||||
|
- Displaying required status for required fields (first_name, last_name, email)
|
||||||
|
- Toggle functionality to change show_in_overview flag
|
||||||
|
- Settings are correctly updated after toggle
|
||||||
|
- Current status is displayed based on settings.member_field_visibility
|
||||||
|
- Default status is "Yes" (visible) when not configured in settings
|
||||||
|
"""
|
||||||
|
use MvWeb.ConnCase, async: false
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
alias Mv.Membership
|
||||||
|
|
||||||
|
setup %{conn: conn} do
|
||||||
|
user = create_test_user(%{email: "admin@example.com"})
|
||||||
|
conn = conn_with_oidc_user(conn, user)
|
||||||
|
{:ok, conn: conn, user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "rendering" do
|
||||||
|
test "renders all member fields from Constants", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# Check that all member fields are displayed
|
||||||
|
member_fields = Mv.Constants.member_fields()
|
||||||
|
|
||||||
|
for field <- member_fields do
|
||||||
|
field_name = String.replace(Atom.to_string(field), "_", " ") |> String.capitalize()
|
||||||
|
# Field name should appear in the table (either as label or in some form)
|
||||||
|
assert html =~ field_name or html =~ Atom.to_string(field)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "displays show_in_overview status as badge", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# Should have "Show in overview" column header
|
||||||
|
assert html =~ "Show in overview" or html =~ "Show in Overview"
|
||||||
|
|
||||||
|
# Should have badge elements (Yes/No)
|
||||||
|
assert html =~ "badge" or html =~ "Yes" or html =~ "No"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "displays required status for required fields", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# Required fields: first_name, last_name, email
|
||||||
|
# Should have "Required" column or indicator
|
||||||
|
assert html =~ "Required" or html =~ "required"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows default status as Yes when not configured", %{conn: conn} do
|
||||||
|
# Ensure settings have no member_field_visibility configured
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
|
||||||
|
{:ok, _updated} =
|
||||||
|
Membership.update_settings(settings, %{member_field_visibility: %{}})
|
||||||
|
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# All fields should show as visible (Yes) by default
|
||||||
|
# Check for "Yes" badge or similar indicator
|
||||||
|
assert html =~ "Yes" or html =~ "badge-success"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows configured visibility status from settings", %{conn: conn} do
|
||||||
|
# Configure some fields as hidden
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
visibility_config = %{"street" => false, "house_number" => false}
|
||||||
|
|
||||||
|
{:ok, _updated} =
|
||||||
|
Membership.update_member_field_visibility(settings, visibility_config)
|
||||||
|
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# Street and house_number should show as hidden (No)
|
||||||
|
# Other fields should show as visible (Yes)
|
||||||
|
assert html =~ "street" or html =~ "Street"
|
||||||
|
assert html =~ "house_number" or html =~ "House number"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "toggle functionality" do
|
||||||
|
test "toggles field visibility from visible to hidden", %{conn: conn} do
|
||||||
|
# Start with field visible (default)
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
|
||||||
|
{:ok, _updated} =
|
||||||
|
Membership.update_member_field_visibility(settings, %{"street" => true})
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# Find and click toggle button for street field
|
||||||
|
# This will fail until component is implemented
|
||||||
|
assert has_element?(view, "#member-field-street-toggle") or
|
||||||
|
has_element?(view, "[phx-click='toggle_field_visibility'][data-field='street']")
|
||||||
|
|
||||||
|
# Click toggle
|
||||||
|
view
|
||||||
|
|> element("#member-field-street-toggle")
|
||||||
|
|> render_click(%{"field" => "street"})
|
||||||
|
|
||||||
|
# Verify settings updated
|
||||||
|
{:ok, updated_settings} = Membership.get_settings()
|
||||||
|
visibility = updated_settings.member_field_visibility || %{}
|
||||||
|
assert Map.get(visibility, "street") == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "toggles field visibility from hidden to visible", %{conn: conn} do
|
||||||
|
# Start with field hidden
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
|
||||||
|
{:ok, _updated} =
|
||||||
|
Membership.update_member_field_visibility(settings, %{"street" => false})
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# Click toggle to make visible
|
||||||
|
view
|
||||||
|
|> element("#member-field-street-toggle")
|
||||||
|
|> render_click(%{"field" => "street"})
|
||||||
|
|
||||||
|
# Verify settings updated
|
||||||
|
{:ok, updated_settings} = Membership.get_settings()
|
||||||
|
visibility = updated_settings.member_field_visibility || %{}
|
||||||
|
assert Map.get(visibility, "street") == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "sends message to parent LiveView after toggle", %{conn: conn} do
|
||||||
|
{:ok, settings} = Membership.get_settings()
|
||||||
|
|
||||||
|
{:ok, _updated} =
|
||||||
|
Membership.update_member_field_visibility(settings, %{"street" => true})
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# Toggle field
|
||||||
|
view
|
||||||
|
|> element("#member-field-street-toggle")
|
||||||
|
|> render_click(%{"field" => "street"})
|
||||||
|
|
||||||
|
# Check for flash message (handled by parent LiveView)
|
||||||
|
assert render(view) =~ "updated" or render(view) =~ "success"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "required fields" do
|
||||||
|
test "marks first_name as required", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# first_name should be marked as required
|
||||||
|
assert html =~ "first_name" or html =~ "First name"
|
||||||
|
# Should have required indicator
|
||||||
|
assert html =~ "required" or html =~ "Required"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "marks last_name as required", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# last_name should be marked as required
|
||||||
|
assert html =~ "last_name" or html =~ "Last name"
|
||||||
|
# Should have required indicator
|
||||||
|
assert html =~ "required" or html =~ "Required"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "marks email as required", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# email should be marked as required
|
||||||
|
assert html =~ "email" or html =~ "Email"
|
||||||
|
# Should have required indicator
|
||||||
|
assert html =~ "required" or html =~ "Required"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not mark optional fields as required", %{conn: conn} do
|
||||||
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
||||||
|
|
||||||
|
# Optional fields should not have required indicator
|
||||||
|
# Check that street (optional) doesn't have required badge
|
||||||
|
# This test verifies that only required fields show the indicator
|
||||||
|
assert html =~ "street" or html =~ "Street"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
157
test/mv_web/member_live/index_required_display_test.exs
Normal file
157
test/mv_web/member_live/index_required_display_test.exs
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
defmodule MvWeb.MemberLive.IndexRequiredDisplayTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests for displaying "required" badge in member overview.
|
||||||
|
|
||||||
|
Tests cover:
|
||||||
|
- "required" badge for required member fields (first_name, last_name, email)
|
||||||
|
- "required" badge for required custom fields
|
||||||
|
- No "required" badge for optional member fields
|
||||||
|
- No "required" badge for optional custom fields
|
||||||
|
- Badge is positioned in column header
|
||||||
|
"""
|
||||||
|
# async: false to prevent PostgreSQL deadlocks when creating members and custom fields
|
||||||
|
use MvWeb.ConnCase, async: false
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
require Ash.Query
|
||||||
|
|
||||||
|
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
|
||||||
|
|
||||||
|
setup do
|
||||||
|
# Create test member
|
||||||
|
{:ok, member} =
|
||||||
|
Member
|
||||||
|
|> Ash.Changeset.for_create(:create_member, %{
|
||||||
|
first_name: "Alice",
|
||||||
|
last_name: "Anderson",
|
||||||
|
email: "alice@example.com"
|
||||||
|
})
|
||||||
|
|> Ash.create()
|
||||||
|
|
||||||
|
# Create required custom field
|
||||||
|
{:ok, required_field} =
|
||||||
|
CustomField
|
||||||
|
|> Ash.Changeset.for_create(:create, %{
|
||||||
|
name: "emergency_contact",
|
||||||
|
value_type: :string,
|
||||||
|
required: true,
|
||||||
|
show_in_overview: true
|
||||||
|
})
|
||||||
|
|> Ash.create()
|
||||||
|
|
||||||
|
# Create optional custom field
|
||||||
|
{:ok, optional_field} =
|
||||||
|
CustomField
|
||||||
|
|> Ash.Changeset.for_create(:create, %{
|
||||||
|
name: "hobby",
|
||||||
|
value_type: :string,
|
||||||
|
required: false,
|
||||||
|
show_in_overview: true
|
||||||
|
})
|
||||||
|
|> Ash.create()
|
||||||
|
|
||||||
|
# Create custom field values
|
||||||
|
{:ok, _cfv1} =
|
||||||
|
CustomFieldValue
|
||||||
|
|> Ash.Changeset.for_create(:create, %{
|
||||||
|
member_id: member.id,
|
||||||
|
custom_field_id: required_field.id,
|
||||||
|
value: %{"_union_type" => "string", "_union_value" => "John Doe"}
|
||||||
|
})
|
||||||
|
|> Ash.create()
|
||||||
|
|
||||||
|
{:ok, _cfv2} =
|
||||||
|
CustomFieldValue
|
||||||
|
|> Ash.Changeset.for_create(:create, %{
|
||||||
|
member_id: member.id,
|
||||||
|
custom_field_id: optional_field.id,
|
||||||
|
value: %{"_union_type" => "string", "_union_value" => "Reading"}
|
||||||
|
})
|
||||||
|
|> Ash.create()
|
||||||
|
|
||||||
|
%{
|
||||||
|
member: member,
|
||||||
|
required_field: required_field,
|
||||||
|
optional_field: optional_field
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "required badge for member fields" do
|
||||||
|
test "displays required badge for first_name column", %{conn: conn} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, "/members")
|
||||||
|
|
||||||
|
# Check that first_name column header has required badge
|
||||||
|
assert html =~ "first_name" or html =~ "First name" or html =~ "First Name"
|
||||||
|
# Should have required indicator in header
|
||||||
|
assert html =~ "required" or html =~ "Required"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "displays required badge for last_name column", %{conn: conn} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, "/members")
|
||||||
|
|
||||||
|
# Check that last_name column header has required badge
|
||||||
|
assert html =~ "last_name" or html =~ "Last name" or html =~ "Last Name"
|
||||||
|
# Should have required indicator in header
|
||||||
|
assert html =~ "required" or html =~ "Required"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "displays required badge for email column", %{conn: conn} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, "/members")
|
||||||
|
|
||||||
|
# Check that email column header has required badge
|
||||||
|
assert html =~ "email" or html =~ "Email"
|
||||||
|
# Should have required indicator in header
|
||||||
|
assert html =~ "required" or html =~ "Required"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not display required badge for optional member fields", %{conn: conn} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, "/members")
|
||||||
|
|
||||||
|
# Optional fields: street, city, phone_number, etc.
|
||||||
|
# These should not have required badge
|
||||||
|
# We check that street is present but doesn't have required indicator nearby
|
||||||
|
assert html =~ "street" or html =~ "Street"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "required badge for custom fields" do
|
||||||
|
test "displays required badge for required custom field column", %{
|
||||||
|
conn: conn,
|
||||||
|
required_field: field
|
||||||
|
} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, "/members")
|
||||||
|
|
||||||
|
# Check that required custom field column header has required badge
|
||||||
|
assert html =~ field.name
|
||||||
|
# Should have required indicator in header
|
||||||
|
assert html =~ "required" or html =~ "Required"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not display required badge for optional custom field column", %{
|
||||||
|
conn: conn,
|
||||||
|
optional_field: field
|
||||||
|
} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, "/members")
|
||||||
|
|
||||||
|
# Check that optional custom field column header does not have required badge
|
||||||
|
assert html =~ field.name
|
||||||
|
# Should not have required indicator (or it should be clear it's optional)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "badge positioning" do
|
||||||
|
test "required badge is in column header, not in cell content", %{conn: conn} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, _view, html} = live(conn, "/members")
|
||||||
|
|
||||||
|
# Required badge should be in thead (header), not in tbody (data rows)
|
||||||
|
# This is verified by checking that required appears near column headers
|
||||||
|
assert html =~ "thead" or html =~ "th"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue