Модалка редактирования фасовки в обучении

This commit is contained in:
2025-12-17 22:11:32 +03:00
parent c8aab42e8e
commit 47ec8094e5

View File

@@ -1,13 +1,14 @@
import React, { useState, useMemo } from 'react';
import { Card, Button, Flex, AutoComplete, InputNumber, Typography, Select } from 'antd';
import { Card, Button, Flex, AutoComplete, InputNumber, Typography, Select, Divider } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { CatalogSelect } from './CatalogSelect';
import type { CatalogItem, UnmatchedItem, ProductSearchResult } from '../../services/types';
import { CreateContainerModal } from '../invoices/CreateContainerModal'; // Импортируем модалку
import type { CatalogItem, UnmatchedItem, ProductSearchResult, ProductContainer } from '../../services/types';
const { Text } = Typography;
interface Props {
catalog: CatalogItem[]; // Оставляем для совместимости, но CatalogSelect его больше не использует
catalog: CatalogItem[];
unmatched?: UnmatchedItem[];
onSave: (rawName: string, productId: string, quantity: number, containerId?: string) => void;
isLoading: boolean;
@@ -15,13 +16,19 @@ interface Props {
export const AddMatchForm: React.FC<Props> = ({ catalog, unmatched = [], onSave, isLoading }) => {
const [rawName, setRawName] = useState('');
// Состояния товара
const [selectedProduct, setSelectedProduct] = useState<string | undefined>(undefined);
// Сохраняем полный объект товара, полученный из поиска, чтобы иметь доступ к containers
const [selectedProductData, setSelectedProductData] = useState<ProductSearchResult | undefined>(undefined);
const [quantity, setQuantity] = useState<number | null>(1);
const [selectedContainer, setSelectedContainer] = useState<string | null>(null);
// Состояние модалки создания фасовки
const [isModalOpen, setIsModalOpen] = useState(false);
// --- Вычисляемые значения ---
const unmatchedOptions = useMemo(() => {
return unmatched.map(item => ({
value: item.raw_name,
@@ -29,32 +36,24 @@ export const AddMatchForm: React.FC<Props> = ({ catalog, unmatched = [], onSave,
}));
}, [unmatched]);
// Вычисляем активный товар: либо из результатов поиска, либо ищем в старом каталоге (если он передан)
// Активный продукт (из поиска или из старого каталога)
const activeProduct = useMemo(() => {
if (selectedProductData) return selectedProductData;
if (selectedProduct && catalog.length > 0) {
// Приводим типы, так как CatalogItem расширяет ProductSearchResult
return catalog.find(item => item.id === selectedProduct) as unknown as ProductSearchResult;
}
return null;
return undefined;
}, [selectedProduct, selectedProductData, catalog]);
// Хендлер смены товара: принимаем и ID, и объект
const handleProductChange = (val: string, productObj?: ProductSearchResult) => {
setSelectedProduct(val);
if (productObj) {
setSelectedProductData(productObj);
}
setSelectedContainer(null);
};
// Список контейнеров текущего товара
const containers = useMemo(() => {
return activeProduct?.containers || [];
}, [activeProduct]);
// Берем единицу измерения с учетом новой структуры (main_unit)
// Базовая единица
const baseUom = activeProduct?.main_unit?.name || activeProduct?.measure_unit || 'ед.';
// Текстовое отображение текущей единицы
const currentUomName = useMemo(() => {
if (selectedContainer) {
const cont = containers.find(c => c.id === selectedContainer);
@@ -63,10 +62,23 @@ export const AddMatchForm: React.FC<Props> = ({ catalog, unmatched = [], onSave,
return baseUom;
}, [selectedContainer, containers, baseUom]);
const isButtonDisabled = !rawName.trim() || !selectedProduct || !quantity || quantity <= 0 || isLoading;
// --- Хендлеры ---
const handleProductChange = (val: string, productObj?: ProductSearchResult) => {
setSelectedProduct(val);
if (productObj) {
setSelectedProductData(productObj);
}
setSelectedContainer(null);
};
const handleSubmit = () => {
if (rawName.trim() && selectedProduct && quantity && quantity > 0) {
onSave(rawName, selectedProduct, quantity, selectedContainer || undefined);
// Сброс формы
setRawName('');
setSelectedProduct(undefined);
setSelectedProductData(undefined);
@@ -75,11 +87,32 @@ export const AddMatchForm: React.FC<Props> = ({ catalog, unmatched = [], onSave,
}
};
const isButtonDisabled = !rawName.trim() || !selectedProduct || !quantity || quantity <= 0 || isLoading;
// Обработка создания новой фасовки
const handleContainerCreated = (newContainer: ProductContainer) => {
setIsModalOpen(false);
// 1. Обновляем локальные данные продукта, добавляя новую фасовку в список
if (selectedProductData) {
setSelectedProductData({
...selectedProductData,
containers: [...(selectedProductData.containers || []), newContainer]
});
} else if (activeProduct) {
// Если продукт был взят из общего catalog, создаем локальную копию
setSelectedProductData({
...activeProduct,
containers: [...(activeProduct.containers || []), newContainer]
});
}
// 2. Сразу выбираем созданную фасовку
setSelectedContainer(newContainer.id);
};
return (
<Card title="Добавить новую связь" size="small" style={{ marginBottom: 16 }}>
<Flex vertical gap="middle">
{/* Поле текста из чека */}
<div>
<div style={{ marginBottom: 4, fontSize: 12, color: '#888' }}>Текст из чека:</div>
<AutoComplete
@@ -94,23 +127,25 @@ export const AddMatchForm: React.FC<Props> = ({ catalog, unmatched = [], onSave,
/>
</div>
{/* Выбор товара (Поиск) */}
<div>
<div style={{ marginBottom: 4, fontSize: 12, color: '#888' }}>Товар в iiko:</div>
<CatalogSelect
// Удален проп catalog={catalog}, так как компонент теперь ищет товары сам
value={selectedProduct}
onChange={handleProductChange}
disabled={isLoading}
/>
</div>
{containers.length > 0 && (
{/* Выбор фасовки (появляется только если есть активный товар) */}
{activeProduct && (
<div>
<div style={{ marginBottom: 4, fontSize: 12, color: '#888' }}>Единица измерения / Фасовка:</div>
<Select
style={{ width: '100%' }}
value={selectedContainer}
onChange={setSelectedContainer}
placeholder="Выберите ед. измерения"
options={[
{ value: null, label: `Базовая единица (${baseUom})` },
...containers.map(c => ({
@@ -118,10 +153,27 @@ export const AddMatchForm: React.FC<Props> = ({ catalog, unmatched = [], onSave,
label: `${c.name} (=${Number(c.count)} ${baseUom})`
}))
]}
// Рендерим кнопку добавления внизу выпадающего списка
dropdownRender={(menu) => (
<>
{menu}
<Divider style={{ margin: '4px 0' }} />
<Button
type="text"
block
icon={<PlusOutlined />}
onClick={() => setIsModalOpen(true)}
style={{ textAlign: 'left' }}
>
Добавить фасовку
</Button>
</>
)}
/>
</div>
)}
{/* Количество */}
<div>
<div style={{ marginBottom: 4, fontSize: 12, color: '#888' }}>
Количество (в выбранных единицах):
@@ -139,6 +191,7 @@ export const AddMatchForm: React.FC<Props> = ({ catalog, unmatched = [], onSave,
</div>
</div>
{/* Кнопка сохранения */}
<Button
type="primary"
icon={<PlusOutlined />}
@@ -150,6 +203,17 @@ export const AddMatchForm: React.FC<Props> = ({ catalog, unmatched = [], onSave,
Сохранить связь
</Button>
</Flex>
{/* Модальное окно создания фасовки */}
{activeProduct && (
<CreateContainerModal
visible={isModalOpen}
onCancel={() => setIsModalOpen(false)}
productId={activeProduct.id}
productBaseUnit={baseUom}
onSuccess={handleContainerCreated}
/>
)}
</Card>
);
};