import { useMemo } from 'react'; import { useOutletContext, useNavigate } from 'react-router-dom'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'; import { DollarSign, ShoppingCart, TrendingUp } from 'lucide-react'; import DateRangePicker from '../components/DateRangePicker'; import type { OrderData, DateRange } from '../types'; import { parseOrderDate } from '../dataService'; const COLORS = [ // 10 Strong Base Colors '#10b981', '#3b82f6', '#8b5cf6', '#f43f5e', '#f97316', '#06b6d4', '#ec4899', '#eab308', '#6366f1', '#14b8a6', // 10 Softer Versions '#6ee7b7', '#93c5fd', '#c4b5fd', '#fda4af', '#fdba74', '#67e8f9', '#f9a8d4', '#fde047', '#a5b4fc', '#5eead4' ]; const globalColorMap: Record = {}; let globalColorIndex = 0; const getProductColor = (name: string) => { if (!globalColorMap[name]) { globalColorMap[name] = COLORS[globalColorIndex % COLORS.length]; globalColorIndex++; } return globalColorMap[name]; }; const formatCurrency = (value: number) => { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); }; type ChartTooltipPayload = { value: number; name?: string; color?: string; payload?: { fill?: string; }; }; type CustomTooltipProps = { active?: boolean; payload?: ChartTooltipPayload[]; label?: string; isCurrency?: boolean; }; const CustomTooltip = ({ active, payload, label, isCurrency }: CustomTooltipProps) => { if (active && payload && payload.length) { const color = payload[0].payload?.fill || payload[0].color || '#9ECAE1'; const displayLabel = label || payload[0].name; const value = isCurrency ? formatCurrency(payload[0].value) : payload[0].value; const valueLabel = isCurrency ? 'Receita:' : 'Vendas:'; return (

{displayLabel}

{valueLabel} {value}

); } return null; }; const Dashboard = () => { const navigate = useNavigate(); const { dateRange, setDateRange, ordersData, refreshInterval, setRefreshInterval, loadData } = useOutletContext<{ dateRange: DateRange, setDateRange: (range: DateRange) => void, ordersData: OrderData[], refreshInterval: number, setRefreshInterval: (interval: number) => void, loadData: (showLoading?: boolean) => void }>(); const filteredData = useMemo(() => { const orders = ordersData; return orders.filter(order => { const orderDate = parseOrderDate(order.Data_Pedido); return orderDate >= dateRange.start && orderDate <= dateRange.end; }); }, [dateRange, ordersData]); const { totalRevenue, totalOrders, averageOrderValue, salesByProduct, revenueByProduct } = useMemo(() => { let revenue = 0; let totalItems = 0; const productSalesMap: Record = {}; const productRevenueMap: Record = {}; const productNameIdMap: Record = {}; filteredData.forEach(order => { const itemRevenue = order.Quantidade * order.Valor_Unitario; revenue += itemRevenue; totalItems += order.Quantidade; const productName = order.Descricao_Produto.split(' TAMANHO')[0]; productNameIdMap[productName] = order.ID_Produto; if (productSalesMap[productName]) { productSalesMap[productName] += order.Quantidade; productRevenueMap[productName] += itemRevenue; } else { productSalesMap[productName] = order.Quantidade; productRevenueMap[productName] = itemRevenue; } }); // Identify which products will actually be displayed in both charts (Top 10 of each) const topSalesNames = Object.keys(productSalesMap).sort((a, b) => productSalesMap[b] - productSalesMap[a]).slice(0, 10); const topRevenueNames = Object.keys(productRevenueMap).sort((a, b) => productRevenueMap[b] - productRevenueMap[a]).slice(0, 10); // Combine them into a unique set to assign colors only to the VISIBLE products const displayProducts = Array.from(new Set([...topSalesNames, ...topRevenueNames])).sort(); const productColors: Record = {}; displayProducts.forEach((name) => { productColors[name] = getProductColor(name); }); const productsData = topSalesNames.map(name => ({ name, id: productNameIdMap[name], value: productSalesMap[name], fill: productColors[name] })); const revenueData = topRevenueNames.map(name => ({ name, id: productNameIdMap[name], value: productRevenueMap[name], fill: productColors[name] })); return { totalRevenue: revenue, totalOrders: totalItems, averageOrderValue: revenue / (filteredData.length || 1), salesByProduct: productsData, revenueByProduct: revenueData }; }, [filteredData]); return (

Visão Geral

Resumo de vendas e performance dos produtos.

loadData(true)} />

Receita Total

{formatCurrency(totalRevenue)}

Total de Produtos Vendidos

{totalOrders}

Ticket Médio (Por Item)

{formatCurrency(averageOrderValue)}

Produtos Mais Vendidos

} cursor={{ fill: '#222222' }} /> { if(data?.payload?.id) navigate(`/products/${data.payload.id}`) }} style={{ cursor: 'pointer' }}> {salesByProduct.map((entry) => ( ))}
{salesByProduct.map((entry) => (
navigate(`/products/${entry.id}`)}> {entry.name}
))}

Receita por Produto

{ if(data?.payload?.id) navigate(`/products/${data.payload.id}`) }} style={{ cursor: 'pointer' }}> {revenueByProduct.map((entry) => ( ))} } cursor={{ fill: '#222222' }} />
{revenueByProduct.map((entry) => (
navigate(`/products/${entry.id}`)}> {entry.name}
))}
); }; export default Dashboard;