// Dashboard Module function Dashboard({ onNavigate }) { const [events, setEvents] = React.useState([]); const [budgets, setBudgets] = React.useState([]); const [freelancers, setFreelancers] = React.useState([]); const [tasks, setTasks] = React.useState([]); const [loading, setLoading] = React.useState(true); const [selectedEvent, setSelectedEvent] = React.useState(null); React.useEffect(() => { Promise.all([ sb.from('events').select('*').order('event_date'), sb.from('budgets').select('*').order('issued_date', { ascending: false }), sb.from('freelancers').select('id, name, status'), sb.from('tasks').select('id, status'), ]).then(([ev, bud, fr, tsk]) => { setEvents(ev.data || []); setBudgets(bud.data || []); setFreelancers(fr.data || []); setTasks(tsk.data || []); setLoading(false); }); }, []); const now = new Date(); const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0); const eventsThisMonth = events.filter(e => { const d = new Date(e.event_date); return d >= monthStart && d <= monthEnd; }); const eventsInProgress = events.filter(e => e.status === 'Em andamento'); const openBudgets = budgets.filter(b => b.status === 'Pendente' || b.status === 'Em análise'); const openBudgetsTotal = openBudgets.reduce((s, b) => s + (parseFloat(b.total) || 0), 0); const escalatedFreelancers = freelancers.filter(f => f.status === 'Escalado'); const overdueTasks = tasks.filter(t => t.status === 'done' ? false : false); // placeholder const CAT_COLORS = ['#3B82F6', '#8B5CF6', '#10B981', '#F59E0B', '#EF4444']; const revenueData = (() => { const result = []; for (let i = 5; i >= 0; i--) { const d = new Date(now.getFullYear(), now.getMonth() - i, 1); const label = d.toLocaleDateString('pt-BR', { month: 'short' }).replace('.', ''); const yStart = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-01'; const last = new Date(d.getFullYear(), d.getMonth() + 1, 0); const yEnd = last.getFullYear() + '-' + String(last.getMonth() + 1).padStart(2, '0') + '-' + String(last.getDate()).padStart(2, '0'); const total = budgets .filter(b => b.status === 'Aprovado' && b.issued_date && b.issued_date >= yStart && b.issued_date <= yEnd) .reduce((s, b) => s + (parseFloat(b.total) || 0), 0); result.push({ label, value: total }); } return result; })(); const categoryData = (() => { const counts = {}; events.forEach(e => { const t = e.event_type || e.type || 'Outros'; counts[t] = (counts[t] || 0) + 1; }); const total = Math.max(events.length, 1); return Object.entries(counts).map(([label, count], i) => ({ label, color: CAT_COLORS[i % CAT_COLORS.length], value: Math.round((count / total) * 100), })); })(); const approvedTotal = budgets.filter(b => b.status === 'Aprovado').reduce((s, b) => s + (parseFloat(b.total) || 0), 0); const nonZeroMonths = revenueData.filter(d => d.value > 0); const bestMonth = nonZeroMonths.length ? revenueData.reduce((a, b) => b.value > a.value ? b : a) : null; const avgMonthly = nonZeroMonths.length ? nonZeroMonths.reduce((s, d) => s + d.value, 0) / nonZeroMonths.length : 0; const alerts = [ { icon: 'clock', color: '#F59E0B', text: 'Confira os eventos desta semana na Agenda', nav: 'events' }, { icon: 'alert', color: '#EF4444', text: 'Verifique tarefas pendentes no Kanban', nav: 'tasks' }, { icon: 'budget', color: '#3B82F6', text: `${openBudgets.length} orçamento(s) aguardando aprovação`, nav: 'budgets' }, { icon: 'users', color: '#10B981', text: `${escalatedFreelancers.length} freelancer(s) escalado(s)`, nav: 'freelancers' }, ]; const statusVariant = { 'Em andamento': 'primary', 'Confirmado': 'success', 'Planejamento': 'purple', 'Orçamento': 'warning', 'Concluído': 'success', 'Cancelado': 'danger' }; const fmtDate = (iso) => { if (!iso) return '—'; const d = new Date(iso + 'T00:00:00'); return d.toLocaleDateString('pt-BR', { day: '2-digit', month: 'short' }); }; const fmtMoney = (v) => { const n = parseFloat(v) || 0; if (n >= 1000000) return `R$ ${(n / 1000000).toFixed(1)}M`; if (n >= 1000) return `R$ ${(n / 1000).toFixed(0)}k`; return `R$ ${n.toLocaleString('pt-BR')}`; }; const recentEvents = events.slice(0, 5); const StatClickable = ({ label, value, delta, icon, color, subtitle, nav }) => { const [hov, setHov] = React.useState(false); return (
onNavigate && onNavigate(nav)} onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)} style={{ cursor: 'pointer', transform: hov ? 'translateY(-2px)' : 'none', transition: 'transform 0.15s' }}>
); }; if (loading) { return (

Dashboard

Carregando dados…

); } return (
{/* Page header */}

Dashboard

Bem-vindo de volta. Aqui está o resumo de hoje.

{/* Stat cards — clickable */}
{/* Charts + Alerts row */}
{/* Revenue chart */}
Faturamento
Últimos 6 meses
{approvedTotal > 0 ? fmtMoney(approvedTotal) : '—'}
Melhor mês
{bestMonth ? bestMonth.label : '—'}
Média mensal
{avgMonthly > 0 ? fmtMoney(avgMonthly) : '—'}
Total aprovado
{approvedTotal > 0 ? fmtMoney(approvedTotal) : '—'}
{/* Category chart */}
Eventos por categoria
Distribuição atual
{categoryData.length > 0 ? (
{categoryData.map(c => (
onNavigate && onNavigate('events')}>
{c.label}
{c.value}%
))}
) : (
Crie eventos para ver a distribuição
)} {/* Alerts — each one navigates somewhere */}
Alertas
{alerts.map((a, i) => (
onNavigate && onNavigate(a.nav)} style={{ display: 'flex', alignItems: 'flex-start', gap: 10, padding: '10px 12px', background: '#F8FAFC', borderRadius: DS.radius.sm, borderLeft: `3px solid ${a.color}`, cursor: 'pointer', transition: 'background 0.1s' }} onMouseEnter={e => e.currentTarget.style.background = '#F1F5F9'} onMouseLeave={e => e.currentTarget.style.background = '#F8FAFC'}> {a.text}
))}
{/* Recent events table — rows clickable */}
Últimos eventos
onNavigate && onNavigate('events')}>Ver todos
EventoClienteDataTipoStatusOrçamento
{recentEvents.length === 0 && (
Nenhum evento cadastrado ainda. onNavigate && onNavigate('events')}>Criar evento
)} {recentEvents.map((ev, i) => (
setSelectedEvent(ev)} style={{ display: 'grid', gridTemplateColumns: '2fr 1.5fr 0.8fr 1fr 1fr 0.8fr', padding: '14px 24px', borderBottom: i < recentEvents.length - 1 ? `1px solid ${DS.colors.border}` : 'none', alignItems: 'center', cursor: 'pointer', transition: 'background 0.1s' }} onMouseEnter={e => e.currentTarget.style.background = '#F8FAFC'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}> {ev.name} {ev.client_name || '—'} {fmtDate(ev.event_date)} {ev.event_type || ev.type || '—'} {ev.status} {fmtMoney(ev.budget_estimate || ev.budget || 0)}
))}
{/* Event detail modal */} {selectedEvent && ( setSelectedEvent(null)} title="Detalhes do Evento" width={600}>
{selectedEvent.name}
{selectedEvent.client_name || 'Cliente não informado'}
{selectedEvent.status} {selectedEvent.event_type || selectedEvent.type || '—'}
{[ { label: 'Data', value: fmtDate(selectedEvent.event_date) }, { label: 'Horário', value: selectedEvent.start_time || selectedEvent.time || '—' }, { label: 'Local', value: selectedEvent.location || '—' }, { label: 'Orçamento', value: fmtMoney(selectedEvent.budget_estimate || selectedEvent.budget || 0) }, { label: 'Tipo', value: selectedEvent.event_type || selectedEvent.type || '—' }, { label: 'Convidados', value: selectedEvent.guest_count ? `${selectedEvent.guest_count} pessoas` : '—' }, ].map(item => (
{item.label}
{item.value}
))}
{selectedEvent.notes && ( <>
Observações
{selectedEvent.notes}
)}
setSelectedEvent(null)}>Fechar { setSelectedEvent(null); onNavigate && onNavigate('events'); }}>Ver na Agenda
)}
); } Object.assign(window, { Dashboard });