File size: 7,734 Bytes
f4854a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { motion } from 'framer-motion';
import { HiHeart, HiUserGroup, HiFlag, HiDocumentText, HiArrowRight, HiPlusCircle } from 'react-icons/hi';
import api from '../../../services/api';
import { formatCurrency, formatDate } from '../../../utils/helpers';

function KpiCard({ icon, label, value, sub, color, bg, trend, i }) {
  return (
    <motion.div className="dash-kpi" initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.08 }}>
      <div className="dash-kpi__icon-wrap" style={{ background: bg, color: color, fontSize: '1.5rem' }}>{icon}</div>
      <div className="dash-kpi__body">
        <div className="dash-kpi__value">{value ?? 'β€”'}</div>
        <div className="dash-kpi__label">{label}</div>
        {sub && <div style={{ fontSize: '0.72rem', color: '#9CA3AF', marginTop: '0.15rem' }}>{sub}</div>}
        {trend && <span className={`dash-kpi__trend dash-kpi__trend--${trend.up ? 'up' : 'down'}`}>{trend.up ? 'β–²' : 'β–Ό'} {trend.text}</span>}
      </div>
    </motion.div>
  );
}

export default function AdminOverview() {
  const [donStats, setDonStats] = useState(null);
  const [volStats, setVolStats] = useState(null);
  const [campStats, setCampStats] = useState(null);
  const [helpStats, setHelpStats] = useState(null);
  const [recentDons, setRecentDons] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    Promise.allSettled([
      api.get('/donations/stats'),
      api.get('/volunteers/stats'),
      api.get('/campaigns/stats'),
      api.get('/help-requests/stats'),
      api.get('/donations/recent?limit=5'),
    ]).then(([d, v, c, h, rd]) => {
      if (d.status === 'fulfilled') setDonStats(d.value);
      if (v.status === 'fulfilled') setVolStats(v.value);
      if (c.status === 'fulfilled') setCampStats(c.value);
      if (h.status === 'fulfilled') setHelpStats(h.value);
      if (rd.status === 'fulfilled') setRecentDons(rd.value.donations || []);
    }).finally(() => setLoading(false));
  }, []);

  const kpis = [
    {
      icon: 'πŸ’°', label: 'Total Raised', value: donStats ? formatCurrency(donStats.totalAmount) : 'β€”',
      sub: `${donStats?.totalCount || 0} donations β€’ Avg ${donStats ? formatCurrency(donStats.averageAmount) : 'β€”'}`,
      color: '#2D6A4F', bg: '#ecfdf5',
      trend: donStats?.thisMonthAmount ? { up: true, text: `${formatCurrency(donStats.thisMonthAmount)} this month` } : null,
    },
    {
      icon: 'πŸ‘₯', label: 'Volunteers', value: volStats?.total || 'β€”',
      sub: `${volStats?.pending || 0} pending approval`,
      color: '#3B82F6', bg: '#eff6ff',
      trend: volStats?.active ? { up: true, text: `${volStats.active} active` } : null,
    },
    {
      icon: 'πŸ“’', label: 'Active Campaigns', value: campStats?.active || 'β€”',
      sub: `${campStats?.total || 0} total β€’ ${campStats ? formatCurrency(campStats.totalRaised) : 'β€”'} raised`,
      color: '#8B5CF6', bg: '#f5f3ff',
    },
    {
      icon: 'πŸ†˜', label: 'Help Requests', value: helpStats?.total || 'β€”',
      sub: `${helpStats?.submitted || 0} new β€’ ${helpStats?.fulfilled || 0} fulfilled`,
      color: '#EF4444', bg: '#fef2f2',
      trend: helpStats?.submitted ? { up: false, text: `${helpStats.submitted} needs review` } : null,
    },
  ];

  const quickActions = [
    { to: '/dashboard/campaigns', icon: 'πŸ“’', label: 'New Campaign', desc: 'Launch a fundraising campaign' },
    { to: '/dashboard/volunteers', icon: 'βœ…', label: 'Approve Volunteers', desc: `${volStats?.pending || 0} pending` },
    { to: '/dashboard/requests', icon: 'πŸ†˜', label: 'Triage Requests', desc: `${helpStats?.submitted || 0} new requests` },
    { to: '/dashboard/users', icon: 'πŸ‘€', label: 'Manage Users', desc: 'Update roles & permissions' },
  ];

  return (
    <div>
      <div className="dash-page-header">
        <h1>πŸ“Š Admin Overview</h1>
        <p>Real-time snapshot of the foundation's operations.</p>
      </div>

      {/* KPI Grid */}
      <div className="dash-kpi-grid">
        {kpis.map((kpi, i) => (
          <KpiCard key={kpi.label} {...kpi} i={i} loading={loading} />
        ))}
      </div>

      {/* Quick Actions + Recent Donations */}
      <div className="dash-grid-2" style={{ marginTop: '1.25rem' }}>
        {/* Quick Actions */}
        <div className="dash-card">
          <div className="dash-card__title" style={{ marginBottom: '1rem' }}>⚑ Quick Actions</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '0.6rem' }}>
            {quickActions.map(a => (
              <Link key={a.to} to={a.to} style={{ textDecoration: 'none', display: 'flex', alignItems: 'center', gap: '0.75rem', padding: '0.8rem 1rem', background: '#f9fafb', borderRadius: 12, border: '1px solid #f3f4f6', transition: 'background 0.15s' }}
                onMouseEnter={e => e.currentTarget.style.background = '#f3f4f6'}
                onMouseLeave={e => e.currentTarget.style.background = '#f9fafb'}>
                <span style={{ fontSize: '1.3rem' }}>{a.icon}</span>
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 600, color: '#111827', fontSize: '0.9rem' }}>{a.label}</div>
                  <div style={{ fontSize: '0.75rem', color: '#9CA3AF' }}>{a.desc}</div>
                </div>
                <HiArrowRight style={{ color: '#d1d5db' }} />
              </Link>
            ))}
          </div>
        </div>

        {/* Recent Donations */}
        <div className="dash-card">
          <div className="dash-card__header">
            <div className="dash-card__title"><HiHeart /> Recent Donations</div>
            <Link to="/dashboard/donations" style={{ fontSize: '0.82rem', color: '#2D6A4F', display: 'flex', alignItems: 'center', gap: 4 }}>
              View All <HiArrowRight />
            </Link>
          </div>
          {loading ? (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {[1,2,3].map(i => <div key={i} className="dash-skeleton" style={{ height: 40 }} />)}
            </div>
          ) : recentDons.length === 0 ? (
            <div className="dash-empty"><div className="dash-empty__icon">πŸ’³</div><p>No donations yet.</p></div>
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
              {recentDons.map((d, i) => (
                <motion.div key={d.id} initial={{ opacity: 0, x: 10 }} animate={{ opacity: 1, x: 0 }} transition={{ delay: i * 0.06 }}
                  style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', padding: '0.6rem 0.75rem', background: '#f9fafb', borderRadius: 10 }}>
                  <div style={{ width: 36, height: 36, borderRadius: '50%', background: '#ecfdf5', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '1rem', flexShrink: 0 }}>πŸ’°</div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontWeight: 600, color: '#2D6A4F', fontSize: '0.9rem' }}>{formatCurrency(d.amount)}</div>
                    <div style={{ fontSize: '0.75rem', color: '#9CA3AF', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.purpose || 'General'}</div>
                  </div>
                  <div style={{ fontSize: '0.72rem', color: '#9CA3AF', textAlign: 'right', flexShrink: 0 }}>
                    {d.createdAt ? new Date(d.createdAt).toLocaleDateString('en-IN', { day: 'numeric', month: 'short' }) : 'β€”'}
                  </div>
                </motion.div>
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}