import React, { useState, useEffect, useMemo } from 'react';
import { TrendingUp, Calculator, HelpCircle, ArrowRight } from 'lucide-react';
const GrowthSimulator = () => {
// Estado: Inputs Reais
const [inputs, setInputs] = useState({
spend: 15000,
cpc: 2.50,
ctr: 1.8,
conversionRate: 1.2,
aov: 280 // Ticket Médio
});
// Estado: Modificadores de Simulação
const [simulatedInputs, setSimulatedInputs] = useState({ ...inputs });
// Sincronizar simulação quando 'Reais' mudam
useEffect(() => {
setSimulatedInputs(inputs);
}, [inputs]);
// Utilitários de Formatação
const formatMoney = (value) => new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);
const formatNumber = (value) => new Intl.NumberFormat('pt-BR').format(Math.round(value));
// Lógica de Cálculo Central
const calculateMetrics = (data) => {
const { spend, cpc, ctr, conversionRate, aov } = data;
// Evitar divisão por zero
const safeCPC = cpc > 0 ? cpc : 0.01;
const clicks = spend / safeCPC;
const impressions = ctr > 0 ? clicks / (ctr / 100) : 0;
const conversions = clicks * (conversionRate / 100);
const revenue = conversions * aov;
const roas = spend > 0 ? revenue / spend : 0;
const cac = conversions > 0 ? spend / conversions : 0;
const profit = revenue - spend; // Lucro bruto de mídia
return {
spend, cpc, ctr, conversionRate, aov,
clicks, impressions, conversions, revenue, roas, cac, profit
};
};
// Memoized Results
const currentMetrics = useMemo(() => calculateMetrics(inputs), [inputs]);
const simulatedMetrics = useMemo(() => calculateMetrics(simulatedInputs), [simulatedInputs]);
// Handlers para Inputs Calculados (Cliques e Conversões)
const handleClicksChange = (newClicks) => {
if (newClicks <= 0) return;
const newCpc = inputs.spend / newClicks;
setInputs(prev => ({ ...prev, cpc: parseFloat(newCpc.toFixed(4)) }));
};
const handleConversionsChange = (newConversions) => {
if (newConversions < 0) return;
const clicks = inputs.spend / (inputs.cpc || 0.01);
const newCr = clicks > 0 ? (newConversions / clicks) * 100 : 0;
setInputs(prev => ({ ...prev, conversionRate: parseFloat(newCr.toFixed(4)) }));
};
// Dados para o Gráfico
const chartData = [
{ label: 'Receita', current: currentMetrics.revenue, simulated: simulatedMetrics.revenue, format: formatMoney },
{ label: 'Lucro (Mídia)', current: currentMetrics.profit, simulated: simulatedMetrics.profit, format: formatMoney },
];
return (
{/* Header */}
{/* Layout Principal: Stack Vertical */}
{/* SEÇÃO 1: Cenário Atual (Topo Full Width) */}
1. Cenário Atual
Insira suas métricas médias dos últimos 30 dias para estabelecer a base.
Receita Atual
{formatMoney(currentMetrics.revenue)}
{/* Grid Responsivo de Inputs */}
setInputs({...inputs, spend: v})}
prefix="R$"
step="100"
/>
{/* Grupo CPC/Cliques */}
setInputs({...inputs, ctr: v})}
suffix="%"
/>
{/* Grupo Conversão/Pedidos */}
setInputs({...inputs, aov: v})}
prefix="R$"
step="1"
/>
{/* SEÇÃO 2: Simulação e Resultados (Abaixo) */}
{/* Cards de KPI Principal (Comparison) */}
`${v.toFixed(2)}x`}
/>
{/* Bloco de Simulação */}
2. Simulador "E se..."
Ajuste os controles
setSimulatedInputs({...simulatedInputs, spend: v})}
min={inputs.spend * 0.5} max={inputs.spend * 3} step={100}
prefix="R$"
/>
setSimulatedInputs({...simulatedInputs, cpc: v})}
min={0.10} max={inputs.cpc * 2} step={0.05}
prefix="R$"
/>
setSimulatedInputs({...simulatedInputs, conversionRate: v})}
min={0.1} max={5.0} step={0.1}
suffix="%"
/>
setSimulatedInputs({...simulatedInputs, aov: v})}
min={inputs.aov * 0.5} max={inputs.aov * 2} step={5}
prefix="R$"
/>
{/* Bloco Gráfico e Detalhes */}
Impacto Financeiro
{/* Gráfico de Barras Customizado */}
{chartData.map((data, idx) => {
// Calcular altura relativa (max 100%)
const maxVal = Math.max(data.current, data.simulated) * 1.2; // 20% padding
const hCurrent = (data.current / maxVal) * 100;
const hSim = (data.simulated / maxVal) * 100;
return (
{/* Barra Atual */}
{data.format(data.current)}
Atual
{/* Barra Simulada */}
= data.current ? 'text-emerald-600' : 'text-rose-600'}`}>
{data.format(data.simulated)}
= data.current ? 'bg-emerald-500' : 'bg-rose-500'} bg-opacity-90 shadow-lg shadow-blue-100`}
>
{/* Line Pattern Overlay for style */}
Simulado
)
})}
{/* Mini Tabela de Dados Extra */}
Lucro Líquido (Mídia)
{formatMoney(currentMetrics.profit)}
= currentMetrics.profit ? 'text-emerald-600' : 'text-rose-600'}`}>
{formatMoney(simulatedMetrics.profit)}
Volume de Cliques
{formatNumber(currentMetrics.clicks)}
→
{formatNumber(simulatedMetrics.clicks)}
);
};
// Componentes Auxiliares
const Tooltip = ({ text }) => (
);
const MetricInput = ({ label, value, onChange, prefix, suffix, type = "number", step = "0.01", tooltip }) => (
);
const SimulationControl = ({ label, value, baseValue, onChange, min, max, step, prefix, suffix }) => {
const percentChange = ((value - baseValue) / baseValue) * 100;
const isPositive = percentChange > 0;
const isNegative = percentChange < 0;
return (
{percentChange > 0 ? '+' : ''}{percentChange.toFixed(1)}%
);
};
const KPICard = ({ title, current, simulated, formatFn, inverse = false }) => {
const delta = simulated - current;
const deltaPercent = (delta / current) * 100;
const isGood = inverse ? delta < 0 : delta > 0;
const isNeutral = delta === 0;
const colorClass = isNeutral
? 'text-slate-400'
: isGood ? 'text-emerald-600' : 'text-rose-600';
const bgClass = isNeutral
? 'bg-slate-50'
: isGood ? 'bg-emerald-50' : 'bg-rose-50';
return (
{title}
{formatFn(simulated)}
Anterior: {formatFn(current)}
{!isNeutral && (
{delta > 0 ? '▲' : '▼'} {Math.abs(deltaPercent).toFixed(1)}%
)}
);
};
export default GrowthSimulator;