import React, { useState, useEffect } from 'react'; const MarketingCalculator = () => { // Начальные значения параметров const [budget, setBudget] = useState(6400); const [cpm, setCpm] = useState(7); const [ctr, setCtr] = useState(0.9); const [siteConversion, setSiteConversion] = useState(10); const [leadQualificationRate, setLeadQualificationRate] = useState(17); const [salesConversion, setSalesConversion] = useState(20); const [averageSaleValue, setAverageSaleValue] = useState(100); const [profitMargin, setProfitMargin] = useState(30); const [contractorCost, setContractorCost] = useState(0); // LTV множитель const [ltvMultiplier, setLtvMultiplier] = useState(3); // Активные этапы воронки const [activeStages, setActiveStages] = useState({ impressions: true, clicks: true, leads: true, qualifiedLeads: true, customers: true, ltv: true }); // Для анализа чувствительности const [showSensitivityAnalysis, setShowSensitivityAnalysis] = useState(false); const [sensitivityParameter, setSensitivityParameter] = useState('ctr'); // Расчетные показатели const [results, setResults] = useState({ impressions: 0, clicks: 0, leads: 0, qualifiedLeads: 0, customers: 0, revenue: 0, profit: 0, revenueLtv: 0, profitLtv: 0, cpc: 0, cpl: 0, cpql: 0, cpa: 0, roi: 0, roas: 0, ltv: 0 }); // Расчет всех показателей useEffect(() => { const impressions = Math.round(budget * 1000 / cpm); const clicks = Math.round(impressions * ctr / 100); const leads = Math.round(clicks * siteConversion / 100); const qualifiedLeads = Math.round(leads * leadQualificationRate / 100); const customers = Math.round(qualifiedLeads * salesConversion / 100); const ltv = averageSaleValue * ltvMultiplier; const revenue = customers * averageSaleValue; const totalCost = budget + contractorCost; const profit = revenue * profitMargin / 100 - totalCost; const revenueLtv = customers * ltv; const profitLtv = revenueLtv * profitMargin / 100 - totalCost; const cpc = budget / clicks || 0; const cpl = budget / leads || 0; const cpql = budget / qualifiedLeads || 0; const cpa = budget / customers || 0; const roi = (profit / totalCost) * 100 || 0; const roas = (revenue / totalCost) * 100 || 0; setResults({ impressions, clicks, leads, qualifiedLeads, customers, revenue, profit, revenueLtv, profitLtv, cpc: cpc.toFixed(2), cpl: cpl.toFixed(2), cpql: cpql.toFixed(2), cpa: cpa.toFixed(2), roi: roi.toFixed(2), roas: roas.toFixed(2), ltv: ltv.toFixed(2) }); }, [budget, cpm, ctr, siteConversion, leadQualificationRate, salesConversion, averageSaleValue, profitMargin, ltvMultiplier, contractorCost]); // Данные для customer journey map const getJourneyStages = () => { const stages = []; if (activeStages.impressions) { stages.push({ id: 'impressions', name: 'Показы', value: results.impressions, description: 'Первый контакт', kpi: `CPM: $${cpm}` }); } if (activeStages.clicks) { stages.push({ id: 'clicks', name: 'Клики', value: results.clicks, description: 'Проявление интереса', kpi: `CPC: $${results.cpc}` }); } if (activeStages.leads) { stages.push({ id: 'leads', name: 'Лиды', value: results.leads, description: 'Сбор контактов', kpi: `CPL: $${results.cpl}` }); } if (activeStages.qualifiedLeads) { stages.push({ id: 'qualifiedLeads', name: 'Квал. лиды', value: results.qualifiedLeads, description: 'Оценка качества', kpi: `CPQL: $${results.cpql}` }); } if (activeStages.customers) { stages.push({ id: 'customers', name: 'Клиенты', value: results.customers, description: 'Совершение покупки', kpi: `CPA: $${results.cpa}` }); } return stages; }; // Получение данных о конверсии между этапами const getConversionRates = () => { const rates = []; const stages = getJourneyStages(); for (let i = 0; i < stages.length - 1; i++) { let rate = 0; if (stages[i].id === 'impressions' && stages[i+1].id === 'clicks') { rate = ctr; } else if (stages[i].id === 'clicks' && stages[i+1].id === 'leads') { rate = siteConversion; } else if (stages[i].id === 'leads' && stages[i+1].id === 'qualifiedLeads') { rate = leadQualificationRate; } else if (stages[i].id === 'qualifiedLeads' && stages[i+1].id === 'customers') { rate = salesConversion; } else if (stages[i].id === 'leads' && stages[i+1].id === 'customers') { // Если квал. лиды пропущены const combinedRate = (leadQualificationRate * salesConversion) / 100; rate = combinedRate; } rates.push(rate); } return rates; }; // Форматирование чисел для отображения const formatNumber = (num) => { return new Intl.NumberFormat('ru-RU').format(num); }; // Расчет анализа чувствительности const calculateSensitivityAnalysis = () => { const variations = []; let baseValue = 0; let min = 0; let max = 0; let step = 0; // Установка диапазонов и шагов для разных параметров switch(sensitivityParameter) { case 'ctr': baseValue = ctr; min = Math.max(baseValue * 0.5, 0.1); max = baseValue * 2; break; case 'cpm': baseValue = cpm; min = Math.max(baseValue * 0.5, 1); max = baseValue * 2; break; case 'siteConversion': baseValue = siteConversion; min = Math.max(baseValue * 0.5, 1); max = Math.min(baseValue * 2, 100); break; case 'leadQualificationRate': baseValue = leadQualificationRate; min = Math.max(baseValue * 0.5, 1); max = Math.min(baseValue * 2, 100); break; case 'salesConversion': baseValue = salesConversion; min = Math.max(baseValue * 0.5, 1); max = Math.min(baseValue * 2, 100); break; default: return []; } step = (max - min) / 5; // Создание вариаций значения параметра for (let i = 0; i <= 5; i++) { const value = Number((min + step * i).toFixed(2)); variations.push(value); } // Расчет результатов для каждой вариации return variations.map(value => { // Клонирование текущих значений let tempCtr = ctr; let tempCpm = cpm; let tempSiteConversion = siteConversion; let tempLeadQualificationRate = leadQualificationRate; let tempSalesConversion = salesConversion; // Установка текущего варианта switch(sensitivityParameter) { case 'ctr': tempCtr = value; break; case 'cpm': tempCpm = value; break; case 'siteConversion': tempSiteConversion = value; break; case 'leadQualificationRate': tempLeadQualificationRate = value; break; case 'salesConversion': tempSalesConversion = value; break; default: break; } // Расчет новых значений для текущей вариации const impressions = Math.round(budget * 1000 / tempCpm); const clicks = Math.round(impressions * tempCtr / 100); const leads = Math.round(clicks * tempSiteConversion / 100); const qualifiedLeads = Math.round(leads * tempLeadQualificationRate / 100); const customers = Math.round(qualifiedLeads * tempSalesConversion / 100); const revenue = customers * averageSaleValue; const profit = revenue * profitMargin / 100 - budget; const roi = (profit / budget) * 100 || 0; return { value: value, customers, revenue, profit, roi: parseFloat(roi.toFixed(2)) }; }); }; // Переключение активных этапов воронки const toggleStage = (stage) => { setActiveStages(prev => ({ ...prev, [stage]: !prev[stage] })); }; return (
{/* Левая колонка - выбор этапов и параметры */}
{/* Выбор этапов воронки */}

Выбор этапов воронки

toggleStage('impressions')} className="mr-2 h-4 w-4" />
toggleStage('clicks')} className="mr-2 h-4 w-4" />
toggleStage('leads')} className="mr-2 h-4 w-4" />
toggleStage('qualifiedLeads')} className="mr-2 h-4 w-4" />
toggleStage('customers')} className="mr-2 h-4 w-4" />
toggleStage('ltv')} className="mr-2 h-4 w-4" />
{/* Параметры кампании - с двумя столбцами */}

