NitinBot002's picture
Initial commit with all project files
f4854a1
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>
);
}