diff --git a/.planning/phases/07-engagement-overview-new/07-02-SUMMARY.md b/.planning/phases/07-engagement-overview-new/07-02-SUMMARY.md new file mode 100644 index 0000000..241357e --- /dev/null +++ b/.planning/phases/07-engagement-overview-new/07-02-SUMMARY.md @@ -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) +- `` 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.