Small UX fixes closes #281 #293

Open
carla wants to merge 4 commits from feature/281_uxfixes into main
17 changed files with 155 additions and 99 deletions

View file

@ -12,7 +12,6 @@ defmodule Mv.Membership.CustomField do
- `slug` - URL-friendly, immutable identifier automatically generated from name (e.g., "phone-mobile") - `slug` - URL-friendly, immutable identifier automatically generated from name (e.g., "phone-mobile")
- `value_type` - Data type constraint (`:string`, `:integer`, `:boolean`, `:date`, `:email`) - `value_type` - Data type constraint (`:string`, `:integer`, `:boolean`, `:date`, `:email`)
- `description` - Optional human-readable description - `description` - Optional human-readable description
- `immutable` - If true, custom field values cannot be changed after creation
- `required` - If true, all members must have this custom field (future feature) - `required` - If true, all members must have this custom field (future feature)
- `show_in_overview` - If true, this custom field will be displayed in the member overview table and can be sorted - `show_in_overview` - If true, this custom field will be displayed in the member overview table and can be sorted
@ -60,10 +59,10 @@ defmodule Mv.Membership.CustomField do
actions do actions do
defaults [:read, :update] defaults [:read, :update]
default_accept [:name, :value_type, :description, :immutable, :required, :show_in_overview] default_accept [:name, :value_type, :description, :required, :show_in_overview]
create :create do create :create do
accept [:name, :value_type, :description, :immutable, :required, :show_in_overview] accept [:name, :value_type, :description, :required, :show_in_overview]
change Mv.Membership.CustomField.Changes.GenerateSlug change Mv.Membership.CustomField.Changes.GenerateSlug
validate string_length(:slug, min: 1) validate string_length(:slug, min: 1)
end end
@ -113,10 +112,6 @@ defmodule Mv.Membership.CustomField do
trim?: true trim?: true
] ]
attribute :immutable, :boolean,
default: false,
allow_nil?: false
attribute :required, :boolean, attribute :required, :boolean,
default: false, default: false,
allow_nil?: false allow_nil?: false

View file

@ -95,9 +95,11 @@ defmodule MvWeb.CoreComponents do
<.button>Send!</.button> <.button>Send!</.button>
<.button phx-click="go" variant="primary">Send!</.button> <.button phx-click="go" variant="primary">Send!</.button>
<.button navigate={~p"/"}>Home</.button> <.button navigate={~p"/"}>Home</.button>
<.button disabled={true}>Disabled</.button>
""" """
attr :rest, :global, include: ~w(href navigate patch method) attr :rest, :global, include: ~w(href navigate patch method)
attr :variant, :string, values: ~w(primary) attr :variant, :string, values: ~w(primary)
attr :disabled, :boolean, default: false, doc: "Whether the button is disabled"
slot :inner_block, required: true slot :inner_block, required: true
def button(%{rest: rest} = assigns) do def button(%{rest: rest} = assigns) do
@ -105,14 +107,34 @@ defmodule MvWeb.CoreComponents do
assigns = assign(assigns, :class, Map.fetch!(variants, assigns[:variant])) assigns = assign(assigns, :class, Map.fetch!(variants, assigns[:variant]))
if rest[:href] || rest[:navigate] || rest[:patch] do if rest[:href] || rest[:navigate] || rest[:patch] do
# For links, we can't use disabled attribute, so we use btn-disabled class
# DaisyUI's btn-disabled provides the same styling as :disabled on buttons
link_class =
if assigns[:disabled],
do: ["btn", assigns.class, "btn-disabled"],
else: ["btn", assigns.class]
# Prevent interaction when disabled
link_attrs =
if assigns[:disabled] do
Map.merge(rest, %{tabindex: "-1", "aria-disabled": "true"})
else
rest
end
assigns =
assigns
|> assign(:link_class, link_class)
|> assign(:link_attrs, link_attrs)
~H""" ~H"""
<.link class={["btn", @class]} {@rest}> <.link class={@link_class} {@link_attrs}>
{render_slot(@inner_block)} {render_slot(@inner_block)}
</.link> </.link>
""" """
else else
~H""" ~H"""
<button class={["btn", @class]} {@rest}> <button class={["btn", @class]} disabled={@disabled} {@rest}>
{render_slot(@inner_block)} {render_slot(@inner_block)}
</button> </button>
""" """

