# Руководство по созданию новых калькуляторов Это руководство поможет вам быстро создать новый калькулятор в модульной системе. ## Структура модуля калькулятора Каждый калькулятор состоит из двух файлов в папке `frontend/calculators/[название]/`: - `config.ts` — конфигурация калькулятора (поля, формулы, подитоги) - `calc.ts` — функции расчета (опционально, если нужны сложные вычисления) ## Шаг 1: Создание папки модуля Создайте папку для вашего калькулятора: ``` frontend/calculators/candles/ ``` ## Шаг 2: Создание файла calc.ts (если нужны функции расчета) Если у вас простые формулы, можно обойтись без этого файла и указать формулы прямо в конфигурации. Пример для калькулятора свечей: ```typescript // calculators/candles/calc.ts export function calculateCandleStep( stepId: string, values: Record ): number { const { waxWeight = 0, waxPrice = 0, wickCount = 0, wickPrice = 0 } = values; switch (stepId) { case 'wax': return (waxWeight / 1000) * waxPrice; case 'wick': return wickCount * wickPrice; default: return 0; } } export function round(val: number): number { return Math.round(val * 10) / 10; } ``` ## Шаг 3: Создание файла config.ts Это основной файл конфигурации калькулятора: ```typescript // calculators/candles/config.ts import { CalculatorConfig } from '@/lib/calculator-types'; import { calculateCandleStep, round } from './calc'; export const candlesCalculatorConfig: CalculatorConfig = { id: 'candles', name: 'Калькулятор свечей', description: 'Расчет себестоимости свечей', icon: '🕯️', // Поля ввода fields: [ { id: 'candleName', type: 'text', label: 'Название свечи', placeholder: 'Введите название', defaultValue: '', gridCols: 1, required: false, }, { id: 'waxWeight', type: 'number', label: 'Вес воска, г', defaultValue: '', gridCols: 2, }, { id: 'waxPrice', type: 'number', label: 'Цена воска за 1 кг, руб', defaultValue: '', gridCols: 2, }, { id: 'wickCount', type: 'number', label: 'Количество фитилей', defaultValue: '', gridCols: 2, }, { id: 'wickPrice', type: 'number', label: 'Цена одного фитиля, руб', defaultValue: '', gridCols: 2, }, ], // Шаги расчета calculationSteps: [ { id: 'wax', name: 'Себестоимость воска', formula: (values) => round(calculateCandleStep('wax', values)), formulaDescription: '(вес_воска / 1000) * цена_воска', }, { id: 'wick', name: 'Себестоимость фитилей', formula: (values) => round(calculateCandleStep('wick', values)), formulaDescription: 'количество_фитилей * цена_фитиля', }, ], // Подитоги subtotals: [ { id: 'total', name: 'Итого себестоимость', formula: (values, steps) => { return round((steps.wax || 0) + (steps.wick || 0)); }, highlight: true, formulaDescription: 'воск + фитили', }, ], // Дополнительные расчеты (опционально) additionalCalculations: [ { id: 'pricePerCandle', name: 'Цена за свечу', formula: (values, steps, subtotals) => { const total = subtotals.total || 0; return round(total); }, }, ], // Форматирование сообщения для Telegram (опционально) formatTelegramMessage: (values, steps, subtotals, additional) => { const candleName = values.candleName || 'Без названия'; const totalCost = subtotals.total || 0; let text = `🕯️ Расчёт свечи: ${candleName}\n\n`; text += `📊 Итоги расчёта:\n`; text += ` 💵 Себестоимость: ${totalCost.toFixed(1)} ₽\n`; return text; }, }; ``` ## Шаг 4: Регистрация калькулятора Откройте файл `frontend/lib/calculator-registry.ts` и добавьте регистрацию: ```typescript import { candlesCalculatorConfig } from '@/calculators/candles/config'; // В функции initializeCalculators(): registerCalculator(candlesCalculatorConfig); ``` ## Типы полей ### text Текстовое поле ввода: ```typescript { id: 'productName', type: 'text', label: 'Название продукта', defaultValue: '', gridCols: 1, // 1 или 2 колонки } ``` ### number Числовое поле ввода: ```typescript { id: 'weight', type: 'number', label: 'Вес, г', defaultValue: '', gridCols: 2, validation: { min: 0, max: 10000, }, // Группировка полей (опционально) groupName: 'base', // Имя группы для группировки связанных полей showStepAfter: 'base', // ID шага расчета, который показать после этой группы } ``` ### file Поле для загрузки файла (обычно фото): ```typescript { id: 'photo', type: 'file', label: 'Фото продукта', accept: 'image/*', gridCols: 1, } ``` ## Группировка полей Для правильного расположения блоков расчета сразу после соответствующих полей используйте группировку: ```typescript fields: [ // Группа "base" - после этих полей покажется расчет "base" { id: 'weight', type: 'number', label: 'Вес, г', groupName: 'base', showStepAfter: 'base', // Первое поле группы должно иметь showStepAfter }, { id: 'price', type: 'number', label: 'Цена, руб', groupName: 'base', // Та же группа }, // Блок расчета "base" автоматически появится здесь // Группа "packaging" { id: 'box', type: 'number', label: 'Коробка, руб', groupName: 'packaging', showStepAfter: 'packaging', }, { id: 'ribbon', type: 'number', label: 'Лента, руб', groupName: 'packaging', }, // Блок расчета "packaging" автоматически появится здесь ] ``` ## Формулы расчета Формулы — это функции, которые принимают значения полей и возвращают число: ```typescript { id: 'baseCost', name: 'Базовая стоимость', formula: (values) => { const weight = values.weight || 0; const price = values.price || 0; return weight * price; }, formulaDescription: 'вес * цена', // Опционально, для документации } ``` В формуле доступны все значения полей через объект `values`, где ключ — это `id` поля. ## Подитоги Подитоги могут зависеть от шагов расчета: ```typescript { id: 'total', name: 'Итого', formula: (values, steps) => { // values — значения полей // steps — результаты всех шагов расчета return (steps.base || 0) + (steps.packaging || 0); }, highlight: true, // Выделить итоговое значение } ``` ## Дополнительные расчеты Используются для расчетов, которые зависят от подитогов или других дополнительных расчетов: ```typescript { id: 'finalPrice', name: 'Итоговая цена', formula: (values, steps, subtotals, additional) => { const total = subtotals.total || 0; const markup = values.markup || 0; return total * (1 + markup / 100); }, } ``` **Важно**: Дополнительные расчеты выполняются последовательно. Если один расчет зависит от другого, расположите зависимый расчет **после** того, от которого он зависит: ```typescript additionalCalculations: [ { id: 'finalPrice', name: 'Итоговая цена с наценкой', formula: (values, steps, subtotals) => { const total = subtotals.total || 0; const markup = values.markup || 0; return total * (1 + markup / 100); }, }, { id: 'pricePer100g', name: 'Цена за 100 г', formula: (values, steps, subtotals, additional) => { // Используем уже рассчитанный finalPrice из additional const finalPrice = additional?.finalPrice || 0; const weight = values.weight || 0; if (weight > 0) { return (finalPrice / weight) * 100; } return 0; }, }, ] ``` ## Форматирование сообщения для Telegram Если не указать `formatTelegramMessage`, будет использовано универсальное форматирование. Но лучше создать свою функцию: ```typescript formatTelegramMessage: (values, steps, subtotals, additional) => { // values — все поля формы (строки или числа) // steps — результаты шагов расчета // subtotals — результаты подитогов // additional — результаты дополнительных расчетов let text = `📊 Расчёт:\n\n`; text += `Итого: ${subtotals.total.toFixed(1)} ₽\n`; return text; } ``` ## Проверка работы 1. Убедитесь, что калькулятор появился в меню на главной странице 2. Заполните форму и проверьте расчеты 3. Отправьте результат в Telegram и убедитесь, что сообщение корректно форматировано ## Советы - Используйте функцию `round()` для округления результатов (обычно до 1 знака после запятой) - Для сложных расчетов выносите логику в `calc.ts` - Группируйте связанные поля визуально, используя `gridCols` - Всегда указывайте `formulaDescription` для документации - Тестируйте с нулевыми и пустыми значениями ## Примеры Полные примеры калькуляторов можно найти в: **Калькулятор мыла:** - `frontend/calculators/soap/config.ts` - `frontend/calculators/soap/calc.ts` **Калькулятор свечей (пример простого калькулятора):** - `frontend/calculators/candles/config.ts` - `frontend/calculators/candles/calc.ts` Оба примера демонстрируют использование группировки полей, шагов расчета, подитогов и дополнительных расчетов.