// components/SoapCalculator.tsx 'use client'; import { useState, useEffect, ChangeEvent, FormEvent } from 'react'; import Image from 'next/image'; import { calculateTotal } from '@/lib/calc'; import { API_BASE_URL } from '@/lib/config'; type InputNumberProps = { label: string; value: string; onChange: (v: string) => void; }; const InputNumber = ({ label, value, onChange }: InputNumberProps) => { const id = label.toLowerCase().replace(/\s+/g, '-'); return (
onChange(e.target.value)} placeholder=" " className={` peer h-10 w-full bg-gray-700 border-2 border-gray-600 rounded-md text-gray-200 placeholder-transparent pl-3 focus:outline-none focus:border-sky-500 appearance-none `} />
); }; export default function SoapCalculator() { 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(''); const [photoFile, setPhotoFile] = useState(null); const [chatId, setChatId] = useState(null); useEffect(() => { const params = new URLSearchParams(window.location.search); const id = params.get('chat_id'); if (id) { setChatId(id); } }, []); 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); 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, }, }); const finalPrice = result.total * (1 + markupNum / 100); const pricePer100g = weightNum > 0 ? (finalPrice / weightNum) * 100 : 0; const handlePhotoChange = (e: ChangeEvent) => { if (e.target.files && e.target.files[0]) { setPhotoFile(e.target.files[0]); } else { setPhotoFile(null); } }; const handleSubmit = async (e: FormEvent) => { e.preventDefault(); if (!chatId) { alert('❗ Не найден chat_id. Откройте калькулятор через Telegram-бота.'); return; } 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 { const res = await fetch(`${API_BASE_URL}/api/submit`, { method: 'POST', body: formData, }); if (res.ok) { alert('✅ Расчёт успешно отправлен в Telegram!'); 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 (
{/* Центрированный адаптивный логотип */}
Logo
{chatId === null && (
❗ Не найден chat_id. Откройте калькулятор через Telegram-бота.
)} {/* Название мыла */}
setSoapName(e.target.value)} placeholder=" " className={` peer h-10 w-full bg-gray-700 border-2 border-gray-600 rounded-md text-gray-200 placeholder-transparent pl-3 focus:outline-none focus:border-sky-500 appearance-none `} />
{/* Фото мыла */} {photoFile && ( Предпросмотр мыла )} {/* Блок «Основа» */}
{/* Блок «Отдушка» */}
{/* Блок «Пигмент» */}
{/* Блок «Форма» */} {/* Блок «Упаковка» */}
{/* Блок «Наценка и цена 100 г» */}
{/* Кнопка «Отправить расчёт» */} ); } type CostBlockProps = { title: string; value: number; highlight?: boolean; }; const CostBlock = ({ title, value, highlight = false }: CostBlockProps) => (
{title}: {value.toFixed(1)} руб
);