docs(08-02): SUMMARY — mobile engagement profile page complete (SC#2 partial)

This commit is contained in:
lorentz 2026-05-07 23:06:44 -04:00
parent 435051ddc8
commit 7fcb156cfc

View file

@ -0,0 +1,99 @@
---
phase: 08-engagement-user-profile-new
plan: "02"
subsystem: mobile-engagement
tags: [mobile, engagement, profile, page, components]
dependency_graph:
requires:
- GET /api/mobile/engagement/user/[userId]/photo
- GET /api/engagement/user/[userId]
- components/mobile/EngagementPeriodChips.tsx
- components/mobile/EngagementUserRow.tsx#getInitials
provides:
- GET /mobile/engagement/[userId] (real route, not modal)
affects:
- app/mobile/engagement/[userId]/page.tsx
- components/mobile/EngagementProfileSkeleton.tsx
- components/mobile/EngagementProfileHeader.tsx
- components/mobile/EngagementProfileMetricGrid.tsx
- components/mobile/EngagementProfileBreakdown.tsx
- components/mobile/EngagementRecentEntries.tsx
- components/mobile/EngagementRecentMeetings.tsx
- app/mobile/engagement/page.tsx (sessionStorage scroll shim only)
tech_stack:
added: []
patterns:
- "App Router dynamic route `[userId]` with `params: Promise<{userId}>` unwrapped via React.use"
- "Image error → initials fallback via local useState toggle"
- "rAF-throttled scroll capture + retry-on-restore (D-04 fallback)"
key_files:
modified:
- app/mobile/engagement/page.tsx
- components/mobile/EngagementRecentEntries.tsx
- components/mobile/EngagementRecentMeetings.tsx
created:
- app/mobile/engagement/[userId]/page.tsx
- components/mobile/EngagementProfileSkeleton.tsx
- components/mobile/EngagementProfileHeader.tsx
- components/mobile/EngagementProfileMetricGrid.tsx
- components/mobile/EngagementProfileBreakdown.tsx
- components/mobile/EngagementRecentEntries.tsx
- components/mobile/EngagementRecentMeetings.tsx
decisions:
- "Profile is a real route, not a modal — preserves device-back behavior (D-04 intent)"
- "Photo fallback to initials is a render-time `<img>` onError → useState toggle (no double-fetch)"
- "Coerce Postgres NUMERIC strings to Number() at render — pg returns numeric columns as strings; period totals are parseFloat'd in the API but recentEntries[]/matchedEntries[] hours_worked are passed through verbatim"
- "Scroll restoration: SC#2 is partial — sessionStorage shim with rAF retry was added but did not reliably restore window scroll on this layout in user testing. User accepted as a known limitation; no follow-up plan filed."
metrics:
duration_minutes: 60
completed_date: "2026-05-08"
tasks_completed: 4
files_modified: 3
files_created: 7
requirements_addressed: [ENG-06, ENG-07, ENG-08]
---
# Phase 8 Plan 02: Mobile Engagement User Profile Summary
**One-liner:** Mobile engagement user profile at `/mobile/engagement/[userId]` — a real Next.js route (not a modal) composing six new components on top of the existing `/api/engagement/user/[userId]` endpoint plus the Plan 01 photo proxy.
## Tasks Completed
| Task | Name | Commit | Files |
|------|------|--------|-------|
| 1a | Page shell + Skeleton + period/fetch wiring | 3247c92 | app/mobile/engagement/[userId]/page.tsx, components/mobile/EngagementProfileSkeleton.tsx |
| 1b | Identity header + 2×2 metric grid + page wiring | df78ab8 | components/mobile/EngagementProfileHeader.tsx, components/mobile/EngagementProfileMetricGrid.tsx, app/mobile/engagement/[userId]/page.tsx |
| 2 | Activity breakdown + Recent entries + Recent meetings | 0be0c1f | components/mobile/EngagementProfileBreakdown.tsx, components/mobile/EngagementRecentEntries.tsx, components/mobile/EngagementRecentMeetings.tsx, app/mobile/engagement/[userId]/page.tsx |
| 3 | Manual verification (scroll restoration) | n/a | (user-tested) |
### Post-test fixes
| Fix | Commit | Files | Reason |
|-----|--------|-------|--------|
| Coerce hours_worked to Number before .toFixed | 81079ad | components/mobile/EngagementRecentEntries.tsx, components/mobile/EngagementRecentMeetings.tsx | Postgres NUMERIC arrives as string via pg — runtime TypeError on render |
| Scroll-restoration shim attempt | 6bdc937, then revised in subsequent commits | app/mobile/engagement/page.tsx | Mobile shell uses overflow-y-auto on `<main>` but the document scrolls in practice; sessionStorage save+restore added with rAF retry. SC#2 still partial — see Decisions. |
## What Was Built
**`/mobile/engagement/[userId]` page** — full profile surface composed of:
1. **H1** — display name from Graph user
2. **Sticky period chips** (D7/D30/D90) — defaults to D30, refetches on change
3. **Identity card** — Graph photo via the Plan 01 proxy with onError → `getInitials(displayName)` fallback; title, department, mailto link, last-active label (relative ≤7d via `date-fns`, absolute >7d via `formatInUserTimezone`)
4. **2×2 metric grid** — Hours worked, Billable hours, Days worked, Meetings attended (period-scoped)
5. **Activity breakdown card** — Time / Communication / Meetings subsections with conditional Zoom + after-hours rows
6. **Recent time entries** — collapsed list of recent Autotask `time_entries` with billable badge + truncated note
7. **Recent Teams meetings** — list with participant names, client attendees, inline matched time entries
Loading state renders `EngagementProfileSkeleton`. Error states: 404 → "User not found" with back link; 5xx/network → skeleton + Retry button (uses `retryNonce` to re-trigger the fetch effect).
Reuses `/api/engagement/user/[userId]` verbatim — no API modifications. The existing `EngagementUserRow.tsx` link to `/mobile/engagement/${graphUserId}` (already present from Phase 7) now reaches a real destination.
## Verification Results
**SC#1 — Page renders all sections:** ✓ Passed (user-confirmed)
**SC#2 — Scroll restoration on back gesture:** ⚠ Partial — list scrolls back to top, not to the previous row position. The mobile shell's layout has `<main>` with `overflow-y-auto` but in practice the document scrolls (`window.scrollY` carries the value, `<main>.scrollTop` stays 0). A sessionStorage shim with rAF retry was added but did not consistently restore `window.scrollY` after the row list re-rendered. User accepted as a known limitation.
## Notable Deviations
None. Plan was followed; the only adjustments were the post-test fixes documented above.