refactor(overview): derive payment badge colour from shared status variant

This commit is contained in:
Simon 2026-07-10 16:27:16 +02:00
parent 0acd12360f
commit e967cb59e9
3 changed files with 25 additions and 17 deletions

View file

@ -471,15 +471,17 @@
sort_field={@sort_field} sort_field={@sort_field}
sort_order={@sort_order} sort_order={@sort_order}
/> />
<span <.badge
:if={MvWeb.MemberLive.Index.payment_period_active?(@payment_period)} :if={MvWeb.MemberLive.Index.payment_period_active?(@payment_period)}
class="badge badge-soft badge-primary badge-sm font-normal shrink-0" variant="primary"
size="sm"
class="font-normal shrink-0"
title={PaymentAging.period_tooltip(@payment_period)} title={PaymentAging.period_tooltip(@payment_period)}
aria-label={PaymentAging.period_tooltip(@payment_period)} aria-label={PaymentAging.period_tooltip(@payment_period)}
data-testid="payment-period-badge" data-testid="payment-period-badge"
> >
{PaymentAging.period_short_code(@payment_period) || gettext("filtered")} {PaymentAging.period_short_code(@payment_period) || gettext("filtered")}
</span> </.badge>
</div> </div>
""" """
} }
@ -499,7 +501,6 @@
id={"payment-badge-#{member.id}"} id={"payment-badge-#{member.id}"}
class={["badge badge-soft badge-md", badge.color_class]} class={["badge badge-soft badge-md", badge.color_class]}
style={count > 0 && "anchor-name: #{anchor}"} style={count > 0 && "anchor-name: #{anchor}"}
data-variant={badge.variant}
phx-hook={count > 0 && "HoverPopover"} phx-hook={count > 0 && "HoverPopover"}
data-popover-target={count > 0 && tip_id} data-popover-target={count > 0 && tip_id}
aria-details={count > 0 && tip_id} aria-details={count > 0 && tip_id}

View file

@ -158,13 +158,12 @@ defmodule MvWeb.MemberLive.Index.PaymentAging do
def suspended_to_params(_), do: %{} def suspended_to_params(_), do: %{}
@doc """ @doc """
Badge descriptor for an unpaid-cycle count. A count of 0 reads "Alle bezahlt" Badge descriptor for an unpaid-cycle count. A count of 0 reads "All paid"
(success/green); a positive count reads "N offen" (error/red). Reuses the (success/green); a positive count reads "N open" (error/red). The color class
established cycle-status color and hero-icon codes so the overview badge reads is derived from the shared `MembershipFeeHelpers.status_variant/1` mapping, so
consistently with the member show page. the overview badge reads consistently with the member show page.
""" """
@spec badge(non_neg_integer()) :: %{ @spec badge(non_neg_integer()) :: %{
variant: atom(),
color_class: String.t(), color_class: String.t(),
icon: String.t(), icon: String.t(),
label: String.t(), label: String.t(),
@ -172,8 +171,7 @@ defmodule MvWeb.MemberLive.Index.PaymentAging do
} }
def badge(0) do def badge(0) do
%{ %{
variant: :success, color_class: status_badge_class(:paid),
color_class: cycle_color_class(:paid),
icon: MembershipFeeHelpers.status_icon(:paid), icon: MembershipFeeHelpers.status_icon(:paid),
label: gettext("All paid"), label: gettext("All paid"),
count: 0 count: 0
@ -182,14 +180,23 @@ defmodule MvWeb.MemberLive.Index.PaymentAging do
def badge(count) when is_integer(count) and count > 0 do def badge(count) when is_integer(count) and count > 0 do
%{ %{
variant: MembershipFeeHelpers.status_variant(:unpaid), color_class: status_badge_class(:unpaid),
color_class: cycle_color_class(:unpaid),
icon: MembershipFeeHelpers.status_icon(:unpaid), icon: MembershipFeeHelpers.status_icon(:unpaid),
label: gettext("%{count} open", count: count), label: gettext("%{count} open", count: count),
count: count count: count
} }
end end
@doc """
DaisyUI badge color class for a cycle status, derived from the single shared
`MembershipFeeHelpers.status_variant/1` mapping so the overview badges, the
tooltip badges and the member show page all stay in lock-step. daisyUI emits
all `badge-*` component colors, so the interpolated class is always bundled.
"""
@spec status_badge_class(:paid | :unpaid | :suspended) :: String.t()
def status_badge_class(status),
do: "badge-#{MembershipFeeHelpers.status_variant(status)}"
@doc """ @doc """
Compact short code for a payment period (§1.28), or nil when the period is not Compact short code for a payment period (§1.28), or nil when the period is not
compactly codeable (the header then falls back to a plain "filtered" badge). compactly codeable (the header then falls back to a plain "filtered" badge).

View file

@ -144,16 +144,16 @@ defmodule MvWeb.MemberLive.IndexPaymentPeriodTest do
end end
describe "badge/1" do describe "badge/1" do
test "0 renders as All paid (success/green)" do test "0 renders as All paid (success/green), color derived from the shared status variant" do
badge = PaymentAging.badge(0) badge = PaymentAging.badge(0)
assert badge.variant == :success refute Map.has_key?(badge, :variant)
assert badge.color_class == "badge-success" assert badge.color_class == "badge-success"
assert badge.label == "All paid" assert badge.label == "All paid"
end end
test "N > 0 renders as N open (error/red)" do test "N > 0 renders as N open (error/red), color derived from the shared status variant" do
badge = PaymentAging.badge(2) badge = PaymentAging.badge(2)
assert badge.variant == :error refute Map.has_key?(badge, :variant)
assert badge.color_class == "badge-error" assert badge.color_class == "badge-error"
assert badge.label =~ "2" assert badge.label =~ "2"
assert badge.label =~ "open" assert badge.label =~ "open"