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 */}
Growth Tools

Simulador de Tráfego & Performance

Simule o impacto de mudanças no CPC, Investimento e Conversão no seu resultado final.

{/* 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 */}
CPC setInputs({...inputs, cpc: parseFloat(e.target.value) || 0})} className="w-20 text-right font-bold text-sm text-slate-700 outline-none focus:text-blue-600" />
Cliques handleClicksChange(parseInt(e.target.value) || 0)} className="w-20 text-right font-bold text-sm text-slate-700 outline-none focus:text-blue-600 bg-slate-50 rounded px-1" />
setInputs({...inputs, ctr: v})} suffix="%" /> {/* Grupo Conversão/Pedidos */}
Taxa % setInputs({...inputs, conversionRate: parseFloat(e.target.value) || 0})} className="w-20 text-right font-bold text-sm text-slate-700 outline-none focus:text-blue-600" />
Pedidos handleConversionsChange(parseInt(e.target.value) || 0)} className="w-20 text-right font-bold text-sm text-slate-700 outline-none focus:text-blue-600 bg-slate-50 rounded px-1" />
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 }) => (
{text}
); const MetricInput = ({ label, value, onChange, prefix, suffix, type = "number", step = "0.01", tooltip }) => (
{prefix && {prefix}} onChange(parseFloat(e.target.value) || 0)} step={step} className={`w-full bg-slate-50 border border-slate-200 text-slate-800 font-bold text-lg rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all py-2 ${prefix ? 'pl-10' : 'pl-3'} ${suffix ? 'pr-8' : 'pr-3'}`} /> {suffix && {suffix}}
); 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)}%
onChange(parseFloat(e.target.value))} className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blue-600" />
{prefix && {prefix}} onChange(parseFloat(e.target.value))} className={`w-full text-right text-sm font-semibold border rounded p-1 pl-4 focus:ring-1 focus:ring-blue-500 outline-none ${value !== baseValue ? 'border-blue-300 bg-blue-50 text-blue-700' : 'border-slate-300 text-slate-600'}`} /> {suffix && {suffix}}
); }; 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;