import React, { useEffect, useState } from 'react'; import { LayoutDashboard, Users, CreditCard, LogOut, ArrowLeft, TrendingUp, Ticket, Search, ShieldAlert, Download, Plus, DollarSign, Calendar, CheckCircle2, XCircle, MoreHorizontal, UserPlus, Activity, AlertTriangle, User, Clock, Info, Settings, Save, Smartphone } from 'lucide-react'; import { supabase } from '@/lib/supabase'; import { User as AppUser } from '@/types'; interface AdminPanelProps { user: AppUser; onExitAdmin: () => void; onLogout: () => void; } // Tipos baseados nas novas tabelas SQL type TabType = 'overview' | 'users' | 'financial' | 'coupons' | 'settings'; const AdminPanel: React.FC = ({ user, onExitAdmin, onLogout }) => { const [activeTab, setActiveTab] = useState('overview'); const [loading, setLoading] = useState(true); // Data States const [stats, setStats] = useState(null); const [usersList, setUsersList] = useState([]); const [coupons, setCoupons] = useState([]); // Settings State const [config, setConfig] = useState({ whatsapp_number: '' // Inicializa vazio para não confundir }); const [savingConfig, setSavingConfig] = useState(false); // UI States const [searchTerm, setSearchTerm] = useState(''); const [showCouponModal, setShowCouponModal] = useState(false); const [newCoupon, setNewCoupon] = useState({ code: '', percent: 10, uses: 100 }); useEffect(() => { fetchDashboardData(); }, []); const fetchDashboardData = async () => { setLoading(true); try { // 1. Stats const { data: sData } = await supabase.rpc('get_admin_dashboard_stats'); if (sData) setStats(sData); // 2. Users (Robust Fetch) await fetchUsersSafe(); // 3. Coupons const { data: cData } = await supabase.from('coupons').select('*').order('created_at', { ascending: false }); if (cData) setCoupons(cData); // 4. Settings const { data: configData } = await supabase .from('app_settings') .select('value') .eq('key', 'whatsapp_number') .maybeSingle(); if (configData) { setConfig({ whatsapp_number: configData.value }); } else { // Fallback visual apenas se não tiver nada no banco setConfig({ whatsapp_number: '5541999999999' }); } } catch (error) { console.error("Admin fetch error", error); } finally { setLoading(false); } }; const fetchUsersSafe = async () => { // Tenta usar a função avançada (RPC) que tem dados do plano const { data: rpcData, error: rpcError } = await supabase.rpc('get_admin_users_list', { limit_count: 50 }); if (!rpcError && rpcData) { setUsersList(rpcData); return; } // Se falhar (ex: SQL não atualizado), busca o básico da tabela profiles para não deixar a tela vazia console.warn("RPC falhou, usando fallback de perfis:", rpcError); const { data: basicData } = await supabase .from('profiles') .select('*') .order('created_at', { ascending: false }) .limit(50); if (basicData) { const mapped = basicData.map(p => ({ id: p.id, full_name: p.full_name, email: p.email, phone: p.phone_e164, created_at: p.created_at, plan_status: 'free', plan_interval: 'free', lifetime_value: 0, plan_start_date: null, plan_end_date: null })); setUsersList(mapped); } }; const handleCreateCoupon = async (e: React.FormEvent) => { e.preventDefault(); try { const { error } = await supabase.rpc('admin_create_coupon', { p_code: newCoupon.code, p_percent: newCoupon.percent, p_uses: newCoupon.uses }); if (error) throw error; const { data: cData } = await supabase.from('coupons').select('*').order('created_at', { ascending: false }); if (cData) setCoupons(cData); setShowCouponModal(false); setNewCoupon({ code: '', percent: 10, uses: 100 }); alert("Cupom criado com sucesso!"); } catch (err: any) { alert("Erro ao criar cupom: " + err.message); } }; const handleToggleProfessional = async (userId: string, newValue: boolean) => { try { const { error } = await supabase .from('profiles') .update({ is_professional: newValue }) .eq('id', userId); if (error) throw error; // Optimistic update setUsersList(prev => prev.map(u => u.id === userId ? { ...u, is_professional: newValue } : u )); // If enhancing to Professional, check/create the professionals record if (newValue) { const { data: existing } = await supabase.from('professionals').select('id').eq('id', userId).maybeSingle(); if (!existing) { // Auto-init profile // We don't have the user name here easily unless we look it up, // but we can trust the 'professionals' RLS or just let them create it on first login. // Ideally, we create a stub here. const user = usersList.find(u => u.id === userId); if (user) { await supabase.from('professionals').insert({ id: userId, business_name: user.full_name || 'Novo Profissional', primary_color: '#059669' // Default Green }); } } } // toast.success(`Status alterado para ${newValue ? 'Profissional' : 'Aluno'}`); } catch (error) { console.error("Error toggling pro status:", error); alert("Erro ao alterar status!"); } }; const handleSaveSettings = async (e: React.FormEvent) => { e.preventDefault(); setSavingConfig(true); try { const { error } = await supabase .from('app_settings') .upsert({ key: 'whatsapp_number', value: config.whatsapp_number }, { onConflict: 'key' }); if (error) throw error; alert("Configurações salvas com sucesso!"); } catch (err: any) { console.error(err); alert("Erro ao salvar: " + err.message); } finally { setSavingConfig(false); } }; // Safe filter logic (handles null names) const filteredUsers = usersList.filter(u => (u.full_name || '').toLowerCase().includes(searchTerm.toLowerCase()) || (u.email || '').toLowerCase().includes(searchTerm.toLowerCase()) ); const formatCurrency = (cents: number) => { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(cents / 100); }; const formatDate = (dateStr: string | null) => { if (!dateStr) return '-'; return new Date(dateStr).toLocaleDateString('pt-BR'); }; return (
{/* Sidebar Premium */} {/* Main Content */}
{loading ? (
) : (
{/* Top Bar */}

{activeTab === 'overview' && 'Visão Geral'} {activeTab === 'users' && 'Gestão de Usuários'} {activeTab === 'financial' && 'Controle Financeiro'} {activeTab === 'coupons' && 'Cupons de Desconto'} {activeTab === 'settings' && 'Configurações do Sistema'}

Sistema Operacional • {new Date().toLocaleDateString()}

{activeTab !== 'settings' && ( )} {activeTab === 'coupons' && ( )}
{/* --- OVERVIEW TAB --- */} {activeTab === 'overview' && stats && (
{/* Stats Grid */}
} color="bg-emerald-500" trend="+8.2%" /> } color="bg-blue-500" trend="+12" /> } color="bg-indigo-500" trend="+24" /> } color="bg-purple-500" trend="Hoje" />
{/* Recent Activity Section */}
{/* Chart placeholder area */}

