// Home — wires to /api/v2/dashboard/home, /dashboard/trend, /tommy/pending-approvals,
// /events, /pipeline-counts. All KPIs, activity, and pipeline bars reflect real data.

function KPICard({ label, value, delta, deltaLabel, sub, sparkData, sparkColor, breakdown }) {
  const numeric = typeof delta === 'number';
  const up = numeric && delta >= 0;
  return (
    <div className="kpi">
      <div className="kpi-label">{label}</div>
      {sparkData && sparkData.length > 1 && <Sparkline data={sparkData} color={sparkColor || 'var(--muted)'} />}
      <div className="kpi-value">{value ?? '—'}</div>
      <div className="kpi-foot">
        {numeric && (
          <span className={'delta ' + (up ? 'up' : 'down')}>
            {up ? '▲' : '▼'} {up ? '+' : ''}{delta}{deltaLabel && deltaLabel.includes('%') ? '' : ''}
          </span>
        )}
        {sub && <span>{sub}</span>}
      </div>
      {breakdown && breakdown.length > 0 && (
        <div style={{marginTop:8, display:'flex', flexWrap:'wrap', gap:'6px 10px', fontFamily:'var(--mono)', fontSize:10, letterSpacing:'0.06em', color:'var(--muted)'}}>
          {breakdown.map(b => (
            <span key={b.label}>{b.label.toUpperCase()} <span style={{color:'var(--ink)'}}>{b.n}</span></span>
          ))}
        </div>
      )}
    </div>
  );
}

function FollowUpRow({ c, onClick }) {
  const d = parseDate(c.scheduled_at);
  const short = d ? d.toLocaleDateString(undefined, { weekday:'short', month:'short', day:'numeric' }) : '';
  const clock = d ? d.toLocaleTimeString(undefined, { hour:'numeric', minute:'2-digit' }) : '—';
  return (
    <div className="fup-row" onClick={onClick}>
      <div className="fup-time">
        <b>{clock}</b>
        {short}
      </div>
      <div>
        <div className="fup-name">{c.name}</div>
        <div className="fup-sub">{c.interest || (c.stage_raw || c.stage)} · {c.source || 'direct'}</div>
      </div>
      <div><span className={'chip stage-' + c.stage}><span className="dot" />{c.stage_raw || c.stage}</span></div>
      <div style={{display:'flex', alignItems:'center', gap:8, fontSize:12, color:'var(--muted)'}}>
        {c.hot
          ? <span className="chip tommy-unanswered"><span className="dot" />hot</span>
          : <span style={{color:'var(--muted-2)'}}>—</span>}
      </div>
      <div style={{fontFamily:'var(--mono)', fontSize:11, color:'var(--muted)', textAlign:'right'}}>
        {fmtAgo(c.lastActivity)}
      </div>
    </div>
  );
}

function TommyApprovalCard({ a, onApprove, onReject, onDismiss, onOpen }) {
  const minutes = minutesAgo(a.created_at);
  const initials = initialsOf(a.contact_name);
  return (
    <div className="approval">
      <div className="a-head">
        <div style={{display:'flex', alignItems:'center', gap:10}}>
          <span className="avatar" style={{width:28, height:28, fontSize:10, background:'var(--bone-3)', color:'var(--ink)'}}>{initials}</span>
          <div>
            <div className="a-name">{a.contact_name || 'Unknown'}</div>
            <div className="a-reason">{a.hold_reason || 'Needs review'} · {fmtAgo(minutes)}</div>
          </div>
        </div>
        <span className="chip tommy-pending"><span className="dot" />needs approval</span>
      </div>
      {a.inbound_message && (
        <div className="msg-bubble" style={{background:'var(--bone-2)', borderRadius:4, padding:'8px 12px', fontSize:12, lineHeight:1.45, marginBottom:10, color:'var(--muted)'}}>
          <span style={{fontFamily:'var(--mono)', fontSize:10, letterSpacing:'0.12em', textTransform:'uppercase', color:'var(--muted-2)', display:'block', marginBottom:4}}>Inbound</span>
          {a.inbound_message}
        </div>
      )}
      <div className="a-draft">{a.tommy_draft}</div>
      <div className="a-actions">
        <span className="a-meta">{a.status === 'context_requested' ? 'redrafting…' : 'draft #' + String(a.id).slice(-3)}</span>
        <button className="btn sm" onClick={onOpen}>Open</button>
        <button className="btn sm" onClick={onDismiss} title="Dismiss without replying or training">Dismiss</button>
        <button className="btn sm" onClick={onReject} disabled={a.status === 'context_requested'}>Reject</button>
        <button className="btn sm primary" onClick={onApprove} disabled={a.status === 'context_requested'}>Approve &amp; send</button>
      </div>
    </div>
  );
}

