← back to Norma

components/dashboard/DashboardTab.tsx

1090 lines

'use client';

/**
 * DashboardTab — Priority landing page.
 *
 * Usage:
 *   <DashboardTab onNavigate={(tab) => setActiveTab(tab)} />
 *
 * Sections:
 *   1. Priority Banner  — most recent urgent alert
 *   2. Stats Row        — 4 KPI cards (drafts, petitions, grants, media)
 *   3. Two-column body  — Action Items (left 60%) + Recent Activity (right 40%)
 *   4. Quick Actions    — shortcut buttons to primary tabs
 */

import { useState, useEffect, useCallback } from 'react';
import {
  AlertTriangle,
  FileText,
  Megaphone,
  Award,
  Newspaper,
  ArrowRight,
  CheckCircle2,
  Clock,
  Calendar,
  Zap,
  Plus,
  Search,
  GitBranch,
  Activity,
  RefreshCw,
  Bell,
  TrendingUp,
  DollarSign,
  Users,
  Vote,
  Building2,
  Mail,
} from 'lucide-react';
import IntelDashboardWidget from '@/components/intelligence/IntelDashboardWidget';
import MemberEmailModal from '@/components/email-sends/MemberEmailModal';

/* ─── Types ─────────────────────────────────────────────────────────────── */

interface AlertItem {
  id: string;
  type: 'news' | 'grant' | 'petition' | 'congress' | 'donation' | 'community' | 'organization';
  title: string;
  summary: string;
  date: string;
  source: string;
}

interface Session {
  id: string;
  title?: string;
  session_date: string;
  status: string;
  created_at: string;
  drafts?: Array<{ id: string; lane: string; status: string }>;
}

interface Petition {
  id: string;
  title: string;
  status: string;
  signature_count?: number;
  signature_goal?: number;
  ai_urgency?: number;
  deadline?: string;
}

interface Grant {
  id: string;
  title: string;
  funder?: string;
  deadline?: string;
  amount_max?: number;
  status: string;
  priority?: string;
}

interface ActionItem {
  id: string;
  priority: 'red' | 'amber' | 'green';
  type: 'draft' | 'petition' | 'grant' | 'media';
  title: string;
  subtitle?: string;
  actionLabel: string;
  targetTab: string;
  icon: typeof FileText;
}

interface DashboardData {
  urgentAlert: AlertItem | null;
  pendingDraftsCount: number;
  activePetitionsCount: number;
  grantDeadlinesCount: number;
  mediaAlertsCount: number;
  recentSessions: Session[];
  activePetitions: Petition[];
  upcomingGrants: Grant[];
  recentActivity: AlertItem[];
}

export interface DashboardTabProps {
  onNavigate?: (tab: string) => void;
}

/* ─── Constants ─────────────────────────────────────────────────────────── */

// NOTE: Hex colors required for template-literal rgba tints (e.g. `${color}18`).
// CSS variable references cannot be used inside rgba() template strings.
const ALERT_TYPE_META: Record<
  AlertItem['type'],
  { icon: typeof Newspaper; color: string; label: string }
> = {
  news:         { icon: Newspaper,  color: '#06b6d4', label: 'News' },
  grant:        { icon: Award,      color: '#f59e0b', label: 'Grant' },
  petition:     { icon: Megaphone,  color: '#8b5cf6', label: 'Petition' },
  congress:     { icon: Vote,       color: '#6366f1', label: 'Congress' },
  donation:     { icon: DollarSign, color: '#22c55e', label: 'Donation' },
  community:    { icon: Users,      color: '#f97316', label: 'Community' },
  organization: { icon: Building2,  color: '#34d399', label: 'Org' },
};

const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;

/* ─── Helpers ────────────────────────────────────────────────────────────── */