Параметры кампании

{/* Левый столбец параметров - Показатели эффективности */}
{activeStages.impressions && (
setCpm(Number(e.target.value))} className="w-full p-1 border rounded text-sm" /> setCpm(Number(e.target.value))} className="w-full mt-1" />
)} {activeStages.impressions && activeStages.clicks && (
setCtr(Number(e.target.value))} step="0.1" className="w-full p-1 border rounded text-sm" /> setCtr(Number(e.target.value))} className="w-full mt-1" />
)} {activeStages.clicks && activeStages.leads && (
setSiteConversion(Number(e.target.value))} className="w-full p-1 border rounded text-sm" /> setSiteConversion(Number(e.target.value))} className="w-full mt-1" />
)} {activeStages.leads && activeStages.qualifiedLeads && (
setLeadQualificationRate(Number(e.target.value))} className="w-full p-1 border rounded text-sm" /> setLeadQualificationRate(Number(e.target.value))} className="w-full mt-1" />
)} {((activeStages.qualifiedLeads && activeStages.customers) || (!activeStages.qualifiedLeads && activeStages.leads && activeStages.customers)) && (
setSalesConversion(Number(e.target.value))} className="w-full p-1 border rounded text-sm" /> setSalesConversion(Number(e.target.value))} className="w-full mt-1" />
)}
{/* Правый столбец параметров - Финансовые параметры */}
setBudget(Number(e.target.value))} className="w-full p-1 border rounded text-sm" /> setBudget(Number(e.target.value))} className="w-full mt-1" />
setContractorCost(Number(e.target.value))} className="w-full p-1 border rounded text-sm" /> setContractorCost(Number(e.target.value))} className="w-full mt-1" />
{activeStages.customers && (
setAverageSaleValue(Number(e.target.value))} className="w-full p-1 border rounded text-sm" /> setAverageSaleValue(Number(e.target.value))} className="w-full mt-1" />
)} {activeStages.customers && (
setProfitMargin(Number(e.target.value))} className="w-full p-1 border rounded text-sm" /> setProfitMargin(Number(e.target.value))} className="w-full mt-1" />
)} {activeStages.customers && activeStages.ltv && (
setLtvMultiplier(Number(e.target.value))} className="w-full p-1 border rounded text-sm" /> setLtvMultiplier(Number(e.target.value))} className="w-full mt-1" />
LTV = ${results.ltv} (Средняя стоимость продажи × Множитель)
)}
{/* Правая колонка - результаты */}

