"use client"; import { useState } from "react"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Sparkline } from "@/components/charts"; import { formatCurrency } from "@/lib/crm"; import type { RepStats, Salesperson } from "@/lib/crm"; import { cn } from "@/lib/utils"; const ROLE_STYLES: Record = { AE: "bg-blue-100 text-blue-700", SDR: "bg-violet-100 text-violet-700", Manager: "bg-amber-100 text-amber-700", }; /** Two-letter initials from a person's name (e.g. "Nathan Brooks" -> "NB"). */ function initials(name: string): string { const parts = name.trim().split(/\s+/).filter(Boolean); if (parts.length === 0) return "?"; if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase(); return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); } /** Circular avatar from a plain ; falls back to initials on load error. */ function RepAvatar({ name, src }: { name: string; src?: string }) { const [errored, setErrored] = useState(false); const showImg = src && !errored; return ( {showImg ? ( // eslint-disable-next-line @next/next/no-img-element {name} setErrored(true)} /> ) : ( {initials(name)} )} ); } /** * Per-rep performance card: avatar, name, role+region chip, an attainment gauge * (% + progress bar), bookings / open-pipeline / deal-count stats, and a * Sparkline of the rep's 8-month Closed-Won trend. Hover lift. */ export function RepCard({ stats }: { stats: RepStats }) { const { rep, bookings, openPipeline, attainment, dealCount, winRate, trend } = stats; const pct = Math.round(attainment * 100); const hasQuota = rep.quota > 0; return (
{rep.name}
{rep.role} {rep.region}
Quota attainment {hasQuota ? `${pct}%` : "—"}
Bookings
{formatCurrency(bookings)}
Open pipe
{formatCurrency(openPipeline)}
Deals
{dealCount} {winRate !== null ? ( · {Math.round(winRate * 100)}% win ) : null}
Bookings · last 8 mo
); }