← back to Norma

components/email-analyzer/SdccEmailsPanel.tsx

221 lines

'use client';

/**
 * "Old Emails from SDC" — browse & load historical SDCC (Student Debt Crisis)
 * emails from Natalia's Gmail archive straight into the Email Analyzer.
 *
 * Uses the existing /api/gmail/messages endpoint (DB-backed, already synced).
 */

import { useState, useEffect, useCallback } from 'react';
import { Inbox, Search, Calendar, User, Play } from 'lucide-react';
import { useToast } from '@/components/ToastProvider';

interface GmailMessage {
  id: number | string;
  subject: string | null;
  from_address: string | null;
  from_name: string | null;
  body_text: string | null;
  body_html: string | null;
  received_at: string | null;
  is_read: boolean;
}

interface SdccEmailsPanelProps {
  onLoadIntoAnalyzer: (email: { subject: string; body: string; from: string }) => void;
}

export default function SdccEmailsPanel({ onLoadIntoAnalyzer }: SdccEmailsPanelProps) {
  const { addToast } = useToast();
  const [q, setQ] = useState('');
  const [messages, setMessages] = useState<GmailMessage[]>([]);
  const [loading, setLoading] = useState(false);
  const [loadingDetail, setLoadingDetail] = useState<string | number | null>(null);

  const load = useCallback(async () => {
    setLoading(true);
    try {
      const params = new URLSearchParams({ limit: '40', sort: 'date', sort_dir: 'desc' });
      if (q.trim()) params.set('q', q.trim());
      const res = await fetch(`/api/gmail/messages?${params.toString()}`);
      if (!res.ok) throw new Error('load failed');
      const data = await res.json();
      setMessages(data.messages || data || []);
    } catch {
      addToast('Failed to load SDCC emails', 'error');
    } finally {
      setLoading(false);
    }
  }, [q, addToast]);

  useEffect(() => {
    const t = setTimeout(() => { void load(); }, 220);
    return () => clearTimeout(t);
  }, [load]);

  async function handleLoad(m: GmailMessage) {
    setLoadingDetail(m.id);
    try {
      let body = m.body_text || m.body_html || '';
      // If the list endpoint didn't include the full body, fetch detail.
      if (!body.trim()) {
        const res = await fetch(`/api/gmail/messages/${m.id}`);
        if (res.ok) {
          const data = await res.json();
          body = data.message?.body_html || data.message?.body_text || data.body_html || data.body_text || '';
        }
      }
      if (!body.trim()) {
        addToast('This email has no body content to analyze', 'error');
        return;
      }
      onLoadIntoAnalyzer({
        subject: m.subject || '(no subject)',
        body,
        from: m.from_name ? `${m.from_name} <${m.from_address || ''}>` : (m.from_address || 'unknown'),
      });
      addToast(`Loaded "${m.subject?.slice(0, 40) || 'email'}" into analyzer`, 'success');
    } catch {
      addToast('Failed to load email body', 'error');
    } finally {
      setLoadingDetail(null);
    }
  }

  function timeAgo(iso: string | null): string {
    if (!iso) return '';
    const diff = Date.now() - new Date(iso).getTime();
    const days = Math.floor(diff / (1000 * 60 * 60 * 24));
    if (days < 1) return 'today';
    if (days < 7) return `${days}d ago`;
    if (days < 31) return `${Math.floor(days / 7)}w ago`;
    if (days < 365) return `${Math.floor(days / 30)}mo ago`;
    return `${Math.floor(days / 365)}y ago`;
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      <div style={{ position: 'relative' }}>
        <Search
          size={13}
          style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)', color: 'var(--color-text-muted)' }}
        />
        <input
          type="text"
          className="input"
          style={{ width: '100%', paddingLeft: 30, fontSize: 13 }}
          placeholder="Search SDCC emails... (e.g., 'SAVE plan', 'newsletter')"
          value={q}
          onChange={(e) => setQ(e.target.value)}
        />
      </div>

      {loading ? (
        <div style={{ padding: '18px 10px', textAlign: 'center', fontSize: 12, color: 'var(--color-text-muted)' }}>
          Loading SDCC archive...
        </div>
      ) : messages.length === 0 ? (
        <div style={{ padding: '18px 10px', textAlign: 'center', fontSize: 12, color: 'var(--color-text-muted)' }}>
          <Inbox size={18} style={{ opacity: 0.4, marginBottom: 4 }} />
          <div>No emails found{q ? ` matching "${q}"` : ''}.</div>
        </div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 4, maxHeight: 520, overflowY: 'auto' }}>
          {messages.map((m) => (
            <div
              key={m.id}
              style={{
                padding: 8,
                border: '1px solid var(--color-border)',
                borderRadius: 8,
                background: m.is_read ? 'var(--color-surface)' : 'var(--color-surface-el)',
                cursor: 'pointer',
                transition: 'var(--transition)',
              }}
              onClick={() => { void handleLoad(m); }}
              role="button"
              tabIndex={0}
              onKeyDown={(e) => {
                if (e.key === 'Enter' || e.key === ' ') {
                  e.preventDefault();
                  void handleLoad(m);
                }
              }}
            >
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 3 }}>
                <span
                  style={{
                    fontSize: 13,
                    fontWeight: m.is_read ? 500 : 700,
                    color: 'var(--color-text)',
                    flex: 1,
                    overflow: 'hidden',
                    textOverflow: 'ellipsis',
                    whiteSpace: 'nowrap',
                  }}
                  title={m.subject || ''}
                >
                  {m.subject || '(no subject)'}
                </span>
                <span style={{ fontSize: 10, color: 'var(--color-text-muted)', flexShrink: 0, display: 'flex', alignItems: 'center', gap: 3 }}>
                  <Calendar size={10} />
                  {timeAgo(m.received_at)}
                </span>
              </div>
              <div
                style={{
                  fontSize: 11,
                  color: 'var(--color-text-secondary)',
                  display: 'flex',
                  alignItems: 'center',
                  gap: 4,
                  marginBottom: 4,
                  overflow: 'hidden',
                  textOverflow: 'ellipsis',
                  whiteSpace: 'nowrap',
                }}
              >
                <User size={10} style={{ color: 'var(--color-text-muted)', flexShrink: 0 }} />
                <span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
                  {m.from_name || m.from_address || 'unknown'}
                </span>
              </div>
              <div
                style={{
                  fontSize: 11,
                  color: 'var(--color-text-muted)',
                  display: '-webkit-box',
                  WebkitLineClamp: 2,
                  WebkitBoxOrient: 'vertical',
                  overflow: 'hidden',
                  lineHeight: 1.35,
                  marginBottom: 4,
                }}
              >
                {(m.body_text || '').slice(0, 200)}
              </div>
              <button
                type="button"
                className="btn btn-ghost btn-sm"
                style={{
                  gap: 3,
                  fontSize: 11,
                  padding: '2px 8px',
                  minHeight: 24,
                  color: 'var(--color-primary)',
                }}
                onClick={(e) => { e.stopPropagation(); void handleLoad(m); }}
                disabled={loadingDetail === m.id}
              >
                <Play size={10} />
                {loadingDetail === m.id ? 'Loading...' : 'Analyze this email'}
              </button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}