Финансовый результат

{activeStages.customers && (
Выручка
${formatNumber(Math.round(results.revenue))}
)} {activeStages.customers && activeStages.ltv && (
Выручка (LTV)
${formatNumber(Math.round(results.revenueLtv))}
)} {activeStages.customers && (
Прибыль
${formatNumber(Math.round(results.profit))}
)} {activeStages.customers && activeStages.ltv && (
Прибыль (LTV)
${formatNumber(Math.round(results.profitLtv))}
)}
{activeStages.customers && (
ROAS
{results.roas}%
)} {activeStages.customers && (
ROI
{results.roi}%
)} {activeStages.customers && activeStages.ltv && (
ROI (LTV)
{(results.profitLtv / budget * 100).toFixed(2)}%
)}
{/* Customer Journey Map / Анализ чувствительности */}

{showSensitivityAnalysis ? 'Анализ чувствительности' : 'Customer Journey Map'}

{showSensitivityAnalysis && ( )}
{/* Содержимое в зависимости от выбранного представления */} {showSensitivityAnalysis ? (

График показывает, как изменение параметра "{ sensitivityParameter === 'ctr' ? 'CTR (%)' : sensitivityParameter === 'cpm' ? 'CPM ($)' : sensitivityParameter === 'siteConversion' ? 'Конверсия сайта (%)' : sensitivityParameter === 'leadQualificationRate' ? 'Квалификация лидов (%)' : 'Конверсия продаж (%)' }" влияет на ключевые показатели.

{/* Таблица результатов анализа чувствительности */}
{calculateSensitivityAnalysis().map((result, index) => ( ))}
{ sensitivityParameter === 'ctr' ? 'CTR (%)' : sensitivityParameter === 'cpm' ? 'CPM ($)' : sensitivityParameter === 'siteConversion' ? 'Конверсия сайта (%)' : sensitivityParameter === 'leadQualificationRate' ? 'Квалификация лидов (%)' : 'Конверсия продаж (%)' } Клиенты Выручка ($) Прибыль ($) ROI (%)
{result.value} {formatNumber(result.customers)} ${formatNumber(Math.round(result.revenue))} ${formatNumber(Math.round(result.profit))} {result.roi}%
) : (
{/* Journey Steps */}
{getJourneyStages().map((stage, index) => (
{index + 1}
{stage.name}
{formatNumber(stage.value)}
{stage.description}
))}
{/* Conversion rates between steps */} {getJourneyStages().length > 1 && (
{getConversionRates().map((rate, index) => (
Конверсия
{rate.toFixed(1)}%
))}
)} {/* Line connecting all steps */} {getJourneyStages().length > 1 && (
)} {/* KPIs for each stage */}
{getJourneyStages().map((stage) => (
Стоимость
{stage.kpi}
))}
)}
); }; export default MarketingCalculator;
Калькулятор разработан агентством Social Expert - экспертами по лидогенерации в промышленных масштабах