import React, { useState, useEffect } from 'react'; import { TrendingUp, Users, AlertTriangle, XCircle, Search, Settings, CheckCircle2, RefreshCw, Link as LinkIcon, ShieldCheck, HelpCircle, Eye } from 'lucide-react'; import { SubscriptionStatus } from '../types'; interface WebhookLog { id: string; event: string; payload: string; receivedAt: string; } interface Student { id: string; name: string; email: string; subscriptionStatus: SubscriptionStatus; asaasCustomerId: string | null; createdAt: string; totalPaid: number; unlockedCourses?: string[]; } interface PaymentLog { id: string; userId: string; studentName: string; studentEmail: string; value: number; status: 'PENDING' | 'RECEIVED' | 'CONFIRMED' | 'OVERDUE' | 'REFUNDED'; billingType: 'PIX' | 'CREDIT_CARD' | 'BOLETO'; invoiceUrl: string; dueDate: string; paidAt: string | null; createdAt: string; } interface DashboardStats { mrr: number; activeSubscribers: number; trialSubscribers: number; overdueSubscribers: number; canceledSubscribers: number; } interface AdminDashboardProps { token: string | null; } export default function AdminDashboard({ token }: AdminDashboardProps) { const [stats, setStats] = useState(null); const [payments, setPayments] = useState([]); const [webhookLogs, setWebhookLogs] = useState([]); const [students, setStudents] = useState([]); const [courses, setCourses] = useState([]); const [expandedStudentId, setExpandedStudentId] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [activeTab, setActiveTab] = useState<'finance' | 'students' | 'webhooks'>('finance'); const [isLoading, setIsLoading] = useState(true); const [actionLoading, setActionLoading] = useState(null); useEffect(() => { fetchDashboardData(); fetchStudents(); fetchCourses(); }, []); const fetchCourses = async () => { try { const res = await fetch('/api/courses', { headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const data = await res.json(); setCourses(data); } } catch (err) { console.error('Error fetching courses:', err); } }; const fetchDashboardData = async () => { setIsLoading(true); try { const res = await fetch('/api/admin/dashboard', { headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const data = await res.json(); setStats(data.stats); setPayments(data.recentPayments); setWebhookLogs(data.webhookLogs); } } catch (err) { console.error('Error fetching dashboard stats:', err); } finally { setIsLoading(false); } }; const fetchStudents = async () => { try { const res = await fetch('/api/admin/students', { headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const data = await res.json(); setStudents(data); } } catch (err) { console.error('Error fetching students:', err); } }; const handleChangeStudentStatus = async (studentId: string, newStatus: SubscriptionStatus) => { setActionLoading(studentId); try { const res = await fetch(`/api/admin/students/${studentId}/status`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ status: newStatus }) }); if (res.ok) { // Refresh local student details await fetchStudents(); await fetchDashboardData(); } } catch (err) { console.error('Error changing student status:', err); } finally { setActionLoading(null); } }; const handleToggleCourseUnlock = async (studentId: string, courseId: string, currentUnlocked: boolean) => { setActionLoading(`${studentId}_${courseId}`); try { const res = await fetch(`/api/admin/students/${studentId}/unlock-course`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ courseId, unlocked: !currentUnlocked }) }); if (res.ok) { await fetchStudents(); } } catch (err) { console.error('Error toggling course unlock:', err); } finally { setActionLoading(null); } }; const getStatusTag = (status: SubscriptionStatus) => { switch (status) { case 'ACTIVE': return ATIVO; case 'TRIAL': return PERÍODO TESTE; case 'OVERDUE': return INADIMPLENTE; case 'CANCELED': return CANCELADO; default: return {status}; } }; const getPaymentStatusTag = (status: string) => { switch (status) { case 'CONFIRMED': case 'RECEIVED': return ✓ Confirmado; case 'PENDING': return ● Pendente; case 'OVERDUE': return ⚠ Vencido; default: return {status}; } }; const filteredStudents = students.filter(student => student.name.toLowerCase().includes(searchQuery.toLowerCase()) || student.email.toLowerCase().includes(searchQuery.toLowerCase()) || (student.asaasCustomerId && student.asaasCustomerId.toLowerCase().includes(searchQuery.toLowerCase())) ); if (isLoading || !stats) { return (

Atualizando métricas de faturamento...

); } return (
{/* Dashboard Top Header */}

Painel de Controle Financeiro & Alunos

Gerencie métricas de faturamento, assinaturas recorrentes e controle acessos.

{/* METRICS ROW (MRR, Actives, Overdue, Cancelled) */}
{/* MRR Card */}

MRR (Recorrente Mensal)

R$ {stats.mrr.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}

Faturamento estimado ativo

{/* Active Subscribers Card */}

Assinantes Ativos

{stats.activeSubscribers}

({stats.trialSubscribers} em período de teste)

{/* Overdue Subscribers Card */}

Inadimplentes

{stats.overdueSubscribers}

Bloqueados ou com cobrança pendente

{/* Cancelled Card */}

Cancelamentos

{stats.canceledSubscribers}

Histórico total de contas canceladas

{/* TABS CONTAINER */}
{/* TAB 1: FINANCE / PAYMENTS */} {activeTab === 'finance' && (

Transações e Cobranças Recorrentes

Últimos lançamentos
{payments.length === 0 ? ( ) : ( payments.map(pay => ( )) )}
Aluno Valor Tipo Vencimento Status Comprovante Asaas
Nenhum pagamento registrado ainda.
{pay.studentName}
{pay.studentEmail}
R$ {pay.value.toFixed(2)} {pay.billingType} {new Date(pay.dueDate).toLocaleDateString('pt-BR')} {getPaymentStatusTag(pay.status)} Segunda Via / Fatura
)} {/* TAB 2: STUDENTS MANAGEMENT */} {activeTab === 'students' && (
{/* Search Bar */}
setSearchQuery(e.target.value)} className="w-full glass-input rounded-lg py-2.5 pl-10 pr-4 text-sm text-zinc-100 placeholder-zinc-500 focus:outline-none transition-colors" />
{filteredStudents.length === 0 ? ( ) : ( filteredStudents.map(student => ( {expandedStudentId === student.id && ( )} )) )}
Nome / E-mail ID Cliente Asaas Data Cadastro Total Pago Status Assinatura Ação Manual (Alterar Status)
Nenhum assinante correspondente encontrado.
{student.name}
{student.email}
{student.unlockedCourses && student.unlockedCourses.length > 0 && (
{student.unlockedCourses.length} {student.unlockedCourses.length === 1 ? 'Curso Avulso' : 'Cursos Avulsos'}
)}
{student.asaasCustomerId || Sem vínculo} {new Date(student.createdAt).toLocaleDateString('pt-BR')} R$ {student.totalPaid.toFixed(2)} {getStatusTag(student.subscriptionStatus)}
{/* Manage Individual Courses Access */} {/* Activate */} {/* Suspend / Overdue */} {/* Grant Trial */} {/* Cancel */}

Liberar Acesso Manual a Cursos Trancados para {student.name}

O preço de venda é definido no Gerenciador de Cursos
{courses.filter(c => c.isLocked).length === 0 ? (

Não há cursos trancados cadastrados para desbloqueio individual.

) : (
{courses.filter(c => c.isLocked).map(course => { const isUnlockedForStudent = student.unlockedCourses?.includes(course.id) || false; const isPendingAction = actionLoading === `${student.id}_${course.id}`; return (

{course.title}

R$ {course.price?.toFixed(2) || '0.00'}

); })}
)}
)} {/* TAB 3: WEBHOOK AUDIT LOGS */} {activeTab === 'webhooks' && (

Os logs de eventos abaixo auditam as chamadas disparadas pela API do Asaas para o endpoint de integração do webhook /api/webhooks/asaas.

{webhookLogs.length === 0 ? ( ) : ( webhookLogs.map(log => ( )) )}
ID Log Evento Asaas Data Processamento Payload Recebido
Nenhum evento registrado no banco de dados.
{log.id} {log.event} {new Date(log.receivedAt).toLocaleString('pt-BR')}
                              {log.payload}
                            
)}
); }