diff --git a/CODE_GUIDELINES.md b/CODE_GUIDELINES.md index 2f2516b..d68d0b5 100644 --- a/CODE_GUIDELINES.md +++ b/CODE_GUIDELINES.md @@ -2775,6 +2775,10 @@ Building accessible applications ensures that all users, including those with di
|
-
+
0} class="mb-4">
-
+
@@ -249,7 +249,7 @@ defmodule MvWeb.Components.MemberFilterComponent do
0} class="mb-2">
-
+
@@ -316,7 +316,7 @@ defmodule MvWeb.Components.MemberFilterComponent do
<.button
diff --git a/lib/mv_web/live/group_live/show.ex b/lib/mv_web/live/group_live/show.ex
index 7e802b8..4ecc6f3 100644
--- a/lib/mv_web/live/group_live/show.ex
+++ b/lib/mv_web/live/group_live/show.ex
@@ -130,149 +130,153 @@ defmodule MvWeb.GroupLive.Show do
-
- {gettext("Members")}-
- - {ngettext( - "Total: %{count} member", - "Total: %{count} members", - @group.member_count || 0, - count: @group.member_count || 0 - )} - +
+ {gettext("Members")}+
+ + {ngettext( + "Total: %{count} member", + "Total: %{count} members", + @group.member_count || 0, + count: @group.member_count || 0 + )} + - <%= if can?(@current_user, :update, @group) do %> -
- <%= if assigns[:show_add_member_input] do %>
-
-
+ <% end %>
+
+ <%= if Enum.empty?(@group.members || []) do %>
{gettext("No members in this group")} 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 d6f87b1..d8b2616 100644 --- a/lib/mv_web/live/member_field_live/index_component.ex +++ b/lib/mv_web/live/member_field_live/index_component.ex @@ -52,6 +52,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do :if={!@show_form} id="member_fields" rows={@member_fields} + row_id={fn {field_name, _field_data} -> "member_field-#{field_name}" end} row_click={ fn {field_name, _field_data} -> JS.push("edit_member_field", value: %{"field" => field_name}, target: @myself) diff --git a/lib/mv_web/live/member_live/form.ex b/lib/mv_web/live/member_live/form.ex index 45da418..e4a627b 100644 --- a/lib/mv_web/live/member_live/form.ex +++ b/lib/mv_web/live/member_live/form.ex @@ -85,218 +85,219 @@ defmodule MvWeb.MemberLive.Form do > <%!-- Personal Data and Custom Fields Row --%>
- <%!-- Personal Data Section --%>
-
- <.form_section title={gettext("Personal Data")}>
-
- <%!-- Name Row --%>
-
-
- <.input
- field={@form[:first_name]}
- label={gettext("First Name")}
- required={@member_field_required_map[:first_name]}
- />
+ <%!-- Personal Data Section --%>
+
diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex
index c63ced5..a957b61 100644
--- a/lib/mv_web/live/member_live/show.ex
+++ b/lib/mv_web/live/member_live/show.ex
@@ -254,7 +254,9 @@ defmodule MvWeb.MemberLive.Show do
/>
<.data_field label={gettext("Last Cycle")} class="min-w-32">
<%= if @member.last_cycle_status do %>
- <.badge variant={MembershipFeeHelpers.status_variant(@member.last_cycle_status)}>
+ <.badge variant={
+ MembershipFeeHelpers.status_variant(@member.last_cycle_status)
+ }>
{format_status_label(@member.last_cycle_status)}
<% else %>
diff --git a/lib/mv_web/live/role_live/index.ex b/lib/mv_web/live/role_live/index.ex
index 0bdc226..58f98d4 100644
--- a/lib/mv_web/live/role_live/index.ex
+++ b/lib/mv_web/live/role_live/index.ex
@@ -18,8 +18,7 @@ defmodule MvWeb.RoleLive.Index do
require Ash.Query
- import MvWeb.RoleLive.Helpers,
- only: [format_error: 1, permission_set_badge_variant: 1, opts_with_actor: 3]
+ import MvWeb.RoleLive.Helpers, only: [permission_set_badge_variant: 1]
@impl true
def mount(_params, _session, socket) do
diff --git a/lib/mv_web/live/role_live/index.html.heex b/lib/mv_web/live/role_live/index.html.heex
index 1dc41c8..94d1fc6 100644
--- a/lib/mv_web/live/role_live/index.html.heex
+++ b/lib/mv_web/live/role_live/index.html.heex
@@ -16,6 +16,7 @@
<.table
id="roles"
rows={@roles}
+ row_id={fn role -> "role-#{role.id}" end}
row_click={fn role -> JS.navigate(~p"/admin/roles/#{role}") end}
row_tooltip={gettext("Click for role details")}
>
+ <.form_section title={gettext("Personal Data")}>
+
diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex
index 412a5c4..c49e343 100644
--- a/lib/mv_web/live/member_live/index.html.heex
+++ b/lib/mv_web/live/member_live/index.html.heex
@@ -109,7 +109,7 @@
sort_field={@sort_field}
sort_order={@sort_order}
>
-
+
<:col
:let={member}
@@ -134,286 +134,286 @@
aria-label={gettext("Select member")}
role="checkbox"
/>
-
- <:col
- :let={member}
- :if={:first_name in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_first_name}
- field={:first_name}
- label={gettext("First name")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {member.first_name}
-
- <:col
- :let={member}
- :if={:last_name in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_last_name}
- field={:last_name}
- label={gettext("Last name")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {member.last_name}
-
- <:col
- :let={member}
- :if={:email in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_email}
- field={:email}
- label={gettext("Email")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {member.email}
-
- <:col
- :let={member}
- :if={:join_date in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_join_date}
- field={:join_date}
- label={gettext("Join Date")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {MvWeb.MemberLive.Index.format_date(member.join_date)}
-
- <:col
- :let={member}
- :if={:exit_date in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_exit_date}
- field={:exit_date}
- label={gettext("Exit Date")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {MvWeb.MemberLive.Index.format_date(member.exit_date)}
-
- <:col
- :let={member}
- :if={:notes in @member_fields_visible}
- label={gettext("Notes")}
- >
- {member.notes}
-
- <:col
- :let={member}
- :if={:country in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_country}
- field={:country}
- label={gettext("Country")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {member.country}
-
- <:col
- :let={member}
- :if={:city in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_city}
- field={:city}
- label={gettext("City")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {member.city}
-
- <:col
- :let={member}
- :if={:street in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_street}
- field={:street}
- label={gettext("Street")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {member.street}
-
- <:col
- :let={member}
- :if={:house_number in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_house_number}
- field={:house_number}
- label={gettext("House Number")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {member.house_number}
-
- <:col
- :let={member}
- :if={:postal_code in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_postal_code}
- field={:postal_code}
- label={gettext("Postal Code")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {member.postal_code}
-
- <:col
- :let={member}
- :if={:membership_fee_start_date in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_membership_fee_start_date}
- field={:membership_fee_start_date}
- label={gettext("Membership Fee Start Date")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {MvWeb.MemberLive.Index.format_date(member.membership_fee_start_date)}
-
- <:col
- :let={member}
- :if={:membership_fee_type in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_membership_fee_type}
- field={:membership_fee_type}
- label={gettext("Fee Type")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- <%= if member.membership_fee_type do %>
- {member.membership_fee_type.name}
- <% else %>
- —
- <% end %>
-
- <:col
- :let={member}
- :if={:membership_fee_status in @member_fields_visible}
- label={gettext("Membership Fee Status")}
- >
- <%= if badge = MembershipFeeStatus.format_cycle_status_badge(
+
+ <:col
+ :let={member}
+ :if={:first_name in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_first_name}
+ field={:first_name}
+ label={gettext("First name")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {member.first_name}
+
+ <:col
+ :let={member}
+ :if={:last_name in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_last_name}
+ field={:last_name}
+ label={gettext("Last name")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {member.last_name}
+
+ <:col
+ :let={member}
+ :if={:email in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_email}
+ field={:email}
+ label={gettext("Email")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {member.email}
+
+ <:col
+ :let={member}
+ :if={:join_date in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_join_date}
+ field={:join_date}
+ label={gettext("Join Date")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {MvWeb.MemberLive.Index.format_date(member.join_date)}
+
+ <:col
+ :let={member}
+ :if={:exit_date in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_exit_date}
+ field={:exit_date}
+ label={gettext("Exit Date")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {MvWeb.MemberLive.Index.format_date(member.exit_date)}
+
+ <:col
+ :let={member}
+ :if={:notes in @member_fields_visible}
+ label={gettext("Notes")}
+ >
+ {member.notes}
+
+ <:col
+ :let={member}
+ :if={:country in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_country}
+ field={:country}
+ label={gettext("Country")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {member.country}
+
+ <:col
+ :let={member}
+ :if={:city in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_city}
+ field={:city}
+ label={gettext("City")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {member.city}
+
+ <:col
+ :let={member}
+ :if={:street in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_street}
+ field={:street}
+ label={gettext("Street")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {member.street}
+
+ <:col
+ :let={member}
+ :if={:house_number in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_house_number}
+ field={:house_number}
+ label={gettext("House Number")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {member.house_number}
+
+ <:col
+ :let={member}
+ :if={:postal_code in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_postal_code}
+ field={:postal_code}
+ label={gettext("Postal Code")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {member.postal_code}
+
+ <:col
+ :let={member}
+ :if={:membership_fee_start_date in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_membership_fee_start_date}
+ field={:membership_fee_start_date}
+ label={gettext("Membership Fee Start Date")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ {MvWeb.MemberLive.Index.format_date(member.membership_fee_start_date)}
+
+ <:col
+ :let={member}
+ :if={:membership_fee_type in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_membership_fee_type}
+ field={:membership_fee_type}
+ label={gettext("Fee Type")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ <%= if member.membership_fee_type do %>
+ {member.membership_fee_type.name}
+ <% else %>
+ —
+ <% end %>
+
+ <:col
+ :let={member}
+ :if={:membership_fee_status in @member_fields_visible}
+ label={gettext("Membership Fee Status")}
+ >
+ <%= if badge = MembershipFeeStatus.format_cycle_status_badge(
MembershipFeeStatus.get_cycle_status_for_member(member, @show_current_cycle)
) do %>
- <.badge variant={badge.variant}>
- <.icon name={badge.icon} class="size-4" />
- {badge.label}
-
- <% else %>
- <.badge variant="neutral">{gettext("No cycle")}
- <% end %>
-
- <:col
- :let={member}
- :if={:groups in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_groups}
- field={:groups}
- label={gettext("Groups")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- <%= for group <- (member.groups || []) do %>
- <.badge
- variant="primary"
- style="outline"
- aria-label={gettext("Member of group %{name}", name: group.name)}
- >
- {group.name}
-
- <% end %>
- <%= if (member.groups || []) == [] do %>
- —
- <% end %>
-
- <:action :let={member}>
-
+ <%!-- Name Row --%>
+
- <%!-- Address: Country, Postal Code, City in one row --%>
-
+
-
+ <.input
+ field={@form[:first_name]}
+ label={gettext("First Name")}
+ required={@member_field_required_map[:first_name]}
+ />
+
+
+ <.input
+ field={@form[:last_name]}
+ label={gettext("Last Name")}
+ required={@member_field_required_map[:last_name]}
+ />
+
- <.input
- field={@form[:last_name]}
- label={gettext("Last Name")}
- required={@member_field_required_map[:last_name]}
- />
-
-
-
+ <%!-- Bottom Action Buttons --%>
+
- <.input field={@form[:country]} label={gettext("Country")} />
+ <%!-- Address: Country, Postal Code, City in one row --%>
+
- <%!-- Street and Nr. below --%>
-
+
-
+ <.input field={@form[:country]} label={gettext("Country")} />
+
+
+ <.input
+ field={@form[:postal_code]}
+ label={gettext("Postal Code")}
+ required={@member_field_required_map[:postal_code]}
+ />
+
+
+ <.input field={@form[:city]} label={gettext("City")} />
+
- <.input
- field={@form[:postal_code]}
- label={gettext("Postal Code")}
- required={@member_field_required_map[:postal_code]}
- />
-
-
- <.input field={@form[:city]} label={gettext("City")} />
-
-
+ <%!-- Street and Nr. below --%>
+
- <%!-- Email --%>
-
+
+
+ <%!-- Email --%>
+ <.input field={@form[:street]} label={gettext("Street")} />
+
+
+ <.input field={@form[:house_number]} label={gettext("Nr.")} />
+
+
- <.input field={@form[:street]} label={gettext("Street")} />
+ <.input field={@form[:email]} label={gettext("Email")} required type="email" />
-
- <.input field={@form[:house_number]} label={gettext("Nr.")} />
-
-
- <.input field={@form[:email]} label={gettext("Email")} required type="email" />
-
-
- <%!-- Membership Dates Row --%>
-
-
+
+ <%!-- Membership Fee Section --%>
+
- <.input
- field={@form[:join_date]}
- label={gettext("Join Date")}
- type="date"
- required={@member_field_required_map[:join_date]}
- />
+ <%!-- Membership Dates Row --%>
+
- <%!-- Notes --%>
+ <%!-- Custom Fields Section --%>
+ <%= if Enum.any?(@custom_fields) do %>
+
+
-
+ <.input
+ field={@form[:join_date]}
+ label={gettext("Join Date")}
+ type="date"
+ required={@member_field_required_map[:join_date]}
+ />
+
+
+ <.input
+ field={@form[:exit_date]}
+ label={gettext("Exit Date")}
+ type="date"
+ required={@member_field_required_map[:exit_date]}
+ />
+
+
+ <%!-- Notes --%>
+
+
+
<.input
- field={@form[:exit_date]}
- label={gettext("Exit Date")}
- type="date"
- required={@member_field_required_map[:exit_date]}
+ field={@form[:notes]}
+ label={gettext("Notes")}
+ type="textarea"
+ required={@member_field_required_map[:notes]}
/>
+ <.form_section title={gettext("Custom Fields")}>
+
+ <% end %>
+
+ <%!-- Render in sorted order by finding the form for each sorted custom field --%>
+ <%= for cf <- @sorted_custom_fields do %>
+ <.inputs_for :let={f_cfv} field={@form[:custom_field_values]}>
+ <%= if f_cfv[:custom_field_id].value == cf.id do %>
+
+
+
+ <.inputs_for :let={value_form} field={f_cfv[:value]}>
+ <.input
+ field={value_form[:value]}
+ label={cf.name}
+ type={custom_field_input_type(cf.value_type)}
+ required={cf.required}
+ />
+
+
+
+ <% end %>
+
+ <% end %>
+
+ <.form_section title={gettext("Membership Fee")}>
+
- <%!-- Custom Fields Section --%>
- <%= if Enum.any?(@custom_fields) do %>
-
- <.input
- field={@form[:notes]}
- label={gettext("Notes")}
- type="textarea"
- required={@member_field_required_map[:notes]}
- />
+
+
+ <%= for error <- List.wrap(@form.errors[:membership_fee_type_id] || []) do %>
+ <% {msg, _opts} = if is_tuple(error), do: error, else: {error, []} %>
+
{msg} + <% end %> + <%= if @interval_warning do %> +
+ <.icon name="hero-exclamation-triangle" class="size-5" />
+ {@interval_warning}
+
+ <% end %>
+ + {gettext( + "Select a membership fee type for this member. Members can only switch between types with the same interval." + )} +
- <.form_section title={gettext("Custom Fields")}>
-
- <% end %>
-
- <%!-- Render in sorted order by finding the form for each sorted custom field --%>
- <%= for cf <- @sorted_custom_fields do %>
- <.inputs_for :let={f_cfv} field={@form[:custom_field_values]}>
- <%= if f_cfv[:custom_field_id].value == cf.id do %>
-
-
-
- <.inputs_for :let={value_form} field={f_cfv[:value]}>
- <.input
- field={value_form[:value]}
- label={cf.name}
- type={custom_field_input_type(cf.value_type)}
- required={cf.required}
- />
-
-
-
- <% end %>
-
- <% end %>
-
+ <.button navigate={return_path(@return_to, @member)} variant="neutral" type="button">
+ {gettext("Cancel")}
+
+ <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
+ {gettext("Save Member")}
+
+
- <%!-- Membership Fee Section --%>
-
- <.form_section title={gettext("Membership Fee")}>
-
-
-
- <%!-- Bottom Action Buttons --%>
-
-
-
- <%= for error <- List.wrap(@form.errors[:membership_fee_type_id] || []) do %>
- <% {msg, _opts} = if is_tuple(error), do: error, else: {error, []} %>
-
-
- {msg} - <% end %> - <%= if @interval_warning do %> -
- <.icon name="hero-exclamation-triangle" class="size-5" />
- {@interval_warning}
-
- <% end %>
-
+ <%!-- Danger zone: same section pattern as MemberLive.Show (canonical) --%>
+ <%= if @member && can?(@current_user, :destroy, @member) do %>
+
{gettext(
- "Select a membership fee type for this member. Members can only switch between types with the same interval."
+ "Deleting this member cannot be undone. All related data (e.g. membership fee cycles) will be removed."
)}
- <.button navigate={return_path(@return_to, @member)} variant="neutral" type="button">
- {gettext("Cancel")}
-
- <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
- {gettext("Save Member")}
-
-
-
- <%!-- Danger zone: same section pattern as MemberLive.Show (canonical) --%>
- <%= if @member && can?(@current_user, :destroy, @member) do %>
- - {gettext("Danger zone")} --
-
- - {gettext( - "Deleting this member cannot be undone. All related data (e.g. membership fee cycles) will be removed." - )} - - <.button - variant="danger" - type="button" - phx-click="delete" - phx-value-id={@member.id} - data-confirm={ - gettext("Are you sure you want to delete %{name}? This action cannot be undone.", - name: MvWeb.Helpers.MemberHelpers.display_name(@member) - ) - } - data-testid="member-delete" - aria-label={ - gettext("Delete member %{name}", - name: MvWeb.Helpers.MemberHelpers.display_name(@member) - ) - } - > - <.icon name="hero-trash" class="size-4" /> - {gettext("Delete member")} - -
- <.link navigate={~p"/members/#{member}"} data-testid="member-show-link">
- {gettext("Show")}
-
-
-
-
+ <.badge variant={badge.variant}>
+ <.icon name={badge.icon} class="size-4" />
+ {badge.label}
+
+ <% else %>
+ <.badge variant="neutral">{gettext("No cycle")}
+ <% end %>
+
+ <:col
+ :let={member}
+ :if={:groups in @member_fields_visible}
+ label={
+ ~H"""
+ <.live_component
+ module={MvWeb.Components.SortHeaderComponent}
+ id={:sort_groups}
+ field={:groups}
+ label={gettext("Groups")}
+ sort_field={@sort_field}
+ sort_order={@sort_order}
+ />
+ """
+ }
+ >
+ <%= for group <- (member.groups || []) do %>
+ <.badge
+ variant="primary"
+ style="outline"
+ aria-label={gettext("Member of group %{name}", name: group.name)}
+ >
+ {group.name}
+
+ <% end %>
+ <%= if (member.groups || []) == [] do %>
+ —
+ <% end %>
+
+ <:action :let={member}>
+
+ <.link navigate={~p"/members/#{member}"} data-testid="member-show-link">
+ {gettext("Show")}
+
+
+
+
|