View file

@ -36,12 +36,16 @@ defmodule MvWeb.Layouts do
default: nil, default: nil,
doc: "the current [scope](https://hexdocs.pm/phoenix/scopes.html)" doc: "the current [scope](https://hexdocs.pm/phoenix/scopes.html)"
attr :club_name, :string,
default: nil,
doc: "optional club name to pass to navbar"
slot :inner_block, required: true slot :inner_block, required: true
def app(assigns) do def app(assigns) do
~H""" ~H"""
<%= if @current_user do %> <%= if @current_user do %>
<.navbar current_user={@current_user} /> <.navbar current_user={@current_user} club_name={@club_name} />
<% end %> <% end %>
<main class="px-4 py-20 sm:px-6 lg:px-16"> <main class="px-4 py-20 sm:px-6 lg:px-16">
<div class="mx-auto max-full space-y-4"> <div class="mx-auto max-full space-y-4">

View file

@ -12,15 +12,18 @@ defmodule MvWeb.Layouts.Navbar do
required: true, required: true,
doc: "The current user - navbar is only shown when user is present" doc: "The current user - navbar is only shown when user is present"
def navbar(assigns) do attr :club_name, :string,
club_name = get_club_name() default: nil,
doc: "Optional club name - if not provided, will be loaded from database"
def navbar(assigns) do
club_name = assigns[:club_name] || get_club_name()
assigns = assign(assigns, :club_name, club_name) assigns = assign(assigns, :club_name, club_name)
~H""" ~H"""
<header class="navbar bg-base-100 shadow-sm"> <header class="navbar bg-base-100 shadow-sm">
<div class="flex-1"> <div class="flex-1">
<a class="btn btn-ghost text-xl">{@club_name}</a> <a href="/members" class="btn btn-ghost text-xl">{@club_name}</a>
<ul class="menu menu-horizontal bg-base-200"> <ul class="menu menu-horizontal bg-base-200">
<li><.link navigate="/members">{gettext("Members")}</.link></li> <li><.link navigate="/members">{gettext("Members")}</.link></li>
<li><.link navigate="/settings">{gettext("Settings")}</.link></li> <li><.link navigate="/settings">{gettext("Settings")}</.link></li>

View file

@ -77,7 +77,7 @@ defmodule MvWeb.Components.PaymentFilterComponent do
phx-target={@myself} phx-target={@myself}
> >
<.icon name="hero-users" class="h-4 w-4" /> <.icon name="hero-users" class="h-4 w-4" />
{gettext("All")} {gettext("All payment statuses")}
</button> </button>
</li> </li>
<li role="none"> <li role="none">
@ -140,7 +140,7 @@ defmodule MvWeb.Components.PaymentFilterComponent do
defp parse_filter(_), do: nil defp parse_filter(_), do: nil
# Get display label for current filter # Get display label for current filter
defp filter_label(nil), do: gettext("All") defp filter_label(nil), do: gettext("All payment statuses")
defp filter_label(:paid), do: gettext("Paid") defp filter_label(:paid), do: gettext("Paid")
defp filter_label(:not_paid), do: gettext("Not paid") defp filter_label(:not_paid), do: gettext("Not paid")
end end

View file

@ -6,7 +6,7 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
- Create new custom field definitions - Create new custom field definitions
- Edit existing custom fields - Edit existing custom fields
- Select value type from supported types - Select value type from supported types
- Set immutable and required flags - Set required flag
- Real-time validation - Real-time validation
## Props ## Props
@ -50,10 +50,10 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
label={gettext("Value type")} label={gettext("Value type")}
options={ options={
Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of] Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of]
|> Enum.map(fn type -> {MvWeb.Translations.FieldTypes.label(type), type} end)
} }
/> />
<.input field={@form[:description]} type="text" label={gettext("Description")} /> <.input field={@form[:description]} type="text" label={gettext("Description")} />
<.input field={@form[:immutable]} type="checkbox" label={gettext("Immutable")} />
<.input field={@form[:required]} type="checkbox" label={gettext("Required")} /> <.input field={@form[:required]} type="checkbox" label={gettext("Required")} />
<.input <.input
field={@form[:show_in_overview]} field={@form[:show_in_overview]}

View file