function relativeTime(iso: string): string {
  if (!iso) return '';
  const diffMs = Date.now() - new Date(iso).getTime();
  if (isNaN(diffMs) || diffMs < 0) return 'just now';
  const minutes = Math.floor(diffMs / 60_000);
  if (minutes < 1) return 'just now';
  if (minutes < 60) return `${minutes}m ago`;
  const hours = Math.floor(minutes / 60);
  if (hours < 24) return `${hours}h ago`;
  const days = Math.floor(hours / 24);
  if (days === 1) return 'Yesterday';
  if (days < 7) return `${days}d ago`;
  const weeks = Math.floor(days / 7);
  if (weeks < 5) return `${weeks}w ago`;
  return `${Math.floor(days / 30)}mo ago`;
}

function daysUntil(iso: string): number {
  if (!iso) return Infinity;
  return Math.ceil((new Date(iso).getTime() - Date.now()) / 86_400_000);
}

function groupByRecency(items: AlertItem[]): Record<string, AlertItem[]> {
  const now = Date.now();
  const groups: Record<string, AlertItem[]> = {
    Today: [],
    Yesterday: [],
    'This Week': [],
  };
  for (const item of items) {
    const diffH = (now - new Date(item.date).getTime()) / 3_600_000;
    if (diffH < 24) groups['Today'].push(item);
    else if (diffH < 48) groups['Yesterday'].push(item);
    else groups['This Week'].push(item);
  }
  return groups;
}

function buildActionItems(
  sessions: Session[],
  petitions: Petition[],
  grants: Grant[]
): ActionItem[] {
  const items: ActionItem[] = [];

  // Pending email drafts — highest urgency first (most recent unsent)
  const pendingSessions = sessions
    .filter((s) => s.status !== 'sent')
    .slice(0, 3);
  for (const s of pendingSessions) {
    items.push({
      id: `draft-${s.id}`,
      priority: s.status === 'review' ? 'red' : 'amber',
      type: 'draft',
      title: s.title ?? `Batch ${new Date(s.session_date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}`,
      subtitle: `Status: ${s.status}`,
      actionLabel: 'Review',
      targetTab: 'drafts',
      icon: FileText,
    });
  }

  // Petitions approaching signature milestones
  const urgentPetitions = petitions
    .filter((p) => {
      if (!p.signature_count || !p.signature_goal) return false;
      const ratio = p.signature_count / p.signature_goal;
      return ratio >= 0.8 && ratio < 1;
    })
    .slice(0, 2);
  for (const p of urgentPetitions) {
    const pct = p.signature_count && p.signature_goal
      ? Math.round((p.signature_count / p.signature_goal) * 100)
      : 0;
    items.push({
      id: `petition-${p.id}`,
      priority: pct >= 90 ? 'red' : 'amber',
      type: 'petition',
      title: p.title,
      subtitle: `${pct}% of goal reached`,
      actionLabel: 'View',
      targetTab: 'petitions',
      icon: Megaphone,
    });
  }

  // Grants with upcoming deadlines (within 30 days)
  const urgentGrants = grants
    .filter((g) => {
      if (!g.deadline) return false;
      const d = daysUntil(g.deadline);
      return d >= 0 && d <= 30;
    })
    .sort((a, b) => daysUntil(a.deadline!) - daysUntil(b.deadline!))
    .slice(0, 3);
  for (const g of urgentGrants) {
    const d = daysUntil(g.deadline!);
    items.push({
      id: `grant-${g.id}`,
      priority: d <= 7 ? 'red' : d <= 14 ? 'amber' : 'green',
      type: 'grant',
      title: g.title,
      subtitle: `Due in ${d}d${g.funder ? ` · ${g.funder}` : ''}`,
      actionLabel: 'Apply',
      targetTab: 'agent-grants',
      icon: Award,
    });
  }

  // Sort: red → amber → green, max 8
  const ORDER = { red: 0, amber: 1, green: 2 } as const;
  return items
    .sort((a, b) => ORDER[a.priority] - ORDER[b.priority])
    .slice(0, 8);
}

/* ─── Sub-components ────────────────────────────────────────────────────── */

function PriorityDot({ level }: { level: 'red' | 'amber' | 'green' }) {
  const colorMap = { red: '#f43f5e', amber: '#f59e0b', green: '#10b981' };
  return (
    <span
      aria-hidden="true"
      style={{
        display: 'inline-block',
        width: 8,
        height: 8,
        borderRadius: '50%',
        backgroundColor: colorMap[level],
        flexShrink: 0,
      }}
    />
  );
}

function StatCard({
  icon: Icon,
  count,
  label,
  accentColor,
  loading,
  onClick,
}: {
  icon: typeof FileText;
  count: number;
  label: string;
  accentColor: string;
  loading: boolean;
  onClick?: () => void;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      className="card card-enter hover-lift"
      style={{
        flex: '1 1 0',
        minWidth: 120,
        display: 'flex',
        flexDirection: 'column',
        gap: 8,
        padding: '1rem',
        cursor: onClick ? 'pointer' : 'default',
        textAlign: 'left',
        border: '1px solid var(--color-border)',
        background: `linear-gradient(135deg, var(--color-surface) 0%, ${accentColor}08 100%)`,
      }}
    >
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'space-between',
        }}
      >
        <div
          style={{
            width: 32,
            height: 32,
            borderRadius: 8,
            backgroundColor: `${accentColor}18`,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          <Icon size={16} style={{ color: accentColor }} aria-hidden="true" />
        </div>
        {loading ? (
          <div className="skeleton" style={{ width: 36, height: 28, borderRadius: 6 }} />
        ) : (
          <span
            className="num"
            style={{
              fontSize: '1.75rem',
              fontWeight: 700,
              lineHeight: 1,
              color: 'var(--color-text)',
            }}
          >
            {count}
          </span>
        )}
      </div>
      <span className="stat-label">{label}</span>
    </button>
  );
}

function SkeletonRows({ count }: { count: number }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {Array.from({ length: count }).map((_, i) => (
        <div key={i} className="skeleton" style={{ height: 44, borderRadius: 8 }} />
      ))}
    </div>
  );
}

/* ─── Main Component ─────────────────────────────────────────────────────── */

