From 035edae522a4dafbb06a45971593fc471d9dad69 Mon Sep 17 00:00:00 2001 From: Moritz Date: Mon, 8 Jun 2026 12:35:30 +0200 Subject: [PATCH] feat(web): add tooltips to icon-only action buttons --- lib/mv_web/live/global_settings_live.ex | 24 ++++++++++++----- lib/mv_web/live/group_live/show.ex | 24 +++++++++-------- .../show/membership_fees_component.ex | 21 +++++++++------ .../live/membership_fee_settings_live.ex | 23 +++++++++------- .../live/membership_fee_type_live/index.ex | 23 +++++++++------- priv/gettext/de/LC_MESSAGES/default.po | 10 +++++++ priv/gettext/default.pot | 10 +++++++ priv/gettext/en/LC_MESSAGES/default.po | 10 +++++++ .../mv_web/live/global_settings_live_test.exs | 27 +++++++++++++++++++ .../member_live/show_membership_fees_test.exs | 16 +++++++++++ 10 files changed, 144 insertions(+), 44 deletions(-) diff --git a/lib/mv_web/live/global_settings_live.ex b/lib/mv_web/live/global_settings_live.ex index c7a36b1..735c165 100644 --- a/lib/mv_web/live/global_settings_live.ex +++ b/lib/mv_web/live/global_settings_live.ex @@ -586,15 +586,25 @@ defmodule MvWeb.GlobalSettingsLive do > {gettext("Test Integration")} - <.button + <.tooltip :if={Mv.Config.vereinfacht_configured?()} - type="button" - variant="secondary" - phx-click="sync_vereinfacht_contacts" - phx-disable-with={gettext("Syncing...")} + content={ + gettext( + "Creates a Vereinfacht finance contact for every member that does not have one yet." + ) + } + position="top" > - {gettext("Sync all members without Vereinfacht contact")} - + <.button + type="button" + variant="secondary" + phx-click="sync_vereinfacht_contacts" + phx-disable-with={gettext("Syncing...")} + aria-label={gettext("Sync all members without Vereinfacht contact")} + > + {gettext("Sync all members without Vereinfacht contact")} + + <%= if @vereinfacht_test_result do %> <.vereinfacht_test_result result={@vereinfacht_test_result} /> diff --git a/lib/mv_web/live/group_live/show.ex b/lib/mv_web/live/group_live/show.ex index 7cd4378..ad24110 100644 --- a/lib/mv_web/live/group_live/show.ex +++ b/lib/mv_web/live/group_live/show.ex @@ -250,17 +250,19 @@ defmodule MvWeb.GroupLive.Show do <% end %> - <.button - type="button" - variant="primary" - phx-click="add_selected_members" - data-testid="group-show-add-selected-members-btn" - disabled={Enum.empty?(@selected_member_ids)} - aria-label={gettext("Add members")} - class="join-item" - > - <.icon name="hero-plus" class="size-5" /> - + <.tooltip content={gettext("Add members")} position="top"> + <.button + type="button" + variant="primary" + phx-click="add_selected_members" + data-testid="group-show-add-selected-members-btn" + disabled={Enum.empty?(@selected_member_ids)} + aria-label={gettext("Add members")} + class="join-item" + > + <.icon name="hero-plus" class="size-5" /> + + <.button type="button" variant="neutral" diff --git a/lib/mv_web/live/member_live/show/membership_fees_component.ex b/lib/mv_web/live/member_live/show/membership_fees_component.ex index 0cba316..33b0456 100644 --- a/lib/mv_web/live/member_live/show/membership_fees_component.ex +++ b/lib/mv_web/live/member_live/show/membership_fees_component.ex @@ -143,16 +143,21 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do <%!-- Action Buttons (only when user has permission) --%>
- <.button + <.tooltip :if={@member.membership_fee_type != nil and @can_create_cycle} - phx-click="regenerate_cycles" - phx-target={@myself} - class={["btn btn-sm btn-outline", if(@regenerating, do: "btn-disabled", else: "")]} - title={gettext("Generate cycles from the last existing cycle to today")} + content={gettext("Generate cycles from the last existing cycle to today")} + position="top" > - <.icon name="hero-arrow-path" class="size-4" /> - {if(@regenerating, do: gettext("Regenerating..."), else: gettext("Regenerate Cycles"))} - + <.button + phx-click="regenerate_cycles" + phx-target={@myself} + class={["btn btn-sm btn-outline", if(@regenerating, do: "btn-disabled", else: "")]} + aria-label={gettext("Regenerate membership fee cycles")} + > + <.icon name="hero-arrow-path" class="size-4" /> + {if(@regenerating, do: gettext("Regenerating..."), else: gettext("Regenerate Cycles"))} + + <.button :if={Enum.any?(@cycles) and @can_destroy_cycle} variant="outline" diff --git a/lib/mv_web/live/membership_fee_settings_live.ex b/lib/mv_web/live/membership_fee_settings_live.ex index 15030c1..4df6608 100644 --- a/lib/mv_web/live/membership_fee_settings_live.ex +++ b/lib/mv_web/live/membership_fee_settings_live.ex @@ -298,17 +298,22 @@ defmodule MvWeb.MembershipFeeSettingsLive do <.icon name="hero-trash" class="size-4" /> - <.button + <.tooltip :if={get_member_count(mft, @member_counts) == 0} - variant="danger" - size="sm" - phx-click="delete" - phx-value-id={mft.id} - data-confirm={gettext("Are you sure?")} - aria-label={gettext("Delete Membership Fee Type")} + content={gettext("Delete Membership Fee Type")} + position="left" > - <.icon name="hero-trash" class="size-4" /> - + <.button + variant="danger" + size="sm" + phx-click="delete" + phx-value-id={mft.id} + data-confirm={gettext("Are you sure?")} + aria-label={gettext("Delete Membership Fee Type")} + > + <.icon name="hero-trash" class="size-4" /> + +
diff --git a/lib/mv_web/live/membership_fee_type_live/index.ex b/lib/mv_web/live/membership_fee_type_live/index.ex index 65f840d..1d51ce1 100644 --- a/lib/mv_web/live/membership_fee_type_live/index.ex +++ b/lib/mv_web/live/membership_fee_type_live/index.ex @@ -115,17 +115,22 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do <.icon name="hero-trash" class="size-4" /> - <.button + <.tooltip :if={get_member_count(mft, @member_counts) == 0} - variant="danger" - size="sm" - phx-click="delete" - phx-value-id={mft.id} - data-confirm={gettext("Are you sure?")} - aria-label={gettext("Delete Membership Fee Type")} + content={gettext("Delete Membership Fee Type")} + position="left" > - <.icon name="hero-trash" class="size-4" /> - + <.button + variant="danger" + size="sm" + phx-click="delete" + phx-value-id={mft.id} + data-confirm={gettext("Are you sure?")} + aria-label={gettext("Delete Membership Fee Type")} + > + <.icon name="hero-trash" class="size-4" /> + + diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 0334332..a5ffa81 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -791,6 +791,11 @@ msgstr "Beitragsart erstellen" msgid "Created at:" msgstr "Erstellt am:" +#: lib/mv_web/live/global_settings_live.ex +#, elixir-autogen, elixir-format +msgid "Creates a Vereinfacht finance contact for every member that does not have one yet." +msgstr "Legt für jedes Mitglied ohne Vereinfacht-Kontakt einen Finanzkontakt an." + #: lib/mv_web/live/member_live/show/membership_fees_component.ex #, elixir-autogen, elixir-format msgid "Credit" @@ -2872,6 +2877,11 @@ msgstr "Weiterleitungs-URI" msgid "Regenerate Cycles" msgstr "Zyklen regenerieren" +#: lib/mv_web/live/member_live/show/membership_fees_component.ex +#, elixir-autogen, elixir-format +msgid "Regenerate membership fee cycles" +msgstr "Beitragszyklen neu generieren" + #: lib/mv_web/live/member_live/show/membership_fees_component.ex #, elixir-autogen, elixir-format msgid "Regenerating..." diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index c989f7b..91a111d 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -792,6 +792,11 @@ msgstr "" msgid "Created at:" msgstr "" +#: lib/mv_web/live/global_settings_live.ex +#, elixir-autogen, elixir-format +msgid "Creates a Vereinfacht finance contact for every member that does not have one yet." +msgstr "" + #: lib/mv_web/live/member_live/show/membership_fees_component.ex #, elixir-autogen, elixir-format msgid "Credit" @@ -2873,6 +2878,11 @@ msgstr "" msgid "Regenerate Cycles" msgstr "" +#: lib/mv_web/live/member_live/show/membership_fees_component.ex +#, elixir-autogen, elixir-format +msgid "Regenerate membership fee cycles" +msgstr "" + #: lib/mv_web/live/member_live/show/membership_fees_component.ex #, elixir-autogen, elixir-format msgid "Regenerating..." diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 489020f..d8a7fe9 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -792,6 +792,11 @@ msgstr "" msgid "Created at:" msgstr "Created at:" +#: lib/mv_web/live/global_settings_live.ex +#, elixir-autogen, elixir-format +msgid "Creates a Vereinfacht finance contact for every member that does not have one yet." +msgstr "" + #: lib/mv_web/live/member_live/show/membership_fees_component.ex #, elixir-autogen, elixir-format msgid "Credit" @@ -2873,6 +2878,11 @@ msgstr "" msgid "Regenerate Cycles" msgstr "" +#: lib/mv_web/live/member_live/show/membership_fees_component.ex +#, elixir-autogen, elixir-format +msgid "Regenerate membership fee cycles" +msgstr "" + #: lib/mv_web/live/member_live/show/membership_fees_component.ex #, elixir-autogen, elixir-format msgid "Regenerating..." diff --git a/test/mv_web/live/global_settings_live_test.exs b/test/mv_web/live/global_settings_live_test.exs index 6cb8f5b..6b886ae 100644 --- a/test/mv_web/live/global_settings_live_test.exs +++ b/test/mv_web/live/global_settings_live_test.exs @@ -300,4 +300,31 @@ defmodule MvWeb.GlobalSettingsLiveTest do "Single Sign-On" end end + + describe "Vereinfacht sync control tooltip (§1.9)" do + setup %{conn: conn} do + user = create_test_user(%{email: "admin@example.com"}) + conn = conn_with_oidc_user(conn, user) + + System.put_env("VEREINFACHT_API_URL", "https://example.test/api/v1") + System.put_env("VEREINFACHT_API_KEY", "test-key") + System.put_env("VEREINFACHT_CLUB_ID", "club-1") + + on_exit(fn -> + System.delete_env("VEREINFACHT_API_URL") + System.delete_env("VEREINFACHT_API_KEY") + System.delete_env("VEREINFACHT_CLUB_ID") + end) + + {:ok, conn: conn} + end + + test "global Vereinfacht sync control carries a tooltip and accessible label", %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/settings") + + # The sync button is wrapped in a <.tooltip> (data-tip) and carries an aria-label + assert has_element?(view, ".tooltip[data-tip] button[phx-click=sync_vereinfacht_contacts]") + assert has_element?(view, "button[phx-click=sync_vereinfacht_contacts][aria-label]") + end + end end diff --git a/test/mv_web/member_live/show_membership_fees_test.exs b/test/mv_web/member_live/show_membership_fees_test.exs index 59dc471..394d743 100644 --- a/test/mv_web/member_live/show_membership_fees_test.exs +++ b/test/mv_web/member_live/show_membership_fees_test.exs @@ -55,6 +55,22 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do |> Ash.create!(actor: system_actor) end + describe "cycle-regeneration control tooltip (§3.5 icon/tooltip audit)" do + test "the regenerate_cycles control carries a tooltip and accessible label", %{conn: conn} do + fee_type = create_fee_type(%{interval: :yearly}) + member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id}) + + {:ok, view, _html} = live(conn, "/members/#{member.id}") + + view + |> element("button[phx-click='switch_tab'][phx-value-tab='membership_fees']") + |> render_click() + + assert has_element?(view, ".tooltip[data-tip] button[phx-click=regenerate_cycles]") + assert has_element?(view, "button[phx-click=regenerate_cycles][aria-label]") + end + end + describe "cycles table display" do test "displays all cycles for member", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly})