'use client'; import { useState } from 'react'; import { Activity, ExternalLink } from 'lucide-react'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Button } from '@/components/ui/button'; import { computeDealHealth, type DealHealthInput } from '@/lib/services/deal-health'; import { cn } from '@/lib/utils'; const PULSE_TINT: Record<'cold' | 'warm' | 'hot', string> = { hot: 'border-emerald-200 bg-emerald-50 text-emerald-800 hover:bg-emerald-100', warm: 'border-amber-200 bg-amber-50 text-amber-800 hover:bg-amber-100', cold: 'border-rose-200 bg-rose-50 text-rose-800 hover:bg-rose-100', }; const PULSE_LABEL: Record<'cold' | 'warm' | 'hot', string> = { hot: 'Hot', warm: 'Warm', cold: 'Cold', }; /** * Header chip surfacing the rule-based deal-health score. * * Click opens a popover with the full per-signal breakdown + plain-language * explanation of how the score is computed, plus a link to the docs page * for users who want the deep-dive. Replaces the prior hover-tooltip so * the content is keyboard-accessible, doesn't time out, and reads on * touch devices. */ export function DealPulseChip({ interest }: { interest: DealHealthInput }) { const [open, setOpen] = useState(false); // Closed / archived deals don't get a pulse — UX would be confusing. if (interest.archivedAt || interest.outcome) return null; const health = computeDealHealth(interest); const tint = PULSE_TINT[health.pulse]; const label = PULSE_LABEL[health.pulse]; return (

Deal pulse - {label} ({health.score} / 100)

How likely this deal is to keep moving forward, scored from 0 to 100.

What pushed the score

{health.signals.length === 0 ? (

Nothing notable yet - the score is sitting at the baseline (50). Log a contact, progress the stage, or send a signing request and you'll see the dial move.

) : (
    {health.signals.map((s) => (
  • 0 ? 'bg-emerald-100 text-emerald-800' : 'bg-rose-100 text-rose-800', )} > {s.delta > 0 ? `+${s.delta}` : s.delta} {s.detail}
  • ))}
)}

How this is calculated

Every signal above traces to a specific date or pipeline stage on this deal. Recent contact + recent stage movement push the score up; long silences and outdated documents pull it down.

); }