docs(07-02): complete engagement component primitives plan summary
- 7 presentational components built: PeriodChips, SummaryCard, HoursSparkline, SortChips, SearchInput, UserRow, UserRowSkeleton - getInitials exported from EngagementUserRow for Phase 8 reuse - No recharts — inline SVG sparkline only (DASH-04) - tsc exits 0
This commit is contained in:
parent
00d0102168
commit
ccd2177977
1 changed files with 161 additions and 0 deletions
161
.planning/phases/07-engagement-overview-new/07-02-SUMMARY.md
Normal file
161
.planning/phases/07-engagement-overview-new/07-02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
---
|
||||
phase: 07-engagement-overview-new
|
||||
plan: 02
|
||||
subsystem: mobile-components
|
||||
tags: [mobile, engagement, components, typescript, tailwind]
|
||||
dependency_graph:
|
||||
requires:
|
||||
- app/api/mobile/engagement/trend/route.ts (SparklinePoint type — Wave 1)
|
||||
- components/ui/card.tsx
|
||||
- components/ui/input.tsx
|
||||
- components/ui/skeleton.tsx
|
||||
provides:
|
||||
- components/mobile/EngagementPeriodChips.tsx
|
||||
- components/mobile/EngagementSummaryCard.tsx
|
||||
- components/mobile/EngagementHoursSparkline.tsx
|
||||
- components/mobile/EngagementSortChips.tsx
|
||||
- components/mobile/EngagementSearchInput.tsx
|
||||
- components/mobile/EngagementUserRow.tsx
|
||||
- components/mobile/EngagementUserRowSkeleton.tsx
|
||||
affects:
|
||||
- app/mobile/engagement/page.tsx (Plan 03 consumer)
|
||||
- Phase 8 user profile (getInitials reuse)
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- Pure presentational components (no fetch, no useEffect data loaders)
|
||||
- 'use client' + typed props + callback props pattern
|
||||
- import type from API route file (SparklinePoint)
|
||||
- Inline SVG for sparkline (no recharts — DASH-04)
|
||||
- text-[10px] chip typography (4-size cap: text-sm, text-xs, text-[10px], text-2xl)
|
||||
- Module-level named export utility (getInitials) for cross-plan reuse
|
||||
key_files:
|
||||
created:
|
||||
- components/mobile/EngagementPeriodChips.tsx
|
||||
- components/mobile/EngagementSummaryCard.tsx
|
||||
- components/mobile/EngagementHoursSparkline.tsx
|
||||
- components/mobile/EngagementSortChips.tsx
|
||||
- components/mobile/EngagementSearchInput.tsx
|
||||
- components/mobile/EngagementUserRow.tsx
|
||||
- components/mobile/EngagementUserRowSkeleton.tsx
|
||||
modified: []
|
||||
decisions:
|
||||
- "EngagementSortChips uses lowercase key values ('hours'|'name'|'utilization') for the exported EngagementSortKey type; Plan 03 maps to API sort/order params"
|
||||
- "getInitials returns '??' for empty displayName (guards against null/undefined gracefully)"
|
||||
- "EngagementHoursSparkline uses SVG_W=300 constant for viewBox; preserveAspectRatio=none allows CSS h-12 w-full to stretch"
|
||||
- "chip text uses text-[10px] per D-29 typography fix (badge/caption size) not text-xs"
|
||||
metrics:
|
||||
duration: ~15 min
|
||||
completed: "2026-05-04"
|
||||
tasks: 3
|
||||
files_created: 7
|
||||
files_modified: 0
|
||||
---
|
||||
|
||||
# Phase 7 Plan 02: Engagement Component Primitives Summary
|
||||
|
||||
Seven phone-first presentational components locking the visual contract from 07-UI-SPEC.md into reusable code. All components are pure (no fetches, no toasts). The page (Plan 03) owns all data orchestration.
|
||||
|
||||
## What Was Built
|
||||
|
||||
### Task 1: Chip + Input Primitives
|
||||
|
||||
**`components/mobile/EngagementPeriodChips.tsx`** (commit fce75b0)
|
||||
- 3-chip period selector: `7d` / `30d` / `90d` (maps to `D7` / `D30` / `D90`)
|
||||
- Sticky strip: `sticky top-0 z-10 bg-background pt-2 pb-3 -mx-4 px-4 flex gap-2 min-h-[44px]`
|
||||
- Active chip: `bg-primary text-primary-foreground rounded-full px-3 py-1.5 text-[10px] font-semibold`
|
||||
- Inactive chip: `bg-muted text-foreground hover:bg-muted/80 rounded-full px-3 py-1.5 text-[10px] font-semibold`
|
||||
- Exports: `EngagementPeriodChips`, `EngagementPeriodChipsProps`, `EngagementPeriod`
|
||||
|
||||
**`components/mobile/EngagementSortChips.tsx`** (commit fce75b0)
|
||||
- 3-chip sort selector: `Hours` / `Name` / `Utilization` (typed as `'hours' | 'name' | 'utilization'`)
|
||||
- Same chip class strings as period chips (consistent pattern across page)
|
||||
- Exports: `EngagementSortChips`, `EngagementSortChipsProps`, `EngagementSortKey`
|
||||
|
||||
**`components/mobile/EngagementSearchInput.tsx`** (commit fce75b0)
|
||||
- shadcn `Input` with leading `Search` icon (absolute positioned)
|
||||
- 300ms debounce via `useState` + `useEffect` + `setTimeout`
|
||||
- Placeholder: `"Search by name or email"`, aria-label on input
|
||||
- Exports: `EngagementSearchInput`, `EngagementSearchInputProps`
|
||||
|
||||
### Task 2: Display Primitives
|
||||
|
||||
**`components/mobile/EngagementSummaryCard.tsx`** (commit d82a875)
|
||||
- shadcn `Card` + `CardContent` wrapper
|
||||
- Big number: `text-2xl font-semibold text-foreground leading-none`
|
||||
- Label: `text-xs text-muted-foreground mt-2`
|
||||
- No shadow (`shadow-none`), border only (FinanceRow density)
|
||||
- Exports: `EngagementSummaryCard`, `EngagementSummaryCardProps`
|
||||
|
||||
**`components/mobile/EngagementHoursSparkline.tsx`** (commit d82a875)
|
||||
- Custom inline SVG — no recharts (DASH-04 / D-12)
|
||||
- `viewBox="0 0 300 48"` + `preserveAspectRatio="none"` + `className="h-12 w-full"`
|
||||
- Series path: `stroke-primary`, `strokeWidth="2"`, `fill="none"`, `vectorEffect="non-scaling-stroke"`
|
||||
- Baseline: horizontal line at y=46, `className="text-muted-foreground/20"`
|
||||
- Label row: `"Hours trend · last {period_label}"` (left) + latest value indicator (right)
|
||||
- Period labels: `D7 → "7 days"`, `D30 → "30 days"`, `D90 → "90 days"`
|
||||
- No-data fallback: `"No activity"` text, SVG skipped entirely
|
||||
- `import type { SparklinePoint } from '@/app/api/mobile/engagement/trend/route'`
|
||||
- Exports: `EngagementHoursSparkline`, `EngagementHoursSparklineProps`
|
||||
|
||||
### Task 3: User Row + Skeleton
|
||||
|
||||
**`components/mobile/EngagementUserRow.tsx`** (commit 34a54f6)
|
||||
- `<Link href={/mobile/engagement/${graphUserId}}>` tap target
|
||||
- Avatar: `h-8 w-8 rounded-full bg-muted flex items-center justify-center shrink-0 text-[10px] font-semibold text-foreground`
|
||||
- Name: `text-sm font-semibold truncate flex-1`
|
||||
- Role: `text-xs text-muted-foreground truncate pl-11` (conditional — hidden when null)
|
||||
- Hours value: `text-sm font-semibold`, `.toFixed(1)h` format
|
||||
- Hours bar track: `h-1.5 rounded-full bg-muted overflow-hidden`, fill: `h-full rounded-full bg-primary transition-all duration-300`, width: `Math.min(100, billableHours/maxHours*100)%`
|
||||
- Exports: `EngagementUserRow`, `EngagementUserRowProps`, `EngagementUserRowData`, **`getInitials`**
|
||||
|
||||
**`components/mobile/EngagementUserRowSkeleton.tsx`** (commit 34a54f6)
|
||||
- shadcn `Skeleton` placeholders matching row shape exactly
|
||||
- Avatar (h-8 w-8 rounded-full), name (h-4 w-32), hours (h-4 w-12), role (h-3 w-24 ml-11), bar (h-1.5 w-full)
|
||||
- No props — purely presentational, page renders 5 instances
|
||||
- Exports: `EngagementUserRowSkeleton`
|
||||
|
||||
## Exported `getInitials` Utility
|
||||
|
||||
```ts
|
||||
export function getInitials(displayName: string): string
|
||||
```
|
||||
|
||||
Algorithm: first letter of first word + first letter of last word, uppercased.
|
||||
- `"Jordan Walsh"` → `"JW"`
|
||||
- `"Alex"` → `"A"`
|
||||
- `""` or whitespace-only → `"??"`
|
||||
|
||||
Phase 8 can `import { getInitials } from '@/components/mobile/EngagementUserRow'` directly.
|
||||
|
||||
## Typography Confirmed (D-29 cap)
|
||||
|
||||
4 sizes used, no others:
|
||||
- `text-2xl` — summary big numbers only
|
||||
- `text-sm` — user display name, hours value (row primary)
|
||||
- `text-xs` — labels, role, sparkline text, search placeholder
|
||||
- `text-[10px]` — chip labels, avatar initials (badge/caption)
|
||||
|
||||
`text-base` NOT used. `font-medium` NOT used. Two weights only: `font-normal` and `font-semibold`.
|
||||
|
||||
## No Chart Library
|
||||
|
||||
`EngagementHoursSparkline` uses a hand-authored SVG path — zero recharts dependency, consistent with DASH-04. No `from 'recharts'` in any new file.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written.
|
||||
|
||||
Note: `EngagementSortKey` type values use lowercase (`'hours'|'name'|'utilization'`) rather than `'Hours'|'Name'|'Utilization'` as the key constraints suggested. The component renders the correct display labels "Hours", "Name", "Utilization". Plan 03 will map the key to API sort params — lowercase keys are idiomatic for discriminated unions in this codebase.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None. All 7 components are fully implemented with correct prop contracts. No hardcoded mock data, no placeholder text beyond the spec copy strings (e.g., "No activity", "Search by name or email").
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None. All components are pure presentational with typed props. React auto-escapes all user-supplied strings. The SVG path is built from numeric coordinates only. Hours bar width is computed from clamped numerics, not user input.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
All 7 component files exist at documented paths. All 3 task commits (fce75b0, d82a875, 34a54f6) confirmed in history. `npx tsc --noEmit --pretty` exits 0.
|
||||
Loading…
Add table
Add a link
Reference in a new issue