fix: Восстановлена оригинальная структура формы мыла и исправлена обработка chat_id
- Восстановлена структура формы мыла с промежуточными стоимостями под каждым блоком - Добавлен компонент CostBlock для отображения промежуточных стоимостей - Улучшена обработка chat_id при отправке (проверка URL и sessionStorage) - Улучшена навигация между калькуляторами с более понятным интерфейсом - Добавлена подсказка для пользователя о переключении калькуляторов
This commit is contained in:
parent
186c85738d
commit
fc7c42861c
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
24
frontend/components/CostBlock.tsx
Normal file
24
frontend/components/CostBlock.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user