mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-05 03:12:34 -06:00
Модалка редактирования фасовки в обучении
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user