Add groups to membership overview closes #373 #422

Merged
simon merged 8 commits from feature/member-overview-groups into main 2026-02-16 17:20:55 +01:00
Owner

Here’s a PR description you can use:


Description of the implemented changes

The changes were:

  • Bugfixing
  • New Feature
  • Breaking Change
  • Refactoring

Add groups to member overview: column, per-group filter (All/Yes/No), sortable, URL params, and fixes

What has been changed?

New feature

  • Groups column in the member table: shows each member’s groups (names as badges); column can be shown/hidden via “Show/Hide Columns”.
  • Per-group filter in the filter dropdown: one row per group with options All / Yes / No (same pattern as boolean custom field filters); filters are applied via Ash (e.g. exists(member_groups, …)).
  • Groups sort: sort by “Groups” (members with groups first, then by first group name).
  • URL params: group filters are encoded in the URL (e.g. group_<uuid>=in / not_in) so filtered views are bookmarkable and work with back/forward.
  • Integration: group filter and sort work together with existing payment filter, custom field filters, search, and field visibility.

Bugfixes

  • Group filter not applied: apply_group_filters used a %{} pattern, which in Elixir matches any map. Replaced with a guard when group_filters == %{} so only the empty map is skipped and real filters are applied.
  • Integration test “groups work with membership fee status filter”: member now gets membership_fee_type_id set before creating the paid cycle so get_last_completed_cycle (which uses member.membership_fee_type) finds the cycle and the “Paid” filter includes the member.

Refactoring / code quality

  • Credo: Reduced nesting in MemberFilterComponent (group filter send logic); replaced single-branch cond with if in sort_members_in_memory; moved group-filter reduce into add_group_filter_entry/4 to lower nesting in maybe_update_group_filters; grouped maybe_update_group_filters/2 clauses.
  • Group filter logic: Removed temporary state and debug code; group filters are derived only from URL params (same approach as boolean filters).

i18n

  • Translations: Filled German msgstr for “Any” → “Alle” and “Member of group %{name}” → “Mitglied der Gruppe %{name}” in priv/gettext/de/LC_MESSAGES/default.po.

Tests

  • New test files: index_groups_filter_test.exs, index_groups_sorting_test.exs, index_groups_url_params_test.exs, index_groups_integration_test.exs, index_groups_performance_test.exs, index_groups_display_test.exs, index_groups_accessibility_test.exs.

Docs

  • docs/feature-roadmap.md: short note that groups are integrated in the member overview (filter/sort/column).

Definition of Done

Code quality

  • No new technical depths (nesting reduced, helpers used)
  • Linting passed (format, compile --warnings-as-errors, Credo)
  • Documentation is added where needed (roadmap updated; inline comments where non-obvious)

Accessibility

  • New elements use appropriate HTML (e.g. badges/links for groups)
  • Colour contrast follows WCAG (existing badge/UI styles)
  • Aria labels where needed (e.g. “Member of group %{name}” for group badges, filter controls)
  • Keyboard accessible (filter dropdown and column visibility unchanged)
  • Tab order remains logical
  • Interactive elements have visible focus (existing patterns)

Testing

  • New code covered by tests (filter, sort, URL params, integration, performance, display, accessibility)
  • All tests pass
  • axe-core dev tools: no critical/major issues (to be confirmed by reviewer if desired)

Additional notes

  • Group filter state is only taken from URL params in handle_params (no _pending_group_filters / _group_filters_before_params). Empty group_* params yield empty filters; that matches the boolean-filter behaviour and avoids subtle bugs.
  • Performance: groups are loaded once in mount and with Ash.Query.load(query, groups: [:id, :name, :slug]) for the table; dedicated performance tests cover N+1 and many members/many groups.
  • If you run axe-core or manual a11y checks on the new filter and groups column, results can be added to this section.
Here’s a PR description you can use: --- ## Description of the implemented changes The changes were: - [x] Bugfixing - [x] New Feature - [ ] Breaking Change - [x] Refactoring **Add groups to member overview: column, per-group filter (All/Yes/No), sortable, URL params, and fixes** ## What has been changed? ### New feature - **Groups column** in the member table: shows each member’s groups (names as badges); column can be shown/hidden via “Show/Hide Columns”. - **Per-group filter** in the filter dropdown: one row per group with options All / Yes / No (same pattern as boolean custom field filters); filters are applied via Ash (e.g. `exists(member_groups, …)`). - **Groups sort**: sort by “Groups” (members with groups first, then by first group name). - **URL params**: group filters are encoded in the URL (e.g. `group_<uuid>=in` / `not_in`) so filtered views are bookmarkable and work with back/forward. - **Integration**: group filter and sort work together with existing payment filter, custom field filters, search, and field visibility. ### Bugfixes - **Group filter not applied**: `apply_group_filters` used a `%{}` pattern, which in Elixir matches any map. Replaced with a guard `when group_filters == %{}` so only the empty map is skipped and real filters are applied. - **Integration test “groups work with membership fee status filter”**: member now gets `membership_fee_type_id` set before creating the paid cycle so `get_last_completed_cycle` (which uses `member.membership_fee_type`) finds the cycle and the “Paid” filter includes the member. ### Refactoring / code quality - **Credo**: Reduced nesting in `MemberFilterComponent` (group filter send logic); replaced single-branch `cond` with `if` in `sort_members_in_memory`; moved group-filter reduce into `add_group_filter_entry/4` to lower nesting in `maybe_update_group_filters`; grouped `maybe_update_group_filters/2` clauses. - **Group filter logic**: Removed temporary state and debug code; group filters are derived only from URL params (same approach as boolean filters). ### i18n - **Translations**: Filled German `msgstr` for “Any” → “Alle” and “Member of group %{name}” → “Mitglied der Gruppe %{name}” in `priv/gettext/de/LC_MESSAGES/default.po`. ### Tests - New test files: `index_groups_filter_test.exs`, `index_groups_sorting_test.exs`, `index_groups_url_params_test.exs`, `index_groups_integration_test.exs`, `index_groups_performance_test.exs`, `index_groups_display_test.exs`, `index_groups_accessibility_test.exs`. ### Docs - `docs/feature-roadmap.md`: short note that groups are integrated in the member overview (filter/sort/column). ## Definition of Done ### Code quality - [x] No new technical depths (nesting reduced, helpers used) - [x] Linting passed (format, compile --warnings-as-errors, Credo) - [ ] Documentation is added where needed (roadmap updated; inline comments where non-obvious) ### Accessibility - [x] New elements use appropriate HTML (e.g. badges/links for groups) - [x] Colour contrast follows WCAG (existing badge/UI styles) - [x] Aria labels where needed (e.g. “Member of group %{name}” for group badges, filter controls) - [x] Keyboard accessible (filter dropdown and column visibility unchanged) - [x] Tab order remains logical - [x] Interactive elements have visible focus (existing patterns) ### Testing - [x] New code covered by tests (filter, sort, URL params, integration, performance, display, accessibility) - [x] All tests pass - [ ] axe-core dev tools: no critical/major issues (to be confirmed by reviewer if desired) ## Additional notes - Group filter state is **only** taken from URL params in `handle_params` (no `_pending_group_filters` / `_group_filters_before_params`). Empty `group_*` params yield empty filters; that matches the boolean-filter behaviour and avoids subtle bugs. - Performance: groups are loaded once in `mount` and with `Ash.Query.load(query, groups: [:id, :name, :slug])` for the table; dedicated performance tests cover N+1 and many members/many groups. - If you run axe-core or manual a11y checks on the new filter and groups column, results can be added to this section.
simon added 5 commits 2026-02-13 18:23:16 +01:00
test: adapt earlier tests to groups implementation
Some checks failed
continuous-integration/drone/push Build is failing
3322efcdf6
feat: improve groups fillter
Some checks failed
continuous-integration/drone/push Build is failing
5fd7c0e7f6
fix: test
Some checks failed
continuous-integration/drone/push Build is failing
1133ffb28f
simon changed title from WIP: Add groups to membership overview to WIP: Add groups to membership overview closes #373 2026-02-13 18:25:29 +01:00
simon added 1 commit 2026-02-13 18:26:20 +01:00
style: fix formatting
Some checks failed
continuous-integration/drone/push Build is failing
65581d0639
simon added 1 commit 2026-02-16 15:30:30 +01:00
fix: implement review comments
All checks were successful
continuous-integration/drone/push Build is passing
ace59bbae6
simon added 1 commit 2026-02-16 15:58:11 +01:00
Merge remote-tracking branch 'origin/main' into feature/member-overview-groups
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
6831ba046f
simon changed title from WIP: Add groups to membership overview closes #373 to Add groups to membership overview closes #373 2026-02-16 17:20:43 +01:00
simon merged commit 7b13d03bb7 into main 2026-02-16 17:20:55 +01:00
simon deleted branch feature/member-overview-groups 2026-02-16 17:20:57 +01:00
Sign in to join this conversation.
No description provided.