diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 4960523..f273114 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useOutletContext, useNavigate } from 'react-router-dom'; import { AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'; -import { DollarSign, ShoppingCart, TrendingUp } from 'lucide-react'; +import { AlertTriangle, Bot, ClipboardList, DollarSign, Package, ShoppingCart, TrendingUp } from 'lucide-react'; import DateRangePicker from '../components/DateRangePicker'; import RefreshStatus from '../components/RefreshStatus'; import type { DashboardAnalytics, OrderData, DateRange } from '../types'; @@ -161,6 +161,15 @@ const kpiIconStyle = (color: string) => ({ color }); +type OperationalInsight = { + title: string; + description: string; + icon: typeof Bot; + accentClassName: string; + actionLabel: string; + actionTo: string; +}; + const DashboardSkeleton = () => (
@@ -502,6 +511,60 @@ const Dashboard = () => { const focusedSeller = focusedSellerId ? sellerTimeSeries.sellers.find(seller => seller.id === focusedSellerId) : null; const effectiveFocusedSellerId = focusedSeller?.id || null; + const operationalInsights = useMemo(() => { + const insights: OperationalInsight[] = []; + const topProduct = salesByProduct[0]; + const topRevenueProduct = revenueByProduct[0]; + const topSeller = chartRevenueBySeller[0]; + const soldUnits = salesByProduct.reduce((total, product) => total + product.value, 0); + const topProductShare = topProduct && soldUnits ? topProduct.value / soldUnits : 0; + + if (topProduct && topProductShare >= 0.35) { + insights.push({ + title: `${topProduct.name} concentra ${Math.round(topProductShare * 100)}% das unidades vendidas`, + description: 'Priorize a conferência de estoque e corte para evitar ruptura no item mais sensível do período.', + icon: AlertTriangle, + accentClassName: 'border-amber-400/30 bg-amber-400/10 text-amber-300', + actionLabel: 'Abrir corte', + actionTo: '/cutting' + }); + } + + if (topRevenueProduct && topRevenueProduct.value > 0) { + insights.push({ + title: `${topRevenueProduct.name} lidera a receita do período`, + description: `${formatCurrency(topRevenueProduct.value)} em receita. Compare disponibilidade e necessidade de compra antes de vender sem cobertura.`, + icon: Package, + accentClassName: 'border-sky-400/30 bg-sky-400/10 text-sky-300', + actionLabel: 'Ver suprimentos', + actionTo: '/supplies' + }); + } + + if (topSeller && topSeller.value > 0) { + insights.push({ + title: `${topSeller.name} puxa a maior receita por vendedor`, + description: `${formatCurrency(topSeller.value)} no período selecionado. Use o gráfico abaixo para comparar ritmo e concentração.`, + icon: TrendingUp, + accentClassName: 'border-emerald-400/30 bg-emerald-400/10 text-emerald-300', + actionLabel: 'Analisar vendas', + actionTo: '/graph' + }); + } + + if (totalOrders > 0 && averageOrderValue > 0) { + insights.push({ + title: `Ticket médio por item em ${formatCurrency(averageOrderValue)}`, + description: `${new Intl.NumberFormat('pt-BR').format(totalOrders)} unidades vendidas no período. Revise mix de produtos e abastecimento dos campeões.`, + icon: ClipboardList, + accentClassName: 'border-brand-primary/30 bg-brand-primary/10 text-brand-primary', + actionLabel: 'Ver produtos', + actionTo: '/products' + }); + } + + return insights.slice(0, 4); + }, [averageOrderValue, chartRevenueBySeller, revenueByProduct, salesByProduct, totalOrders]); const handleManualRefresh = () => { void loadDashboardMetrics(dateRange, { force: true }); @@ -532,6 +595,62 @@ const Dashboard = () => { ) : (
+
+
+
+
+
+ +
+
+

Copiloto operacional

+

+ Leituras automáticas do período selecionado para orientar vendas, estoque e produção. +

+
+
+ +
+
+ + {operationalInsights.length ? ( +
+ {operationalInsights.map((insight) => ( +
+
+
+ +
+
+

{insight.title}

+

{insight.description}

+ +
+
+
+ ))} +
+ ) : ( +
+
+ Sem sinais operacionais suficientes no período selecionado. +
+
+ )} +
+