fix: Восстановлена оригинальная структура формы мыла и исправлена обработка chat_id

- Восстановлена структура формы мыла с промежуточными стоимостями под каждым блоком
- Добавлен компонент CostBlock для отображения промежуточных стоимостей
- Улучшена обработка chat_id при отправке (проверка URL и sessionStorage)
- Улучшена навигация между калькуляторами с более понятным интерфейсом
- Добавлена подсказка для пользователя о переключении калькуляторов
This commit is contained in:
dosai 2025-11-01 20:27:36 +03:00
parent 186c85738d
commit fc7c42861c
3 changed files with 250 additions and 77 deletions

View File

@ -44,11 +44,11 @@ export default function CalculatorNav() {
};
return (
<div className="mb-6 bg-gray-700 rounded-lg p-4">
<h2 className="text-lg font-semibold text-gray-200 mb-3">
Выберите калькулятор:
<div className="mb-6 bg-gray-700 rounded-lg p-4 border-2 border-gray-600">
<h2 className="text-lg font-semibold text-gray-200 mb-3 text-center">
🔄 Переключить калькулятор:
</h2>
<div className="grid grid-cols-2 gap-2">
<div className="grid grid-cols-2 gap-3">
{calculators.map((calc) => {
const isActive = pathname === `/${calc.id}`;
@ -56,19 +56,24 @@ export default function CalculatorNav() {
<button
key={calc.id}
onClick={() => handleCalculatorChange(calc.id)}
type="button"
className={`
px-4 py-2 rounded-md text-center font-medium transition-colors
px-4 py-3 rounded-md text-center font-semibold transition-all
${isActive
? 'bg-sky-500 text-gray-100'
: 'bg-gray-600 text-gray-200 hover:bg-gray-500 active:bg-gray-400'
? 'bg-sky-500 text-gray-100 shadow-lg scale-105'
: 'bg-gray-600 text-gray-200 hover:bg-gray-500 active:bg-gray-400 hover:scale-102'
}
`}
>
{isActive && '✓ '}
{calc.name}
</button>
);
})}
</div>
<p className="text-xs text-gray-400 mt-2 text-center">
Нажмите на кнопку выше, чтобы переключиться между калькуляторами
</p>
</div>
);
}

View File

@ -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 (
<div
className={`
p-2
${highlight ? 'bg-gray-600 font-semibold' : 'bg-gray-700'}
text-gray-200
rounded
`}
>
{title}: {value.toFixed(1)} руб
</div>
);
}

View File

