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})