Replace paid_filter with cycle_status_filter that filters based on membership fee cycle status (last or current cycle). Update PaymentFilterComponent to use new filter with options All, Paid, Unpaid. Remove membership fee status filter dropdown. Extend filter_members_by_cycle_status/3 to support both paid and unpaid filtering. Update toggle_cycle_view to preserve filter state in URL.
179 lines
5.7 KiB
Elixir
179 lines
5.7 KiB
Elixir
defmodule MvWeb.MemberLive.Index.MembershipFeeStatus do
|
|
@moduledoc """
|
|
Helper module for membership fee status display in member list view.
|
|
|
|
Provides functions to efficiently load and determine cycle status for members
|
|
in the list view, avoiding N+1 queries.
|
|
"""
|
|
|
|
use Gettext, backend: MvWeb.Gettext
|
|
|
|
alias Mv.Membership.Member
|
|
alias MvWeb.Helpers.MembershipFeeHelpers
|
|
|
|
@doc """
|
|
Loads membership fee cycles for members efficiently.
|
|
|
|
Preloads cycles with membership_fee_type relationship to avoid N+1 queries.
|
|
Only loads the relevant cycle per member (last completed or current, depending on show_current).
|
|
|
|
## Parameters
|
|
|
|
- `query` - Ash query for members
|
|
- `show_current` - If true, load current cycle; if false, load last completed cycle
|
|
- `today` - Optional date to use as reference (defaults to today)
|
|
|
|
## Returns
|
|
|
|
Modified query with cycles loaded
|
|
|
|
## Performance
|
|
|
|
Uses Ash.Query.load to efficiently preload cycles in a single query.
|
|
Filters cycles at database level to only load the relevant cycle per member.
|
|
"""
|
|
@spec load_cycles_for_members(Ash.Query.t(), boolean(), Date.t() | nil) :: Ash.Query.t()
|
|
def load_cycles_for_members(query, _show_current \\ false, _today \\ nil) do
|
|
# Load membership_fee_type and cycles with efficient filtering
|
|
query
|
|
|> Ash.Query.load([:membership_fee_type, membership_fee_cycles: [:membership_fee_type]])
|
|
end
|
|
|
|
@doc """
|
|
Gets the cycle status for a member.
|
|
|
|
Returns the status of either the last completed cycle or the current cycle,
|
|
depending on the `show_current` parameter.
|
|
|
|
## Parameters
|
|
|
|
- `member` - Member struct with loaded cycles and membership_fee_type
|
|
- `show_current` - If true, get current cycle status; if false, get last completed cycle status
|
|
|
|
## Returns
|
|
|
|
- `:paid`, `:unpaid`, or `:suspended` if cycle exists
|
|
- `nil` if no cycle exists
|
|
|
|
## Examples
|
|
|
|
# Get last completed cycle status
|
|
iex> MvWeb.MemberLive.Index.MembershipFeeStatus.get_cycle_status_for_member(member, false)
|
|
:paid
|
|
|
|
# Get current cycle status
|
|
iex> MvWeb.MemberLive.Index.MembershipFeeStatus.get_cycle_status_for_member(member, true)
|
|
:unpaid
|
|
"""
|
|
@spec get_cycle_status_for_member(Member.t(), boolean()) :: :paid | :unpaid | :suspended | nil
|
|
def get_cycle_status_for_member(member, show_current \\ false) do
|
|
cycle =
|
|
if show_current do
|
|
MembershipFeeHelpers.get_current_cycle(member)
|
|
else
|
|
MembershipFeeHelpers.get_last_completed_cycle(member)
|
|
end
|
|
|
|
case cycle do
|
|
nil -> nil
|
|
cycle -> cycle.status
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Formats cycle status as a badge component.
|
|
|
|
Returns a map with badge information for rendering in templates.
|
|
|
|
## Parameters
|
|
|
|
- `status` - Cycle status (`:paid`, `:unpaid`, `:suspended`, or `nil`)
|
|
|
|
## Returns
|
|
|
|
Map with `:color`, `:icon`, and `:label` keys, or `nil` if status is nil
|
|
|
|
## Examples
|
|
|
|
iex> MvWeb.MemberLive.Index.MembershipFeeStatus.format_cycle_status_badge(:paid)
|
|
%{color: "badge-success", icon: "hero-check-circle", label: "Paid"}
|
|
|
|
iex> MvWeb.MemberLive.Index.MembershipFeeStatus.format_cycle_status_badge(nil)
|
|
nil
|
|
"""
|
|
@spec format_cycle_status_badge(:paid | :unpaid | :suspended | nil) ::
|
|
%{color: String.t(), icon: String.t(), label: String.t()} | nil
|
|
def format_cycle_status_badge(nil), do: nil
|
|
|
|
def format_cycle_status_badge(status) when status in [:paid, :unpaid, :suspended] do
|
|
%{
|
|
color: MembershipFeeHelpers.status_color(status),
|
|
icon: MembershipFeeHelpers.status_icon(status),
|
|
label: format_status_label(status)
|
|
}
|
|
end
|
|
|
|
@doc """
|
|
Filters members by cycle status (paid or unpaid).
|
|
|
|
Returns members that have the specified status in either the last completed cycle
|
|
or the current cycle, depending on `show_current`.
|
|
|
|
## Parameters
|
|
|
|
- `members` - List of member structs with loaded cycles
|
|
- `status` - Cycle status to filter by (`:paid` or `:unpaid`)
|
|
- `show_current` - If true, filter by current cycle; if false, filter by last completed cycle
|
|
|
|
## Returns
|
|
|
|
List of members with the specified cycle status
|
|
|
|
## Examples
|
|
|
|
# Filter unpaid members in last cycle
|
|
iex> filter_members_by_cycle_status(members, :unpaid, false)
|
|
[%Member{}, ...]
|
|
|
|
# Filter paid members in current cycle
|
|
iex> filter_members_by_cycle_status(members, :paid, true)
|
|
[%Member{}, ...]
|
|
"""
|
|
@spec filter_members_by_cycle_status([Member.t()], :paid | :unpaid, boolean()) :: [Member.t()]
|
|
def filter_members_by_cycle_status(members, status, show_current \\ false)
|
|
when status in [:paid, :unpaid] do
|
|
Enum.filter(members, fn member ->
|
|
member_status = get_cycle_status_for_member(member, show_current)
|
|
member_status == status
|
|
end)
|
|
end
|
|
|
|
@doc """
|
|
Filters members by unpaid cycle status.
|
|
|
|
Returns members that have unpaid cycles in either the last completed cycle
|
|
or the current cycle, depending on `show_current`.
|
|
|
|
## Parameters
|
|
|
|
- `members` - List of member structs with loaded cycles
|
|
- `show_current` - If true, filter by current cycle; if false, filter by last completed cycle
|
|
|
|
## Returns
|
|
|
|
List of members with unpaid cycles
|
|
|
|
## Deprecated
|
|
|
|
This function is kept for backwards compatibility. Use `filter_members_by_cycle_status/3` instead.
|
|
"""
|
|
@spec filter_unpaid_members([Member.t()], boolean()) :: [Member.t()]
|
|
def filter_unpaid_members(members, show_current \\ false) do
|
|
filter_members_by_cycle_status(members, :unpaid, show_current)
|
|
end
|
|
|
|
# Private helper function to format status label
|
|
defp format_status_label(:paid), do: gettext("Paid")
|
|
defp format_status_label(:unpaid), do: gettext("Unpaid")
|
|
defp format_status_label(:suspended), do: gettext("Suspended")
|
|
end
|