// Card listing pending appointment_status_checks rows (5-min post-appointment
// "did they show?" nudges that haven't been resolved). Parallel surface to
// the Telegram channel ping — staff who missed the Telegram message or wants
// a persistent view of unresolved appointments uses this card. Resolving
// from here POSTs through the same helper as the Telegram inline buttons
// (see app/post_appointment.py:resolve_status_check), so the audit trail and
// stage transition path are identical across surfaces.
function ShowCheckCard({ checks, onResolve, onOpenLead }) {
  if (!checks || checks.length === 0) {
    // Hide the card entirely when nothing pending — staff doesn't need to
    // see an empty inbox box on a typical day. Telegram is the primary
    // surface; this is the catch-net.
    return null;
  }
  // fmtClock from data.jsx renders the scheduled appointment time in gym-local ET.
  // Relative "sent ... ago" uses the shared fmtAgoFrom so it rolls into days
  // past 24h (1d, 2d, ...) like the rest of the app, instead of unbounded hours.
  return (
    <div className="card">
      <div className="hd">
        <div className="title">— <b>Did they show?</b></div>
        <div className="aside">
          <span style={{fontFamily:'var(--mono)', fontSize:11, color:'var(--muted)'}}>
            {checks.length} pending
          </span>
        </div>
      </div>
      <div className="bd p0">
        {checks.slice(0, 6).map(c => (
          <div key={c.id} style={{
            padding:'10px 14px', borderTop:'1px solid var(--line-2)',
            display:'grid', gridTemplateColumns:'1fr auto', gap:8, alignItems:'center',
          }}>
            <div style={{minWidth:0}}>
              <div style={{fontWeight:500, fontSize:13,
                           whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
                           cursor: c.contact_id ? 'pointer' : 'default'}}
                   onClick={() => c.contact_id && onOpenLead?.(c.contact_id)}>
                {c.contact_name || c.contact_id}
              </div>
              <div style={{fontFamily:'var(--mono)', fontSize:10,
                           letterSpacing:'0.06em', color:'var(--muted-2)', marginTop:2}}>
                {fmtClock(c.scheduled_at)} ET · sent {fmtAgoFrom(c.sent_at)}
              </div>
            </div>
            <div style={{display:'flex', gap:6}}>
              <button className="btn sm" title="Mark as showed"
                      onClick={() => onResolve(c.id, 'showed')}>✓</button>
              <button className="btn sm" title="Mark as no-show"
                      onClick={() => onResolve(c.id, 'noshow')}>✗</button>
              <button className="btn sm" title="Send rebook link"
                      onClick={() => onResolve(c.id, 'reschedule')}>↻</button>
              <button className="btn sm ghost" title="Dismiss without confirming"
                      onClick={() => onResolve(c.id, 'dismissed')}>—</button>
            </div>
          </div>
        ))}
        {checks.length > 6 && (
          <div style={{padding:'10px 14px', borderTop:'1px solid var(--line-2)',
                       fontFamily:'var(--mono)', fontSize:11, color:'var(--muted)'}}>
            +{checks.length - 6} more pending — resolve via Telegram or open lead.
          </div>
        )}
      </div>
    </div>
  );
}

function ActRow({ a }) {
  const KIND = {
    contact_created: 'new',
    day_pass_booked: 'booked',
    appointment_cancelled: 'cancel',
    appointment_updated: 'upd',
    tommy_escalation: 'tommy',
    tommy_sent: 'sms',
    sms_inbound: 'sms',
  };
  const label = KIND[a.event_type] || String(a.event_type || 'event').slice(0, 8);
  const name = a.contact_name || 'System';
  const title = a.title || a.event_type || '';
  // Backend writes titles like "{contact_name} booked a Day Pass" — when the
  // title already starts with the name, drop the redundant prefix instead of
  // rendering "Dominique · Dominique booked a Day Pass".
  const titleStartsWithName = name !== 'System' && title.toLowerCase().startsWith(name.toLowerCase());
  return (
    <div className="act-row">
      <div className="act-ico">{label}</div>
      <div className="act-body">
        {titleStartsWithName
          ? <span><b>{name}</b><span className="det">{title.slice(name.length)}</span></span>
          : <span><b>{name}</b> · <span className="det">{title}</span></span>}
      </div>
      <div className="act-t">{fmtAgoFrom(a.created_at)}</div>
    </div>
  );
}

