/* global React, AppShell, Tag, Eyebrow, useFoyerData, allLoadedVisitors, leadBucket, fmtRelative, fmtClock, greetingHour */ const Dashboard = () => { const { user, summaries, sessionsById, loading, error } = useFoyerData(); const recordedSummaries = summaries.filter(s => (s.kind || 'recorded') !== 'manual'); const featured = recordedSummaries.find(s => s.status === 'ready') || summaries[0]; const featuredSession = featured ? sessionsById[featured.id] : null; const featuredVisitors = (featuredSession?.result?.visitors || []).slice().sort((a, b) => (b.analysis?.score || 0) - (a.analysis?.score || 0) ); const visitors = allLoadedVisitors(sessionsById); const needs = visitors.filter(v => leadBucket(v.lead_state) === 'needs'); const topScore = visitors.reduce((m, v) => Math.max(m, v.analysis?.score || 0), 0); const topScoreVisitor = visitors.find(v => (v.analysis?.score || 0) === topScore); const firstName = (user?.name || '').split(' ')[0] || 'there'; return (
{loading && (
LOADING SESSIONS…
)} {error && (
Couldn't load: {error}
)} {!loading && ( <> {/* greeting */}
{new Date().toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' }).toUpperCase()}

Good {greetingHour()}, {firstName}.

{needs.length === 0 ? <>Inbox zero — every captured lead has been handled. Nice. : <>You have {needs.length} {needs.length === 1 ? 'lead' : 'leads'} waiting on a follow-up. }

Open kiosk All sessions
{/* numbers row */}
{[ { v: featuredVisitors.length || 0, label: 'Last session', sub: featured?.address || '—' }, { v: topScore || 0, s: topScore ? '/100' : '', label: 'Top lead score', sub: topScoreVisitor?.visitor?.name || '—' }, { v: needs.length, label: 'Needs action', sub: 'in the inbox' }, { v: visitors.length, label: 'Leads captured', sub: `${recordedSummaries.length} open houses` }, ].map(stat => (
{stat.label}
{stat.v} {stat.s && {stat.s}}
{(stat.sub || '').toUpperCase()}
))}
{/* featured session */}
{featured ? 'Most recent session' : 'No sessions yet'} {featured && ( Browse all → )}
{featured ? ( <>
{featured.address || 'Untitled session'}
{fmtClock(featured.created_at)} · {featured.visitor_count || 0} {(featured.visitor_count || 0) === 1 ? 'GUEST' : 'GUESTS'} {featured.completed_at ? ` · COMPLETED ${fmtRelative(featured.completed_at)}` : ''}
{featuredVisitors.length === 0 ? (
No guests detected — recording may have been too short.
) : featuredVisitors.map((v, i) => { const tagToken = (v.analysis?.tag || '').toLowerCase(); const sig = (v.analysis?.signals || [])[0]; return (
goToSession(featured.id, v.visitor.name)} style={{ padding: '22px 4px', borderTop: '1px solid var(--hairline)', borderBottom: i === featuredVisitors.length - 1 ? '1px solid var(--hairline)' : 'none', display: 'grid', gridTemplateColumns: '40px 1fr auto', gap: 20, alignItems: 'start', }}>
{String(i + 1).padStart(2, '0')}
{v.visitor.name} {v.analysis.tag} · {v.analysis.score}
{(v.analysis?.summary || '').split('. ')[0]}.
{sig && (
{sig.toUpperCase()}
)}
{leadStateLabel(v.lead_state)}
Review →
); })}
) : (
Record an open house from the iOS app and it'll show up here.
)}
The follow-up queue
{needs.length === 0 ? (
Nothing waiting on you. New recordings will land here as drafts.
) : needs.slice(0, 8).map((v) => { const tagToken = (v.analysis?.tag || '').toLowerCase(); return (
goToSession(v._session.id, v.visitor.name)} style={{ padding: '16px 6px', borderBottom: '1px solid var(--hairline)' }}>
{v.visitor.name}
{(v._session.address || 'NO ADDRESS').toUpperCase()} {leadStateLabel(v.lead_state)}
); })}
)}
); }; function leadStateLabel(s) { if (!s) return 'NEEDS DRAFT'; if (s.snoozed_until) { const t = Date.parse(s.snoozed_until); if (!Number.isNaN(t) && t > Date.now()) return `SNOOZED · ${new Date(t).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }).toUpperCase()}`; } switch (s.status) { case 'drafted': return 'DRAFT'; case 'sent': return s.sent_at ? `SENT · ${fmtRelative(s.sent_at)}` : 'SENT'; case 'replied': return 'REPLIED'; case 'archived': return 'ARCHIVED'; default: return (s.status || '').toUpperCase(); } } function goToSession(sessionId, visitorName) { window.foyerActiveSessionId = sessionId; if (visitorName) window.foyerActiveVisitorName = visitorName; window.foyerGo('#/session'); } Object.assign(window, { Dashboard, foyerGoToSession: goToSession });