WIP: Implements settings for member fields closes #223 #300

Draft
carla wants to merge 8 commits from feature/223_memberfields_settings into main
10 changed files with 1530 additions and 139 deletions

View file

@ -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

View file

@ -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(

View 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

View 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

View file

@ -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"

View file

@ -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 ""

View file

@ -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"

View file

@ -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

View 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

View 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