style: highlight selected table and add tooltip
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
carla 2026-02-25 13:16:27 +01:00
parent 02af136fd9
commit 49fd2181a7
19 changed files with 687 additions and 151 deletions

View file

@ -209,6 +209,7 @@ If these cannot be met, use `secondary`/`outline` instead of `ghost`.
### 8.1 Default behavior: row click opens details ### 8.1 Default behavior: row click opens details
- **DEFAULT:** Clicking a row navigates to the details page. - **DEFAULT:** Clicking a row navigates to the details page.
- **EXCEPTIONS:** Highly interactive rows may disable row-click (document why). - **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):** **IMPORTANT (correctness with our `<.table>` CoreComponent):**
Our table implementation attaches the `phx-click` to the **`<td>`** when `row_click` is set. That means click events bubble from inner elements up to the cell unless we stop propagation. Our table implementation attaches the `phx-click` to the **`<td>`** when `row_click` is set. That means click events bubble from inner elements up to the cell unless we stop propagation.

View file

@ -660,6 +660,10 @@ defmodule MvWeb.CoreComponents do
Renders a table with generic styling. Renders a table with generic styling.
When `row_click` is set, clicking a row (or a data cell) triggers the handler. 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 `<td>`, so action buttons do not trigger row navigation. The action column has no phx-click on its `<td>`, so action buttons do not trigger row navigation.
For interactive elements inside other columns (e.g. checkboxes, buttons), use 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. `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> <:col :let={user} label="id">{user.id}</:col>
<:col :let={user} label="username">{user.username}</:col> <:col :let={user} label="username">{user.username}</:col>
</.table> </.table>
<.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}</:col>
</.table>
""" """
attr :id, :string, required: true attr :id, :string, required: true
attr :rows, :list, required: true attr :rows, :list, required: true
attr :row_id, :any, default: nil, doc: "the function for generating the row id" 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 :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, attr :row_item, :any,
default: &Function.identity/1, default: &Function.identity/1,
doc: "the function for mapping each row before calling the :col and :action slots" 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) assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
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""" ~H"""
<div class="overflow-auto"> <div class="overflow-auto">
<table class="table table-zebra"> <table class="table table-zebra">
@ -732,9 +766,15 @@ defmodule MvWeb.CoreComponents do
</tr> </tr>
</thead> </thead>
<tbody id={@id} phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && "stream"}> <tbody id={@id} phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && "stream"}>
<tr :for={row <- @rows} id={@row_id && @row_id.(row)}> <tr
:for={row <- @rows}
id={@row_id && @row_id.(row)}
class={table_row_tr_class(@row_click, table_row_selected?(assigns, row))}
data-selected={table_row_selected?(assigns, row) && "true"}
title={@row_click && @row_tooltip}
>
<td <td
:for={col <- @col} :for={{col, col_idx} <- Enum.with_index(@col)}
phx-click={ phx-click={
(col[:col_click] && col[:col_click].(@row_item.(row))) || (col[:col_click] && col[:col_click].(@row_item.(row))) ||
(@row_click && @row_click.(row)) (@row_click && @row_click.(row))
@ -768,6 +808,9 @@ defmodule MvWeb.CoreComponents do
Enum.join(classes, " ") Enum.join(classes, " ")
} }
> >
<%= if col_idx == 0 && @row_click && @row_tooltip do %>
<span class="sr-only">{@row_tooltip}</span>
<% end %>
{render_slot(col, @row_item.(row))} {render_slot(col, @row_item.(row))}
</td> </td>
<td <td
@ -801,6 +844,43 @@ defmodule MvWeb.CoreComponents do
""" """
end end
# Returns true if the row is selected (via row_selected?/1 or selected_row_id match).
defp table_row_selected?(assigns, row) do
item = assigns.row_item.(row)
if assigns[:row_selected?] do
assigns.row_selected?.(item)
else
assigns[:selected_row_id] != nil and
assigns.row_value_id_fn.(row) == assigns.selected_row_id
end
end
# Returns CSS classes for table row: hover/focus-within outline when row_click is set,
# and stronger selected outline when selected (WCAG: not color-only).
# Hover/focus-within are omitted for the selected row so the selected ring stays visible.
defp table_row_tr_class(row_click, selected?) do
has_row_click? = not is_nil(row_click)
base = []
base =
if has_row_click? and not selected?,
do:
base ++
[
"hover:ring-2",
"hover:ring-inset",
"hover:ring-base-content/10",
"focus-within:ring-2",
"focus-within:ring-inset",
"focus-within:ring-base-content/10"
],
else: base
base = if selected?, do: base ++ ["ring-2", "ring-inset", "ring-primary"], else: base
Enum.join(base, " ")
end
defp table_th_aria_sort(col, sort_field, sort_order) do defp table_th_aria_sort(col, sort_field, sort_order) do
col_sort = Map.get(col, :sort_field) col_sort = Map.get(col, :sort_field)

View file

@ -460,7 +460,7 @@ defmodule MvWeb.Components.MemberFilterComponent do
boolean_filter_label(boolean_custom_fields, boolean_filters) boolean_filter_label(boolean_custom_fields, boolean_filters)
true -> true ->
gettext("All") gettext("Apply filters")
end end
end end
@ -487,7 +487,7 @@ defmodule MvWeb.Components.MemberFilterComponent do
# Get boolean filter label (comma-separated list of active filter names) # Get boolean filter label (comma-separated list of active filter names)
defp boolean_filter_label(_boolean_custom_fields, boolean_filters) defp boolean_filter_label(_boolean_custom_fields, boolean_filters)
when map_size(boolean_filters) == 0 do when map_size(boolean_filters) == 0 do
gettext("Apply filters") gettext("All")
end end
defp boolean_filter_label(boolean_custom_fields, boolean_filters) do defp boolean_filter_label(boolean_custom_fields, boolean_filters) do

View file

@ -59,6 +59,7 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself) JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)
end end
} }
row_tooltip={gettext("Click for dataield details")}
> >
<:col :let={{_id, custom_field}} label={gettext("Name")}>{custom_field.name}</:col> <:col :let={{_id, custom_field}} label={gettext("Name")}>{custom_field.name}</:col>

View file

@ -62,6 +62,7 @@ defmodule MvWeb.GroupLive.Index do
rows={@groups} rows={@groups}
row_id={fn group -> "group-#{group.id}" end} row_id={fn group -> "group-#{group.id}" end}
row_click={fn group -> JS.navigate(~p"/groups/#{group.slug}") 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")}> <:col :let={group} label={gettext("Name")}>
{group.name} {group.name}

View file

@ -57,6 +57,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do
JS.push("edit_member_field", value: %{"field" => field_name}, target: @myself) JS.push("edit_member_field", value: %{"field" => field_name}, target: @myself)
end end
} }
row_tooltip={gettext("Click for datafield details")}
> >
<:col :let={{_field_name, field_data}} label={gettext("Name")}> <:col :let={{_field_name, field_data}} label={gettext("Name")}>
{MemberFields.label(field_data.field)} {MemberFields.label(field_data.field)}

View file

@ -122,6 +122,7 @@ defmodule MvWeb.MemberLive.Index do
|> assign(:groups, groups) |> assign(:groups, groups)
|> assign(:boolean_custom_field_filters, %{}) |> assign(:boolean_custom_field_filters, %{})
|> assign(:selected_members, MapSet.new()) |> assign(:selected_members, MapSet.new())
|> assign(:selected_member_id, nil)
|> assign(:settings, settings) |> assign(:settings, settings)
|> assign(:custom_fields_visible, custom_fields_visible) |> assign(:custom_fields_visible, custom_fields_visible)
|> assign(:all_custom_fields, all_custom_fields) |> assign(:all_custom_fields, all_custom_fields)
@ -160,6 +161,12 @@ defmodule MvWeb.MemberLive.Index do
- `"select_all"` - Toggles selection of all visible members - `"select_all"` - Toggles selection of all visible members
- `"sort"` - Sort event from SortHeaderComponent. Updates sort field/order and syncs URL - `"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 @impl true
def handle_event("select_member", %{"id" => id}, socket) do def handle_event("select_member", %{"id" => id}, socket) do
selected = selected =
@ -599,6 +606,7 @@ defmodule MvWeb.MemberLive.Index do
|> assign(:member_fields_visible_db, visible_member_fields_db) |> assign(:member_fields_visible_db, visible_member_fields_db)
|> assign(:member_fields_visible_computed, visible_member_fields_computed) |> assign(:member_fields_visible_computed, visible_member_fields_computed)
|> assign(:visible_custom_field_ids, extract_custom_field_ids(visible_custom_fields)) |> 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) next_sig = build_signature(socket)
@ -798,6 +806,18 @@ defmodule MvWeb.MemberLive.Index do
end end
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, nil), do: params
defp merge_fields_param_from_uri(params, %URI{query: query}) when is_binary(query) do defp merge_fields_param_from_uri(params, %URI{query: query}) when is_binary(query) do

