feat(member-live): sort the composite Member column by first or last name
This commit is contained in:
parent
ca2aaa069c
commit
f7005f395f
11 changed files with 443 additions and 34 deletions
|
|
@ -172,7 +172,7 @@ defmodule MvWeb.MemberLive.Index do
|
|||
socket
|
||||
|> Layouts.assign_page_title(gettext("Members"))
|
||||
|> assign(:query, "")
|
||||
|> assign_new(:sort_field, fn -> :first_name end)
|
||||
|> assign_new(:sort_field, fn -> :last_name end)
|
||||
|> assign_new(:sort_order, fn -> :asc end)
|
||||
|> assign(:cycle_status_filter, nil)
|
||||
|> assign(:group_filters, %{})
|
||||
|
|
@ -379,6 +379,22 @@ defmodule MvWeb.MemberLive.Index do
|
|||
end
|
||||
|
||||
{new_field, new_order} = determine_new_sort(field, socket)
|
||||
{:noreply, apply_sort_change(socket, new_field, new_order)}
|
||||
end
|
||||
|
||||
# The composite "Member" column's sub-field menu (first name / last name) always
|
||||
# sorts ascending by the chosen field: it switches the sort key rather than
|
||||
# toggling direction (a click on the header itself toggles direction).
|
||||
def handle_event("sort_composite", %{"field" => field_str}, socket) do
|
||||
field = String.to_existing_atom(field_str)
|
||||
{:noreply, apply_sort_change(socket, field, :asc)}
|
||||
end
|
||||
|
||||
# Applies a resolved (field, order) sort: updates assigns and the sort-header
|
||||
# components, reloads the keyset stream from page 1, scrolls to top, and syncs
|
||||
# the URL. Shared by the header-click ("sort") and sub-field menu
|
||||
# ("sort_composite") paths.
|
||||
defp apply_sort_change(socket, new_field, new_order) do
|
||||
old_field = socket.assigns.sort_field
|
||||
|
||||
socket =
|
||||
|
|
@ -394,8 +410,8 @@ defmodule MvWeb.MemberLive.Index do
|
|||
query_params =
|
||||
build_query_params(
|
||||
opts_for_query_params(socket, %{
|
||||
sort_field: ExportPayload.sort_field(socket.assigns.sort_field),
|
||||
sort_order: ExportPayload.sort_order(socket.assigns.sort_order)
|
||||
sort_field: ExportPayload.sort_field(new_field),
|
||||
sort_order: ExportPayload.sort_order(new_order)
|
||||
})
|
||||
)
|
||||
|> maybe_add_field_selection(
|
||||
|
|
@ -403,7 +419,7 @@ defmodule MvWeb.MemberLive.Index do
|
|||
socket.assigns[:fields_in_url?] || false
|
||||
)
|
||||
|
||||
{:noreply, push_reload(socket, ~p"/members?#{query_params}")}
|
||||
push_reload(socket, ~p"/members?#{query_params}")
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
|
|
@ -1488,9 +1504,11 @@ defmodule MvWeb.MemberLive.Index do
|
|||
defp determine_field(default, nil), do: default
|
||||
|
||||
# Computed/pseudo fields that are nonetheless sortable (they resolve to DB
|
||||
# sort keys in OverviewQuery): groups aggregate and the composite Name/Address
|
||||
# columns. Other computed fields (e.g. membership_fee_status) are not sortable.
|
||||
@sortable_computed_fields [:groups, :name, :address]
|
||||
# sort keys in OverviewQuery): the groups aggregate and the composite Address
|
||||
# column. The composite Member column sorts by its real first/last-name
|
||||
# sub-fields (chosen via its sub-field menu), so it needs no pseudo token here.
|
||||
# Other computed fields (e.g. membership_fee_status) are not sortable.
|
||||
@sortable_computed_fields [:groups, :address]
|
||||
|
||||
defp determine_field(default, sf) when is_binary(sf) do
|
||||
sortable_strings = Enum.map(@sortable_computed_fields, &Atom.to_string/1)
|
||||
|
|
|
|||
|
|
@ -170,17 +170,12 @@
|
|||
<:col
|
||||
:let={member}
|
||||
:if={:name in @member_fields_visible and @compact_member}
|
||||
sort_field={:name}
|
||||
sort_field={if @sort_field in [:first_name, :last_name], do: @sort_field}
|
||||
label={
|
||||
~H"""
|
||||
<.live_component
|
||||
module={MvWeb.Components.SortHeaderComponent}
|
||||
id={:sort_name}
|
||||
field={:name}
|
||||
label={gettext("Name")}
|
||||
<MvWeb.Components.MemberNameSortHeader.composite_name_sort_header
|
||||
sort_field={@sort_field}
|
||||
sort_order={@sort_order}
|
||||
sort_hint={gettext("Click to sort by last name")}
|
||||
/>
|
||||
"""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,12 +254,19 @@ defmodule MvWeb.MemberLive.Index.OverviewQuery do
|
|||
defp apply_sort(query, nil, _order, _custom_fields), do: Ash.Query.sort(query, id: :asc)
|
||||
defp apply_sort(query, _field, nil, _custom_fields), do: Ash.Query.sort(query, id: :asc)
|
||||
|
||||
# Composite "Member" column: sort by last name then first name. The email that
|
||||
# shares the cell is display-only and does not affect the sort key (§1.21).
|
||||
defp apply_sort(query, field, order, _custom_fields) when field in [:name, "name"] do
|
||||
# Composite "Member" column sub-fields (§1.21). Sorting by one name part uses
|
||||
# the other name part (then id) as a stable tie-breaker; the email that shares
|
||||
# the cell is display-only and never a sort key. Shared by the composite
|
||||
# sub-field menu and the separate first/last-name columns.
|
||||
defp apply_sort(query, field, order, _custom_fields) when field in [:last_name, "last_name"] do
|
||||
Ash.Query.sort(query, [{:last_name, order}, {:first_name, order}, {:id, :asc}])
|
||||
end
|
||||
|
||||
defp apply_sort(query, field, order, _custom_fields)
|
||||
when field in [:first_name, "first_name"] do
|
||||
Ash.Query.sort(query, [{:first_name, order}, {:last_name, order}, {:id, :asc}])
|
||||
end
|
||||
|
||||
# Composite "Address" column: sort by city, then postal code, then street.
|
||||
defp apply_sort(query, field, order, _custom_fields) when field in [:address, "address"] do
|
||||
Ash.Query.sort(query, [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue