[M04] Member Person Detail

Summary

A rework of the existing person detail screen (/person/:id/view) so it presents a concise identity summary and, below it, the person’s membership history. Today the screen is a flat demographic dump (name, identity, contact, emergency, parent, doctor, medical, address) and never shows memberships. This use case narrows the identity block to the essentials and adds the membership history that operators actually come here for — it is the landing target of M03.

Actor & Context

Actor: membership administrator / desk operator (authenticated staff user).

Frequency: routine — every time a member is opened from the lookup screen or a link.

Precondition: user is authenticated with access to the person (org + person security dimensions apply). Person id is in the route.

Entry point: /person/:id/view — reached by clicking a result on M03, or any existing link to a person record.

Main Flow

  1. Operator arrives at /person/:id/view. The person is resolved by the existing route resolver.

  2. The screen renders an identity summary: a responsive two-column grid showing only first name, last name, ID number + identity type, DOB, gender.

  3. The screen loads the person’s memberships via GET /api/memberships filtered by personId and sorted by validTo descending (newest first).

  4. Below the identity summary, a membership history list renders each membership newest→oldest with: Membership Type, Period, Criteria, and Status (as a coloured tag).

  5. Operator reads the record; existing Back / Edit actions are retained.

Alternative Flows

  • AF-1 — No memberships. Person has no membership records. Show an empty state under the history heading ("No memberships on record.") — the identity summary still renders.

  • AF-2 — Memberships still loading. Show a spinner / skeleton in the history section while the identity summary is already visible (the two loads are independent).

  • AF-3 — Membership load error. Show a dismissible error in the history section without blocking the identity summary.

Acceptance Criteria

  • The identity summary is a responsive two-column grid showing ONLY: first name, last name, ID number + identity type, DOB, gender. The former medical / emergency / parent / doctor / address blocks are removed from this screen.

  • Membership history lists every membership for the person, newest→oldest (by validTo desc).

  • Each membership row shows Membership Type, Period, Criteria, and Status; status renders as a coloured tag (reusing the membership-list status→severity mapping).

  • The history uses GET /api/memberships?personId.equals=<id>&sort=validTo,desc — no new backend endpoint is introduced.

  • Empty / loading / error states are handled per AF-1 / AF-2 / AF-3.

  • The screen remains responsive (desktop table / mobile cards) and retains Back / Edit actions.

API Surface

Call Purpose

GET /api/people/{id}

(existing, via resolver) fetch the person for the identity summary.

GET /api/memberships?personId.equals={id}&sort=validTo,desc

(existing) list the person’s memberships newest-first; already org+person secured via MembershipQueryServiceEx. Response carries period.membershipType.name, period.name, criteria.name, status.

No new backend work: MembershipCriteria already exposes a personId filter and the standard membership list endpoint applies security. MembershipService.queryPerson() (GET /api/memberships/person) is a dead frontend method with no backend mapping and MUST NOT be used.

Out of Scope

  • Editing memberships or person details from this screen (Edit still routes to the existing update screens).

  • Restoring the removed demographic blocks — they remain available on the edit screen; this view is deliberately narrowed.

  • Any change to membership data shape or the membership list endpoint.

Design Anchors

Notes

  • Reuse targets: IMembership model (already carries the four display fields); the MembershipStatusp-tag severity mapping and the responsive table/card markup from the membership list; the existing person resolver and Back/Edit chrome in person-detail.component.

  • Implementation injects MembershipService into PersonDetailComponent (currently only takes ActivatedRoute) and loads memberships in ngOnInit. Pick a sane page-size cap for history.