View file

@ -59,6 +59,7 @@
variant="secondary" variant="secondary"
class={["gap-2", @show_current_cycle && "btn-active"]} class={["gap-2", @show_current_cycle && "btn-active"]}
phx-click="toggle_cycle_view" phx-click="toggle_cycle_view"
data-testid="toggle-cycle-view"
aria-label={ aria-label={
if(@show_current_cycle, if(@show_current_cycle,
do: gettext("Current Cycle Payment Status"), do: gettext("Current Cycle Payment Status"),
@ -93,7 +94,9 @@
id="members" id="members"
rows={@members} rows={@members}
row_id={fn member -> "row-#{member.id}" end} 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} dynamic_cols={@dynamic_cols}
sort_field={@sort_field} sort_field={@sort_field}
sort_order={@sort_order} sort_order={@sort_order}

View file

@ -34,7 +34,7 @@ defmodule MvWeb.MemberLive.Show do
{MvWeb.Helpers.MemberHelpers.display_name(@member)} {MvWeb.Helpers.MemberHelpers.display_name(@member)}
<:actions> <:actions>
<.button <.button
navigate={~p"/members"} navigate={~p"/members?highlight=#{@member.id}"}
variant="neutral" variant="neutral"
aria-label={gettext("Back to members list")} aria-label={gettext("Back to members list")}
> >

View file

@ -17,6 +17,7 @@
id="roles" id="roles"
rows={@roles} rows={@roles}
row_click={fn role -> JS.navigate(~p"/admin/roles/#{role}") end} row_click={fn role -> JS.navigate(~p"/admin/roles/#{role}") end}
row_tooltip={gettext("Click for role details")}
> >
<:col :let={role} label={gettext("Name")}> <:col :let={role} label={gettext("Name")}>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">

View file

@ -174,8 +174,8 @@ defmodule MvWeb.RoleLive.Show do
{gettext("Back")} {gettext("Back")}
</.button> </.button>
<%= if can?(@current_user, :update, Mv.Authorization.Role) do %> <%= if can?(@current_user, :update, Mv.Authorization.Role) do %>
<.button variant="primary" navigate={~p"/admin/roles/#{@role}/edit"}> <.button variant="primary" navigate={~p"/admin/roles/#{@role}/edit"} data-testid=role-edit">
<.icon name="hero-pencil-square" /> {gettext("Rolle bearbeiten")} {gettext("Edit role")}
</.button> </.button>
<% end %> <% end %>
<%= if can?(@current_user, :destroy, Mv.Authorization.Role) and not @role.is_system_role do %> <%= if can?(@current_user, :destroy, Mv.Authorization.Role) and not @role.is_system_role do %>

View file

@ -15,6 +15,7 @@
rows={@users} rows={@users}
row_id={fn user -> "row-#{user.id}" end} row_id={fn user -> "row-#{user.id}" end}
row_click={fn user -> JS.navigate(~p"/users/#{user}") end} row_click={fn user -> JS.navigate(~p"/users/#{user}") end}
row_tooltip={gettext("Click for user details")}
sort_field={@sort_field} sort_field={@sort_field}
sort_order={@sort_order} sort_order={@sort_order}
> >

View file

@ -11,13 +11,11 @@ msgstr ""
"Language: de\n" "Language: de\n"
#: lib/mv_web/components/core_components.ex #: lib/mv_web/components/core_components.ex
#: lib/mv_web/live/group_live/index.ex
#: lib/mv_web/live/group_live/show.ex #: lib/mv_web/live/group_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Actions" msgid "Actions"
msgstr "Aktionen" 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_settings_live.ex
#: lib/mv_web/live/membership_fee_type_live/index.ex #: lib/mv_web/live/membership_fee_type_live/index.ex
#: lib/mv_web/live/role_live/index.html.heex #: 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/custom_field_live/index_component.ex
#: lib/mv_web/live/group_live/show.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/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/role_live/index.html.heex #: lib/mv_web/live/role_live/index.html.heex
#: lib/mv_web/live/user_live/index.html.heex #: lib/mv_web/live/user_live/index.html.heex
@ -50,15 +47,8 @@ msgstr "Stadt"
msgid "Delete" msgid "Delete"
msgstr "Löschen" 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/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/form.ex
#: lib/mv_web/live/user_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Edit" msgid "Edit"
msgstr "Bearbeiten" 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/form.ex
#: lib/mv_web/live/member_live/show/membership_fees_component.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/membership_fee_type_live/form.ex
#: lib/mv_web/live/role_live/form.ex
#: lib/mv_web/live/user_live/form.ex #: lib/mv_web/live/user_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Cancel" msgid "Cancel"
@ -790,19 +779,18 @@ msgstr "Alle"
msgid "Address" msgid "Address"
msgstr "Adresse" 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/form.ex
#: lib/mv_web/live/group_live/show.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/form.ex
#: lib/mv_web/live/member_live/show.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 #, elixir-autogen, elixir-format
msgid "Back" msgid "Back"
msgstr "Zurück" 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/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
@ -820,7 +808,6 @@ msgid "Payment Data"
msgstr "Beitragsdaten" msgstr "Beitragsdaten"
#: lib/mv_web/live/components/member_filter_component.ex #: lib/mv_web/live/components/member_filter_component.ex
#: lib/mv_web/live/member_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Payments" msgid "Payments"
msgstr "Zahlungen" msgstr "Zahlungen"
@ -834,6 +821,7 @@ msgstr "Persönliche Daten"
#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/form.ex
#: lib/mv_web/live/member_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/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/role_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
@ -851,11 +839,6 @@ msgstr "Mitglied erstellen"
msgid "Amount" msgid "Amount"
msgstr "Betrag" 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_settings_live.ex
#: lib/mv_web/live/membership_fee_type_live/index.ex #: lib/mv_web/live/membership_fee_type_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -1575,6 +1558,7 @@ msgid "Show/Hide Columns"
msgstr "Spalten ein-/ausblenden" msgstr "Spalten ein-/ausblenden"
#: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/member_field_live/form_component.ex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Back to settings" msgid "Back to settings"
msgstr "Zurück zu den Einstellungen" msgstr "Zurück zu den Einstellungen"
@ -1630,11 +1614,6 @@ msgstr "System-Rolle kann nicht gelöscht werden"
msgid "Custom" msgid "Custom"
msgstr "Benutzerdefiniert" 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/index.ex
#: lib/mv_web/live/role_live/show.ex #: lib/mv_web/live/role_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -1734,11 +1713,6 @@ msgstr "Rolle nicht gefunden."
msgid "Role saved successfully." msgid "Role saved successfully."
msgstr "Rolle erfolgreich gespeichert." 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 #: lib/mv_web/live/role_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select permission set" msgid "Select permission set"
@ -1890,22 +1864,17 @@ msgstr "aktualisiert"
msgid "Unknown error" msgid "Unknown error"
msgstr "Unbekannter Fehler" msgstr "Unbekannter Fehler"
#: lib/mv_web/live/member_live/index.ex #: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Member deleted successfully" msgid "Member deleted successfully"
msgstr "Mitglied wurde erfolgreich gelöscht" 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 #, elixir-autogen, elixir-format
msgid "Member not found" msgid "Member not found"
msgstr "Mitglied nicht gefunden" msgstr "Mitglied nicht gefunden"
#: 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 access this member"
msgstr "Du hast keine Berechtigung, auf dieses Mitglied zuzugreifen"
#: lib/mv_web/live/member_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You do not have permission to delete this member" msgid "You do not have permission to delete this member"
msgstr "Du hast keine Berechtigung, dieses Mitglied zu löschen" msgstr "Du hast keine Berechtigung, dieses Mitglied zu löschen"
@ -1990,11 +1959,6 @@ msgstr "Mitgliedsfilter"
msgid "Payment Status" msgid "Payment Status"
msgstr "Bezahlstatus" 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 #: lib/mv_web/live/import_live/components.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid " (Field: %{field})" msgid " (Field: %{field})"
@ -3098,3 +3062,125 @@ msgstr "Nur OIDC-Anmeldung (Passwort-Login ausblenden)"
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." 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." 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"

View file

@ -12,13 +12,11 @@ msgid ""
msgstr "" msgstr ""
#: lib/mv_web/components/core_components.ex #: lib/mv_web/components/core_components.ex
#: lib/mv_web/live/group_live/index.ex
#: lib/mv_web/live/group_live/show.ex #: lib/mv_web/live/group_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Actions" msgid "Actions"
msgstr "" 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_settings_live.ex
#: lib/mv_web/live/membership_fee_type_live/index.ex #: lib/mv_web/live/membership_fee_type_live/index.ex
#: lib/mv_web/live/role_live/index.html.heex #: 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/custom_field_live/index_component.ex
#: lib/mv_web/live/group_live/show.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/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/role_live/index.html.heex #: lib/mv_web/live/role_live/index.html.heex
#: lib/mv_web/live/user_live/index.html.heex #: lib/mv_web/live/user_live/index.html.heex
@ -51,15 +48,8 @@ msgstr ""
msgid "Delete" msgid "Delete"
msgstr "" 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/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/form.ex
#: lib/mv_web/live/user_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
@ -278,7 +268,6 @@ msgstr ""
#: lib/mv_web/live/member_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/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/membership_fee_type_live/form.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 #: lib/mv_web/live/user_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Cancel" msgid "Cancel"
@ -791,19 +780,18 @@ msgstr ""
msgid "Address" msgid "Address"
msgstr "" 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/form.ex
#: lib/mv_web/live/group_live/show.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/form.ex
#: lib/mv_web/live/member_live/show.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 #, elixir-autogen, elixir-format
msgid "Back" msgid "Back"
msgstr "" 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/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
@ -821,7 +809,6 @@ msgid "Payment Data"
msgstr "" msgstr ""
#: lib/mv_web/live/components/member_filter_component.ex #: lib/mv_web/live/components/member_filter_component.ex
#: lib/mv_web/live/member_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Payments" msgid "Payments"
msgstr "" msgstr ""
@ -835,6 +822,7 @@ msgstr ""
#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/form.ex
#: lib/mv_web/live/member_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/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/role_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Save" msgid "Save"
msgstr "" msgstr ""
@ -852,11 +840,6 @@ msgstr ""
msgid "Amount" msgid "Amount"
msgstr "" 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_settings_live.ex
#: lib/mv_web/live/membership_fee_type_live/index.ex #: lib/mv_web/live/membership_fee_type_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -1576,6 +1559,7 @@ msgid "Show/Hide Columns"
msgstr "" msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/member_field_live/form_component.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Back to settings" msgid "Back to settings"
msgstr "" msgstr ""
@ -1631,11 +1615,6 @@ msgstr ""
msgid "Custom" msgid "Custom"
msgstr "" 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/index.ex
#: lib/mv_web/live/role_live/show.ex #: lib/mv_web/live/role_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -1735,11 +1714,6 @@ msgstr ""
msgid "Role saved successfully." msgid "Role saved successfully."
msgstr "" 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 #: lib/mv_web/live/role_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select permission set" msgid "Select permission set"
@ -1891,22 +1865,17 @@ msgstr ""
msgid "Unknown error" msgid "Unknown error"
msgstr "" msgstr ""
#: lib/mv_web/live/member_live/index.ex #: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Member deleted successfully" msgid "Member deleted successfully"
msgstr "" msgstr ""
#: lib/mv_web/live/member_live/index.ex #: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Member not found" msgid "Member not found"
msgstr "" 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 access this member"
msgstr ""
#: lib/mv_web/live/member_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You do not have permission to delete this member" msgid "You do not have permission to delete this member"
msgstr "" msgstr ""
@ -1991,11 +1960,6 @@ msgstr ""
msgid "Payment Status" msgid "Payment Status"
msgstr "" 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 #: lib/mv_web/live/import_live/components.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid " (Field: %{field})" msgid " (Field: %{field})"
@ -3098,3 +3062,100 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button."
msgstr "" 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 ""

View file

@ -12,13 +12,11 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: lib/mv_web/components/core_components.ex #: lib/mv_web/components/core_components.ex
#: lib/mv_web/live/group_live/index.ex
#: lib/mv_web/live/group_live/show.ex #: lib/mv_web/live/group_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Actions" msgid "Actions"
msgstr "" 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_settings_live.ex
#: lib/mv_web/live/membership_fee_type_live/index.ex #: lib/mv_web/live/membership_fee_type_live/index.ex
#: lib/mv_web/live/role_live/index.html.heex #: 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/custom_field_live/index_component.ex
#: lib/mv_web/live/group_live/show.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/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/role_live/index.html.heex #: lib/mv_web/live/role_live/index.html.heex
#: lib/mv_web/live/user_live/index.html.heex #: lib/mv_web/live/user_live/index.html.heex
@ -51,15 +48,8 @@ msgstr ""
msgid "Delete" msgid "Delete"
msgstr "" 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/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/form.ex
#: lib/mv_web/live/user_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
@ -278,7 +268,6 @@ msgstr ""
#: lib/mv_web/live/member_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/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/membership_fee_type_live/form.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 #: lib/mv_web/live/user_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Cancel" msgid "Cancel"
@ -791,19 +780,18 @@ msgstr ""
msgid "Address" msgid "Address"
msgstr "" 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/form.ex
#: lib/mv_web/live/group_live/show.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/form.ex
#: lib/mv_web/live/member_live/show.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 #, elixir-autogen, elixir-format
msgid "Back" msgid "Back"
msgstr "" 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/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
@ -821,7 +809,6 @@ msgid "Payment Data"
msgstr "" msgstr ""
#: lib/mv_web/live/components/member_filter_component.ex #: lib/mv_web/live/components/member_filter_component.ex
#: lib/mv_web/live/member_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Payments" msgid "Payments"
msgstr "" msgstr ""
@ -835,6 +822,7 @@ msgstr ""
#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/form.ex
#: lib/mv_web/live/member_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/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/role_live/form.ex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Save" msgid "Save"
msgstr "" msgstr ""
@ -852,11 +840,6 @@ msgstr ""
msgid "Amount" msgid "Amount"
msgstr "" 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_settings_live.ex
#: lib/mv_web/live/membership_fee_type_live/index.ex #: lib/mv_web/live/membership_fee_type_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -1576,6 +1559,7 @@ msgid "Show/Hide Columns"
msgstr "" msgstr ""
#: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/form_component.ex
#: lib/mv_web/live/member_field_live/form_component.ex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Back to settings" msgid "Back to settings"
msgstr "" msgstr ""
@ -1631,11 +1615,6 @@ msgstr ""
msgid "Custom" msgid "Custom"
msgstr "" 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/index.ex
#: lib/mv_web/live/role_live/show.ex #: lib/mv_web/live/role_live/show.ex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
@ -1735,11 +1714,6 @@ msgstr ""
msgid "Role saved successfully." msgid "Role saved successfully."
msgstr "" 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 #: lib/mv_web/live/role_live/form.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select permission set" msgid "Select permission set"
@ -1891,22 +1865,17 @@ msgstr ""
msgid "Unknown error" msgid "Unknown error"
msgstr "" msgstr ""
#: lib/mv_web/live/member_live/index.ex #: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Member deleted successfully" msgid "Member deleted successfully"
msgstr "" msgstr ""
#: lib/mv_web/live/member_live/index.ex #: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Member not found" msgid "Member not found"
msgstr "" 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 access this member"
msgstr ""
#: lib/mv_web/live/member_live/index.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You do not have permission to delete this member" msgid "You do not have permission to delete this member"
msgstr "" msgstr ""
@ -1991,11 +1960,6 @@ msgstr ""
msgid "Payment Status" msgid "Payment Status"
msgstr "" 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 #: lib/mv_web/live/import_live/components.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid " (Field: %{field})" msgid " (Field: %{field})"
@ -3098,3 +3062,125 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button."
msgstr "" 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 ""

View file

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

View file

@ -24,6 +24,7 @@ defmodule MvWeb.MemberLiveAuthorizationTest do
{:ok, view, _html} = live(conn, "/members") {: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-edit]")
refute has_element?(view, "#row-#{member.id} [data-testid=member-delete]") refute has_element?(view, "#row-#{member.id} [data-testid=member-delete]")
end end
@ -31,17 +32,18 @@ defmodule MvWeb.MemberLiveAuthorizationTest do
describe "Member Index - Kassenwart (normal_user)" do describe "Member Index - Kassenwart (normal_user)" do
@tag role: :normal_user @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() member = Fixtures.member_fixture()
{:ok, view, _html} = live(conn, "/members") {:ok, view, _html} = live(conn, "/members")
assert has_element?(view, "[data-testid=member-new]") 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 end
@tag role: :normal_user @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() member = Fixtures.member_fixture()
{:ok, view, _html} = live(conn, "/members") {:ok, view, _html} = live(conn, "/members")
@ -52,14 +54,14 @@ defmodule MvWeb.MemberLiveAuthorizationTest do
describe "Member Index - Admin" do describe "Member Index - Admin" do
@tag role: :admin @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() member = Fixtures.member_fixture()
{:ok, view, _html} = live(conn, "/members") {:ok, view, _html} = live(conn, "/members")
assert has_element?(view, "[data-testid=member-new]") 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/Delete are on member show page)
assert has_element?(view, "#row-#{member.id} [data-testid=member-delete]") assert has_element?(view, "#row-#{member.id} [data-testid=member-show-link]")
end end
end end

View file

@ -107,9 +107,9 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do
{:ok, view, _html} = live(conn, "/members") {: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 view
|> element("button[phx-click='toggle_cycle_view'].btn.gap-2") |> element("[data-testid=toggle-cycle-view]")
|> render_click() |> render_click()
html = render(view) html = render(view)

View file

@ -304,6 +304,44 @@ defmodule MvWeb.MemberLive.IndexTest do
assert_redirect(view, ~p"/members/#{member}") assert_redirect(view, ~p"/members/#{member}")
end 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 describe "copy_emails feature" do
setup do setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor() system_actor = Mv.Helpers.SystemActor.get_system_actor()