docs(member-live): document the overview and use English throughout its internals

This commit is contained in:
Simon 2026-07-06 10:58:39 +02:00
parent 77fc11a0b0
commit 1c67527c56
4 changed files with 48 additions and 32 deletions

View file

@ -4,24 +4,40 @@ defmodule MvWeb.MemberLive.Index do
## Features
- Full-text search across member profiles using PostgreSQL tsvector
- Sortable columns (name, email, address fields)
- Bulk selection for future batch operations
- Real-time updates via LiveView
- Bookmarkable URLs with query parameters
- DB-side filtering, sorting, and keyset pagination (infinite scroll)
- Sortable columns, including composite Name (lastfirst) and Address (citypostalstreet)
- Column manager and per-browser view settings (density, composite Member/Address, include-email)
- Bulk selection spanning the whole filtered set (select-all / copy emails / export)
- Bookmarkable URLs: filter/sort/search/view state is URL-encoded
## URL Parameters
- `query` - Search query string for full-text search
- `sort_field` - Field to sort by (e.g., :first_name, :email, :join_date)
- `sort_order` - Sort direction (:asc or :desc)
All list state is URL-encoded so the view is bookmarkable and restored on reload:
- `query` - full-text search string
- `sort_field` / `sort_order` - sort column and direction (`asc` / `desc`)
- `fields` - visible-column selection
- `highlight` - member id to render as selected (return from the show page)
- group / fee-type / boolean / date custom-field filter params (see
`MvWeb.MemberLive.Index.FilterParams` and `DateFilter` for the encoding)
## Events
- `select_member` - Toggle individual member selection
- `select_all` - Toggle selection of all visible members
- `copy_emails` - Copy email addresses of the selected members, or of all/filtered members when nothing is selected
## Events (`handle_event`)
- `select_member` / `select_all` - toggle individual / whole-filtered-set selection
- `select_row_and_navigate` - open a member's show page
- `load_more` - fetch and append the next keyset page (infinite scroll)
- `sort` - change the active sort column/direction
- `toggle_cycle_view` - switch the payment-status view between current and last cycle
- `copy_emails` - copy emails of the selected members, or of the whole filtered set when none are selected
## Messages (`handle_info`, from the filter/search/view-settings components)
- `search_changed`, `view_setting_toggled`, `field_toggled`, `fields_selected`,
`fields_reset`, `reset_all_filters`, and the per-filter change messages
(`group_filter_changed`, `fee_type_filter_changed`, `boolean_filter_changed`,
`date_filters_changed`, `payment_filter_changed`)
## Implementation Notes
- Search uses PostgreSQL full-text search (plainto_tsquery)
- Sort state is synced with URL for bookmarkability
- Filtering/sorting/pagination run in PostgreSQL via Ash; the socket holds only
the loaded keyset window, not the whole table
- List state is synced with the URL for bookmarkability; a signature guard
avoids redundant reloads
- Components communicate via `handle_info` for decoupling
"""
use MvWeb, :live_view
@ -980,7 +996,7 @@ defmodule MvWeb.MemberLive.Index do
# Flips the "include email" sub-toggle. Only meaningful while the composite
# Member cell is on: it moves the email between an in-cell line (on) and a
# separate E-Mail column (off), so besides re-rendering the rows it recomputes
# separate email column (off), so besides re-rendering the rows it recomputes
# which columns the manager offers and their visibility.
defp toggle_view_setting(socket, :member_include_email) do
new_value = not socket.assigns.view_settings.member_include_email
@ -995,10 +1011,10 @@ defmodule MvWeb.MemberLive.Index do
end
# Flips the compact Member field. Besides re-rendering the rows, this changes
# which columns the manager offers (composite "Name" vs. Vorname/Nachname/E-Mail,
# which columns the manager offers (composite "Name" vs. first-name/last-name/email,
# §7b). The email visibility carries over between the two representations,
# analogously to the "include email" sub-toggle: composite + include-email ⇄ the
# E-Mail column being visible.
# email column being visible.
defp toggle_view_setting(socket, :compact_member) do
new_value = not socket.assigns.view_settings.compact_member
email_visible? = email_currently_visible?(socket)
@ -1013,8 +1029,8 @@ defmodule MvWeb.MemberLive.Index do
|> push_field_selection_url()
end
# Flips the compact Address field: offers the composite "Adresse" vs. the
# separate Straße/Hausnummer/PLZ/Ort columns, making the newly relevant columns
# Flips the compact Address field: offers the composite "Address" vs. the
# separate street/house-number/postal-code/city columns, making the newly relevant columns
# visible and recomputing the offered set + visibility for the new mode.
defp toggle_view_setting(socket, :compact_address) do
new_value = not socket.assigns.view_settings.compact_address
@ -1031,7 +1047,7 @@ defmodule MvWeb.MemberLive.Index do
end
# Whether the member email is currently surfaced: while the composite is on,
# either folded into the cell (include_email) or as a separate E-Mail column;
# either folded into the cell (include_email) or as a separate email column;
# while it is off, as its own column. Read before the flip, so `compact_member`
# still holds the previous mode.
defp email_currently_visible?(socket) do
@ -1063,8 +1079,8 @@ defmodule MvWeb.MemberLive.Index do
# Makes the now-relevant Member columns visible for the new mode. Turning the
# composite on surfaces "Name" (with the email either folded via include_email
# or kept as a separate column); turning it off surfaces Vorname/Nachname and
# carries the prior email visibility onto the E-Mail column.
# or kept as a separate column); turning it off surfaces first-name/last-name and
# carries the prior email visibility onto the email column.
defp apply_member_toggle(socket, true = _new_compact, email_visible?) do
selection =
socket.assigns.user_field_selection
@ -1910,7 +1926,7 @@ defmodule MvWeb.MemberLive.Index do
@doc """
Returns true when the member list is restricted by a non-empty search term or
any active filter (cycle status, group, fee type, boolean custom field, or a
date filter differing from the default). Drives the "(gefiltert)" vs "(alle)"
date filter differing from the default). Drives the "filtered" vs "all"
trigger label and reads only assigns no DB access.
"""
def filters_active?(assigns) do