197 lines
12 KiB
TypeScript
197 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { motion } from 'framer-motion';
|
|
import DashboardLayout from '@/components/layout/DashboardLayout';
|
|
import { api } from '@/lib/api';
|
|
|
|
export default function AdminDashboard() {
|
|
const [dashboard, setDashboard] = useState<any>(null);
|
|
const [users, setUsers] = useState<any[]>([]);
|
|
const [partners, setPartners] = useState<any[]>([]);
|
|
const [logs, setLogs] = useState<any[]>([]);
|
|
const [tab, setTab] = useState('dashboard');
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const t = params.get('tab');
|
|
if (t) setTab(t);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
(async () => {
|
|
setLoading(true);
|
|
try {
|
|
if (tab === 'dashboard') {
|
|
const d = await api.getDashboard();
|
|
setDashboard(d);
|
|
} else if (tab === 'users') {
|
|
const d = await api.getUsers();
|
|
setUsers(d.users || []);
|
|
} else if (tab === 'partners') {
|
|
const d = await api.getPartners();
|
|
setPartners(d.partners || []);
|
|
} else if (tab === 'logs') {
|
|
const d = await api.getAuditLogs();
|
|
setLogs(d.logs || []);
|
|
}
|
|
} catch (e) { console.error(e); }
|
|
setLoading(false);
|
|
})();
|
|
}, [tab]);
|
|
|
|
const statCards = dashboard ? [
|
|
{ icon: '👥', label: 'Total Usuários', value: dashboard.totalUsers || 0, color: 'from-blue-500 to-cyan-400' },
|
|
{ icon: '🏢', label: 'Parceiros', value: dashboard.totalPartners || 0, color: 'from-green-500 to-emerald-400' },
|
|
{ icon: '🎁', label: 'Benefícios', value: dashboard.totalBenefits || 0, color: 'from-purple-500 to-pink-400' },
|
|
{ icon: '📊', label: 'Transações', value: dashboard.totalTransactions || 0, color: 'from-orange-500 to-red-400' },
|
|
] : [];
|
|
|
|
return (
|
|
<DashboardLayout>
|
|
<div className="space-y-8">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Painel <span className="gradient-text">Administrativo</span></h1>
|
|
<p className="text-dark-400 mt-1">Gerencie toda a plataforma em um só lugar.</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="flex gap-2 overflow-x-auto pb-2">
|
|
{[
|
|
{ key: 'dashboard', label: '📊 Dashboard' },
|
|
{ key: 'users', label: '👥 Usuários' },
|
|
{ key: 'partners', label: '🏢 Parceiros' },
|
|
{ key: 'benefits', label: '🎁 Benefícios' },
|
|
{ key: 'plugins', label: '🔌 Plugins' },
|
|
{ key: 'logs', label: '📋 Logs' },
|
|
].map(t => (
|
|
<button key={t.key} onClick={() => setTab(t.key)}
|
|
className={`px-4 py-2 rounded-xl text-sm font-medium whitespace-nowrap transition-all ${tab === t.key ? 'bg-brand-600 text-white' : 'glass text-dark-400 hover:text-white hover:bg-white/5'
|
|
}`}>
|
|
{t.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Dashboard Tab */}
|
|
{tab === 'dashboard' && (
|
|
<div className="space-y-6">
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
{loading ? [1, 2, 3, 4].map(i => <div key={i} className="skeleton h-28 rounded-2xl" />) :
|
|
statCards.map((card, i) => (
|
|
<motion.div key={i} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: i * 0.1 }}
|
|
className="glass rounded-2xl p-5">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<div className={`w-10 h-10 rounded-xl bg-gradient-to-br ${card.color} flex items-center justify-center text-lg`}>{card.icon}</div>
|
|
<span className="text-dark-400 text-sm">{card.label}</span>
|
|
</div>
|
|
<div className="text-2xl font-bold">{card.value}</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Users Tab */}
|
|
{tab === 'users' && (
|
|
<div className="glass rounded-2xl overflow-hidden">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-dark-900/50">
|
|
<tr>
|
|
<th className="text-left px-6 py-4 text-sm text-dark-400 font-medium">Nome</th>
|
|
<th className="text-left px-6 py-4 text-sm text-dark-400 font-medium">E-mail</th>
|
|
<th className="text-left px-6 py-4 text-sm text-dark-400 font-medium">Função</th>
|
|
<th className="text-left px-6 py-4 text-sm text-dark-400 font-medium">Status</th>
|
|
<th className="text-left px-6 py-4 text-sm text-dark-400 font-medium">Data</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-dark-800">
|
|
{loading ? <tr><td colSpan={5} className="px-6 py-8 text-center text-dark-500">Carregando...</td></tr> :
|
|
users.map(u => (
|
|
<tr key={u.id} className="hover:bg-dark-800/50 transition-colors">
|
|
<td className="px-6 py-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-brand-500 to-purple-600 flex items-center justify-center text-xs font-bold">
|
|
{u.name?.charAt(0)}
|
|
</div>
|
|
<span className="text-sm font-medium">{u.name}</span>
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-dark-400">{u.email}</td>
|
|
<td className="px-6 py-4">
|
|
<span className={`text-xs px-2 py-1 rounded-lg font-medium ${u.role === 'super_admin' ? 'bg-red-500/15 text-red-400' :
|
|
u.role === 'partner_admin' ? 'bg-blue-500/15 text-blue-400' :
|
|
'bg-gray-500/15 text-gray-400'
|
|
}`}>{u.role}</span>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<span className={`text-xs px-2 py-1 rounded-lg ${u.status === 'active' ? 'bg-green-500/15 text-green-400' : 'bg-yellow-500/15 text-yellow-400'
|
|
}`}>{u.status}</span>
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-dark-500">{new Date(u.created_at).toLocaleDateString('pt-BR')}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Partners Tab */}
|
|
{tab === 'partners' && (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{loading ? [1, 2, 3].map(i => <div key={i} className="skeleton h-40 rounded-2xl" />) :
|
|
partners.map(p => (
|
|
<motion.div key={p.id} initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }}
|
|
className="glass rounded-2xl p-6 hover:bg-white/5 transition-all cursor-pointer group">
|
|
<div className="flex items-center gap-4 mb-4">
|
|
<div className={`w-12 h-12 rounded-xl flex items-center justify-center text-2xl`}
|
|
style={{ background: `linear-gradient(135deg, ${p.gradient_from || '#3B82F6'}, ${p.gradient_to || '#8B5CF6'})` }}>
|
|
{p.icon || '🏢'}
|
|
</div>
|
|
<div>
|
|
<h3 className="font-bold group-hover:text-brand-400 transition-colors">{p.company_name}</h3>
|
|
<p className="text-xs text-dark-500">{p.type} • {p.slug}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center justify-between text-sm">
|
|
<span className={`px-2 py-1 rounded-lg text-xs ${p.status === 'active' ? 'bg-green-500/15 text-green-400' : 'bg-red-500/15 text-red-400'
|
|
}`}>{p.status === 'active' ? 'Ativo' : 'Inativo'}</span>
|
|
<span className="text-dark-500">{p.subscription_plan || 'basic'}</span>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Logs Tab */}
|
|
{tab === 'logs' && (
|
|
<div className="glass rounded-2xl p-6">
|
|
<h2 className="text-lg font-bold mb-4">Logs de Auditoria</h2>
|
|
<div className="space-y-2">
|
|
{loading ? [1, 2, 3, 4, 5].map(i => <div key={i} className="skeleton h-12 rounded-xl" />) :
|
|
logs.length === 0 ? (
|
|
<p className="text-dark-500 text-center py-8">Nenhum log registrado.</p>
|
|
) : logs.map((l: any) => (
|
|
<div key={l.id} className="flex items-center gap-4 p-3 rounded-xl bg-dark-900/50">
|
|
<div className="w-9 h-9 rounded-lg bg-brand-500/15 flex items-center justify-center text-brand-400 text-sm">📋</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium truncate">{l.action}: {l.description || l.entity_type}</p>
|
|
<p className="text-xs text-dark-500">{new Date(l.created_at).toLocaleString('pt-BR')}</p>
|
|
</div>
|
|
<span className="text-xs text-dark-500 shrink-0">{l.ip_address}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</DashboardLayout>
|
|
);
|
|
}
|