@ -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,6 +288,137 @@ export default function DynamicCalculator({ calculatorType }: DynamicCalculatorP
Калькулятор: {calculator.name}
</h1>
{/* Специфичная структура для мыла с промежуточными стоимостями */}
{calculator.id === 'soap' && (
<>
{/* Название мыла */}
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'soapName')!}
value={formData.soapName || ''}
onChange={(value) => updateField('soapName', value)}
/>
{/* Фото */}
<label className="flex flex-col gap-1">
<span className="text-gray-300">Фото мыла (необязательно)</span>
<input
type="file"
accept="image/*"
onChange={handlePhotoChange}
className="
bg-gray-700
text-gray-200
rounded-md
border border-gray-600
p-2
focus:outline-none focus:border-sky-500
"
/>
</label>
{photoFile && (
<img
src={URL.createObjectURL(photoFile)}
alt="Предпросмотр мыла"
className="w-32 h-32 object-cover rounded-lg border border-sky-400"
/>
)}
{/* Блок «Основа» */}
<div className="grid grid-cols-2 gap-4">
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'weight')!}
value={formData.weight || ''}
onChange={(value) => updateField('weight', value)}
/>
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'basePrice')!}
value={formData.basePrice || ''}
onChange={(value) => updateField('basePrice', value)}
/>
</div>
<CostBlock title="Себестоимость основы" value={result.base} />
{/* Блок «Отдушка» */}
<div className="grid grid-cols-2 gap-4">
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'aromaPrice')!}
value={formData.aromaPrice || ''}
onChange={(value) => updateField('aromaPrice', value)}
/>
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'aromaWeight')!}
value={formData.aromaWeight || ''}
onChange={(value) => updateField('aromaWeight', value)}
/>
</div>
<CostBlock title="Себестоимость отдушки (1 %)" value={result.aroma} />
{/* Блок «Пигмент» */}
<div className="grid grid-cols-2 gap-4">
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'pigmentPrice')!}
value={formData.pigmentPrice || ''}
onChange={(value) => updateField('pigmentPrice', value)}
/>
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'pigmentWeight')!}
value={formData.pigmentWeight || ''}
onChange={(value) => updateField('pigmentWeight', value)}
/>
</div>
<CostBlock title="Себестоимость пигмента (0.5 %)" value={result.pigment} />
{/* Блок «Форма» */}
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'moldPrice')!}
value={formData.moldPrice || ''}
onChange={(value) => updateField('moldPrice', value)}
/>
<CostBlock title="Себестоимость формы" value={result.mold} />
{/* Блок «Упаковка» */}
<div className="grid grid-cols-2 gap-4">
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'box')!}
value={formData.box || ''}
onChange={(value) => updateField('box', value)}
/>
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'filler')!}
value={formData.filler || ''}
onChange={(value) => updateField('filler', value)}
/>
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'ribbon')!}
value={formData.ribbon || ''}
onChange={(value) => updateField('ribbon', value)}
/>
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'labelValue')!}
value={formData.labelValue || ''}
onChange={(value) => updateField('labelValue', value)}
/>
</div>
<CostBlock title="Стоимость упаковки" value={result.packaging} />
<CostBlock title="Операционные расходы (5 %)" value={result.operational} />
<CostBlock title="Итого себестоимость" value={result.total} highlight />
{/* Блок «Наценка и цена 100 г» */}
<div className="grid grid-cols-2 gap-4">
<FormField
field={calculator.fieldSchema.find((f) => f.name === 'markup')!}
value={formData.markup || ''}
onChange={(value) => updateField('markup', value)}
/>
</div>
<CostBlock title="Итоговая цена с наценкой" value={finalPrice} />
<CostBlock title="Цена за 100 г" value={pricePer100g} />
</>
)}
{/* Универсальная структура для других калькуляторов */}
{calculator.id !== 'soap' && (
<>
{/* Основные поля (без группы) */}
{groupedFields.general && (
<div className="space-y-4">
@ -312,7 +458,7 @@ export default function DynamicCalculator({ calculatorType }: DynamicCalculatorP
/>
)}
{/* Группированные поля (например, упаковка) */}
{/* Группированные поля */}
{Object.entries(groupedFields)
.filter(([group]) => group !== 'general')
.map(([group, fields]) => (
@ -338,22 +484,20 @@ export default function DynamicCalculator({ calculatorType }: DynamicCalculatorP
{Object.entries(result)
.filter(([key]) => key !== 'total')
.map(([key, value]) => (
<div key={key} className="p-2 bg-gray-700 text-gray-200 rounded">
{formatResultLabel(key)}: {value.toFixed(1)}
</div>
<CostBlock
key={key}
title={formatResultLabel(key)}
value={value}
/>
))}
<div className="p-2 bg-gray-600 font-semibold text-gray-200 rounded">
Итого себестоимость: {result.total.toFixed(1)}
</div>
<div className="p-2 bg-gray-700 text-gray-200 rounded">
Итоговая цена с наценкой: {finalPrice.toFixed(1)}
</div>
<CostBlock title="Итого себестоимость" value={result.total} highlight />
<CostBlock title="Итоговая цена с наценкой" value={finalPrice} />
{weight > 0 && (
<div className="p-2 bg-gray-700 text-gray-200 rounded">
Цена за 100 г: {pricePer100g.toFixed(1)}
</div>
<CostBlock title="Цена за 100 г" value={pricePer100g} />
)}
</div>
</>
)}
{/* Кнопка отправки */}
<button