feat(overview): replace filter panel with add-filter builder and aging column

Swaps the unbounded vertical filter panel for a Polaris-style add-filter
builder (searchable grouped field picker, type-aware value control, applied
chips) and colour-codes the fees column with each member's period-scoped
unpaid-cycle count.
This commit is contained in:
Simon 2026-07-10 16:27:15 +02:00
parent 7d1a71b1fd
commit c547ca19af
35 changed files with 2817 additions and 3149 deletions

View file

@ -47,17 +47,10 @@ defmodule MvWeb.MemberLive.Index.OverviewQuery do
)
|> apply_date_filters(opts[:date_filters])
|> apply_custom_date_filters(opts[:date_filters], opts[:date_custom_fields])
|> apply_cycle_status_filter(
opts[:cycle_status_filter],
opts[:show_current_cycle],
today(opts)
)
|> apply_payment_filter(opts[:payment_filter], payment_period(opts))
|> apply_sort(opts[:sort_field], opts[:sort_order], opts[:custom_fields] || [])
end
defp today(opts), do: opts[:today] || Date.utc_today()
# ---------------------------------------------------------------------------
# Search
# ---------------------------------------------------------------------------
@ -184,55 +177,6 @@ defmodule MvWeb.MemberLive.Index.OverviewQuery do
defp apply_one_boolean_filter(query, _uuid, _bool), do: query
# ---------------------------------------------------------------------------
# Cycle-status filter (paid/unpaid, current or last-completed cycle)
#
# Backed by the DB cycle-status aggregates (denormalized `cycle_end`). A member
# matches only when its selected-cycle status equals the requested status;
# members with no matching cycle (nil aggregate) are excluded — exactly as the
# previous in-memory classifier behaved.
# ---------------------------------------------------------------------------
defp apply_cycle_status_filter(query, status, _show_current, _today)
when status not in [:paid, :unpaid],
do: query
defp apply_cycle_status_filter(query, status, true = _show_current, today) do
# Current cycle: contains today; when several would, the one with the latest
# cycle_start wins. Keep members whose winning current cycle has `status`.
Ash.Query.filter(
query,
expr(
exists(
membership_fee_cycles,
cycle_start <= ^today and cycle_end >= ^today and status == ^status and
not exists(
member.membership_fee_cycles,
cycle_start <= ^today and cycle_end >= ^today and
cycle_start > parent(cycle_start)
)
)
)
)
end
defp apply_cycle_status_filter(query, status, _show_current, today) do
# Last completed cycle: most recent cycle that has ended (cycle_end < today).
Ash.Query.filter(
query,
expr(
exists(
membership_fee_cycles,
cycle_end < ^today and status == ^status and
not exists(
member.membership_fee_cycles,
cycle_end < ^today and cycle_start > parent(cycle_start)
)
)
)
)
end
# ---------------------------------------------------------------------------
# Payment aging filter (period-scoped unpaid-cycle count)
#