feat: keep empty cells consistent empty
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
carla 2026-02-26 13:37:35 +01:00
parent 9751525a0c
commit 4ac56958b4
18 changed files with 263 additions and 372 deletions

View file

@ -2779,6 +2779,10 @@ Building accessible applications ensures that all users, including those with di
- When `row_click` is set, the first column that does not use `col_click` gets `tabindex="0"` and `role="button"` so each row is reachable via Tab. The `TableRowKeydown` hook triggers the row action on Enter and Space (WCAG 2.1.1). Use `row_id` and `row_tooltip` for all clickable tables (e.g. Groups, Users, Roles, Members, Custom Fields, Member Fields) so the table is fully keyboard accessible.
**Empty table cells (missing values):**
- Do not use dashes ("-", "—", "") or "n/a" as placeholders. Use CoreComponents `<.empty_cell sr_text="…">` for a cell with no value, or `<.maybe_value value={…} empty_sr_text="…">` when content is conditional. The cell is visually empty; screen readers get the `sr_text` (e.g. "No cycle", "No group assignment", "Not specified"). See Design Guidelines §8.6.
**Tab Order:**
- Ensure logical tab order matches visual order

View file

@ -293,6 +293,12 @@ Notes:
- On **mobile**, sticky headers are not used; the layout uses normal flow (header and table scroll with the page) to preserve vertical space.
- When the table is inside such a scroll container, use the CoreComponents tables `sticky_header={true}` so the tables `<thead>` stays sticky within the scroll area on desktop (`lg:sticky lg:top-0`, opaque background `bg-base-100`, z-index). Sticky areas must not overlap content at 200% zoom; focus order must remain header → filters → table.
### 8.6 Empty table cells (missing values)
- **MUST:** Missing values in tables are shown as **visually empty cells** (no dash, no "n/a").
- **MUST NOT:** Use dashes ("-", "—", "") or "n/a" as placeholders for empty cells.
- **MUST:** For accessibility, render a screen-reader-only label so the cell is not announced as "blank". Use the CoreComponents `<.empty_cell sr_text="…">` for a cell that has no value, or `<.maybe_value value={…} empty_sr_text="…">` when the cell content is conditional (value present vs. absent).
- **SHOULD:** Use context-specific `sr_text` where it helps (e.g. "No cycle", "No group assignment", "Not specified"). Default for "no value" is "Not specified".
---
## 9) Flash / Toast messages (mandatory UX)

View file

