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

View file

@ -76,7 +76,7 @@ defmodule MvWeb.MemberLive.Index.FieldVisibility do
defp apply_name_default(set, true = _compact, true = _include_email), do: set
# Compact Member cell without folded email: the composite plus a separate
# default-visible E-Mail column.
# default-visible email column.
defp apply_name_default(set, true = _compact, false = _include_email),
do: MapSet.put(set, :email)
@ -111,7 +111,7 @@ defmodule MvWeb.MemberLive.Index.FieldVisibility do
3. the address group the composite `:address` when compact, otherwise its
constituents `:street`, `:house_number`, `:postal_code`, `:city`.
Everything else follows in the configured Datenfelder order. This keeps the
Everything else follows in the configured data-fields order. This keeps the
name group ahead of the address group even when the name group is expanded
into `:first_name`/`:last_name` while the address stays compact.
"""
@ -145,7 +145,7 @@ defmodule MvWeb.MemberLive.Index.FieldVisibility do
address_variant_hidden?(field, compact_address)
end
# Compact hides Vorname/Nachname (folded into the composite). The email is
# Compact hides first-name/last-name (folded into the composite). The email is
# hidden only when it is folded into the cell (compact + include_email); with
# include_email off it stays offered as a separate column. Non-compact hides the
# composite in favour of the constituents.

View file

@ -6,11 +6,11 @@ defmodule MvWeb.MemberLive.Index.ViewSettings do
* `:density` `:compact` or `:comfortable` (the row-spacing / compact mode)
* `:compact_member` composite "Member" cell (last+first name) vs separate
Vorname/Nachname columns
first-name/last-name columns
* `:member_include_email` whether the email is surfaced (email line in the
composite cell, or a separate Email column when the member field is split)
* `:compact_address` composite Address cell (street / postal+city) vs
separate Straße/PLZ/Ort columns
separate street/postal-code/city columns
Persistence is per browser (not per account), resolved in priority order from
the LiveView connect params, the session, the request cookie, and finally the