diff --git a/src/app/(dashboard)/leads/page.tsx b/src/app/(dashboard)/leads/page.tsx index 3884918..308d6a9 100644 --- a/src/app/(dashboard)/leads/page.tsx +++ b/src/app/(dashboard)/leads/page.tsx @@ -213,7 +213,8 @@ export default function LeadsPage() { {lead.priority} - + {new Date(lead.created_at).toLocaleDateString()} + {lead.status} diff --git a/src/components/account/AccountLeads.tsx b/src/components/account/AccountLeads.tsx index 27e3e78..ab2a99c 100644 --- a/src/components/account/AccountLeads.tsx +++ b/src/components/account/AccountLeads.tsx @@ -9,6 +9,12 @@ const URGENCY_COLORS: Record = { low: '#6B7280', }; +const PRIORITY_STYLES: Record = { + priority: 'bg-red-100 text-red-800', + standard: 'bg-blue-100 text-blue-800', + low: 'bg-gray-100 text-gray-600', +}; + const STATUS_STYLES: Record = { pending: 'bg-amber-100 text-amber-800', accepted: 'bg-green-100 text-green-800', @@ -22,9 +28,177 @@ function api(action: string, body: Record = {}) { return fetch('/api/phase2', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action, ...body }) }).then(r => r.json()); } +function LeadDetailModal({ lead, onClose, onAction }: { lead: Lead; onClose: () => void; onAction: (id: string, status: string, note?: string) => void }) { + const [actionNote, setActionNote] = useState(''); + let signals: { source?: string; signal_type?: string; content?: string; published_at?: string; url?: string }[] = []; + let talkingPoints: string[] = []; + let contacts: { name?: string; title?: string; email?: string }[] = []; + let renewalCtx: { product?: string; renewalDate?: string; daysUntil?: number } | null = null; + try { signals = JSON.parse(lead.signals_json || '[]'); } catch { /* empty */ } + try { talkingPoints = JSON.parse(lead.talking_points_json || '[]'); } catch { /* empty */ } + try { contacts = JSON.parse(lead.target_contacts_json || '[]'); } catch { /* empty */ } + try { renewalCtx = lead.renewal_context_json ? JSON.parse(lead.renewal_context_json) : null; } catch { /* empty */ } + + return ( +
+
e.stopPropagation()} + > +
+
+
+
+ {lead.capability_name} + {lead.priority} + {lead.status} + {new Date(lead.created_at).toLocaleDateString()} +
+
{lead.headline}
+
+
+
{Math.round(lead.composite_score * 100)}
+
score
+
+ +
+ +
+
+
Executive Summary
+
{lead.executive_summary}
+
+ + {lead.outreach_hook && ( +
+
Outreach Hook
+
“{lead.outreach_hook}”
+
+ )} + + {talkingPoints.length > 0 && ( +
+
Talking Points
+
    + {talkingPoints.map((tp, i) => ( +
  • + + {tp} +
  • + ))} +
+
+ )} + + {signals.length > 0 && ( +
+
Signals ({signals.length})
+
+ {signals.map((s, i) => ( +
+
+ {s.source || 'Unknown'} + {s.signal_type} + {s.published_at && {new Date(s.published_at).toLocaleDateString()}} +
+
{s.content}
+ {s.url && ( + Source → + )} +
+ ))} +
+
+ )} + + {contacts.length > 0 && ( +
+
Target Contacts
+
+ {contacts.map((c, i) => ( +
+ {c.name} + {c.title && — {c.title}} +
+ ))} +
+
+ )} + + {renewalCtx && ( +
+ 💰 + + Renewal Window: {renewalCtx.product} — {renewalCtx.daysUntil} days ({renewalCtx.renewalDate}) + +
+ )} + +
+ Confidence: {Math.round(lead.confidence_score * 100)}% + Urgency: {lead.urgency_level} + {lead.feedback_at && Last action: {new Date(lead.feedback_at).toLocaleDateString()}} +
+ + {lead.status === 'pending' && ( +
+ setActionNote(e.target.value)} + className="flex-1 text-xs border border-card-border rounded px-2 py-1.5" + /> + + + +
+ )} + + {lead.status === 'accepted' && ( +
+ Outcome: + + +
+ )} +
+
+
+ ); +} + export function AccountLeads({ accountName }: { accountName: string }) { const [leads, setLeads] = useState([]); const [loading, setLoading] = useState(true); + const [selectedLead, setSelectedLead] = useState(null); const fetchLeads = useCallback(async () => { const res = await api('leads.list', { Account_Name: accountName, limit: 20 }); @@ -35,8 +209,9 @@ export function AccountLeads({ accountName }: { accountName: string }) { fetchLeads().finally(() => setLoading(false)); }, [fetchLeads]); - async function handleAction(id: string, status: string) { - await api('leads.action', { id, status }); + async function handleAction(id: string, status: string, note?: string) { + await api('leads.action', { id, status, note: note || undefined }); + setSelectedLead(null); await fetchLeads(); } @@ -44,56 +219,71 @@ export function AccountLeads({ accountName }: { accountName: string }) { if (leads.length === 0) return
No leads found for this account.
; return ( -
- {leads.map(lead => { - let talkingPoints: string[] = []; - try { talkingPoints = JSON.parse(lead.talking_points_json || '[]'); } catch { /* empty */ } + <> +
+ {leads.map(lead => { + let talkingPoints: string[] = []; + try { talkingPoints = JSON.parse(lead.talking_points_json || '[]'); } catch { /* empty */ } - return ( -
-
-
-
-
- {lead.capability_name} - {lead.status} - {Math.round(lead.composite_score * 100)} -
-
{lead.headline}
-
{lead.executive_summary}
- {talkingPoints.length > 0 && ( -
- {talkingPoints.length} talking point{talkingPoints.length !== 1 ? 's' : ''} + return ( +
setSelectedLead(lead)} + > +
+
+
+
+ {lead.capability_name} + {lead.status} + {new Date(lead.created_at).toLocaleDateString()} + {Math.round(lead.composite_score * 100)}
- )} +
{lead.headline}
+
{lead.executive_summary}
+ {talkingPoints.length > 0 && ( +
+ {talkingPoints.length} talking point{talkingPoints.length !== 1 ? 's' : ''} +
+ )} +
-
- {lead.status === 'pending' && ( -
- - - -
- )} -
- ); - })} -
+ {lead.status === 'pending' && ( +
+ + + +
+ )} +
+ ); + })} +
+ + {selectedLead && ( + setSelectedLead(null)} + onAction={handleAction} + /> + )} + ); }