@ -300,6 +300,81 @@ defmodule MvWeb.CoreComponents do
defp badge_style_class("outline"), do: "badge-outline"
defp badge_style_class(_), do: nil
@doc """
Renders a visually empty table cell with screen-reader-only text (WCAG).
Use when a table cell has no value so that:
- The cell appears empty (no dash, no "n/a").
- Screen readers still get a meaningful label (e.g. "No cycle", "No group assignment").
See CODE_GUIDELINES §8 (Empty table cells) and Design Guidelines §8.6.
## Examples
<.empty_cell sr_text={gettext("No cycle")} />
<.empty_cell sr_text={gettext("No group assignment")} />
<.empty_cell sr_text={gettext("Not specified")} />
"""
attr :sr_text, :string,
required: true,
doc: "Text read by screen readers when the cell is visually empty"
def empty_cell(assigns) do
~H"""
<span class="sr-only">{@sr_text}</span>
"""
end
@doc """
Renders content when value is present, otherwise an accessible empty cell.
Use in table cells for optional fields: when `value` is blank, only the
screen-reader text is shown (visually empty). Otherwise the inner block is rendered.
Blank check: `nil`, `false`, `[]`, `""`, whitespace-only string, or `%Ash.NotLoaded{}` count as empty.
See CODE_GUIDELINES §8 (Empty table cells) and Design Guidelines §8.6.
## Examples
<.maybe_value value={member.membership_fee_type} empty_sr_text={gettext("No fee type")}>
{member.membership_fee_type.name}
</.maybe_value>
<.maybe_value value={member.groups} empty_sr_text={gettext("No group assignment")}>
<%= for g <- member.groups do %>
<.badge variant="primary" style="outline">{g.name}</.badge>
<% end %>
</.maybe_value>
"""
attr :value, :any, doc: "Value to check; if blank, empty_cell is rendered"
attr :empty_sr_text, :string,
default: nil,
doc: "Screen-reader text when value is blank (default: gettext \"Not specified\")"
slot :inner_block, required: true
def maybe_value(assigns) do
empty_sr = assigns.empty_sr_text || gettext("Not specified")
assigns = assign(assigns, :empty_sr_text, empty_sr)
assigns = assign(assigns, :blank?, value_blank?(assigns.value))
~H"""
<%= if @blank? do %>
<.empty_cell sr_text={@empty_sr_text} />
<% else %>
{render_slot(@inner_block)}
<% end %>
"""
end
defp value_blank?(nil), do: true
defp value_blank?(false), do: true
defp value_blank?([]), do: true
defp value_blank?(%Ash.NotLoaded{}), do: true
defp value_blank?(v) when is_binary(v), do: String.trim(v) == ""
defp value_blank?(_), do: false
@doc """
Wraps content with a DaisyUI tooltip. Use for icon-only actions, truncated content,
or status badges that need explanation (Design Guidelines §8.2).

View file

@ -126,7 +126,7 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
</p>
<p class="mt-2 text-sm">
{gettext(
"All datafield values will be permanently deleted when you delete this datfield."
"All datafield values will be permanently deleted when you delete this datafield."
)}
</p>
</div>

View file

@ -68,11 +68,9 @@ defmodule MvWeb.GroupLive.Index do
{group.name}
</:col>
<:col :let={group} label={gettext("Description")}>
<%= if group.description do %>
<.maybe_value value={group.description} empty_sr_text={gettext("Not specified")}>
{group.description}
<% else %>
<span class="text-base-content/50 italic"></span>
<% end %>
</.maybe_value>
</:col>
<:col :let={group} label={gettext("Members")} class="text-right">
{group.member_count || 0}

View file

@ -304,16 +304,14 @@ defmodule MvWeb.GroupLive.Show do
</.link>
</td>
<td>
<%= if member.email do %>
<.maybe_value value={member.email} empty_sr_text={gettext("No email")}>
<a
href={"mailto:#{member.email}"}
class="link link-primary"
>
{member.email}
</a>
<% else %>
<span class="text-base-content/50 italic"></span>
<% end %>
</.maybe_value>
</td>
<%= if can?(@current_user, :update, @group) do %>
<td>

View file

@ -356,11 +356,9 @@
"""
}
>
<%= if member.membership_fee_type do %>
<.maybe_value value={member.membership_fee_type} empty_sr_text={gettext("Not specified")}>
{member.membership_fee_type.name}
<% else %>
<span class="text-base-content/50">—</span>
<% end %>
</.maybe_value>
</:col>
<:col
:let={member}
@ -375,7 +373,7 @@
{badge.label}
</.badge>
<% else %>
<.badge variant="neutral">{gettext("No cycle")}</.badge>
<.empty_cell sr_text={gettext("No cycle")} />
<% end %>
</:col>
<:col
@ -394,18 +392,17 @@
"""
}
>
<%= for group <- (member.groups || []) do %>
<.badge
variant="primary"
style="outline"
aria-label={gettext("Member of group %{name}", name: group.name)}
>
{group.name}
</.badge>
<% end %>
<%= if (member.groups || []) == [] do %>
<span class="text-base-content/50">—</span>
<% end %>
<.maybe_value value={member.groups} empty_sr_text={gettext("No group assignment")}>
<%= for group <- (member.groups || []) do %>
<.badge
variant="primary"
style="outline"
aria-label={gettext("Member of group %{name}", name: group.name)}
>
{group.name}
</.badge>
<% end %>
</.maybe_value>
</:col>
<:action :let={member}>
<div class="sr-only">

View file

@ -559,7 +559,11 @@ defmodule MvWeb.MemberLive.Show do
<%= if @inner_block != [] do %>
{render_slot(@inner_block)}
<% else %>
{display_value(@value)}
<%= if value_blank?(@value) do %>
<.empty_cell sr_text={gettext("Not set")} />
<% else %>
{@value}
<% end %>
<% end %>
</dd>
</dl>
@ -593,9 +597,9 @@ defmodule MvWeb.MemberLive.Show do
# Helper Functions
# -----------------------------------------------------------------
defp display_value(nil), do: render_empty_value()
defp display_value(""), do: render_empty_value()
defp display_value(value), do: value
defp value_blank?(nil), do: true
defp value_blank?(v) when is_binary(v), do: String.trim(v) == ""
defp value_blank?(_), do: false
defp format_status_label(:paid), do: gettext("Paid")
defp format_status_label(:unpaid), do: gettext("Unpaid")
@ -684,10 +688,10 @@ defmodule MvWeb.MemberLive.Show do
if String.trim(value) == "" do
render_empty_value()
else
assigns = %{email: value}
assigns = %{email: value, display: value}
~H"""
<.mailto_link email={@email} display={@email} />
<.mailto_link email={@email} display={@display} />
"""
end
end
@ -702,17 +706,10 @@ defmodule MvWeb.MemberLive.Show do
defp format_custom_field_value(value, _type), do: to_string(value)
# Renders accessible placeholder for empty values
# Uses translated text for screen readers while maintaining visual consistency
# The visual "—" is hidden from screen readers, while the translated text is only visible to screen readers
# Renders accessible empty value: visually empty, screen-reader text only (see Design Guidelines §8.6).
# Returns safe HTML so it can be used from helpers without LiveView assigns.
defp render_empty_value do
assigns = %{text: gettext("Not set")}
~H"""
<span class="text-base-content/50 italic">
<span aria-hidden="true"></span>
<span class="sr-only">{@text}</span>
</span>
"""
text = gettext("Not set")
{:safe, ["<span class=\"sr-only\">", Phoenix.HTML.Engine.html_escape(text), "</span>"]}
end
end

View file

@ -101,7 +101,13 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
<%= for r <- receipts do %>
<tr>
<%= for {col_key, _header_key} <- cols do %>
<td>{format_receipt_cell(col_key, r[col_key])}</td>
<td>
<%= if (cell_content = format_receipt_cell(col_key, r[col_key])) != nil do %>
{cell_content}
<% else %>
<.empty_cell sr_text={receipt_empty_sr_text(col_key)} />
<% end %>
</td>
<% end %>
</tr>
<% end %>
@ -1157,7 +1163,11 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|> Enum.map(fn {key, msgid} -> {key, Gettext.gettext(MvWeb.Gettext, msgid)} end)
end
defp format_receipt_cell(:amount, nil), do: ""
# Screen-reader text for empty receipt table cells (visually empty, A11y)
defp receipt_empty_sr_text(:status), do: gettext("Not set")
defp receipt_empty_sr_text(_), do: gettext("Not specified")
defp format_receipt_cell(:amount, nil), do: nil
defp format_receipt_cell(:amount, val) when is_number(val) do
case Decimal.cast(val) do
@ -1175,7 +1185,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
defp format_receipt_cell(:amount, val), do: to_string(val)
defp format_receipt_cell(:status, nil), do: ""
defp format_receipt_cell(:status, nil), do: nil
defp format_receipt_cell(:status, val) when is_binary(val) do
translate_receipt_status(val)
@ -1183,7 +1193,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
defp format_receipt_cell(:status, val), do: translate_receipt_status(to_string(val))
defp format_receipt_cell(:receiptType, nil), do: ""
defp format_receipt_cell(:receiptType, nil), do: nil
defp format_receipt_cell(:receiptType, val) when is_binary(val) do
translate_receipt_type(val)
@ -1192,7 +1202,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
defp format_receipt_cell(:receiptType, val), do: translate_receipt_type(to_string(val))
defp format_receipt_cell(col_key, nil) when col_key in [:bookingDate, :createdAt, :updatedAt],
do: ""
do: nil
defp format_receipt_cell(col_key, val) when col_key in [:bookingDate, :createdAt, :updatedAt] do
format_receipt_date(val)
@ -1253,7 +1263,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
defp translate_receipt_status("draft"), do: gettext("Draft")
defp translate_receipt_status("incompleted"), do: gettext("Incompleted")
defp translate_receipt_status("completed"), do: gettext("Completed")
defp translate_receipt_status("empty"), do: ""
defp translate_receipt_status("empty"), do: nil
defp translate_receipt_status(other), do: other
# Translate API receipt type values (extend as API returns more values)

View file

@ -329,7 +329,7 @@ defmodule MvWeb.MembershipFeeSettingsLive do
</:col>
<:col :let={mft} label={gettext("Members")}>
<.badge variant="neutral">{get_member_count(mft, @member_counts)}</.badge>
<span class="text-sm">{get_member_count(mft, @member_counts)}</span>
</:col>
<:action :let={mft}>

View file

@ -53,7 +53,7 @@
</:col>
<:col :let={role} label={gettext("Users")}>
<.badge variant="neutral">{get_user_count(role, @user_counts)}</.badge>
<span class="text-sm">{get_user_count(role, @user_counts)}</span>
</:col>
</.table>
</Layouts.app>

View file

@ -38,25 +38,25 @@
{user.role.name}
</:col>
<:col :let={user} label={gettext("Linked Member")}>
<%= if user.member do %>
<.maybe_value value={user.member} empty_sr_text={gettext("No member linked")}>
{MvWeb.Helpers.MemberHelpers.display_name(user.member)}
<% else %>
<span class="text-base-content/70">{gettext("No member linked")}</span>
<% end %>
</.maybe_value>
</:col>
<:col :let={user} label={gettext("Password")}>
<%= if MvWeb.Helpers.UserHelpers.has_password?(user) do %>
<.maybe_value
value={MvWeb.Helpers.UserHelpers.has_password?(user)}
empty_sr_text={gettext("Not set")}
>
<span>{gettext("Enabled")}</span>
<% else %>
<span class="text-base-content/70">—</span>
<% end %>
</.maybe_value>
</:col>
<:col :let={user} label={gettext("OIDC")}>
<%= if MvWeb.Helpers.UserHelpers.has_oidc?(user) do %>
<.maybe_value
value={MvWeb.Helpers.UserHelpers.has_oidc?(user)}
empty_sr_text={gettext("Not set")}
>
<span>{gettext("Linked")}</span>
<% else %>
<span class="text-base-content/70">—</span>
<% end %>
</.maybe_value>
</:col>
</.table>
</Layouts.app>

View file

@ -591,18 +591,6 @@ msgstr "Diese E-Mail-Adresse ist bereits mit einem anderen OIDC-Konto verknüpft
msgid "Custom Fields"
msgstr "Benutzerdefinierte Felder"
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "%{count} member has a value assigned for this custom field."
msgid_plural "%{count} members have values assigned for this custom field."
msgstr[0] "%{count} Mitglied hat einen Wert für dieses benutzerdefinierte Feld zugewiesen."
msgstr[1] "%{count} Mitglieder haben Werte für dieses benutzerdefinierte Feld zugewiesen."
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "All custom field values will be permanently deleted when you delete this custom field."
msgstr "Alle benutzerdefinierten Feldwerte werden beim Löschen dieses benutzerdefinierten Feldes dauerhaft gelöscht."
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Enter the text above to confirm"
@ -788,6 +776,7 @@ msgstr "Beitragsdaten"
msgid "Payments"
msgstr "Zahlungen"
#: lib/mv_web/live/datafields_live.ex
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format
@ -1386,6 +1375,8 @@ msgid "None (no default)"
msgstr "Keine (kein Standard)"
#: lib/mv_web/live/member_live/show.ex
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/user_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Not set"
msgstr "Nicht gesetzt"
@ -2918,11 +2909,6 @@ msgstr "Client-ID"
msgid "Client Secret"
msgstr "Client-Geheimnis"
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Custom fields"
msgstr "Benutzerdefinierte Felder"
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format
@ -2964,11 +2950,6 @@ msgstr "Aus OIDC_REDIRECT_URI"
msgid "Groups claim"
msgstr "Gruppenclaim"
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format
msgid "Member fields"
msgstr "Mitgliedsfelder"
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Membership fee settings"
@ -3225,128 +3206,32 @@ msgstr "Verwalte welche Daten du für eure Mitglieder speichern möchtest. Lege
msgid "Manage users and their permissions."
msgstr "Verwalte Benutzer*innen und ihre Berechtigungen."
#~ #: 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/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "%{count} member has a value assigned for this datafield."
msgid_plural "%{count} members have values assigned for this datafield."
msgstr[0] "%{count} Mitglied hat einen Wert für dieses benutzerdefinierte Feld zugewiesen."
msgstr[1] "%{count} Mitglieder haben Werte für dieses benutzerdefinierte Feld zugewiesen."
#~ #: lib/mv_web/live/role_live/index.html.heex
#~ #, elixir-autogen, elixir-format
#~ msgid "Cannot delete system role"
#~ msgstr "System-Rolle kann nicht gelöscht werden"
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Individual Datafields"
msgstr "Individuelle Datenfelder"
#~ #: 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_live/index.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "No group assignment"
msgstr "Keine Gruppenzuordnung"
#~ #: 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/components/core_components.ex
#: lib/mv_web/live/group_live/index.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgid "Not specified"
msgstr "Nicht angegeben"
#~ #: lib/mv_web/live/member_live/form.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Coming soon"
#~ msgstr "Demnächst verfügbar"
#~ #: lib/mv_web/live/membership_fee_settings_live.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Configure global settings and fee types for membership fees."
#~ msgstr "Globale Einstellungen für Mitgliedsbeiträge konfigurieren."
#~ #: lib/mv_web/live/datafields_live.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Configure member fields and custom data fields."
#~ msgstr "Mitgliedsfelder und benutzerdefinierte Datenfelder konfigurieren."
#~ #: lib/mv_web/live/components/field_visibility_dropdown_component.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Custom Field %{id}"
#~ msgstr "Benutzerdefiniertes Feld %{id}"
#~ #: lib/mv_web/live/import_live.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Datei auswählen"
#~ msgstr "Datei auswählen"
#~ #: 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/user_live/index.html.heex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Edit user"
#~ msgstr "Benutzer*in bearbeiten"
#~ #: lib/mv_web/live/statistics_live.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Overview from first membership to today"
#~ msgstr "Übersicht vom ersten Eintritt bis heute"
#~ #: lib/mv_web/live/components/member_filter_component.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Reset"
#~ msgstr "Zurücksetzen"
#~ #: lib/mv_web/live/role_live/show.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Rolle bearbeiten"
#~ msgstr "Rolle bearbeiten"
#~ #: lib/mv_web/live/role_live/form.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Save Role"
#~ msgstr "Rolle speichern"
#~ #: lib/mv_web/live/user_live/index.html.heex
#~ #, elixir-autogen, elixir-format
#~ msgid "Select all users"
#~ msgstr "Alle Benutzer*innen auswählen"
#~ #: lib/mv_web/live/user_live/index.html.heex
#~ #, elixir-autogen, elixir-format
#~ msgid "Select user"
#~ msgstr "Benutzer*in auswählen"
#~ #: lib/mv_web/live/role_live/index.html.heex
#~ #, elixir-autogen, elixir-format
#~ msgid "System roles cannot be deleted"
#~ msgstr "System-Rollen können nicht gelöscht werden"
#~ #: lib/mv_web/live/user_live/show.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "This is a user record from your database."
#~ msgstr "Dies ist ein Benutzer*innen-Datensatz aus Ihrer Datenbank."
#~ #: lib/mv_web/live/membership_fee_type_live/form.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Use this form to manage membership fee types in your database."
#~ msgstr "Verwende dieses Formular, um Mitgliedsbeitragsarten in deiner Datenbank zu verwalten."
#~ #: lib/mv_web/live/role_live/form.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Use this form to manage roles in your database."
#~ msgstr "Verwende dieses Formular, um Rollen in deiner Datenbank zu verwalten."
#~ #: lib/mv_web/live/user_live/form.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Use this form to manage user records in your database."
#~ msgstr "Verwende dieses Formular, um Benutzer*innen-Datensätze zu verwalten."
#~ #: lib/mv_web/live/group_live/index.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "View"
#~ msgstr "Anzeigen"
#~ #: 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/user_live/index.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "You do not have permission to access this user"
#~ msgstr "Du hast keine Berechtigung, auf diese*n Benutzer*in zuzugreifen"
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "All datafield values will be permanently deleted when you delete this datafield."
msgstr "Alle benutzerdefinierten Feldwerte werden beim Löschen dieses benutzerdefinierten Feldes dauerhaft gelöscht."

View file

@ -592,18 +592,6 @@ msgstr ""
msgid "Custom Fields"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "%{count} member has a value assigned for this custom field."
msgid_plural "%{count} members have values assigned for this custom field."
msgstr[0] ""
msgstr[1] ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "All custom field values will be permanently deleted when you delete this custom field."
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Enter the text above to confirm"
@ -789,6 +777,7 @@ msgstr ""
msgid "Payments"
msgstr ""
#: lib/mv_web/live/datafields_live.ex
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format
@ -1387,6 +1376,8 @@ msgid "None (no default)"
msgstr ""
#: lib/mv_web/live/member_live/show.ex
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/user_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "Not set"
msgstr ""
@ -2918,11 +2909,6 @@ msgstr ""
msgid "Client Secret"
msgstr ""
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format
msgid "Custom fields"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format
@ -2964,11 +2950,6 @@ msgstr ""
msgid "Groups claim"
msgstr ""
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format
msgid "Member fields"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format
msgid "Membership fee settings"
@ -3224,3 +3205,33 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "Manage users and their permissions."
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "%{count} member has a value assigned for this datafield."
msgid_plural "%{count} members have values assigned for this datafield."
msgstr[0] ""
msgstr[1] ""
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format
msgid "Individual Datafields"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex
#, elixir-autogen, elixir-format
msgid "No group assignment"
msgstr ""
#: lib/mv_web/components/core_components.ex
#: lib/mv_web/live/group_live/index.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgid "Not specified"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "All datafield values will be permanently deleted when you delete this datafield."
msgstr ""

View file

@ -592,18 +592,6 @@ msgstr ""
msgid "Custom Fields"
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "%{count} member has a value assigned for this custom field."
msgid_plural "%{count} members have values assigned for this custom field."
msgstr[0] ""
msgstr[1] ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "All custom field values will be permanently deleted when you delete this custom field."
msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format
msgid "Enter the text above to confirm"
@ -789,6 +777,7 @@ msgstr ""
msgid "Payments"
msgstr ""
#: lib/mv_web/live/datafields_live.ex
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show.ex
#, elixir-autogen, elixir-format
@ -1387,6 +1376,8 @@ msgid "None (no default)"
msgstr ""
#: lib/mv_web/live/member_live/show.ex
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#: lib/mv_web/live/user_live/index.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "Not set"
msgstr ""
@ -2918,11 +2909,6 @@ msgstr ""
msgid "Client Secret"
msgstr ""
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Custom fields"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format
@ -2964,11 +2950,6 @@ msgstr ""
msgid "Groups claim"
msgstr ""
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Member fields"
msgstr ""
#: lib/mv_web/components/layouts/sidebar.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Membership fee settings"
@ -3225,123 +3206,32 @@ msgstr ""
msgid "Manage users and their permissions."
msgstr ""
#~ #: lib/mv_web/live/member_field_live/form_component.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Back to Settings"
#~ msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "%{count} member has a value assigned for this datafield."
msgid_plural "%{count} members have values assigned for this datafield."
msgstr[0] ""
msgstr[1] ""
#~ #: lib/mv_web/live/role_live/index.html.heex
#~ #, elixir-autogen, elixir-format
#~ msgid "Cannot delete system role"
#~ msgstr ""
#: lib/mv_web/live/datafields_live.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Individual Datafields"
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_live/index.html.heex
#, elixir-autogen, elixir-format, fuzzy
msgid "No group assignment"
msgstr ""
#~ #: lib/mv_web/live/member_field_live/index_component.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Click for datafield details"
#~ msgstr ""
#: lib/mv_web/components/core_components.ex
#: lib/mv_web/live/group_live/index.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgid "Not specified"
msgstr ""
#~ #: lib/mv_web/live/member_live/form.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Coming soon"
#~ msgstr ""
#~ #: lib/mv_web/live/membership_fee_settings_live.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Configure global settings and fee types for membership fees."
#~ msgstr ""
#~ #: lib/mv_web/live/datafields_live.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Configure member fields and custom data fields."
#~ msgstr ""
#~ #: lib/mv_web/live/components/field_visibility_dropdown_component.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Custom Field %{id}"
#~ 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/user_live/index.html.heex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Edit user"
#~ msgstr ""
#~ #: lib/mv_web/live/statistics_live.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Overview from first membership to today"
#~ msgstr ""
#~ #: lib/mv_web/live/components/member_filter_component.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Reset"
#~ msgstr ""
#~ #: lib/mv_web/live/role_live/show.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Rolle bearbeiten"
#~ msgstr ""
#~ #: lib/mv_web/live/role_live/form.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Save Role"
#~ msgstr ""
#~ #: lib/mv_web/live/user_live/index.html.heex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Select all users"
#~ msgstr ""
#~ #: lib/mv_web/live/user_live/index.html.heex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Select user"
#~ msgstr ""
#~ #: lib/mv_web/live/role_live/index.html.heex
#~ #, elixir-autogen, elixir-format
#~ msgid "System roles cannot be deleted"
#~ msgstr ""
#~ #: lib/mv_web/live/user_live/show.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "This is a user record from your database."
#~ msgstr ""
#~ #: lib/mv_web/live/membership_fee_type_live/form.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Use this form to manage membership fee types in your database."
#~ msgstr ""
#~ #: lib/mv_web/live/role_live/form.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Use this form to manage roles in your database."
#~ msgstr ""
#~ #: lib/mv_web/live/user_live/form.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Use this form to manage user records in your database."
#~ msgstr ""
#~ #: lib/mv_web/live/group_live/index.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "View"
#~ 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/user_live/index.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "You do not have permission to access this user"
#~ msgstr ""
#: lib/mv_web/live/custom_field_live/index_component.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "All datafield values will be permanently deleted when you delete this datafield."
msgstr ""

View file

@ -76,7 +76,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
refute has_element?(view, "h2", "Custom fields")
# Should show correct member count (1 member)
assert render(view) =~ "1 member has a value assigned for this custom field"
assert render(view) =~ "1 member has a value assigned for this datafield"
# Should show the slug
assert render(view) =~ custom_field.slug
@ -95,7 +95,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
open_delete_modal(view, custom_field)
# Should show plural form
assert render(view) =~ "2 members have values assigned for this custom field"
assert render(view) =~ "2 members have values assigned for this datafield"
end
test "shows 0 members for custom field without values", %{conn: conn} do
@ -105,7 +105,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
open_delete_modal(view, custom_field)
# Should show 0 members
assert render(view) =~ "0 members have values assigned for this custom field"
assert render(view) =~ "0 members have values assigned for this datafield"
end
end

View file

@ -95,6 +95,20 @@ defmodule MvWeb.MemberLive.IndexGroupsDisplayTest do
assert html =~ member3.first_name
end
test "empty group cell is visually empty with sr-only text (no dash)", %{
conn: conn,
member3: member3
} do
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, "/members")
assert html =~ member3.first_name
# Screen reader gets a meaningful label for the empty cell
assert html =~ "sr-only"
assert html =~ "No group assignment"
# No visible dash as placeholder (Design Guidelines §8.6)
refute html =~ ~r/<span[^>]*class="[^"]*text-base-content\/50[^"]*"[^>]*>—<\/span>/
end
test "displays group name correctly in badge", %{conn: conn, group1: group1} do
conn = conn_with_oidc_user(conn)
{:ok, _view, html} = live(conn, "/members")

View file

@ -210,7 +210,9 @@ defmodule MvWeb.UserLive.IndexTest do
end
describe "Password column display" do
test "user without password shows em dash in Password column", %{conn: conn} do
test "user without password shows empty cell with sr-only text in Password column", %{
conn: conn
} do
# User created with hashed_password: nil (no password) - must not get default password
user_no_pw =
create_test_user(%{
@ -223,9 +225,13 @@ defmodule MvWeb.UserLive.IndexTest do
assert html =~ "no-password@example.com"
# Password column must show "—" (em dash) for user without password, not "Enabled"
# Password column: visually empty, screen-reader gets "Not set" (Design Guidelines §8.6)
row = view |> element("tr#row-#{user_no_pw.id}") |> render()
assert row =~ "", "Password column should show em dash for user without password"
assert row =~ "sr-only", "Password column should have sr-only text for accessibility"
assert row =~ "Not set", "Screen reader should get 'Not set' for empty password"
refute row =~ "",
"Password column must not show dash (use empty cell + sr-only per CODE_GUIDELINES §8)"
refute row =~ "Enabled",
"Password column must not show Enabled when user has no password"