@ -5,7 +5,7 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
## Features ## Features
- List all custom fields - List all custom fields
- Display type information (name, value type, description) - Display type information (name, value type, description)
- Show immutable and required flags - Show required flag
- Create new custom fields - Create new custom fields
- Edit existing custom fields - Edit existing custom fields
- Delete custom fields with confirmation (cascades to all custom field values) - Delete custom fields with confirmation (cascades to all custom field values)
@ -30,7 +30,7 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
phx-click="new_custom_field" phx-click="new_custom_field"
phx-target={@myself} phx-target={@myself}
> >
<.icon name="hero-plus" /> {gettext("New Custom field")} <.icon name="hero-plus" /> {gettext("New Custom Field")}
</.button> </.button>
</div> </div>
</div> </div>

View file

@ -37,7 +37,7 @@ defmodule MvWeb.GlobalSettingsLive do
@impl true @impl true
def render(assigns) do def render(assigns) do
~H""" ~H"""
<Layouts.app flash={@flash} current_user={@current_user}> <Layouts.app flash={@flash} current_user={@current_user} club_name={@settings.club_name}>
<.header> <.header>
{gettext("Settings")} {gettext("Settings")}
<:subtitle> <:subtitle>
@ -80,10 +80,13 @@ defmodule MvWeb.GlobalSettingsLive do
@impl true @impl true
def handle_event("save", %{"setting" => setting_params}, socket) do def handle_event("save", %{"setting" => setting_params}, socket) do
case AshPhoenix.Form.submit(socket.assigns.form, params: setting_params) do case AshPhoenix.Form.submit(socket.assigns.form, params: setting_params) do
{:ok, updated_settings} -> {:ok, _updated_settings} ->
# Reload settings from database to ensure all dependent data is updated
{:ok, fresh_settings} = Membership.get_settings()
socket = socket =
socket socket
|> assign(:settings, updated_settings) |> assign(:settings, fresh_settings)
|> put_flash(:info, gettext("Settings updated successfully")) |> put_flash(:info, gettext("Settings updated successfully"))
|> assign_form() |> assign_form()

View file

@ -3,23 +3,29 @@
{gettext("Members")} {gettext("Members")}
<:actions> <:actions>
<.button <.button
:if={Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))} class="secondary"
id="copy-emails-btn" id="copy-emails-btn"
phx-hook="CopyToClipboard" phx-hook="CopyToClipboard"
phx-click="copy_emails" phx-click="copy_emails"
disabled={not Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))}
aria-label={gettext("Copy email addresses of selected members")} aria-label={gettext("Copy email addresses of selected members")}
> >
<.icon name="hero-clipboard-document" /> <.icon name="hero-clipboard-document" />
{gettext("Copy emails")} ({Enum.count(@members, &MapSet.member?(@selected_members, &1.id))}) {gettext("Copy email addresses")} ({Enum.count(
@members,
&MapSet.member?(@selected_members, &1.id)
)})
</.button> </.button>
<.button <.button
:if={Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))} class="secondary"
id="open-email-btn"
href={ href={
"mailto:?bcc=" <> "mailto:?bcc=" <>
(MvWeb.MemberLive.Index.format_selected_member_emails(@members, @selected_members) (MvWeb.MemberLive.Index.format_selected_member_emails(@members, @selected_members)
|> Enum.join(", ") |> Enum.join(", ")
|> URI.encode()) |> URI.encode())
} }
disabled={not Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))}
aria-label={gettext("Open email program with BCC recipients")} aria-label={gettext("Open email program with BCC recipients")}
> >
<.icon name="hero-envelope" /> <.icon name="hero-envelope" />

View file