export default function DashboardTab({ onNavigate }: DashboardTabProps) {
  const [data, setData] = useState<DashboardData>({
    urgentAlert: null,
    pendingDraftsCount: 0,
    activePetitionsCount: 0,
    grantDeadlinesCount: 0,
    mediaAlertsCount: 0,
    recentSessions: [],
    activePetitions: [],
    upcomingGrants: [],
    recentActivity: [],
  });
  const [loading, setLoading] = useState(true);
  const [sectionsLoading, setSectionsLoading] = useState({
    banner: true,
    stats: true,
    actions: true,
    activity: true,
  });
  const [memberEmailOpen, setMemberEmailOpen] = useState(false);

  const fetchAll = useCallback(async () => {
    setLoading(true);
    setSectionsLoading({ banner: true, stats: true, actions: true, activity: true });

    // Fire all requests in parallel — failures in individual endpoints are isolated
    const [
      urgentAlertRes,
      sessionsRes,
      petitionsRes,
      grantsRes,
      activityRes,
      mediaCountRes,
    ] = await Promise.allSettled([
      fetch('/api/alerts?categories=news_media,grants,petitions&limit=1'),
      fetch('/api/sessions'),
      fetch('/api/petitions?status=active'),
      fetch('/api/grants?upcoming=true'),
      fetch('/api/alerts?limit=10'),
      fetch('/api/alerts?categories=news_media&limit=1'),
    ]);

    // ── Banner: most urgent alert
    let urgentAlert: AlertItem | null = null;
    if (urgentAlertRes.status === 'fulfilled' && urgentAlertRes.value.ok) {
      try {
        const d = await urgentAlertRes.value.json();
        urgentAlert = d.alerts?.[0] ?? null;
      } catch { /* ignore */ }
    }
    setSectionsLoading((p) => ({ ...p, banner: false }));

    // ── Sessions: pending drafts count + action items
    let sessions: Session[] = [];
    let pendingDraftsCount = 0;
    if (sessionsRes.status === 'fulfilled' && sessionsRes.value.ok) {
      try {
        const d = await sessionsRes.value.json();
        sessions = d.sessions ?? d ?? [];
        pendingDraftsCount = sessions.filter((s) => s.status !== 'sent').length;
      } catch { /* ignore */ }
    }

    // ── Petitions: active count + action items
    let petitions: Petition[] = [];
    let activePetitionsCount = 0;
    if (petitionsRes.status === 'fulfilled' && petitionsRes.value.ok) {
      try {
        const d = await petitionsRes.value.json();
        petitions = d.petitions ?? d ?? [];
        activePetitionsCount = petitions.length;
      } catch { /* ignore */ }
    }

    // ── Grants: deadline count + action items
    let grants: Grant[] = [];
    let grantDeadlinesCount = 0;
    if (grantsRes.status === 'fulfilled' && grantsRes.value.ok) {
      try {
        const d = await grantsRes.value.json();
        grants = d.grants ?? d ?? [];
        grantDeadlinesCount = grants.filter((g) => {
          if (!g.deadline) return false;
          const ms = new Date(g.deadline).getTime() - Date.now();
          return ms >= 0 && ms <= THIRTY_DAYS_MS;
        }).length;
      } catch { /* ignore */ }
    }

    setSectionsLoading((p) => ({ ...p, stats: false, actions: false }));

    // ── Recent activity
    let recentActivity: AlertItem[] = [];
    if (activityRes.status === 'fulfilled' && activityRes.value.ok) {
      try {
        const d = await activityRes.value.json();
        recentActivity = d.alerts ?? [];
      } catch { /* ignore */ }
    }
    setSectionsLoading((p) => ({ ...p, activity: false }));

    // ── Media alerts count (header of most recent only, count inferred from limit)
    let mediaAlertsCount = 0;
    if (mediaCountRes.status === 'fulfilled' && mediaCountRes.value.ok) {
      try {
        const d = await mediaCountRes.value.json();
        mediaAlertsCount = (d.alerts ?? []).length;
      } catch { /* ignore */ }
    }

    setData({
      urgentAlert,
      pendingDraftsCount,
      activePetitionsCount,
      grantDeadlinesCount,
      mediaAlertsCount,
      recentSessions: sessions.slice(0, 10),
      activePetitions: petitions,
      upcomingGrants: grants,
      recentActivity,
    });

    setLoading(false);
  }, []);

  useEffect(() => {
    fetchAll();
  }, [fetchAll]);

  const actionItems = buildActionItems(
    data.recentSessions,
    data.activePetitions,
    data.upcomingGrants
  );

  const activityGroups = groupByRecency(data.recentActivity);

  /* Determine banner severity */
  const bannerColor =
    data.urgentAlert?.type === 'grant'
      ? '#f43f5e'   // red — grant deadlines are critical
      : '#f59e0b';  // orange — general urgent

  /* ── Render ─────────────────────────────────────────────────────────────── */
  return (
    <div
      style={{
        padding: '1.25rem 1.5rem',
        maxWidth: 1200,
        margin: '0 auto',
        display: 'flex',
        flexDirection: 'column',
        gap: '1.25rem',
      }}
    >
      {/* ── Toolbar ──────────────────────────────────────────────────────── */}
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'space-between',
          gap: 12,
        }}
      >
        <div>
          <h1
            style={{
              fontFamily: 'var(--font-heading)',
              fontSize: '1.25rem',
              fontWeight: 700,
              color: 'var(--color-text)',
              margin: 0,
            }}
          >
            Priority Dashboard
          </h1>
          <p style={{ fontSize: '0.8125rem', color: 'var(--color-text-muted)', margin: 0, marginTop: 2 }}>
            At-a-glance view of what needs attention now
          </p>
        </div>
        <button
          type="button"
          onClick={fetchAll}
          disabled={loading}
          className="btn btn-ghost btn-sm"
          aria-label="Refresh dashboard"
          title="Refresh all"
        >
          <RefreshCw
            size={14}
            aria-hidden="true"
            style={{ animation: loading ? 'spin 0.7s linear infinite' : 'none' }}
          />
          Refresh
        </button>
      </div>

      {/* ── 1. Priority Banner ────────────────────────────────────────────── */}
      {sectionsLoading.banner ? (
        <div className="skeleton" style={{ height: 60, borderRadius: 10 }} />
      ) : data.urgentAlert ? (
        <div
          className="card breathe-glow-urgent"
          role="alert"
          aria-live="polite"
          style={{
            borderLeft: `4px solid ${bannerColor}`,
            borderRadius: 'var(--radius-md)',
            padding: '0.75rem 1rem',
            display: 'flex',
            alignItems: 'center',
            gap: 12,
            flexWrap: 'wrap',
          }}
        >
          {/* Icon */}
          <div
            style={{
              width: 32,
              height: 32,
              borderRadius: 8,
              backgroundColor: `${bannerColor}18`,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              flexShrink: 0,
            }}
          >
            <AlertTriangle size={16} style={{ color: bannerColor }} aria-hidden="true" />
          </div>

          {/* Text */}
          <div style={{ flex: 1, minWidth: 0 }}>
            <span
              className="badge badge-warning"
              style={{ fontSize: '0.6rem', marginBottom: 3, display: 'inline-flex' }}
            >
              {ALERT_TYPE_META[data.urgentAlert.type]?.label ?? 'Alert'}
            </span>
            <p
              style={{
                fontSize: '0.875rem',
                fontWeight: 500,
                color: 'var(--color-text)',
                margin: 0,
                overflow: 'hidden',
                textOverflow: 'ellipsis',
                whiteSpace: 'nowrap',
              }}
              title={data.urgentAlert.title}
            >
              {data.urgentAlert.title}
            </p>
          </div>

          {/* Time */}
          <span
            style={{
              fontSize: '0.7rem',
              color: 'var(--color-text-muted)',
              flexShrink: 0,
            }}
          >
            <Clock size={11} style={{ display: 'inline', verticalAlign: 'middle', marginRight: 3 }} aria-hidden="true" />
            {relativeTime(data.urgentAlert.date)}
          </span>

          {/* CTA */}
          <button
            type="button"
            onClick={() => onNavigate?.('news')}
            className="btn btn-sm"
            style={{
              borderColor: bannerColor,
              color: bannerColor,
              backgroundColor: `${bannerColor}10`,
              flexShrink: 0,
            }}
          >
            View
            <ArrowRight size={13} aria-hidden="true" />
          </button>
        </div>
      ) : null}

      {/* ── 2. Stats Row ──────────────────────────────────────────────────── */}
      <div
        style={{
          display: 'flex',
          gap: '0.875rem',
          flexWrap: 'wrap',
        }}
        role="region"
        aria-label="Key metrics"
      >
        <StatCard
          icon={FileText}
          count={data.pendingDraftsCount}
          label="Pending Drafts"
          accentColor="#6366f1"
          loading={sectionsLoading.stats}
          onClick={() => onNavigate?.('drafts')}
        />
        <StatCard
          icon={Megaphone}
          count={data.activePetitionsCount}
          label="Active Petitions"
          accentColor="#f43f5e"
          loading={sectionsLoading.stats}
          onClick={() => onNavigate?.('petitions')}
        />
        <StatCard
          icon={Calendar}
          count={data.grantDeadlinesCount}
          label="Grant Deadlines (30d)"
          accentColor="#f59e0b"
          loading={sectionsLoading.stats}
          onClick={() => onNavigate?.('agent-grants')}
        />
        <StatCard
          icon={Newspaper}
          count={data.mediaAlertsCount}
          label="Media Alerts"
          accentColor="#06b6d4"
          loading={sectionsLoading.stats}
          onClick={() => onNavigate?.('news')}
        />
      </div>

      {/* ── 2b. Intelligence Widget ───────────────────────────────────────── */}
      <IntelDashboardWidget onNavigate={onNavigate} />

      {/* ── 3. Two-column Body ───────────────────────────────────────────── */}
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: '3fr 2fr',
          gap: '1rem',
        }}
      >
        {/* Left: Action Items */}
        <section
          className="card card-enter card-enter-1"
          style={{ padding: 0, overflow: 'hidden' }}
          aria-labelledby="action-items-heading"
        >
          {/* Header */}
          <div
            className="section-header"
            style={{ padding: '0.875rem 1rem', marginBottom: 0 }}
          >
            <Zap size={16} style={{ color: 'var(--color-secondary)' }} aria-hidden="true" />
            <h2 id="action-items-heading" className="section-title" style={{ fontSize: '0.9375rem' }}>
              Action Items
            </h2>
            {!sectionsLoading.actions && (
              <span className="section-count">{actionItems.length}</span>
            )}
          </div>

          {/* Body */}
          <div style={{ padding: '0.5rem 0.875rem 1rem' }}>
            {sectionsLoading.actions ? (
              <SkeletonRows count={5} />
            ) : actionItems.length === 0 ? (
              <div
                style={{
                  textAlign: 'center',
                  padding: '2.5rem 1rem',
                  color: 'var(--color-text-muted)',
                }}
              >
                <CheckCircle2
                  size={32}
                  style={{ color: 'var(--color-success)', marginBottom: 8 }}
                  aria-hidden="true"
                />
                <p style={{ fontSize: '0.875rem', margin: 0 }}>All caught up</p>
                <p style={{ fontSize: '0.75rem', margin: '4px 0 0', color: 'var(--color-text-muted)' }}>
                  No urgent action items right now
                </p>
              </div>
            ) : (
              <ul
                style={{
                  listStyle: 'none',
                  margin: 0,
                  padding: 0,
                  display: 'flex',
                  flexDirection: 'column',
                  gap: 4,
                }}
                role="list"
                aria-label="Action items requiring attention"
              >
                {actionItems.map((item) => {
                  const Icon = item.icon;
                  return (
                    <li
                      key={item.id}
                      style={{
                        display: 'flex',
                        alignItems: 'center',
                        gap: 10,
                        padding: '0.625rem 0.75rem',
                        borderRadius: 'var(--radius-sm)',
                        transition: 'background-color var(--transition)',
                        cursor: 'default',
                      }}
                      onMouseEnter={(e) => {
                        e.currentTarget.style.backgroundColor = 'var(--color-surface-el)';
                      }}
                      onMouseLeave={(e) => {
                        e.currentTarget.style.backgroundColor = 'transparent';
                      }}
                    >
                      {/* Priority dot */}
                      <PriorityDot level={item.priority} />

                      {/* Type icon */}
                      <Icon
                        size={14}
                        style={{ color: 'var(--color-text-muted)', flexShrink: 0 }}
                        aria-hidden="true"
                      />

                      {/* Text */}
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <p
                          style={{
                            fontSize: '0.8125rem',
                            fontWeight: 500,
                            color: 'var(--color-text)',
                            margin: 0,
                            overflow: 'hidden',
                            textOverflow: 'ellipsis',
                            whiteSpace: 'nowrap',
                          }}
                          title={item.title}
                        >
                          {item.title}
                        </p>
                        {item.subtitle && (
                          <p
                            style={{
                              fontSize: '0.7rem',
                              color: 'var(--color-text-muted)',
                              margin: '1px 0 0',
                            }}
                          >
                            {item.subtitle}
                          </p>
                        )}
                      </div>

                      {/* Action button */}
                      <button
                        type="button"
                        onClick={() => onNavigate?.(item.targetTab)}
                        className="btn btn-ghost btn-sm"
                        style={{
                          fontSize: '0.7rem',
                          padding: '2px 8px',
                          flexShrink: 0,
                          color: 'var(--color-primary)',
                        }}
                        aria-label={`${item.actionLabel}: ${item.title}`}
                      >
                        {item.actionLabel}
                        <ArrowRight size={11} aria-hidden="true" />
                      </button>
                    </li>
                  );
                })}
              </ul>
            )}
          </div>
        </section>

        {/* Right: Recent Activity */}
        <section
          className="card card-enter card-enter-2"
          style={{ padding: 0, overflow: 'hidden' }}
          aria-labelledby="recent-activity-heading"
        >
          {/* Header */}
          <div
            className="section-header"
            style={{ padding: '0.875rem 1rem', marginBottom: 0 }}
          >
            <Activity size={16} style={{ color: 'var(--color-primary)' }} aria-hidden="true" />
            <h2 id="recent-activity-heading" className="section-title" style={{ fontSize: '0.9375rem' }}>
              Recent Activity
            </h2>
          </div>

          {/* Body */}
          <div
            style={{
              padding: '0.5rem 0.875rem 1rem',
              overflowY: 'auto',
              maxHeight: 420,
            }}
          >
            {sectionsLoading.activity ? (
              <SkeletonRows count={6} />
            ) : data.recentActivity.length === 0 ? (
              <div
                style={{
                  textAlign: 'center',
                  padding: '2rem 1rem',
                  color: 'var(--color-text-muted)',
                }}
              >
                <Bell size={28} style={{ marginBottom: 8 }} aria-hidden="true" />
                <p style={{ fontSize: '0.8125rem', margin: 0 }}>No recent activity</p>
              </div>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
                {Object.entries(activityGroups).map(([group, items]) => {
                  if (items.length === 0) return null;
                  return (
                    <div key={group}>
                      {/* Group label */}
                      <p
                        style={{
                          fontSize: '0.65rem',
                          fontWeight: 700,
                          textTransform: 'uppercase',
                          letterSpacing: '0.1em',
                          color: 'var(--color-text-muted)',
                          margin: '0 0 6px',
                          padding: '0 0.25rem',
                        }}
                      >
                        {group}
                      </p>

                      {/* Timeline items */}
                      <ul
                        style={{
                          listStyle: 'none',
                          margin: 0,
                          padding: 0,
                          display: 'flex',
                          flexDirection: 'column',
                          gap: 1,
                        }}
                        role="list"
                      >
                        {items.map((item) => {
                          const meta = ALERT_TYPE_META[item.type];
                          const color = meta?.color ?? '#6B7280';
                          const Icon = meta?.icon ?? Bell;

                          return (
                            <li
                              key={`${item.type}-${item.id}`}
                              style={{
                                display: 'flex',
                                alignItems: 'flex-start',
                                gap: 8,
                                padding: '5px 6px',
                                borderRadius: 'var(--radius-sm)',
                                transition: 'background-color var(--transition)',
                              }}
                              onMouseEnter={(e) => {
                                e.currentTarget.style.backgroundColor = 'var(--color-surface-el)';
                              }}
                              onMouseLeave={(e) => {
                                e.currentTarget.style.backgroundColor = 'transparent';
                              }}
                            >
                              {/* Colored dot with icon */}
                              <div
                                style={{
                                  width: 22,
                                  height: 22,
                                  borderRadius: 6,
                                  backgroundColor: `${color}18`,
                                  display: 'flex',
                                  alignItems: 'center',
                                  justifyContent: 'center',
                                  flexShrink: 0,
                                  marginTop: 1,
                                }}
                              >
                                <Icon size={11} style={{ color }} aria-hidden="true" />
                              </div>

                              {/* Text */}
                              <div style={{ flex: 1, minWidth: 0 }}>
                                <p
                                  style={{
                                    fontSize: '0.75rem',
                                    fontWeight: 500,
                                    color: 'var(--color-text)',
                                    margin: 0,
                                    lineHeight: 1.35,
                                    overflow: 'hidden',
                                    textOverflow: 'ellipsis',
                                    whiteSpace: 'nowrap',
                                  }}
                                  title={item.title}
                                >
                                  {item.title}
                                </p>
                                <p
                                  style={{
                                    fontSize: '0.65rem',
                                    color: 'var(--color-text-muted)',
                                    margin: '1px 0 0',
                                  }}
                                >
                                  {relativeTime(item.date)}
                                </p>
                              </div>
                            </li>
                          );
                        })}
                      </ul>
                    </div>
                  );
                })}
              </div>
            )}
          </div>
        </section>
      </div>

      {/* ── 4. Quick Actions Row ─────────────────────────────────────────── */}
      <section
        aria-label="Quick actions"
        style={{
          display: 'flex',
          gap: '0.75rem',
          flexWrap: 'wrap',
        }}
      >
        {[
          {
            icon: Plus,
            label: 'New Morning Batch',
            tab: 'drafts',
            accent: '#6366f1',
          },
          {
            icon: Megaphone,
            label: 'Create Petition',
            tab: 'petition-creator',
            accent: '#f43f5e',
          },
          {
            icon: Search,
            label: 'Find Grants',
            tab: 'agent-grants',
            accent: '#f59e0b',
          },
          {
            icon: GitBranch,
            label: 'View Pipeline',
            tab: 'pipeline',
            accent: '#10b981',
          },
        ].map(({ icon: Icon, label, tab, accent }) => (
          <button
            key={tab}
            type="button"
            onClick={() => onNavigate?.(tab)}
            className="btn btn-secondary hover-lift"
            style={{
              gap: 8,
              fontSize: '0.8125rem',
              border: `1px solid var(--color-border)`,
              color: 'var(--color-text-secondary)',
            }}
            onMouseEnter={(e) => {
              e.currentTarget.style.borderColor = accent;
              e.currentTarget.style.color = accent;
            }}
            onMouseLeave={(e) => {
              e.currentTarget.style.borderColor = 'var(--color-border)';
              e.currentTarget.style.color = 'var(--color-text-secondary)';
            }}
          >
            <Icon size={14} aria-hidden="true" />
            {label}
          </button>
        ))}

        {/* AI Member Email button */}
        <button
          type="button"
          onClick={() => setMemberEmailOpen(true)}
          className="btn btn-secondary hover-lift"
          style={{
            gap: 8,
            fontSize: '0.8125rem',
            border: '1px solid var(--color-border)',
            color: 'var(--color-text-secondary)',
          }}
          onMouseEnter={(e) => {
            e.currentTarget.style.borderColor = '#06b6d4';
            e.currentTarget.style.color = '#06b6d4';
          }}
          onMouseLeave={(e) => {
            e.currentTarget.style.borderColor = 'var(--color-border)';
            e.currentTarget.style.color = 'var(--color-text-secondary)';
          }}
        >
          <Mail size={14} aria-hidden="true" />
          Member Email
        </button>

        {/* Filler spacer for alignment */}
        <div style={{ flex: 1 }} />

        {/* Trending indicator */}
        {!loading && data.activePetitionsCount > 0 && (
          <span
            className="badge badge-success"
            style={{ alignSelf: 'center', gap: 4 }}
          >
            <TrendingUp size={11} aria-hidden="true" />
            {data.activePetitionsCount} active
          </span>
        )}
      </section>

      {/* ── Member Email Modal ──────────────────────────────────────────────── */}
      <MemberEmailModal
        open={memberEmailOpen}
        onClose={() => setMemberEmailOpen(false)}
      />
    </div>
  );
}