From fc7c42861cb1166a10020ecde947f37c3d067be7 Mon Sep 17 00:00:00 2001 From: dosai Date: Sat, 1 Nov 2025 20:27:36 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=92=D0=BE=D1=81=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=B8=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D1=8B=20=D0=BC=D1=8B=D0=BB=D0=B0=20=D0=B8=20=D0=B8?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20chat=5Fid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Восстановлена структура формы мыла с промежуточными стоимостями под каждым блоком - Добавлен компонент CostBlock для отображения промежуточных стоимостей - Улучшена обработка chat_id при отправке (проверка URL и sessionStorage) - Улучшена навигация между калькуляторами с более понятным интерфейсом - Добавлена подсказка для пользователя о переключении калькуляторов --- frontend/components/CalculatorNav.tsx | 19 +- frontend/components/CostBlock.tsx | 24 ++ frontend/components/DynamicCalculator.tsx | 284 ++++++++++++++++------ 3 files changed, 250 insertions(+), 77 deletions(-) create mode 100644 frontend/components/CostBlock.tsx diff --git a/frontend/components/CalculatorNav.tsx b/frontend/components/CalculatorNav.tsx index 003e612..6fb7698 100644 --- a/frontend/components/CalculatorNav.tsx +++ b/frontend/components/CalculatorNav.tsx @@ -44,11 +44,11 @@ export default function CalculatorNav() { }; return ( -
-

- Выберите калькулятор: +
+

+ 🔄 Переключить калькулятор:

-
+
{calculators.map((calc) => { const isActive = pathname === `/${calc.id}`; @@ -56,19 +56,24 @@ export default function CalculatorNav() { ); })}
+

+ Нажмите на кнопку выше, чтобы переключиться между калькуляторами +

); } diff --git a/frontend/components/CostBlock.tsx b/frontend/components/CostBlock.tsx new file mode 100644 index 0000000..5cb4020 --- /dev/null +++ b/frontend/components/CostBlock.tsx @@ -0,0 +1,24 @@ +// components/CostBlock.tsx +// Блок отображения промежуточной стоимости + +type CostBlockProps = { + title: string; + value: number; + highlight?: boolean; +}; + +export default function CostBlock({ title, value, highlight = false }: CostBlockProps) { + return ( +
+ {title}: {value.toFixed(1)} руб +
+ ); +} + diff --git a/frontend/components/DynamicCalculator.tsx b/frontend/components/DynamicCalculator.tsx index 85c2b9c..a1bfbe9 100644 --- a/frontend/components/DynamicCalculator.tsx +++ b/frontend/components/DynamicCalculator.tsx @@ -9,6 +9,7 @@ import { getCalculator } from '@/lib/calculators'; import { submitCalculator } from '@/lib/api'; import FormField from './FormField'; import CalculatorNav from './CalculatorNav'; +import CostBlock from './CostBlock'; import type { Calculator } from '@/types/calculator'; interface DynamicCalculatorProps { @@ -175,7 +176,21 @@ export default function DynamicCalculator({ calculatorType }: DynamicCalculatorP // Обработка отправки формы const handleSubmit = async (e: FormEvent) => { e.preventDefault(); - if (!chatId) { + + // Получаем chat_id из разных источников + let currentChatId = chatId; + if (!currentChatId && typeof window !== 'undefined') { + const params = new URLSearchParams(window.location.search); + currentChatId = params.get('chat_id'); + if (!currentChatId && typeof Storage !== 'undefined') { + currentChatId = sessionStorage.getItem('chat_id'); + } + if (currentChatId) { + setChatId(currentChatId); + } + } + + if (!currentChatId) { alert('❗ Не найден chat_id. Откройте калькулятор через Telegram-бота.'); return; } @@ -218,7 +233,7 @@ export default function DynamicCalculator({ calculatorType }: DynamicCalculatorP // Отправка на backend const response = await submitCalculator( calculatorType, - chatId, + currentChatId!, submitData, photoFile ); @@ -273,55 +288,141 @@ export default function DynamicCalculator({ calculatorType }: DynamicCalculatorP Калькулятор: {calculator.name}

- {/* Основные поля (без группы) */} - {groupedFields.general && ( -
- {groupedFields.general.map((field) => ( - updateField(field.name, value)} + {/* Специфичная структура для мыла с промежуточными стоимостями */} + {calculator.id === 'soap' && ( + <> + {/* Название мыла */} + f.name === 'soapName')!} + value={formData.soapName || ''} + onChange={(value) => updateField('soapName', value)} + /> + + {/* Фото */} +
+ + {photoFile && ( + Предпросмотр мыла + )} + + {/* Блок «Основа» */} +
+ f.name === 'weight')!} + value={formData.weight || ''} + onChange={(value) => updateField('weight', value)} + /> + f.name === 'basePrice')!} + value={formData.basePrice || ''} + onChange={(value) => updateField('basePrice', value)} + /> +
+ + + {/* Блок «Отдушка» */} +
+ f.name === 'aromaPrice')!} + value={formData.aromaPrice || ''} + onChange={(value) => updateField('aromaPrice', value)} + /> + f.name === 'aromaWeight')!} + value={formData.aromaWeight || ''} + onChange={(value) => updateField('aromaWeight', value)} + /> +
+ + + {/* Блок «Пигмент» */} +
+ f.name === 'pigmentPrice')!} + value={formData.pigmentPrice || ''} + onChange={(value) => updateField('pigmentPrice', value)} + /> + f.name === 'pigmentWeight')!} + value={formData.pigmentWeight || ''} + onChange={(value) => updateField('pigmentWeight', value)} + /> +
+ + + {/* Блок «Форма» */} + f.name === 'moldPrice')!} + value={formData.moldPrice || ''} + onChange={(value) => updateField('moldPrice', value)} + /> + + + {/* Блок «Упаковка» */} +
+ f.name === 'box')!} + value={formData.box || ''} + onChange={(value) => updateField('box', value)} + /> + f.name === 'filler')!} + value={formData.filler || ''} + onChange={(value) => updateField('filler', value)} + /> + f.name === 'ribbon')!} + value={formData.ribbon || ''} + onChange={(value) => updateField('ribbon', value)} + /> + f.name === 'labelValue')!} + value={formData.labelValue || ''} + onChange={(value) => updateField('labelValue', value)} + /> +
+ + + + + {/* Блок «Наценка и цена 100 г» */} +
+ f.name === 'markup')!} + value={formData.markup || ''} + onChange={(value) => updateField('markup', value)} + /> +
+ + + )} - {/* Поля с фото */} - - {photoFile && ( - Предпросмотр - )} - - {/* Группированные поля (например, упаковка) */} - {Object.entries(groupedFields) - .filter(([group]) => group !== 'general') - .map(([group, fields]) => ( -
-

- {group === 'packaging' ? 'Упаковка' : group} -

-
- {fields.map((field) => ( + {/* Универсальная структура для других калькуляторов */} + {calculator.id !== 'soap' && ( + <> + {/* Основные поля (без группы) */} + {groupedFields.general && ( +
+ {groupedFields.general.map((field) => ( ))}
-
- ))} + )} - {/* Блоки с результатами расчёта */} -
- {Object.entries(result) - .filter(([key]) => key !== 'total') - .map(([key, value]) => ( -
- {formatResultLabel(key)}: {value.toFixed(1)} ₽ -
- ))} -
- Итого себестоимость: {result.total.toFixed(1)} ₽ -
-
- Итоговая цена с наценкой: {finalPrice.toFixed(1)} ₽ -
- {weight > 0 && ( -
- Цена за 100 г: {pricePer100g.toFixed(1)} ₽ + {/* Поля с фото */} + + {photoFile && ( + Предпросмотр + )} + + {/* Группированные поля */} + {Object.entries(groupedFields) + .filter(([group]) => group !== 'general') + .map(([group, fields]) => ( +
+

+ {group === 'packaging' ? 'Упаковка' : group} +

+
+ {fields.map((field) => ( + updateField(field.name, value)} + /> + ))} +
+
+ ))} + + {/* Блоки с результатами расчёта */} +
+ {Object.entries(result) + .filter(([key]) => key !== 'total') + .map(([key, value]) => ( + + ))} + + + {weight > 0 && ( + + )}
- )} -
+ + )} {/* Кнопка отправки */}