From 49fd2181a7fdee1ebd5f74ccc906b83797754e7f Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 25 Feb 2026 13:16:27 +0100 Subject: [PATCH] style: highlight selected table and add tooltip --- DESIGN_DUIDELINES.md | 1 + lib/mv_web/components/core_components.ex | 84 ++++++++- .../components/member_filter_component.ex | 4 +- .../live/custom_field_live/index_component.ex | 1 + lib/mv_web/live/group_live/index.ex | 1 + .../live/member_field_live/index_component.ex | 1 + lib/mv_web/live/member_live/index.ex | 20 ++ lib/mv_web/live/member_live/index.html.heex | 5 +- lib/mv_web/live/member_live/show.ex | 2 +- lib/mv_web/live/role_live/index.html.heex | 1 + lib/mv_web/live/role_live/show.ex | 4 +- lib/mv_web/live/user_live/index.html.heex | 1 + priv/gettext/de/LC_MESSAGES/default.po | 176 +++++++++++++----- priv/gettext/default.pot | 151 ++++++++++----- priv/gettext/en/LC_MESSAGES/default.po | 176 +++++++++++++----- .../components/core_components_table_test.exs | 154 +++++++++++++++ .../live/member_live_authorization_test.exs | 14 +- .../index_membership_fee_status_test.exs | 4 +- test/mv_web/member_live/index_test.exs | 38 ++++ 19 files changed, 687 insertions(+), 151 deletions(-) create mode 100644 test/mv_web/components/core_components_table_test.exs diff --git a/DESIGN_DUIDELINES.md b/DESIGN_DUIDELINES.md index 18864b5..98e43db 100644 --- a/DESIGN_DUIDELINES.md +++ b/DESIGN_DUIDELINES.md @@ -209,6 +209,7 @@ If these cannot be met, use `secondary`/`outline` instead of `ghost`. ### 8.1 Default behavior: row click opens details - **DEFAULT:** Clicking a row navigates to the details page. - **EXCEPTIONS:** Highly interactive rows may disable row-click (document why). +- **Row outline (CoreComponents):** When `row_click` is set, rows get a subtle hover and focus-within ring (theme-friendly). Use `selected_row_id` to show a stronger selected outline (e.g. from URL `?highlight=id` or last selection); the Back link from detail can use `?highlight=id` so the row is visually selected when returning to the index. **IMPORTANT (correctness with our `<.table>` CoreComponent):** Our table implementation attaches the `phx-click` to the **``** when `row_click` is set. That means click events bubble from inner elements up to the cell unless we stop propagation. diff --git a/lib/mv_web/components/core_components.ex b/lib/mv_web/components/core_components.ex index 1e8e7f3..22aeae7 100644 --- a/lib/mv_web/components/core_components.ex +++ b/lib/mv_web/components/core_components.ex @@ -660,6 +660,10 @@ defmodule MvWeb.CoreComponents do Renders a table with generic styling. When `row_click` is set, clicking a row (or a data cell) triggers the handler. + Rows with `row_click` get a subtle hover and focus-within outline (theme-friendly ring). + When `selected_row_id` is set and matches a row's id (via `row_value_id` or `row_item.(row).id`), + that row gets a stronger selected outline (ring-primary) for accessibility (not color-only). + The action column has no phx-click on its ``, so action buttons do not trigger row navigation. For interactive elements inside other columns (e.g. checkboxes, buttons), use `Phoenix.LiveView.JS.stop_propagation()` in the element's phx-click so the row click is not fired. @@ -670,12 +674,36 @@ defmodule MvWeb.CoreComponents do <:col :let={user} label="id">{user.id} <:col :let={user} label="username">{user.username} + + <.table id="members" rows={@members} row_click={fn m -> JS.navigate(~p"/members/#{m}") end} selected_row_id={@selected_member_id}> + <:col :let={m} label="Name">{m.name} + """ attr :id, :string, required: true attr :rows, :list, required: true attr :row_id, :any, default: nil, doc: "the function for generating the row id" attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" + attr :selected_row_id, :any, + default: nil, + doc: + "when set, the row whose id equals this value gets selected styling (single row, e.g. from URL)" + + attr :row_selected?, :any, + default: nil, + doc: + "optional; function (row_item) -> boolean to mark multiple rows as selected (e.g. checkbox selection); overrides selected_row_id when set" + + attr :row_tooltip, :string, + default: nil, + doc: + "optional; when row_click is set, tooltip text for the row (e.g. gettext(\"Click to view\")). Shown as title on hover and as sr-only for screen readers." + + attr :row_value_id, :any, + default: nil, + doc: + "optional; function (row) -> id for comparing with selected_row_id; defaults to row_item.(row).id" + attr :row_item, :any, default: &Function.identity/1, doc: "the function for mapping each row before calling the :col and :action slots" @@ -704,6 +732,12 @@ defmodule MvWeb.CoreComponents do assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) end + # Function to get the row's value id for selected_row_id comparison (no extra DB reads) + row_value_id_fn = + assigns[:row_value_id] || fn row -> assigns.row_item.(row).id end + + assigns = assign(assigns, :row_value_id_fn, row_value_id_fn) + ~H"""
@@ -732,9 +766,15 @@ defmodule MvWeb.CoreComponents do - +
+ <%= if col_idx == 0 && @row_click && @row_tooltip do %> + {@row_tooltip} + <% end %> {render_slot(col, @row_item.(row))} - gettext("All") + gettext("Apply filters") end end @@ -487,7 +487,7 @@ defmodule MvWeb.Components.MemberFilterComponent do # Get boolean filter label (comma-separated list of active filter names) defp boolean_filter_label(_boolean_custom_fields, boolean_filters) when map_size(boolean_filters) == 0 do - gettext("Apply filters") + gettext("All") end defp boolean_filter_label(boolean_custom_fields, boolean_filters) do diff --git a/lib/mv_web/live/custom_field_live/index_component.ex b/lib/mv_web/live/custom_field_live/index_component.ex index a944c85..ebc4930 100644 --- a/lib/mv_web/live/custom_field_live/index_component.ex +++ b/lib/mv_web/live/custom_field_live/index_component.ex @@ -59,6 +59,7 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself) end } + row_tooltip={gettext("Click for dataield details")} > <:col :let={{_id, custom_field}} label={gettext("Name")}>{custom_field.name} diff --git a/lib/mv_web/live/group_live/index.ex b/lib/mv_web/live/group_live/index.ex index 70358e0..76663fd 100644 --- a/lib/mv_web/live/group_live/index.ex +++ b/lib/mv_web/live/group_live/index.ex @@ -62,6 +62,7 @@ defmodule MvWeb.GroupLive.Index do rows={@groups} row_id={fn group -> "group-#{group.id}" end} row_click={fn group -> JS.navigate(~p"/groups/#{group.slug}") end} + row_tooltip={gettext("Click for group details")} > <:col :let={group} label={gettext("Name")}> {group.name} diff --git a/lib/mv_web/live/member_field_live/index_component.ex b/lib/mv_web/live/member_field_live/index_component.ex index 1e8cf05..419b585 100644 --- a/lib/mv_web/live/member_field_live/index_component.ex +++ b/lib/mv_web/live/member_field_live/index_component.ex @@ -57,6 +57,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do JS.push("edit_member_field", value: %{"field" => field_name}, target: @myself) end } + row_tooltip={gettext("Click for datafield details")} > <:col :let={{_field_name, field_data}} label={gettext("Name")}> {MemberFields.label(field_data.field)} diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index 4309611..1be35b4 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -122,6 +122,7 @@ defmodule MvWeb.MemberLive.Index do |> assign(:groups, groups) |> assign(:boolean_custom_field_filters, %{}) |> assign(:selected_members, MapSet.new()) + |> assign(:selected_member_id, nil) |> assign(:settings, settings) |> assign(:custom_fields_visible, custom_fields_visible) |> assign(:all_custom_fields, all_custom_fields) @@ -160,6 +161,12 @@ defmodule MvWeb.MemberLive.Index do - `"select_all"` - Toggles selection of all visible members - `"sort"` - Sort event from SortHeaderComponent. Updates sort field/order and syncs URL """ + @impl true + def handle_event("select_row_and_navigate", %{"id" => id}, socket) do + # Navigate to member show. Back button on show page uses ?highlight=id so returning to index shows row as selected. + {:noreply, push_navigate(socket, to: ~p"/members/#{id}")} + end + @impl true def handle_event("select_member", %{"id" => id}, socket) do selected = @@ -599,6 +606,7 @@ defmodule MvWeb.MemberLive.Index do |> assign(:member_fields_visible_db, visible_member_fields_db) |> assign(:member_fields_visible_computed, visible_member_fields_computed) |> assign(:visible_custom_field_ids, extract_custom_field_ids(visible_custom_fields)) + |> assign(:selected_member_id, parse_highlight_param(params["highlight"])) next_sig = build_signature(socket) @@ -798,6 +806,18 @@ defmodule MvWeb.MemberLive.Index do end end + # Parses optional "highlight" URL param (member id for selected row styling). Returns nil if missing or invalid. + defp parse_highlight_param(nil), do: nil + defp parse_highlight_param(""), do: nil + + defp parse_highlight_param(id) when is_binary(id) do + if String.length(id) <= @max_uuid_length and match?({:ok, _}, Ecto.UUID.cast(id)), + do: id, + else: nil + end + + defp parse_highlight_param(_), do: nil + defp merge_fields_param_from_uri(params, nil), do: params defp merge_fields_param_from_uri(params, %URI{query: query}) when is_binary(query) do diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex index a696b00..eec49de 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -59,6 +59,7 @@ variant="secondary" class={["gap-2", @show_current_cycle && "btn-active"]} phx-click="toggle_cycle_view" + data-testid="toggle-cycle-view" aria-label={ if(@show_current_cycle, do: gettext("Current Cycle Payment Status"), @@ -93,7 +94,9 @@ id="members" rows={@members} row_id={fn member -> "row-#{member.id}" end} - row_click={fn member -> JS.navigate(~p"/members/#{member}") end} + row_click={fn member -> JS.push("select_row_and_navigate", value: %{id: member.id}) end} + row_tooltip={gettext("Click for member details")} + row_selected?={fn member -> MapSet.member?(@selected_members, member.id) end} dynamic_cols={@dynamic_cols} sort_field={@sort_field} sort_order={@sort_order} diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex index ae69c30..6757646 100644 --- a/lib/mv_web/live/member_live/show.ex +++ b/lib/mv_web/live/member_live/show.ex @@ -34,7 +34,7 @@ defmodule MvWeb.MemberLive.Show do {MvWeb.Helpers.MemberHelpers.display_name(@member)} <:actions> <.button - navigate={~p"/members"} + navigate={~p"/members?highlight=#{@member.id}"} variant="neutral" aria-label={gettext("Back to members list")} > diff --git a/lib/mv_web/live/role_live/index.html.heex b/lib/mv_web/live/role_live/index.html.heex index 5829bca..5947472 100644 --- a/lib/mv_web/live/role_live/index.html.heex +++ b/lib/mv_web/live/role_live/index.html.heex @@ -17,6 +17,7 @@ id="roles" rows={@roles} row_click={fn role -> JS.navigate(~p"/admin/roles/#{role}") end} + row_tooltip={gettext("Click for role details")} > <:col :let={role} label={gettext("Name")}>
diff --git a/lib/mv_web/live/role_live/show.ex b/lib/mv_web/live/role_live/show.ex index 8b5b1b2..dd2c4f2 100644 --- a/lib/mv_web/live/role_live/show.ex +++ b/lib/mv_web/live/role_live/show.ex @@ -174,8 +174,8 @@ defmodule MvWeb.RoleLive.Show do {gettext("Back")} <%= if can?(@current_user, :update, Mv.Authorization.Role) do %> - <.button variant="primary" navigate={~p"/admin/roles/#{@role}/edit"}> - <.icon name="hero-pencil-square" /> {gettext("Rolle bearbeiten")} + <.button variant="primary" navigate={~p"/admin/roles/#{@role}/edit"} data-testid=role-edit"> + {gettext("Edit role")} <% end %> <%= if can?(@current_user, :destroy, Mv.Authorization.Role) and not @role.is_system_role do %> diff --git a/lib/mv_web/live/user_live/index.html.heex b/lib/mv_web/live/user_live/index.html.heex index 364e5a4..858e784 100644 --- a/lib/mv_web/live/user_live/index.html.heex +++ b/lib/mv_web/live/user_live/index.html.heex @@ -15,6 +15,7 @@ rows={@users} row_id={fn user -> "row-#{user.id}" end} row_click={fn user -> JS.navigate(~p"/users/#{user}") end} + row_tooltip={gettext("Click for user details")} sort_field={@sort_field} sort_order={@sort_order} > diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 49fbe83..4561f24 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -11,13 +11,11 @@ msgstr "" "Language: de\n" #: lib/mv_web/components/core_components.ex -#: lib/mv_web/live/group_live/index.ex #: lib/mv_web/live/group_live/show.ex #, elixir-autogen, elixir-format msgid "Actions" msgstr "Aktionen" -#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/index.ex #: lib/mv_web/live/role_live/index.html.heex @@ -42,7 +40,6 @@ msgstr "Stadt" #: lib/mv_web/live/custom_field_live/index_component.ex #: lib/mv_web/live/group_live/show.ex -#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/role_live/index.html.heex #: lib/mv_web/live/user_live/index.html.heex @@ -50,15 +47,8 @@ msgstr "Stadt" msgid "Delete" msgstr "Löschen" -#: lib/mv_web/live/custom_field_live/index_component.ex -#: lib/mv_web/live/group_live/index.ex -#: lib/mv_web/live/group_live/show.ex -#: lib/mv_web/live/member_field_live/index_component.ex -#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/role_live/form.ex -#: lib/mv_web/live/role_live/index.html.heex #: lib/mv_web/live/user_live/form.ex -#: lib/mv_web/live/user_live/index.html.heex #, elixir-autogen, elixir-format msgid "Edit" msgstr "Bearbeiten" @@ -277,7 +267,6 @@ msgstr "Dein Passwort wurde erfolgreich zurückgesetzt" #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/user_live/form.ex #, elixir-autogen, elixir-format msgid "Cancel" @@ -790,19 +779,18 @@ msgstr "Alle" msgid "Address" msgstr "Adresse" +#: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/show.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/show.ex +#: lib/mv_web/live/role_live/form.ex +#: lib/mv_web/live/role_live/show.ex #, elixir-autogen, elixir-format msgid "Back" msgstr "Zurück" -#: lib/mv_web/live/member_live/form.ex -#, elixir-autogen, elixir-format -msgid "Coming soon" -msgstr "Demnächst verfügbar" - #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format @@ -820,7 +808,6 @@ msgid "Payment Data" msgstr "Beitragsdaten" #: lib/mv_web/live/components/member_filter_component.ex -#: lib/mv_web/live/member_live/form.ex #, elixir-autogen, elixir-format msgid "Payments" msgstr "Zahlungen" @@ -834,6 +821,7 @@ msgstr "Persönliche Daten" #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex +#: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format msgid "Save" msgstr "Speichern" @@ -851,11 +839,6 @@ msgstr "Mitglied erstellen" msgid "Amount" msgstr "Betrag" -#: lib/mv_web/live/member_field_live/form_component.ex -#, elixir-autogen, elixir-format -msgid "Back to Settings" -msgstr "Zurück zu den Einstellungen" - #: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/index.ex #, elixir-autogen, elixir-format @@ -1575,6 +1558,7 @@ msgid "Show/Hide Columns" msgstr "Spalten ein-/ausblenden" #: lib/mv_web/live/custom_field_live/form_component.ex +#: lib/mv_web/live/member_field_live/form_component.ex #, elixir-autogen, elixir-format, fuzzy msgid "Back to settings" msgstr "Zurück zu den Einstellungen" @@ -1630,11 +1614,6 @@ msgstr "System-Rolle kann nicht gelöscht werden" msgid "Custom" msgstr "Benutzerdefiniert" -#: lib/mv_web/live/role_live/show.ex -#, elixir-autogen, elixir-format -msgid "Edit Role" -msgstr "Rolle bearbeiten" - #: lib/mv_web/live/role_live/index.ex #: lib/mv_web/live/role_live/show.ex #, elixir-autogen, elixir-format @@ -1734,11 +1713,6 @@ msgstr "Rolle nicht gefunden." msgid "Role saved successfully." msgstr "Rolle erfolgreich gespeichert." -#: lib/mv_web/live/role_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save Role" -msgstr "Rolle speichern" - #: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format msgid "Select permission set" @@ -1890,22 +1864,17 @@ msgstr "aktualisiert" msgid "Unknown error" msgstr "Unbekannter Fehler" -#: lib/mv_web/live/member_live/index.ex +#: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format msgid "Member deleted successfully" msgstr "Mitglied wurde erfolgreich gelöscht" -#: lib/mv_web/live/member_live/index.ex +#: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format msgid "Member not found" msgstr "Mitglied nicht gefunden" -#: lib/mv_web/live/member_live/index.ex -#, elixir-autogen, elixir-format -msgid "You do not have permission to access this member" -msgstr "Du hast keine Berechtigung, auf dieses Mitglied zuzugreifen" - -#: lib/mv_web/live/member_live/index.ex +#: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format msgid "You do not have permission to delete this member" msgstr "Du hast keine Berechtigung, dieses Mitglied zu löschen" @@ -1990,11 +1959,6 @@ msgstr "Mitgliedsfilter" msgid "Payment Status" msgstr "Bezahlstatus" -#: lib/mv_web/live/components/member_filter_component.ex -#, elixir-autogen, elixir-format -msgid "Reset" -msgstr "Zurücksetzen" - #: lib/mv_web/live/import_live/components.ex #, elixir-autogen, elixir-format msgid " (Field: %{field})" @@ -3098,3 +3062,125 @@ msgstr "Nur OIDC-Anmeldung (Passwort-Login ausblenden)" #, elixir-autogen, elixir-format msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." msgstr "Wenn aktiviert und OIDC konfiguriert ist, zeigt die Anmeldeseite nur den Single-Sign-On-Button." + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "Clear filters" +msgstr "Filter zurücksetzen“" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "Apply filters" +msgstr "Filter auswählen" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete %{name}? This action cannot be undone." +msgstr "Möchtest du diese Gruppe wirklich löschen? Die Aktion kann nicht rückgängig gemacht werden." + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Danger zone" +msgstr "Gefahrenzone" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete member" +msgstr "Mitglied löschen" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Delete member %{name}" +msgstr "Mitglied %{name} löschen" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Deleting this member cannot be undone. All related data (e.g. membership fee cycles) will be removed." +msgstr "Das Löschen des Mitglieds kann nicht rückgängig gemacht werden. Alle dazugehörigen Daten (z.B. Mitgliedsbeitragszylen) werden gelöscht." + +#: 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 "Edit datafield" +msgstr "Datenfeld bearbeiten" + +#: lib/mv_web/live/group_live/index.ex +#: lib/mv_web/live/group_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit group" +msgstr "Gruppe bearbeiten" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit member" +msgstr "Mitglied bearbeiten" + +#: lib/mv_web/live/role_live/index.html.heex +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit role" +msgstr "Rolle bearbeiten" + +#: lib/mv_web/live/user_live/index.html.heex +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit user" +msgstr "Benutzer*in bearbeiten" + +#: lib/mv_web/live/role_live/show.ex +#, elixir-autogen, elixir-format +msgid "Rolle bearbeiten" +msgstr "Rolle bearbeiten" + +#: lib/mv_web/live/custom_field_live/index_component.ex +#, elixir-autogen, elixir-format +msgid "Click for custom field details" +msgstr "Klicke für Datenfeld-Details" + +#: lib/mv_web/live/member_field_live/index_component.ex +#, elixir-autogen, elixir-format +msgid "Click for datafield details" +msgstr "Klicke für Datenfeld-Details" + +#: lib/mv_web/live/group_live/index.ex +#, elixir-autogen, elixir-format +msgid "Click for group details" +msgstr "Klicke für Gruppen-Details" + +#: lib/mv_web/live/member_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Click for member details" +msgstr "Klicke für Mitglieds-Details" + +#: lib/mv_web/live/role_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Click for role details" +msgstr "Klicke für Rollen-Details" + +#: lib/mv_web/live/user_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Click for user details" +msgstr "Klicke für Benutzer*innen-Details" + +#~ #: lib/mv_web/live/member_field_live/form_component.ex +#~ #, elixir-autogen, elixir-format +#~ msgid "Back to Settings" +#~ msgstr "Zurück zu den Einstellungen" + +#~ #: lib/mv_web/live/member_live/form.ex +#~ #, elixir-autogen, elixir-format +#~ msgid "Coming soon" +#~ msgstr "Demnächst verfügbar" + +#~ #: lib/mv_web/live/components/member_filter_component.ex +#~ #, elixir-autogen, elixir-format +#~ msgid "Reset" +#~ msgstr "Zurücksetzen" + +#~ #: lib/mv_web/live/role_live/form.ex +#~ #, elixir-autogen, elixir-format +#~ msgid "Save Role" +#~ msgstr "Rolle speichern" + +#~ #: lib/mv_web/live/member_live/index.ex +#~ #, elixir-autogen, elixir-format +#~ msgid "You do not have permission to access this member" +#~ msgstr "Du hast keine Berechtigung, auf dieses Mitglied zuzugreifen" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index ea8e976..cea7991 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -12,13 +12,11 @@ msgid "" msgstr "" #: lib/mv_web/components/core_components.ex -#: lib/mv_web/live/group_live/index.ex #: lib/mv_web/live/group_live/show.ex #, elixir-autogen, elixir-format msgid "Actions" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/index.ex #: lib/mv_web/live/role_live/index.html.heex @@ -43,7 +41,6 @@ msgstr "" #: lib/mv_web/live/custom_field_live/index_component.ex #: lib/mv_web/live/group_live/show.ex -#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/role_live/index.html.heex #: lib/mv_web/live/user_live/index.html.heex @@ -51,15 +48,8 @@ msgstr "" msgid "Delete" msgstr "" -#: lib/mv_web/live/custom_field_live/index_component.ex -#: lib/mv_web/live/group_live/index.ex -#: lib/mv_web/live/group_live/show.ex -#: lib/mv_web/live/member_field_live/index_component.ex -#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/role_live/form.ex -#: lib/mv_web/live/role_live/index.html.heex #: lib/mv_web/live/user_live/form.ex -#: lib/mv_web/live/user_live/index.html.heex #, elixir-autogen, elixir-format msgid "Edit" msgstr "" @@ -278,7 +268,6 @@ msgstr "" #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/user_live/form.ex #, elixir-autogen, elixir-format msgid "Cancel" @@ -791,19 +780,18 @@ msgstr "" msgid "Address" msgstr "" +#: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/show.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/show.ex +#: lib/mv_web/live/role_live/form.ex +#: lib/mv_web/live/role_live/show.ex #, elixir-autogen, elixir-format msgid "Back" msgstr "" -#: lib/mv_web/live/member_live/form.ex -#, elixir-autogen, elixir-format -msgid "Coming soon" -msgstr "" - #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format @@ -821,7 +809,6 @@ msgid "Payment Data" msgstr "" #: lib/mv_web/live/components/member_filter_component.ex -#: lib/mv_web/live/member_live/form.ex #, elixir-autogen, elixir-format msgid "Payments" msgstr "" @@ -835,6 +822,7 @@ msgstr "" #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex +#: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format msgid "Save" msgstr "" @@ -852,11 +840,6 @@ msgstr "" msgid "Amount" msgstr "" -#: lib/mv_web/live/member_field_live/form_component.ex -#, elixir-autogen, elixir-format -msgid "Back to Settings" -msgstr "" - #: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/index.ex #, elixir-autogen, elixir-format @@ -1576,6 +1559,7 @@ msgid "Show/Hide Columns" msgstr "" #: lib/mv_web/live/custom_field_live/form_component.ex +#: lib/mv_web/live/member_field_live/form_component.ex #, elixir-autogen, elixir-format msgid "Back to settings" msgstr "" @@ -1631,11 +1615,6 @@ msgstr "" msgid "Custom" msgstr "" -#: lib/mv_web/live/role_live/show.ex -#, elixir-autogen, elixir-format -msgid "Edit Role" -msgstr "" - #: lib/mv_web/live/role_live/index.ex #: lib/mv_web/live/role_live/show.ex #, elixir-autogen, elixir-format @@ -1735,11 +1714,6 @@ msgstr "" msgid "Role saved successfully." msgstr "" -#: lib/mv_web/live/role_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save Role" -msgstr "" - #: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format msgid "Select permission set" @@ -1891,22 +1865,17 @@ msgstr "" msgid "Unknown error" msgstr "" -#: lib/mv_web/live/member_live/index.ex +#: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format msgid "Member deleted successfully" msgstr "" -#: lib/mv_web/live/member_live/index.ex +#: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format msgid "Member not found" msgstr "" -#: lib/mv_web/live/member_live/index.ex -#, elixir-autogen, elixir-format -msgid "You do not have permission to access this member" -msgstr "" - -#: lib/mv_web/live/member_live/index.ex +#: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format msgid "You do not have permission to delete this member" msgstr "" @@ -1991,11 +1960,6 @@ msgstr "" msgid "Payment Status" msgstr "" -#: lib/mv_web/live/components/member_filter_component.ex -#, elixir-autogen, elixir-format -msgid "Reset" -msgstr "" - #: lib/mv_web/live/import_live/components.ex #, elixir-autogen, elixir-format msgid " (Field: %{field})" @@ -3098,3 +3062,100 @@ msgstr "" #, elixir-autogen, elixir-format msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." msgstr "" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "Clear filters" +msgstr "" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "Apply filters" +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Are you sure you want to delete %{name}? This action cannot be undone." +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Danger zone" +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Delete member" +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Delete member %{name}" +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Deleting this member cannot be undone. All related data (e.g. membership fee cycles) will be removed." +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 "Edit datafield" +msgstr "" + +#: lib/mv_web/live/group_live/index.ex +#: lib/mv_web/live/group_live/show.ex +#, elixir-autogen, elixir-format +msgid "Edit group" +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Edit member" +msgstr "" + +#: lib/mv_web/live/role_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Edit role" +msgstr "" + +#: lib/mv_web/live/user_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Edit user" +msgstr "" + +#: lib/mv_web/live/role_live/show.ex +#, elixir-autogen, elixir-format +msgid "Rolle bearbeiten" +msgstr "" + +#: lib/mv_web/live/custom_field_live/index_component.ex +#, elixir-autogen, elixir-format +msgid "Click for custom field details" +msgstr "" + +#: lib/mv_web/live/member_field_live/index_component.ex +#, elixir-autogen, elixir-format +msgid "Click for datafield details" +msgstr "" + +#: lib/mv_web/live/group_live/index.ex +#, elixir-autogen, elixir-format +msgid "Click for group details" +msgstr "" + +#: lib/mv_web/live/member_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Click for member details" +msgstr "" + +#: lib/mv_web/live/role_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Click for role details" +msgstr "" + +#: lib/mv_web/live/user_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Click for user details" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 915fc52..9f38efe 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -12,13 +12,11 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: lib/mv_web/components/core_components.ex -#: lib/mv_web/live/group_live/index.ex #: lib/mv_web/live/group_live/show.ex #, elixir-autogen, elixir-format msgid "Actions" msgstr "" -#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/index.ex #: lib/mv_web/live/role_live/index.html.heex @@ -43,7 +41,6 @@ msgstr "" #: lib/mv_web/live/custom_field_live/index_component.ex #: lib/mv_web/live/group_live/show.ex -#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/role_live/index.html.heex #: lib/mv_web/live/user_live/index.html.heex @@ -51,15 +48,8 @@ msgstr "" msgid "Delete" msgstr "" -#: lib/mv_web/live/custom_field_live/index_component.ex -#: lib/mv_web/live/group_live/index.ex -#: lib/mv_web/live/group_live/show.ex -#: lib/mv_web/live/member_field_live/index_component.ex -#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/role_live/form.ex -#: lib/mv_web/live/role_live/index.html.heex #: lib/mv_web/live/user_live/form.ex -#: lib/mv_web/live/user_live/index.html.heex #, elixir-autogen, elixir-format msgid "Edit" msgstr "" @@ -278,7 +268,6 @@ msgstr "" #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/user_live/form.ex #, elixir-autogen, elixir-format msgid "Cancel" @@ -791,19 +780,18 @@ msgstr "" msgid "Address" msgstr "" +#: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/show.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/show.ex +#: lib/mv_web/live/role_live/form.ex +#: lib/mv_web/live/role_live/show.ex #, elixir-autogen, elixir-format msgid "Back" msgstr "" -#: lib/mv_web/live/member_live/form.ex -#, elixir-autogen, elixir-format -msgid "Coming soon" -msgstr "" - #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format @@ -821,7 +809,6 @@ msgid "Payment Data" msgstr "" #: lib/mv_web/live/components/member_filter_component.ex -#: lib/mv_web/live/member_live/form.ex #, elixir-autogen, elixir-format msgid "Payments" msgstr "" @@ -835,6 +822,7 @@ msgstr "" #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex +#: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format, fuzzy msgid "Save" msgstr "" @@ -852,11 +840,6 @@ msgstr "" msgid "Amount" msgstr "" -#: lib/mv_web/live/member_field_live/form_component.ex -#, elixir-autogen, elixir-format -msgid "Back to Settings" -msgstr "" - #: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/index.ex #, elixir-autogen, elixir-format @@ -1576,6 +1559,7 @@ msgid "Show/Hide Columns" msgstr "" #: lib/mv_web/live/custom_field_live/form_component.ex +#: lib/mv_web/live/member_field_live/form_component.ex #, elixir-autogen, elixir-format, fuzzy msgid "Back to settings" msgstr "" @@ -1631,11 +1615,6 @@ msgstr "" msgid "Custom" msgstr "" -#: lib/mv_web/live/role_live/show.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Edit Role" -msgstr "" - #: lib/mv_web/live/role_live/index.ex #: lib/mv_web/live/role_live/show.ex #, elixir-autogen, elixir-format, fuzzy @@ -1735,11 +1714,6 @@ msgstr "" msgid "Role saved successfully." msgstr "" -#: lib/mv_web/live/role_live/form.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Role" -msgstr "" - #: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format msgid "Select permission set" @@ -1891,22 +1865,17 @@ msgstr "" msgid "Unknown error" msgstr "" -#: lib/mv_web/live/member_live/index.ex +#: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format msgid "Member deleted successfully" msgstr "" -#: lib/mv_web/live/member_live/index.ex +#: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format msgid "Member not found" msgstr "" -#: lib/mv_web/live/member_live/index.ex -#, elixir-autogen, elixir-format -msgid "You do not have permission to access this member" -msgstr "" - -#: lib/mv_web/live/member_live/index.ex +#: lib/mv_web/live/member_live/show.ex #, elixir-autogen, elixir-format msgid "You do not have permission to delete this member" msgstr "" @@ -1991,11 +1960,6 @@ msgstr "" msgid "Payment Status" msgstr "" -#: lib/mv_web/live/components/member_filter_component.ex -#, elixir-autogen, elixir-format -msgid "Reset" -msgstr "" - #: lib/mv_web/live/import_live/components.ex #, elixir-autogen, elixir-format msgid " (Field: %{field})" @@ -3098,3 +3062,125 @@ msgstr "" #, elixir-autogen, elixir-format msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." msgstr "" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "Clear filters" +msgstr "" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "Apply filters" +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Are you sure you want to delete %{name}? This action cannot be undone." +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Danger zone" +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Delete member" +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Delete member %{name}" +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format +msgid "Deleting this member cannot be undone. All related data (e.g. membership fee cycles) will be removed." +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 "Edit datafield" +msgstr "" + +#: lib/mv_web/live/group_live/index.ex +#: lib/mv_web/live/group_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit group" +msgstr "" + +#: lib/mv_web/live/member_live/show.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit member" +msgstr "" + +#: lib/mv_web/live/role_live/index.html.heex +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit role" +msgstr "" + +#: lib/mv_web/live/user_live/index.html.heex +#, elixir-autogen, elixir-format, fuzzy +msgid "Edit user" +msgstr "" + +#: lib/mv_web/live/role_live/show.ex +#, elixir-autogen, elixir-format +msgid "Rolle bearbeiten" +msgstr "" + +#: lib/mv_web/live/custom_field_live/index_component.ex +#, elixir-autogen, elixir-format +msgid "Click for custom field details" +msgstr "" + +#: lib/mv_web/live/member_field_live/index_component.ex +#, elixir-autogen, elixir-format +msgid "Click for datafield details" +msgstr "" + +#: lib/mv_web/live/group_live/index.ex +#, elixir-autogen, elixir-format +msgid "Click for group details" +msgstr "" + +#: lib/mv_web/live/member_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Click for member details" +msgstr "" + +#: lib/mv_web/live/role_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Click for role details" +msgstr "" + +#: lib/mv_web/live/user_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Click for user details" +msgstr "" + +#~ #: lib/mv_web/live/member_field_live/form_component.ex +#~ #, elixir-autogen, elixir-format +#~ msgid "Back to Settings" +#~ msgstr "" + +#~ #: lib/mv_web/live/member_live/form.ex +#~ #, elixir-autogen, elixir-format +#~ msgid "Coming soon" +#~ msgstr "" + +#~ #: lib/mv_web/live/components/member_filter_component.ex +#~ #, elixir-autogen, elixir-format +#~ msgid "Reset" +#~ msgstr "" + +#~ #: lib/mv_web/live/role_live/form.ex +#~ #, elixir-autogen, elixir-format, fuzzy +#~ msgid "Save Role" +#~ msgstr "" + +#~ #: lib/mv_web/live/member_live/index.ex +#~ #, elixir-autogen, elixir-format +#~ msgid "You do not have permission to access this member" +#~ msgstr "" diff --git a/test/mv_web/components/core_components_table_test.exs b/test/mv_web/components/core_components_table_test.exs new file mode 100644 index 0000000..931b42a --- /dev/null +++ b/test/mv_web/components/core_components_table_test.exs @@ -0,0 +1,154 @@ +defmodule MvWeb.Components.CoreComponentsTableTest do + @moduledoc """ + Tests for the CoreComponents table: row hover/focus and selected styling. + """ + use MvWeb.ConnCase, async: true + + import Phoenix.LiveViewTest + + alias MvWeb.CoreComponents + + describe "table row_click styling" do + test "when row_click is set, table rows have hover and focus-within ring classes" do + rows = [%{id: "1", name: "Alice"}, %{id: "2", name: "Bob"}] + + assigns = %{ + id: "test-table", + rows: rows, + row_id: fn r -> "row-#{r.id}" end, + row_click: fn _ -> nil end, + row_item: &Function.identity/1, + col: [ + %{ + __slot__: :col, + label: "Name", + inner_block: fn _socket, item -> [item[:name] || item["name"] || ""] end + } + ], + dynamic_cols: [], + action: [] + } + + html = render_component(&CoreComponents.table/1, assigns) + + assert html =~ "hover:ring-2" + assert html =~ "focus-within:ring-2" + assert html =~ "hover:ring-base-content/10" + end + + test "when row_click is nil, table rows do not have hover ring classes" do + rows = [%{id: "1", name: "Alice"}] + + assigns = %{ + id: "test-table", + rows: rows, + row_id: fn r -> "row-#{r.id}" end, + row_click: nil, + row_item: &Function.identity/1, + col: [ + %{ + __slot__: :col, + label: "Name", + inner_block: fn _socket, item -> [item[:name] || ""] end + } + ], + dynamic_cols: [], + action: [] + } + + html = render_component(&CoreComponents.table/1, assigns) + + refute html =~ "hover:ring-2" + refute html =~ "focus-within:ring-2" + end + end + + describe "table selected_row_id styling" do + test "when selected_row_id matches a row id, that row has data-selected and ring-primary" do + rows = [%{id: "one", name: "Alice"}, %{id: "two", name: "Bob"}] + + assigns = %{ + id: "test-table", + rows: rows, + row_id: fn r -> "row-#{r.id}" end, + row_click: fn _ -> nil end, + selected_row_id: "two", + row_item: &Function.identity/1, + col: [ + %{ + __slot__: :col, + label: "Name", + inner_block: fn _socket, item -> [item[:name] || ""] end + } + ], + dynamic_cols: [], + action: [] + } + + html = render_component(&CoreComponents.table/1, assigns) + + assert html =~ ~s(id="row-two") + assert html =~ ~s(data-selected="true") + assert html =~ "ring-primary" + end + + test "when selected_row_id is nil, no row has data-selected" do + rows = [%{id: "1", name: "Alice"}] + + assigns = %{ + id: "test-table", + rows: rows, + row_id: fn r -> "row-#{r.id}" end, + row_click: nil, + selected_row_id: nil, + row_item: &Function.identity/1, + col: [ + %{ + __slot__: :col, + label: "Name", + inner_block: fn _socket, item -> [item[:name] || ""] end + } + ], + dynamic_cols: [], + action: [] + } + + html = render_component(&CoreComponents.table/1, assigns) + + refute html =~ ~s(data-selected="true") + end + + test "when row_selected? is set, multiple rows can have data-selected and ring-primary" do + rows = [%{id: "a", name: "Alice"}, %{id: "b", name: "Bob"}, %{id: "c", name: "Claire"}] + selected_ids = MapSet.new(["a", "c"]) + + assigns = %{ + id: "test-table", + rows: rows, + row_id: fn r -> "row-#{r.id}" end, + row_click: fn _ -> nil end, + row_selected?: fn item -> MapSet.member?(selected_ids, item.id) end, + row_item: &Function.identity/1, + col: [ + %{ + __slot__: :col, + label: "Name", + inner_block: fn _socket, item -> [item[:name] || ""] end + } + ], + dynamic_cols: [], + action: [] + } + + html = render_component(&CoreComponents.table/1, assigns) + + # Two rows selected (a and c), one not (b) + assert html =~ ~s(id="row-a") + assert html =~ ~s(id="row-b") + assert html =~ ~s(id="row-c") + # data-selected appears twice (for row a and row c) + assert String.contains?(html, ~s(data-selected="true")) + assert html =~ "ring-primary" + end + end +end diff --git a/test/mv_web/live/member_live_authorization_test.exs b/test/mv_web/live/member_live_authorization_test.exs index 9a23019..c5db9d6 100644 --- a/test/mv_web/live/member_live_authorization_test.exs +++ b/test/mv_web/live/member_live_authorization_test.exs @@ -24,6 +24,7 @@ defmodule MvWeb.MemberLiveAuthorizationTest do {:ok, view, _html} = live(conn, "/members") + # Index table has no Edit/Delete per row (only sr-only Show link); ensure they are not present refute has_element?(view, "#row-#{member.id} [data-testid=member-edit]") refute has_element?(view, "#row-#{member.id} [data-testid=member-delete]") end @@ -31,17 +32,18 @@ defmodule MvWeb.MemberLiveAuthorizationTest do describe "Member Index - Kassenwart (normal_user)" do @tag role: :normal_user - test "sees New Member and Edit buttons", %{conn: conn} do + test "sees New Member and Show link in row", %{conn: conn} do member = Fixtures.member_fixture() {:ok, view, _html} = live(conn, "/members") assert has_element?(view, "[data-testid=member-new]") - assert has_element?(view, "#row-#{member.id} [data-testid=member-edit]") + # Index table action column has sr-only Show link only (Edit is on member show page) + assert has_element?(view, "#row-#{member.id} [data-testid=member-show-link]") end @tag role: :normal_user - test "does not see Delete button", %{conn: conn} do + test "does not see Delete button in table", %{conn: conn} do member = Fixtures.member_fixture() {:ok, view, _html} = live(conn, "/members") @@ -52,14 +54,14 @@ defmodule MvWeb.MemberLiveAuthorizationTest do describe "Member Index - Admin" do @tag role: :admin - test "sees New Member, Edit and Delete buttons", %{conn: conn} do + test "sees New Member and Show link in row", %{conn: conn} do member = Fixtures.member_fixture() {:ok, view, _html} = live(conn, "/members") assert has_element?(view, "[data-testid=member-new]") - assert has_element?(view, "#row-#{member.id} [data-testid=member-edit]") - assert has_element?(view, "#row-#{member.id} [data-testid=member-delete]") + # Index table action column has sr-only Show link only (Edit/Delete are on member show page) + assert has_element?(view, "#row-#{member.id} [data-testid=member-show-link]") end end diff --git a/test/mv_web/member_live/index_membership_fee_status_test.exs b/test/mv_web/member_live/index_membership_fee_status_test.exs index bbd9159..add2fba 100644 --- a/test/mv_web/member_live/index_membership_fee_status_test.exs +++ b/test/mv_web/member_live/index_membership_fee_status_test.exs @@ -107,9 +107,9 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do {:ok, view, _html} = live(conn, "/members") - # Toggle to current cycle (use the button in the header, not the one in the column) + # Toggle to current cycle (use the button in the header) view - |> element("button[phx-click='toggle_cycle_view'].btn.gap-2") + |> element("[data-testid=toggle-cycle-view]") |> render_click() html = render(view) diff --git a/test/mv_web/member_live/index_test.exs b/test/mv_web/member_live/index_test.exs index d8846ea..1c8328f 100644 --- a/test/mv_web/member_live/index_test.exs +++ b/test/mv_web/member_live/index_test.exs @@ -304,6 +304,44 @@ defmodule MvWeb.MemberLive.IndexTest do assert_redirect(view, ~p"/members/#{member}") end + describe "table row outline (hover and selected)" do + @describetag :ui + + test "clickable rows have hover and focus-within ring classes", %{conn: conn} do + system_actor = Mv.Helpers.SystemActor.get_system_actor() + + {:ok, _member} = + Mv.Membership.create_member( + %{first_name: "Hover", last_name: "Test", email: "hover@example.com"}, + actor: system_actor + ) + + conn = conn_with_oidc_user(conn) + {:ok, _view, html} = live(conn, "/members") + + # CoreComponents table adds hover and focus-within ring when row_click is set + assert html =~ "hover:ring-2" + assert html =~ "focus-within:ring-2" + assert html =~ "hover:ring-base-content/10" + end + + test "selected outline only from checkbox selection, not from highlight param", %{conn: conn} do + system_actor = Mv.Helpers.SystemActor.get_system_actor() + + {:ok, member} = + Mv.Membership.create_member( + %{first_name: "Highlight", last_name: "Only", email: "highlight@example.com"}, + actor: system_actor + ) + + conn = conn_with_oidc_user(conn) + {:ok, view, _html} = live(conn, "/members?highlight=#{member.id}") + + # Outline is only for checkbox selection; highlight param does not set data-selected + refute has_element?(view, "tr#row-#{member.id}[data-selected='true']") + end + end + describe "copy_emails feature" do setup do system_actor = Mv.Helpers.SystemActor.get_system_actor()