@ -26,11 +26,11 @@
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
"ex_phone_number": {:hex, :ex_phone_number, "0.4.8", "c1c5e6f0673822a2a7b439b43af7d3eb1a5c19ae582b772b8b8d12625dd51ec1", [:mix], [{:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}], "hexpm", "43e2357c6b8cfe556bcd417f4ce9aaef267a786e31a2938902daaa0d36f69757"}, "ex_phone_number": {:hex, :ex_phone_number, "0.4.8", "c1c5e6f0673822a2a7b439b43af7d3eb1a5c19ae582b772b8b8d12625dd51ec1", [:mix], [{:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}], "hexpm", "43e2357c6b8cfe556bcd417f4ce9aaef267a786e31a2938902daaa0d36f69757"},
"expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"}, "expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"},
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
"finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"},
"fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"}, "fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"},
"gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, "gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"},
"glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"},
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]}, "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
@ -39,7 +39,7 @@
"iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"}, "joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
"jose": {:hex, :jose, "1.11.12", "06e62b467b61d3726cbc19e9b5489f7549c37993de846dfb3ee8259f9ed208b3", [], [], "hexpm", "31e92b653e9210b696765cdd885437457de1add2a9011d92f8cf63e4641bab7b"}, "jose": {:hex, :jose, "1.11.12", "06e62b467b61d3726cbc19e9b5489f7549c37993de846dfb3ee8259f9ed208b3", [:mix, :rebar3], [], "hexpm", "31e92b653e9210b696765cdd885437457de1add2a9011d92f8cf63e4641bab7b"},
"lazy_html": {:hex, :lazy_html, "0.1.8", "677a8642e644eef8de98f3040e2520d42d0f0f8bd6c5cd49db36504e34dffe91", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "0d8167d930b704feb94b41414ca7f5779dff9bca7fcf619fcef18de138f08736"}, "lazy_html": {:hex, :lazy_html, "0.1.8", "677a8642e644eef8de98f3040e2520d42d0f0f8bd6c5cd49db36504e34dffe91", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "0d8167d930b704feb94b41414ca7f5779dff9bca7fcf619fcef18de138f08736"},
"libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"},
"live_debugger": {:hex, :live_debugger, "0.5.0", "95e0f7727d61010f7e9053923fb2a9416904a7533c2dfb36120e7684cba4c0af", [:mix], [{:igniter, ">= 0.5.40 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.8 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "73ebe95118d22aa402675f677abd731cb16b136d1b6ae5f4010441fb50753b14"}, "live_debugger": {:hex, :live_debugger, "0.5.0", "95e0f7727d61010f7e9053923fb2a9416904a7533c2dfb36120e7684cba4c0af", [:mix], [{:igniter, ">= 0.5.40 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.8 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "73ebe95118d22aa402675f677abd731cb16b136d1b6ae5f4010441fb50753b14"},
@ -80,7 +80,7 @@
"telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"},
"telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"},
"text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"},
"thousand_island": {:hex, :thousand_island, "1.4.2", "735fa783005d1703359bbd2d3a5a3a398075ba4456e5afe3c5b7cf4666303d36", [], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1c7637f16558fc1c35746d5ee0e83b18b8e59e18d28affd1f2fa1645f8bc7473"}, "thousand_island": {:hex, :thousand_island, "1.4.2", "735fa783005d1703359bbd2d3a5a3a398075ba4456e5afe3c5b7cf4666303d36", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1c7637f16558fc1c35746d5ee0e83b18b8e59e18d28affd1f2fa1645f8bc7473"},
"tidewave": {:hex, :tidewave, "0.5.2", "f549acffe9daeed8b6b547c232c60de987770da7f827f9b3300140dfc465b102", [:mix], [{:circular_buffer, "~> 0.4 or ~> 1.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_reload, ">= 1.6.1", [hex: :phoenix_live_reload, repo: "hexpm", optional: true]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "34ab3ffee7e402f05cd1eae68d0e77ed0e0d1925677971ef83634247553e8afd"}, "tidewave": {:hex, :tidewave, "0.5.2", "f549acffe9daeed8b6b547c232c60de987770da7f827f9b3300140dfc465b102", [:mix], [{:circular_buffer, "~> 0.4 or ~> 1.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_reload, ">= 1.6.1", [hex: :phoenix_live_reload, repo: "hexpm", optional: true]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "34ab3ffee7e402f05cd1eae68d0e77ed0e0d1925677971ef83634247553e8afd"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},

View file

@ -282,11 +282,6 @@ msgstr "Benutzer*in bearbeiten"
msgid "Enabled" msgid "Enabled"
msgstr "Aktiviert" msgstr "Aktiviert"
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Immutable"
msgstr "Unveränderlich"
#: lib/mv_web/components/layouts/navbar.ex #: lib/mv_web/components/layouts/navbar.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Logout" msgid "Logout"
@ -760,11 +755,6 @@ msgstr[1] "%{count} E-Mail-Adressen in die Zwischenablage kopiert"
msgid "Copy email addresses of selected members" msgid "Copy email addresses of selected members"
msgstr "E-Mail-Adressen der ausgewählten Mitglieder kopieren" msgstr "E-Mail-Adressen der ausgewählten Mitglieder kopieren"
#: lib/mv_web/live/member_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Copy emails"
msgstr "E-Mails kopieren"
#: lib/mv_web/live/member_live/index.ex #: lib/mv_web/live/member_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "No email addresses found" msgid "No email addresses found"
@ -796,7 +786,6 @@ msgid "This field cannot be empty"
msgstr "Dieses Feld darf nicht leer bleiben" msgstr "Dieses Feld darf nicht leer bleiben"
#: lib/mv_web/components/core_components.ex #: lib/mv_web/components/core_components.ex
#: lib/mv_web/live/components/payment_filter_component.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "All" msgid "All"
msgstr "Alle" msgstr "Alle"
@ -1389,14 +1378,10 @@ msgid "Failed to delete custom field: %{error}"
msgstr "Konnte Feld nicht löschen: %{error}" msgstr "Konnte Feld nicht löschen: %{error}"
#: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "New Custom Field"
msgstr "Benutzerdefiniertes Feld speichern"
#: lib/mv_web/live/custom_field_live/index_component.ex #: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "New Custom field" msgid "New Custom Field"
msgstr "Benutzerdefiniertes Feld speichern" msgstr "Neues Benutzerdefiniertes Feld"
#: lib/mv_web/live/global_settings_live.ex #: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -1438,6 +1423,16 @@ msgstr "Textfeld"
msgid "Yes/No-Selection" msgid "Yes/No-Selection"
msgstr "Ja/Nein-Auswahl" msgstr "Ja/Nein-Auswahl"
#: lib/mv_web/live/components/payment_filter_component.ex
#, elixir-autogen, elixir-format
msgid "All payment statuses"
msgstr "Jeder Zahlungs-Zustand"
#: lib/mv_web/live/member_live/index.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "Copy email addresses"
msgstr "E-Mail-Adressen kopieren"
#~ #: 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)"
@ -1449,6 +1444,11 @@ msgstr "Ja/Nein-Auswahl"
#~ msgid "Birth Date" #~ msgid "Birth Date"
#~ msgstr "Geburtsdatum" #~ msgstr "Geburtsdatum"
#~ #: lib/mv_web/live/member_live/index.html.heex
#~ #, elixir-autogen, elixir-format
#~ msgid "Copy emails"
#~ msgstr "E-Mails kopieren"
#~ #: lib/mv_web/live/member_live/form.ex #~ #: lib/mv_web/live/member_live/form.ex
#~ #: lib/mv_web/live/member_live/show.ex #~ #: lib/mv_web/live/member_live/show.ex
#~ #, elixir-autogen, elixir-format #~ #, elixir-autogen, elixir-format
@ -1471,6 +1471,16 @@ msgstr "Ja/Nein-Auswahl"
#~ msgid "Id" #~ msgid "Id"
#~ msgstr "ID" #~ msgstr "ID"
#~ #: lib/mv_web/live/custom_field_live/form_component.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Immutable"
#~ msgstr "Unveränderlich"
#~ #: lib/mv_web/live/custom_field_live/index_component.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "New Custom field"
#~ msgstr "Benutzerdefiniertes Feld speichern"
#~ #: lib/mv_web/live/user_live/form.ex #~ #: lib/mv_web/live/user_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

View file

@ -283,11 +283,6 @@ msgstr ""
msgid "Enabled" msgid "Enabled"
msgstr "" msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Immutable"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex #: lib/mv_web/components/layouts/navbar.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Logout" msgid "Logout"
@ -761,11 +756,6 @@ msgstr[1] ""
msgid "Copy email addresses of selected members" msgid "Copy email addresses of selected members"
msgstr "" msgstr ""
#: lib/mv_web/live/member_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Copy emails"
msgstr ""
#: lib/mv_web/live/member_live/index.ex #: lib/mv_web/live/member_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "No email addresses found" msgid "No email addresses found"
@ -797,7 +787,6 @@ msgid "This field cannot be empty"
msgstr "" msgstr ""
#: lib/mv_web/components/core_components.ex #: lib/mv_web/components/core_components.ex
#: lib/mv_web/live/components/payment_filter_component.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "All" msgid "All"
msgstr "" msgstr ""
@ -1390,13 +1379,9 @@ msgid "Failed to delete custom field: %{error}"
msgstr "" msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "New Custom Field"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex #: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "New Custom field" msgid "New Custom Field"
msgstr "" msgstr ""
#: lib/mv_web/live/global_settings_live.ex #: lib/mv_web/live/global_settings_live.ex
@ -1438,3 +1423,13 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Yes/No-Selection" msgid "Yes/No-Selection"
msgstr "" msgstr ""
#: lib/mv_web/live/components/payment_filter_component.ex
#, elixir-autogen, elixir-format
msgid "All payment statuses"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Copy email addresses"
msgstr ""

View file

@ -283,11 +283,6 @@ msgstr ""
msgid "Enabled" msgid "Enabled"
msgstr "" msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format
msgid "Immutable"
msgstr ""
#: lib/mv_web/components/layouts/navbar.ex #: lib/mv_web/components/layouts/navbar.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Logout" msgid "Logout"
@ -761,11 +756,6 @@ msgstr[1] ""
msgid "Copy email addresses of selected members" msgid "Copy email addresses of selected members"
msgstr "" msgstr ""
#: lib/mv_web/live/member_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Copy emails"
msgstr ""
#: lib/mv_web/live/member_live/index.ex #: lib/mv_web/live/member_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "No email addresses found" msgid "No email addresses found"
@ -797,7 +787,6 @@ msgid "This field cannot be empty"
msgstr "" msgstr ""
#: lib/mv_web/components/core_components.ex #: lib/mv_web/components/core_components.ex
#: lib/mv_web/live/components/payment_filter_component.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "All" msgid "All"
msgstr "" msgstr ""
@ -1390,13 +1379,9 @@ msgid "Failed to delete custom field: %{error}"
msgstr "" msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/form_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "New Custom Field"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex #: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "New Custom field" msgid "New Custom Field"
msgstr "" msgstr ""
#: lib/mv_web/live/global_settings_live.ex #: lib/mv_web/live/global_settings_live.ex
@ -1439,6 +1424,16 @@ msgstr ""
msgid "Yes/No-Selection" msgid "Yes/No-Selection"
msgstr "" msgstr ""
#: lib/mv_web/live/components/payment_filter_component.ex
#, elixir-autogen, elixir-format
msgid "All payment statuses"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "Copy email addresses"
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)"
@ -1450,6 +1445,11 @@ msgstr ""
#~ msgid "Birth Date" #~ msgid "Birth Date"
#~ msgstr "" #~ msgstr ""
#~ #: lib/mv_web/live/member_live/index.html.heex
#~ #, elixir-autogen, elixir-format
#~ msgid "Copy emails"
#~ msgstr ""
#~ #: lib/mv_web/live/member_live/form.ex #~ #: lib/mv_web/live/member_live/form.ex
#~ #: lib/mv_web/live/member_live/show.ex #~ #: lib/mv_web/live/member_live/show.ex
#~ #, elixir-autogen, elixir-format #~ #, elixir-autogen, elixir-format
@ -1471,6 +1471,16 @@ msgstr ""
#~ msgid "Id" #~ msgid "Id"
#~ msgstr "" #~ msgstr ""
#~ #: lib/mv_web/live/custom_field_live/form_component.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Immutable"
#~ msgstr ""
#~ #: lib/mv_web/live/custom_field_live/index_component.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "New Custom field"
#~ msgstr ""
#~ #: lib/mv_web/live/user_live/show.ex #~ #: lib/mv_web/live/user_live/show.ex
#~ #, elixir-autogen, elixir-format, fuzzy #~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Not set" #~ msgid "Not set"

View file

@ -0,0 +1,21 @@
defmodule Mv.Repo.Migrations.RemoveImmutableFromCustomFields do
@moduledoc """
Removes the immutable column from custom_fields table.
The immutable field is no longer needed in the custom field definition.
"""
use Ecto.Migration
def up do
alter table(:custom_fields) do
remove :immutable
end
end
def down do
alter table(:custom_fields) do
add :immutable, :boolean, null: false, default: false
end
end
end

View file

@ -12,28 +12,24 @@ for attrs <- [
name: "String Field", name: "String Field",
value_type: :string, value_type: :string,
description: "Example for a field of type string", description: "Example for a field of type string",
immutable: true,
required: false required: false
}, },
%{ %{
name: "Date Field", name: "Date Field",
value_type: :date, value_type: :date,
description: "Example for a field of type date", description: "Example for a field of type date",
immutable: true,
required: false required: false
}, },
%{ %{
name: "Boolean Field", name: "Boolean Field",
value_type: :boolean, value_type: :boolean,
description: "Example for a field of type boolean", description: "Example for a field of type boolean",
immutable: true,
required: false required: false
}, },
%{ %{
name: "Email Field", name: "Email Field",
value_type: :email, value_type: :email,
description: "Example for a field of type email", description: "Example for a field of type email",
immutable: true,
required: false required: false
}, },
# Realistic custom fields # Realistic custom fields
@ -41,56 +37,48 @@ for attrs <- [
name: "Membership Number", name: "Membership Number",
value_type: :string, value_type: :string,
description: "Unique membership identification number", description: "Unique membership identification number",
immutable: false,
required: false required: false
}, },
%{ %{
name: "Emergency Contact", name: "Emergency Contact",
value_type: :string, value_type: :string,
description: "Emergency contact person name and phone", description: "Emergency contact person name and phone",
immutable: false,
required: false required: false
}, },
%{ %{
name: "T-Shirt Size", name: "T-Shirt Size",
value_type: :string, value_type: :string,
description: "T-Shirt size for events (XS, S, M, L, XL, XXL)", description: "T-Shirt size for events (XS, S, M, L, XL, XXL)",
immutable: false,
required: false required: false
}, },
%{ %{
name: "Newsletter Subscription", name: "Newsletter Subscription",
value_type: :boolean, value_type: :boolean,
description: "Whether member wants to receive newsletter", description: "Whether member wants to receive newsletter",
immutable: false,
required: false required: false
}, },
%{ %{
name: "Date of Last Medical Check", name: "Date of Last Medical Check",
value_type: :date, value_type: :date,
description: "Date of last medical examination", description: "Date of last medical examination",
immutable: false,
required: false required: false
}, },
%{ %{
name: "Secondary Email", name: "Secondary Email",
value_type: :email, value_type: :email,
description: "Alternative email address", description: "Alternative email address",
immutable: false,
required: false required: false
}, },
%{ %{
name: "Membership Type", name: "Membership Type",
value_type: :string, value_type: :string,
description: "Type of membership (e.g., Regular, Student, Senior)", description: "Type of membership (e.g., Regular, Student, Senior)",
immutable: false,
required: false required: false
}, },
%{ %{
name: "Parking Permit", name: "Parking Permit",
value_type: :boolean, value_type: :boolean,
description: "Whether member has parking permit", description: "Whether member has parking permit",
immutable: false,
required: false required: false
} }
] do ] do

