// 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 (
Carregando dados…
Bem-vindo de volta. Aqui está o resumo de hoje.