← back to Grant

components/dashboard/DashboardTab.tsx

448 lines

'use client';

import { useState, useEffect } from 'react';
import { SkeletonStats, SkeletonList } from '../Skeleton';
import {
  Award,
  Newspaper,
  Users,
  Megaphone,
  TrendingUp,
  DollarSign,
  Calendar,
  Target,
  FileText,
  Clock,
} from 'lucide-react';

/* ─── Types ──────────────────────────────────────────────────────────────── */
interface DashboardData {
  org_name: string;
  grant_count: number;
  total_funding: number;
  news_count: number;
  collab_count: number;
  proposal_count: number;
  upcoming_deadlines: {
    id: string;
    title: string;
    funder: string;
    deadline: string;
    status: string;
    priority: string;
    ai_fit_score: number | null;
  }[];
  recent_activity: {
    id: string;
    type: 'grant' | 'collaboration' | 'proposal';
    label: string;
    detail: string;
    status: string;
    created_at: string;
  }[];
}

function formatCurrency(n: number): string {
  if (n === 0) return '$0';
  return '$' + n.toLocaleString('en-US', { maximumFractionDigits: 0 });
}

function formatDate(d: string): string {
  return new Date(d).toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
    year: 'numeric',
  });
}

function timeAgo(d: string): string {
  const diff = Date.now() - new Date(d).getTime();
  const mins = Math.floor(diff / 60000);
  if (mins < 1) return 'just now';
  if (mins < 60) return `${mins}m ago`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}h ago`;
  const days = Math.floor(hours / 24);
  return `${days}d ago`;
}

function daysUntil(d: string): number {
  return Math.ceil((new Date(d).getTime() - Date.now()) / (1000 * 60 * 60 * 24));
}

const ACTIVITY_ICONS: Record<string, React.ElementType> = {
  grant: Award,
  collaboration: Users,
  proposal: FileText,
};

const ACTIVITY_COLORS: Record<string, string> = {
  grant: '#059669',
  collaboration: '#8b5cf6',
  proposal: '#3b82f6',
};

/* ─── Stat Card ──────────────────────────────────────────────────────────── */
function StatCard({
  label,
  value,
  Icon,
  accent,
}: {
  label: string;
  value: string;
  Icon: React.ElementType;
  accent: string;
}) {
  return (
    <div className="stat-card">
      <div className="flex items-center justify-between mb-3">
        <div
          className="w-9 h-9 rounded-lg flex items-center justify-center"
          style={{
            backgroundColor: `${accent}18`,
            border: `1px solid ${accent}30`,
          }}
        >
          <Icon size={18} style={{ color: accent }} />
        </div>
      </div>
      <div className="text-2xl font-bold mb-1" style={{ color: 'var(--color-text)' }}>
        {value}
      </div>
      <div className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
        {label}
      </div>
    </div>
  );
}

/* ─── Dashboard Tab ──────────────────────────────────────────────────────── */
interface DashboardTabProps {
  onNavigate?: (tab: string) => void;
}

export default function DashboardTab({ onNavigate }: DashboardTabProps) {
  const [data, setData] = useState<DashboardData | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    (async () => {
      try {
        const res = await fetch('/api/dashboard', { credentials: 'include' });
        if (res.ok) {
          setData(await res.json());
        }
      } catch {
        // fetch error handled by loading state
      } finally {
        setLoading(false);
      }
    })();
  }, []);

  if (loading) {
    return (
      <div className="p-6" style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
        <SkeletonStats count={4} />
        <SkeletonList count={3} />
      </div>
    );
  }

  if (!data) {
    return (
      <div className="p-6">
        <div className="card text-center py-16">
          <p style={{ color: 'var(--color-text-muted)' }}>Failed to load dashboard data.</p>
        </div>
      </div>
    );
  }

  return (
    <div className="p-6 space-y-6">
      {/* Welcome Banner */}
      <div
        className="rounded-xl p-6"
        style={{
          background: 'linear-gradient(135deg, rgba(5,150,105,0.15), rgba(4,120,87,0.08))',
          border: '1px solid rgba(5,150,105,0.2)',
        }}
      >
        <h2 className="text-xl font-bold mb-2" style={{ color: 'var(--color-text)' }}>
          {data.org_name}
        </h2>
        <p
          className="text-sm leading-relaxed max-w-2xl"
          style={{ color: 'var(--color-text-secondary)' }}
        >
          Your AI-powered fundraising command center. Discover grants, monitor relevant news,
          build collaborations, and manage outreach -- all in one place.
        </p>
      </div>

      {/* Stats Grid */}
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
        <StatCard
          label="Active Grants"
          value={String(data.grant_count)}
          Icon={Award}
          accent="#059669"
        />
        <StatCard
          label="Total Funding"
          value={formatCurrency(data.total_funding)}
          Icon={DollarSign}
          accent="#f59e0b"
        />
        <StatCard
          label="News Items"
          value={String(data.news_count)}
          Icon={Newspaper}
          accent="#3b82f6"
        />
        <StatCard
          label="Collaborations"
          value={String(data.collab_count)}
          Icon={Users}
          accent="#8b5cf6"
        />
      </div>

      {/* Two-Column Layout */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
        {/* Upcoming Deadlines */}
        <div className="card">
          <div className="flex items-center gap-2 mb-4">
            <Calendar size={16} style={{ color: 'var(--color-primary)' }} />
            <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>
              Upcoming Deadlines
            </h3>
          </div>

          {data.upcoming_deadlines.length === 0 ? (
            <div className="flex flex-col items-center justify-center py-8 text-center">
              <div
                className="w-10 h-10 rounded-lg flex items-center justify-center mb-3"
                style={{
                  backgroundColor: 'var(--color-surface-el)',
                  border: '1px solid var(--color-border)',
                }}
              >
                <Calendar size={18} style={{ color: 'var(--color-text-muted)' }} />
              </div>
              <p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
                No upcoming deadlines
              </p>
              <p className="text-xs mt-1" style={{ color: 'var(--color-text-muted)' }}>
                Discovered grants with deadlines will appear here
              </p>
            </div>
          ) : (
            <div className="space-y-2">
              {data.upcoming_deadlines.map((g) => {
                const days = daysUntil(g.deadline);
                const isUrgent = days <= 14;
                return (
                  <div
                    key={g.id}
                    className="flex items-center justify-between p-3 rounded-lg"
                    style={{
                      backgroundColor: 'var(--color-surface-el)',
                      border: `1px solid ${isUrgent ? 'rgba(239,68,68,0.3)' : 'var(--color-border)'}`,
                    }}
                  >
                    <div className="min-w-0 flex-1">
                      <p className="text-sm font-medium truncate" style={{ color: 'var(--color-text)' }}>
                        {g.title}
                      </p>
                      <p className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
                        {g.funder}
                      </p>
                    </div>
                    <div className="flex items-center gap-2 shrink-0 ml-3">
                      <div className="text-right">
                        <p
                          className="text-xs font-semibold"
                          style={{ color: isUrgent ? '#ef4444' : 'var(--color-text-secondary)' }}
                        >
                          {formatDate(g.deadline)}
                        </p>
                        <p
                          className="text-xs"
                          style={{ color: isUrgent ? '#ef4444' : 'var(--color-text-muted)' }}
                        >
                          {days}d left
                        </p>
                      </div>
                      {isUrgent && (
                        <Clock size={14} style={{ color: '#ef4444' }} />
                      )}
                    </div>
                  </div>
                );
              })}
            </div>
          )}
        </div>

        {/* Recent Activity */}
        <div className="card">
          <div className="flex items-center gap-2 mb-4">
            <TrendingUp size={16} style={{ color: 'var(--color-primary)' }} />
            <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>
              Recent Activity
            </h3>
          </div>

          {data.recent_activity.length === 0 ? (
            <div className="flex flex-col items-center justify-center py-8 text-center">
              <div
                className="w-10 h-10 rounded-lg flex items-center justify-center mb-3"
                style={{
                  backgroundColor: 'var(--color-surface-el)',
                  border: '1px solid var(--color-border)',
                }}
              >
                <TrendingUp size={18} style={{ color: 'var(--color-text-muted)' }} />
              </div>
              <p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
                No recent activity
              </p>
              <p className="text-xs mt-1" style={{ color: 'var(--color-text-muted)' }}>
                Your actions and AI-generated insights will appear here
              </p>
            </div>
          ) : (
            <div className="space-y-2">
              {data.recent_activity.map((a) => {
                const ActivityIcon = ACTIVITY_ICONS[a.type] || Award;
                const activityColor = ACTIVITY_COLORS[a.type] || '#059669';
                return (
                  <div
                    key={a.id}
                    className="flex items-start gap-3 p-3 rounded-lg"
                    style={{
                      backgroundColor: 'var(--color-surface-el)',
                      border: '1px solid var(--color-border)',
                    }}
                  >
                    <div
                      className="w-7 h-7 rounded-md flex items-center justify-center shrink-0 mt-0.5"
                      style={{
                        backgroundColor: `${activityColor}18`,
                        border: `1px solid ${activityColor}30`,
                      }}
                    >
                      <ActivityIcon size={13} style={{ color: activityColor }} />
                    </div>
                    <div className="flex-1 min-w-0">
                      <p className="text-xs font-medium truncate" style={{ color: 'var(--color-text)' }}>
                        {a.label}
                      </p>
                      <p className="text-xs truncate" style={{ color: 'var(--color-text-muted)' }}>
                        {a.detail}
                      </p>
                    </div>
                    <span className="text-xs shrink-0" style={{ color: 'var(--color-text-muted)' }}>
                      {timeAgo(a.created_at)}
                    </span>
                  </div>
                );
              })}
            </div>
          )}
        </div>
      </div>

      {/* Quick Actions */}
      <div className="card">
        <div className="flex items-center gap-2 mb-4">
          <Target size={16} style={{ color: 'var(--color-primary)' }} />
          <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>
            Quick Actions
          </h3>
        </div>
        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
          <QuickAction
            Icon={Award}
            label="Discover Grants"
            description="Find new funding opportunities"
            onClick={() => onNavigate?.('grants')}
          />
          <QuickAction
            Icon={Newspaper}
            label="Scan News"
            description="Monitor relevant headlines"
            onClick={() => onNavigate?.('news')}
          />
          <QuickAction
            Icon={Users}
            label="Find Partners"
            description="AI-suggested collaborations"
            onClick={() => onNavigate?.('collaborations')}
          />
          <QuickAction
            Icon={Megaphone}
            label="Draft Outreach"
            description="Generate outreach emails"
            onClick={() => onNavigate?.('outreach')}
          />
        </div>
      </div>
    </div>
  );
}

function QuickAction({
  Icon,
  label,
  description,
  onClick,
}: {
  Icon: React.ElementType;
  label: string;
  description: string;
  onClick?: () => void;
}) {
  return (
    <button
      className="flex items-start gap-3 p-3 rounded-lg text-left w-full"
      onClick={onClick}
      style={{
        backgroundColor: 'var(--color-surface-el)',
        border: '1px solid var(--color-border)',
        transition: 'border-color 0.15s, background-color 0.15s',
        cursor: 'pointer',
      }}
      onMouseEnter={(e) => {
        e.currentTarget.style.borderColor = 'rgba(5,150,105,0.4)';
        e.currentTarget.style.backgroundColor = 'rgba(5,150,105,0.06)';
      }}
      onMouseLeave={(e) => {
        e.currentTarget.style.borderColor = 'var(--color-border)';
        e.currentTarget.style.backgroundColor = 'var(--color-surface-el)';
      }}
    >
      <div
        className="w-8 h-8 rounded-md flex items-center justify-center shrink-0 mt-0.5"
        style={{
          backgroundColor: 'rgba(5,150,105,0.12)',
          border: '1px solid rgba(5,150,105,0.2)',
        }}
      >
        <Icon size={15} style={{ color: '#34d399' }} />
      </div>
      <div>
        <div className="text-sm font-medium" style={{ color: 'var(--color-text)' }}>
          {label}
        </div>
        <div className="text-xs mt-0.5" style={{ color: 'var(--color-text-muted)' }}>
          {description}
        </div>
      </div>
    </button>
  );
}