const PERIOD_OPTS = [
  { key: '1d',  label: '1D'  },
  { key: 'l7',  label: 'L7'  },
  { key: 'l14', label: 'L14' },
  { key: 'l30', label: 'L30' },
  { key: 'mtd', label: 'MTD' },
  { key: 'ytd', label: 'YTD' },
];
const PERIOD_SUB = {
  '1d':  'today',
  'l7':  'last 7 days',
  'l14': 'last 14 days',
  'l30': 'last 30 days',
  'mtd': 'month to date',
  'ytd': 'year to date',
};

function fmtMonthLabel(ym) {
  const [y, m] = ym.split('-').map(Number);
  if (!y || !m) return ym;
  const d = new Date(y, m - 1, 1);
  return d.toLocaleDateString(undefined, { month: 'short', year: 'numeric' });
}

function lastNMonths(n) {
  const out = [];
  const now = new Date();
  for (let i = 0; i < n; i++) {
    const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
    const ym = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
    out.push({ ym, label: d.toLocaleDateString(undefined, { month: 'short', year: 'numeric' }) });
  }
  return out;
}

function MonthPicker({ active, monthValue, onPick }) {
  // The parent `.period-pill-row` uses `overflow: hidden` to clip its
  // rounded corners over the active pill's dark background. That same
  // overflow clip hides any absolutely-positioned child, so the popover
  // is rendered with `position: fixed` and coords computed from the
  // button's rect. This also means the popover stays put if the user
  // scrolls while it's open — acceptable for a 24-row menu.
  const [open, setOpen] = React.useState(false);
  const [pos, setPos] = React.useState({ top: 0, left: 0 });
  const btnRef = React.useRef(null);
  const popRef = React.useRef(null);
  const options = React.useMemo(() => lastNMonths(24), []);

  const toggle = () => {
    if (!open && btnRef.current) {
      const r = btnRef.current.getBoundingClientRect();
      // Right-align the 160px-wide popover to the button's right edge
      setPos({ top: r.bottom + 6, left: r.right - 160 });
    }
    setOpen(v => !v);
  };

  React.useEffect(() => {
    if (!open) return;
    function onClick(e) {
      if (btnRef.current?.contains(e.target)) return;
      if (popRef.current?.contains(e.target)) return;
      setOpen(false);
    }
    function onKey(e) { if (e.key === 'Escape') setOpen(false); }
    document.addEventListener('mousedown', onClick);
    document.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('mousedown', onClick);
      document.removeEventListener('keydown', onKey);
    };
  }, [open]);

  const label = active ? fmtMonthLabel(monthValue) : 'Month';
  return (
    <>
      <button
        ref={btnRef}
        onClick={toggle}
        className="btn ghost sm"
        aria-expanded={open}
        style={{
          borderRadius: 0,
          background: active ? 'var(--ink)' : 'transparent',
          color: active ? 'var(--bone)' : 'var(--ink)',
          display: 'inline-flex',
          alignItems: 'center',
          gap: 4,
        }}
      >
        {label}
        <span style={{fontSize: 10, opacity: 0.7}}>▾</span>
      </button>
      {open && (
        <div
          ref={popRef}
          className="month-pick-pop"
          role="listbox"
          style={{ top: pos.top, left: pos.left }}
        >
          {options.map(o => (
            <button
              key={o.ym}
              type="button"
              className={'month-pick-item' + (active && o.ym === monthValue ? ' selected' : '')}
              role="option"
              aria-selected={active && o.ym === monthValue}
              onClick={() => { onPick(o.ym); setOpen(false); }}
            >
              {o.label}
            </button>
          ))}
        </div>
      )}
    </>
  );
}