View file

@ -99,8 +99,15 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do
# Check that the sort button is a button element (keyboard accessible) # Check that the sort button is a button element (keyboard accessible)
assert html =~ ~r/<button[^>]*data-testid=["']custom_field_#{field.id}["']/ assert html =~ ~r/<button[^>]*data-testid=["']custom_field_#{field.id}["']/
# Extract the button element for the custom field and check it doesn't have tabindex="-1"
button_match =
Regex.run(~r/<button[^>]*data-testid=["']custom_field_#{field.id}["'][^>]*>/, html)
assert button_match != nil, "Button with data-testid='custom_field_#{field.id}' not found"
button_html = List.first(button_match)
# Button should not have tabindex="-1" (which would remove from tab order) # Button should not have tabindex="-1" (which would remove from tab order)
refute html =~ ~r/tabindex=["']-1["']/ refute button_html =~ ~r/tabindex=["']-1["']/
end end
test "custom field column header has proper semantic structure", %{conn: conn, field: field} do test "custom field column header has proper semantic structure", %{conn: conn, field: field} do

View file

@ -410,14 +410,6 @@ defmodule MvWeb.MemberLive.IndexTest do
assert render(view) =~ "1" assert render(view) =~ "1"
end end
test "copy button is not visible when no members are selected", %{conn: conn} do
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members")
# Ensure no members are selected (default state)
refute has_element?(view, "#copy-emails-btn")
end
test "copy button is visible when members are selected", %{ test "copy button is visible when members are selected", %{
conn: conn, conn: conn,
member1: member1 member1: member1