Crescimento de Receita (Simulado)

{[40, 65, 50, 80, 75, 90, 85, 100].map((h, i) => (
))}
JanFevMarAbrMaiJunJulAgo
{/* Quick Actions / Recent */}

Ações Rápidas

)} {/* --- USERS TAB --- */} {activeTab === 'users' && (
setSearchTerm(e.target.value)} />
{filteredUsers.length === 0 ? ( ) : filteredUsers.map((u) => ( ))}
Usuário Status Plano Início Término Pro? LTV
Nenhum usuário encontrado na busca.
{u.full_name ? u.full_name.substring(0, 2).toUpperCase() : 'US'}
{u.full_name || 'Usuário Sem Nome'}
{u.email}
{u.plan_start_date ? ( {formatDate(u.plan_start_date)} ) : (
{formatDate(u.created_at)}
)}
{u.plan_end_date ? formatDate(u.plan_end_date) : -} {formatCurrency(u.lifetime_value || 0)}
)} {/* --- COUPONS TAB --- */} {activeTab === 'coupons' && (

Marketing & Ofertas

Crie códigos promocionais para influenciadores, campanhas de email ou recuperação de carrinho.

{coupons.length === 0 ? (
Nenhum cupom ativo no momento.
) : coupons.map(c => (
{c.code}
{c.is_active ? 'ATIVO' : 'INATIVO'}
{c.discount_percent}% OFF
{c.uses_count} / {c.max_uses} usos
Criado em {new Date(c.created_at).toLocaleDateString()}
))}
)} {/* --- SETTINGS TAB --- */} {activeTab === 'settings' && (

Integração WhatsApp

Configure o número que receberá as mensagens e imagens dos usuários para análise.

setConfig({ ...config, whatsapp_number: e.target.value.replace(/\D/g, '') })} />

Insira apenas números, incluindo o código do país (Ex: 55 para Brasil). Este número será usado para gerar o QR Code no painel do usuário.

)} {/* --- FINANCIAL TAB --- */} {activeTab === 'financial' && (

Integração Stripe Connect

Para visualizar o histórico detalhado de transações em tempo real, configure os Webhooks do Stripe no backend. O sistema atual está pronto para receber os dados na tabela payments.

)}
)}
{/* Coupon Modal */} {showCouponModal && (

Criar Novo Cupom

Atenção: Ao criar o cupom aqui, você apenas registra para métricas internas.
Você deve criar o mesmo código de cupom no Dashboard do Stripe para que o desconto funcione no checkout.

setNewCoupon({ ...newCoupon, code: e.target.value })} />
setNewCoupon({ ...newCoupon, percent: parseInt(e.target.value) })} />
setNewCoupon({ ...newCoupon, uses: parseInt(e.target.value) })} />
)}
); }; // UI Components const NavButton = ({ active, onClick, icon, label }: any) => ( ); const KpiCard = ({ title, value, icon, color, trend }: any) => (
{icon}

{title}

{value}

{trend && (
{trend}
)}
); const StatusBadge = ({ status }: { status: string }) => { let styles = 'bg-gray-100 text-gray-600'; let icon = ; let label = status; if (status === 'pro') { styles = 'bg-green-100 text-green-700 border border-green-200'; icon = ; } else if (status === 'trial') { styles = 'bg-orange-100 text-orange-700 border border-orange-200'; icon = ; } else if (status === 'free' || !status) { return ( Gratuito ); } return ( {icon} {label} ); }; const IntervalBadge = ({ interval }: { interval: string }) => { if (interval === 'free' || !interval) { return Básico; } let color = 'bg-gray-100 text-gray-600'; let label = interval; if (interval === 'monthly') { color = 'bg-blue-50 text-blue-700 border border-blue-100'; label = 'Mensal'; } if (interval === 'quarterly') { color = 'bg-indigo-50 text-indigo-700 border border-indigo-100'; label = 'Trimestral'; } if (interval === 'annual') { color = 'bg-purple-50 text-purple-700 border border-purple-100'; label = 'Anual'; } return ( {label} ); }; const ExternalLinkIcon = () => ( ); export default AdminPanel;