'use client'; /* EngagementUserRow — phase 07 (ENG-04). * Purpose: Per-employee row card — avatar (initials) + name + role + hours + hours bar. * Entire row is a Link to /mobile/engagement/[graphUserId] (D-19; Phase 8 owns destination). * Hours bar width = (billableHours / maxHours) * 100% — bounded at 100%. * Props: user (EngagementUserRowData shape), maxHours (largest billableHours in current page set; computed by parent). */ import Link from 'next/link'; export interface EngagementUserRowData { graphUserId: string; displayName: string; userEmail: string; jobTitle: string | null; billableHours: number; hoursWorked: number; } export interface EngagementUserRowProps { user: EngagementUserRowData; maxHours: number; // largest billableHours in the loaded set (parent computes) } /** * getInitials — first letter of first word + first letter of last word of displayName, * uppercased. E.g. "Jordan Walsh" → "JW", "Alex" → "A". Exported for Phase 8 reuse * (the user profile header may share the avatar identity block per UI-SPEC). */ export function getInitials(displayName: string): string { const parts = displayName.trim().split(/\s+/).filter(Boolean); if (parts.length === 0) return '??'; if (parts.length === 1) return (parts[0]![0] ?? '?').toUpperCase(); const first = parts[0]![0] ?? ''; const last = parts[parts.length - 1]![0] ?? ''; return (first + last).toUpperCase(); } export function EngagementUserRow({ user, maxHours }: EngagementUserRowProps) { const initials = getInitials(user.displayName); const hoursLabel = `${user.billableHours.toFixed(1)}h`; const barWidthPct = maxHours > 0 ? Math.min(100, (user.billableHours / maxHours) * 100) : 0; return ( {/* Top line: avatar + identity + hours value */}
{user.displayName} {hoursLabel}
{/* Role line — render only if jobTitle present (UI-SPEC: "render nothing (no empty line) if absent") */} {user.jobTitle && (

{user.jobTitle}

)} {/* Hours bar */}
); }