// components/SoapCalculator.tsx 'use client'; import { useState, useEffect, ChangeEvent } from 'react'; import { calculateTotal } from '@/lib/calc'; type InputNumberProps = { label: string; value: string; onChange: (v: string) => void; placeholder?: string; }; const InputNumber = ({ label, value, onChange, placeholder }: InputNumberProps) => ( ); const CostBlock = ({ title, value, highlight = false, }: { title: string; value: number; highlight?: boolean; }) => (
{title}: {value.toFixed(1)} руб
); export default function SoapCalculator() { // 1) Поля ввода как строки (по умолчанию пустые) const [soapName, setSoapName] = useState(''); const [weight, setWeight] = useState(''); // г const [basePrice, setBasePrice] = useState(''); // руб/кг const [aromaPrice, setAromaPrice] = useState(''); // руб/фасовка const [aromaWeight, setAromaWeight] = useState(''); // г фасовка const [pigmentPrice, setPigmentPrice] = useState(''); // руб/фасовка const [pigmentWeight, setPigmentWeight] = useState(''); // г фасовка const [moldPrice, setMoldPrice] = useState(''); // руб за форму const [box, setBox] = useState(''); // руб const [filler, setFiller] = useState(''); // руб const [ribbon, setRibbon] = useState(''); // руб const [labelValue, setLabelValue] = useState(''); // руб наклейка const [markup, setMarkup] = useState(''); // % // Файл фотографии и chat_id const [photoFile, setPhotoFile] = useState(null); const [chatId, setChatId] = useState(null); // 2) Извлекаем chat_id из URL при монтировании useEffect(() => { const params = new URLSearchParams(window.location.search); const id = params.get('chat_id'); if (id) { setChatId(id); } }, []); // 3) Преобразуем строковые поля в числа (или 0, если не число) const toNum = (str: string) => { const n = parseFloat(str.replace(',', '.')); return isNaN(n) ? 0 : n; }; const weightNum = toNum(weight); const basePriceNum = toNum(basePrice); const aromaPriceNum = toNum(aromaPrice); const aromaWeightNum = toNum(aromaWeight); const pigmentPriceNum = toNum(pigmentPrice); const pigmentWeightNum = toNum(pigmentWeight); const moldPriceNum = toNum(moldPrice); const boxNum = toNum(box); const fillerNum = toNum(filler); const ribbonNum = toNum(ribbon); const labelNum = toNum(labelValue); const markupNum = toNum(markup); // 4) Считаем все базовые значения через calculateTotal const result = calculateTotal({ weight: weightNum, basePrice: basePriceNum, aromaPrice: aromaPriceNum, aromaWeight: aromaWeightNum, pigmentPrice: pigmentPriceNum, pigmentWeight: pigmentWeightNum, moldPrice: moldPriceNum, packaging: { box: boxNum, filler: fillerNum, ribbon: ribbonNum, label: labelNum, }, }); // 5) Итоговые значения const finalPrice = result.total * (1 + markupNum / 100); const pricePer100g = weightNum > 0 ? (finalPrice / weightNum) * 100 : 0; // 6) Обработчик изменения фото const handlePhotoChange = (e: ChangeEvent) => { if (e.target.files && e.target.files[0]) { setPhotoFile(e.target.files[0]); } else { setPhotoFile(null); } }; // 7) Обработчик отправки формы на backend const handleSubmit = async () => { if (!chatId) { alert('❗ Не найден chat_id. Откройте калькулятор через Telegram-бота.'); return; } // Собираем FormData const formData = new FormData(); formData.append('chat_id', chatId); formData.append('soapName', soapName || ''); formData.append('weight', weightNum.toString()); formData.append('basePrice', basePriceNum.toString()); formData.append('aromaPrice', aromaPriceNum.toString()); formData.append('aromaWeight', aromaWeightNum.toString()); formData.append('pigmentPrice', pigmentPriceNum.toString()); formData.append('pigmentWeight', pigmentWeightNum.toString()); formData.append('moldPrice', moldPriceNum.toString()); formData.append('box', boxNum.toString()); formData.append('filler', fillerNum.toString()); formData.append('ribbon', ribbonNum.toString()); formData.append('labelValue', labelNum.toString()); formData.append('markup', markupNum.toString()); formData.append('totalCost', result.total.toString()); formData.append('finalPrice', finalPrice.toString()); formData.append('pricePer100g', pricePer100g.toString()); if (photoFile) { formData.append('photo', photoFile); } try { // Замените URL на публичный адрес вашего backend (Express/ngrok/другой хостинг) // Например: 'https://abcd1234.ngrok.io/api/submit' const res = await fetch('https://api-dosoap.duckdns.org/api/submit', { method: 'POST', body: formData, }); if (res.ok) { alert('✅ Расчёт успешно отправлен в Telegram!'); // Сбрасываем все поля на пустые строки / null setSoapName(''); setWeight(''); setBasePrice(''); setAromaPrice(''); setAromaWeight(''); setPigmentPrice(''); setPigmentWeight(''); setMoldPrice(''); setBox(''); setFiller(''); setRibbon(''); setLabelValue(''); setMarkup(''); setPhotoFile(null); // Прокручиваем страницу вверх window.scrollTo({ top: 0, behavior: 'smooth' }); } else { const text = await res.text(); alert(`Ошибка при отправке: ${text}`); } } catch (err) { console.error(err); alert('Ошибка сети при отправке расчёта'); } }; return (
{chatId === null && (
❗ Не найден chat_id. Откройте калькулятор через Telegram-бота.
)} {/* Название мыла */} {/* Фото мыла */} {photoFile && ( Предпросмотр мыла )} {/* Блок «Основа» */}
{/* Блок «Отдушка» */}
{/* Блок «Пигмент» */}
{/* Блок «Форма» */} {/* Блок «Упаковка» */}
{/* Блок «Наценка и цена 100 г» */}
{/* Кнопка «Отправить расчёт» */}
); }