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

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