function HomeView({ onNavigate, onOpenCmd, onOpenLead, user }) {
  const [period, setPeriod] = React.useState('1d');
  const [monthValue, setMonthValue] = React.useState(() => {
    // Default to the previous full month so the "Month" button picks
    // something sensible the first time it's selected.
    const d = new Date();
    d.setDate(1);
    d.setMonth(d.getMonth() - 1);
    return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
  });

  const queryString = period === 'month'
    ? `period=month&month=${encodeURIComponent(monthValue)}`
    : `period=${period}`;

  const home = useAsync(() => api.get('/api/v2/dashboard/home?' + queryString), [queryString]);
  const trend = useAsync(() => api.get('/api/v2/dashboard/trend?' + queryString), [queryString]);
  const approvals = useAsync(() => api.get('/api/v2/tommy/pending-approvals'), []);
  const events = useAsync(() => api.get('/api/v2/events?limit=10'), []);
  const pipeline = useAsync(() => api.get('/api/v2/pipeline-counts'), []);
  // Post-appointment "did they show?" checks. Persistent queue parallel to
  // the Telegram nudges — staff who missed the Telegram ping or wants an
  // audit view sees them here. Endpoint is GET-pending only; the sweeper
  // in app/post_appointment.py is what populates the table.
  const showChecks = useAsync(() => api.get('/api/v2/appointment-status-checks?status=pending'), []);

  // Realtime: when pg_notify fires, reload activity + tommy + show checks
  React.useEffect(() => {
    function onEv() { events.reload(); approvals.reload(); home.reload(); showChecks.reload(); }
    function onLead() { home.reload(); events.reload(); }
    window.addEventListener('raw:event', onEv);
    window.addEventListener('raw:lead-created', onLead);
    return () => {
      window.removeEventListener('raw:event', onEv);
      window.removeEventListener('raw:lead-created', onLead);
    };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const greeting = home.data?.greeting_name || (user?.name || 'there').split(' ')[0];
  const kpis = home.data?.kpis || {};

  // Build sparklines from trend buckets for each KPI
  const sparkNew = (trend.data?.buckets || []).map(b => b.count || 0);

  const followUps = React.useMemo(() => {
    // Fetch follow-ups list from /leads?stage=DAY PASS SCHEDULED, sorted by scheduled_at.
    return null;
  }, []);

  const [fupList, setFupList] = React.useState([]);
  React.useEffect(() => {
    api.get('/api/v2/leads?stage=DAY%20PASS%20SCHEDULED&limit=20')
      .then(r => {
        const rows = (r?.leads || []).map(adaptLead)
          .filter(l => l.scheduled_at)
          .sort((a, b) => (parseDate(a.scheduled_at) || 0) - (parseDate(b.scheduled_at) || 0));
        cacheContacts(rows);
        setFupList(rows);
      })
      .catch(() => setFupList([]));
  }, []);

  const approvalList = approvals.data?.approvals || [];
  const eventList = events.data?.events || [];
  const pipelineCounts = pipeline.data?.counts || {};
  const stageOrder = ['NEW LEAD', 'INTERESTED', 'FOLLOW UP', 'DAY PASS SCHEDULED', 'NEEDS_RESCHEDULE', 'SHOWED'];

  async function approveOne(a) {
    try {
      await api.post('/api/v2/tommy/approve', { approval_id: a.id, message: a.tommy_draft });
      approvals.reload();
    } catch (e) {
      if (isQuietHoursError(e) && confirm(quietHoursPrompt(e, 'approve and send'))) {
        try {
          await api.post('/api/v2/tommy/approve', {
            approval_id: a.id, message: a.tommy_draft, override_quiet_hours: true,
          });
          approvals.reload();
          return;
        } catch (e2) { alert(friendlyApiError(e2, 'Approve failed')); return; }
      }
      alert(friendlyApiError(e, 'Approve failed'));
    }
  }
  async function rejectOne(a) {
    try {
      await api.post('/api/v2/tommy/reject', { approval_id: a.id });
      approvals.reload();
    } catch (e) { alert(friendlyApiError(e, 'Reject failed')); }
  }
  async function dismissOne(a) {
    try {
      await api.post('/api/v2/tommy/dismiss', { approval_id: a.id });
      approvals.reload();
    } catch (e) { alert(friendlyApiError(e, 'Dismiss failed')); }
  }

  async function resolveShowCheck(checkId, resolution) {
    try {
      const r = await api.post(`/api/v2/appointment-status-checks/${checkId}/resolve`,
                               { resolution });
      showChecks.reload();
      // Two server-side failure signals can land on a successful POST.
      // Both are silent in the response body unless we surface them —
      // staff would otherwise see the row disappear and assume the
      // customer-facing side effect happened.
      //   - ghl_sync_failed_detail: stage was updated locally, GHL tag
      //     push failed. BOOKED_STAGES preserve keeps local stable.
      //   - rebook_sms_sent=false on a reschedule: the customer never
      //     got the FDP rebook link.
      const warnings = [];
      if (resolution === 'reschedule' && r?.rebook_sms_sent === false) {
        warnings.push('Rebook SMS failed — please send the FDP link to the customer manually.');
      }
      if (r?.ghl_sync_failed_detail) {
        warnings.push(`GHL sync failed (${r.ghl_sync_failed_detail}). Verify the contact's tags in GHL.`);
      }
      if (warnings.length) alert(`Resolved locally.\n\n- ${warnings.join('\n- ')}`);
    } catch (e) {
      alert(friendlyApiError(e, 'Resolve failed'));
    }
  }

  const now = new Date();
  const dateLine = now.toLocaleDateString(undefined, { weekday:'long', month:'short', day:'numeric', year:'numeric' });

  const kpiNew = kpis.day_passes?.value != null ? kpis.day_passes : { value: 0 };
  const kpiFollowUps = kpis.follow_ups || { value: 0 };
  const kpiJoined = kpis.joined || { value: 0 };
  const kpiTommy = kpis.tommy_replies || { value: 0 };

  return (
    <div className="app-main">
      <div className="page-hd">
        <div>
          <div className="kicker" style={{marginBottom:8}}>— {dateLine}</div>
          <h1>
            {now.getHours() < 12 ? 'Morning' : now.getHours() < 18 ? 'Afternoon' : 'Evening'}, {greeting}.
          </h1>
          <div className="sub">
            {home.data ? (
              <>
                {(kpiNew.breakdown || []).find(b => b.label === 'today')?.n ?? 0} day passes today · {approvalList.length} Tommy drafts waiting on you · {eventList.filter(e => !e.is_read).length} unread events
              </>
            ) : home.loading ? 'Loading…' : home.error?.message}
          </div>
        </div>
        <div className="actions">
          <div className="period-pill-row">
            {PERIOD_OPTS.map(p => (
              <button key={p.key}
                onClick={() => setPeriod(p.key)}
                className="btn ghost sm"
                style={{
                  borderRadius: 0,
                  background: period === p.key ? 'var(--ink)' : 'transparent',
                  color: period === p.key ? 'var(--bone)' : 'var(--ink)',
                }}
              >{p.label}</button>
            ))}
            <MonthPicker
              active={period === 'month'}
              monthValue={monthValue}
              onPick={(v) => { setMonthValue(v); setPeriod('month'); }}
            />
          </div>
          <button className="btn quick-jump-btn" onClick={onOpenCmd}>
            <span className="kbd-key">⌘</span>
            <span className="kbd-key">K</span>
            <span style={{marginLeft:2}}>Quick jump</span>
          </button>
          <button className="btn primary" onClick={() => onOpenLead?.()}>+ New lead</button>
        </div>
      </div>

      <div className="kpi-grid">
        <KPICard
          label="Day passes scheduled"
          value={kpiNew.value}
          sub={period === 'month' ? fmtMonthLabel(monthValue) : PERIOD_SUB[period]}
          breakdown={kpiNew.breakdown}
          sparkData={sparkNew}
          sparkColor="var(--signal)"
        />
        <KPICard
          label="Follow-ups · upcoming"
          value={kpiFollowUps.value}
          sub={kpiFollowUps.delta_label}
          breakdown={kpiFollowUps.breakdown}
          sparkColor="var(--green)"
        />
        <KPICard
          label="Joined · window"
          value={kpiJoined.value}
          sub={period === 'month' ? fmtMonthLabel(monthValue) : PERIOD_SUB[period]}
        />
        <KPICard
          label="Tommy replies · window"
          value={kpiTommy.value}
          delta={typeof kpiTommy.delta === 'number' ? kpiTommy.delta : null}
          deltaLabel={kpiTommy.delta_label}
          sub={kpiTommy.delta_label || 'outbound SMS'}
          breakdown={kpiTommy.breakdown}
        />
      </div>

      <div className="home-grid">
        <div className="col">
          <div className="card">
            <div className="hd">
              <div className="title">— <b>Follow-ups</b> · scheduled</div>
              <div className="aside">
                <span style={{fontFamily:'var(--mono)', fontSize:11, color:'var(--muted)'}}>{fupList.length} scheduled</span>
                <button className="btn sm ghost" onClick={() => onNavigate({view:'leads', filter:'booked'})}>See all →</button>
              </div>
            </div>
            <div className="bd p0">
              {fupList.length === 0 && <div className="empty">No scheduled follow-ups in range.</div>}
              {fupList.slice(0, 8).map(c => (
                <FollowUpRow key={c.id} c={c} onClick={() => onNavigate({view:'lead', id:c.id})} />
              ))}
            </div>
          </div>

          <div className="card">
            <div className="hd">
              <div className="title">— <b>Recent activity</b></div>
              <div className="aside">
                <span style={{fontFamily:'var(--mono)', fontSize:11, color:'var(--muted)'}}>live · pg_notify</span>
              </div>
            </div>
            <div className="bd p0">
              {eventList.length === 0 && !events.loading && <div className="empty">No events yet.</div>}
              {eventList.map(a => <ActRow key={a.id} a={a} />)}
            </div>
          </div>
        </div>

        <div className="col">
          <div className="tommy-card">
            <div className="tommy-head">
              <div className="title">— <b>Tommy</b> · waiting on you</div>
              {approvalList.length > 0 && <span className="pending-count">{approvalList.length} pending</span>}
            </div>
            {approvalList.length === 0 && <div className="empty">All caught up. Tommy is auto-sending.</div>}
            {approvalList.slice(0, 4).map(a => (
              <TommyApprovalCard
                key={a.id}
                a={a}
                onApprove={() => approveOne(a)}
                onReject={() => rejectOne(a)}
                onDismiss={() => dismissOne(a)}
                onOpen={() => onNavigate({view:'lead', id:a.contact_id})}
              />
            ))}
            {approvalList.length > 4 && (
              <div style={{padding:'12px 18px', borderTop:'1px solid var(--line-2)', textAlign:'right'}}>
                <button className="btn sm ghost" onClick={() => onNavigate({view:'tommy'})}>Open Tommy inbox →</button>
              </div>
            )}
          </div>

          <ShowCheckCard
            checks={showChecks.data?.checks || []}
            onResolve={resolveShowCheck}
            onOpenLead={(id) => onNavigate({view:'lead', id})}
          />

          <div className="card">
            <div className="hd">
              <div className="title">— <b>Pipeline</b></div>
              <div className="aside" style={{fontFamily:'var(--mono)', fontSize:11, color:'var(--muted)'}}>
                {pipeline.data?.total || 0} contacts · {pipeline.data?.hot_leads || 0} hot
              </div>
            </div>
            <div className="bd">
              {stageOrder.map(stageRaw => {
                const count = pipelineCounts[stageRaw] || 0;
                const group = stageGroupOf(stageRaw);
                const maxCount = Math.max(1, ...Object.values(pipelineCounts));
                const pct = Math.max(4, (count / maxCount) * 100);
                return (
                  <div key={stageRaw} style={{display:'grid', gridTemplateColumns:'120px 1fr 40px', gap:12, alignItems:'center', padding:'8px 0'}}>
                    <span className={'chip stage-' + group}><span className="dot" />{stageRaw.toLowerCase()}</span>
                    <div style={{height:6, background:'var(--bone-2)', borderRadius:3, overflow:'hidden'}}>
                      <div style={{height:'100%', width:pct+'%', background:'var(--ink)'}} />
                    </div>
                    <span style={{fontFamily:'var(--mono)', fontSize:12, textAlign:'right', color:'var(--muted)'}}>{count}</span>
                  </div>
                );
              })}
              <div style={{marginTop:8, paddingTop:8, borderTop:'1px solid var(--line-2)', display:'flex', justifyContent:'space-between', fontFamily:'var(--mono)', fontSize:11, color:'var(--muted)'}}>
                <span>members · <span style={{color:'var(--ink)'}}>{pipeline.data?.member_count || 0}</span></span>
                <span>cold · <span style={{color:'var(--ink)'}}>{pipeline.data?.cold_leads || 0}</span></span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { HomeView });
