feat: scanning mode on receipt page

This commit is contained in:
2025-01-23 17:57:28 +04:00
parent f62d2662d6
commit 4500422f82
22 changed files with 676 additions and 91 deletions

View File

@@ -33,6 +33,7 @@ import AssignUserModal from "../pages/LeadsPage/tabs/EmployeesTab/modals/AssignU
import ResidualProductModal from "../pages/ResiduesPage/modals/ResidualProductModal/ResidualProductModal.tsx"; import ResidualProductModal from "../pages/ResiduesPage/modals/ResidualProductModal/ResidualProductModal.tsx";
import NewReceiptModal from "../pages/ReceiptPage/components/NewReceipt/modals/NewReceiptModal.tsx"; import NewReceiptModal from "../pages/ReceiptPage/components/NewReceipt/modals/NewReceiptModal.tsx";
import ReceiptModal from "../pages/ReceiptPage/components/ReceiptEditing/modals/ReceiptModal.tsx"; import ReceiptModal from "../pages/ReceiptPage/components/ReceiptEditing/modals/ReceiptModal.tsx";
import SelectScannedProductModal from "../pages/ReceiptPage/modals/SelectScannedProductModal.tsx";
export const modals = { export const modals = {
enterDeadline: EnterDeadlineModal, enterDeadline: EnterDeadlineModal,
@@ -63,6 +64,7 @@ export const modals = {
transactionTagsModal: TransactionTagsModal, transactionTagsModal: TransactionTagsModal,
shippingProductModal: ShippingProductModal, shippingProductModal: ShippingProductModal,
residualProductModal: ResidualProductModal, residualProductModal: ResidualProductModal,
selectScannedProductModal: SelectScannedProductModal,
newReceiptModal: NewReceiptModal, newReceiptModal: NewReceiptModal,
receiptModal: ReceiptModal, receiptModal: ReceiptModal,
departmentModal: DepartmentModal, departmentModal: DepartmentModal,

View File

@@ -1,5 +1,5 @@
import { Accordion, ActionIcon, Center } from "@mantine/core"; import { Accordion, ActionIcon, Center } from "@mantine/core";
import { IconBox, IconPlus, IconTrash } from "@tabler/icons-react"; import { IconBarcode, IconBox, IconPlayerPause, IconPlus, IconTrash } from "@tabler/icons-react";
import { ReceiptBox, ReceiptPallet } from "../types/types.tsx"; import { ReceiptBox, ReceiptPallet } from "../types/types.tsx";
import { useReceiptContext } from "../contexts/ReceiptContext.tsx"; import { useReceiptContext } from "../contexts/ReceiptContext.tsx";
import ReceiptProducts from "./ReceiptProducts.tsx"; import ReceiptProducts from "./ReceiptProducts.tsx";
@@ -10,7 +10,17 @@ type Props = {
} }
const AccordionBoxes = ({ pallet, palletIdx }: Props) => { const AccordionBoxes = ({ pallet, palletIdx }: Props) => {
const { boxes, boxesHandlers, onObjectEditClick, palletsHandlers, setBoxData } = useReceiptContext(); const {
boxes,
boxesHandlers,
onObjectEditClick,
palletsHandlers,
setBoxData,
scanningData,
toggleScanning,
} = useReceiptContext();
const { boxId, isScanning } = scanningData;
const deleteBox = (boxId: number) => { const deleteBox = (boxId: number) => {
if (palletIdx && pallet) { if (palletIdx && pallet) {
@@ -23,9 +33,22 @@ const AccordionBoxes = ({ pallet, palletIdx }: Props) => {
const boxActions = (box: ReceiptBox) => { const boxActions = (box: ReceiptBox) => {
return ( return (
<> <>
<ActionIcon
variant={"default"}
onClick={() => toggleScanning(box.id, pallet?.id)}
mr={"sm"}
disabled={isScanning && box.id !== boxId}
>
{isScanning && box.id === boxId ? (
<IconPlayerPause />
) : (
<IconBarcode />
)}
</ActionIcon>
<ActionIcon <ActionIcon
variant={"default"} variant={"default"}
onClick={() => onObjectEditClick(box, true, pallet)} onClick={() => onObjectEditClick(box, true, pallet)}
disabled={isScanning}
mr={"sm"} mr={"sm"}
> >
<IconPlus /> <IconPlus />
@@ -33,6 +56,7 @@ const AccordionBoxes = ({ pallet, palletIdx }: Props) => {
<ActionIcon <ActionIcon
variant={"default"} variant={"default"}
onClick={() => deleteBox(box.id)} onClick={() => deleteBox(box.id)}
disabled={isScanning}
mr={"sm"} mr={"sm"}
> >
<IconTrash /> <IconTrash />
@@ -60,6 +84,7 @@ const AccordionBoxes = ({ pallet, palletIdx }: Props) => {
products={box.residualProducts} products={box.residualProducts}
object={box} object={box}
setObjectData={(box: ReceiptBox) => setBoxData(box, pallet)} setObjectData={(box: ReceiptBox) => setBoxData(box, pallet)}
disabled={isScanning}
/> />
</Accordion.Panel> </Accordion.Panel>
</Accordion.Item> </Accordion.Item>

View File

@@ -1,12 +1,22 @@
import { Accordion, ActionIcon, Box, Button, Center, Flex, Group } from "@mantine/core"; import { Accordion, ActionIcon, Box, Button, Center, Flex, Group } from "@mantine/core";
import { IconPlus, IconSpace, IconTrash } from "@tabler/icons-react"; import { IconBarcode, IconPlayerPause, IconPlus, IconSpace, IconTrash } from "@tabler/icons-react";
import { ReceiptBox, ReceiptPallet } from "../types/types.tsx"; import { ReceiptBox, ReceiptPallet } from "../types/types.tsx";
import { useReceiptContext } from "../contexts/ReceiptContext.tsx"; import { useReceiptContext } from "../contexts/ReceiptContext.tsx";
import AccordionBoxes from "./AccordionBoxes.tsx"; import AccordionBoxes from "./AccordionBoxes.tsx";
import ReceiptProducts from "./ReceiptProducts.tsx"; import ReceiptProducts from "./ReceiptProducts.tsx";
const AccordionPallets = () => { const AccordionPallets = () => {
const { pallets, palletsHandlers, nextId, onObjectEditClick, setPalletData } = useReceiptContext(); const {
pallets,
palletsHandlers,
nextId,
onObjectEditClick,
setPalletData,
scanningData,
toggleScanning,
} = useReceiptContext();
const { palletId, boxId, isScanning } = scanningData;
const deletePallet = (palletId: number) => { const deletePallet = (palletId: number) => {
palletsHandlers.filter(item => item.id !== palletId); palletsHandlers.filter(item => item.id !== palletId);
@@ -23,25 +33,44 @@ const AccordionPallets = () => {
}; };
const palletActions = (pallet: ReceiptPallet) => { const palletActions = (pallet: ReceiptPallet) => {
const isScanModeVisible = pallet.boxes.length === 0;
const isCreateButtonVisible = pallet.boxes.length > 0 || pallet.residualProducts.length > 0; const isCreateButtonVisible = pallet.boxes.length > 0 || pallet.residualProducts.length > 0;
return ( return (
<> <>
{isCreateButtonVisible && <ActionIcon {isScanModeVisible && (
variant={"default"} <ActionIcon
onClick={() => { variant={"default"}
if (pallet.residualProducts.length > 0) { onClick={() => toggleScanning(undefined, pallet.id)}
onObjectEditClick(pallet, false); mr={"sm"}
} else { disabled={isScanning && (pallet.id !== palletId || !!boxId)}
createBox(pallet); >
} {isScanning && pallet.id === palletId && !boxId ? (
}} <IconPlayerPause />
mr={"sm"} ) : (
> <IconBarcode />
<IconPlus /> )}
</ActionIcon>} </ActionIcon>
)}
{isCreateButtonVisible && (
<ActionIcon
variant={"default"}
onClick={() => {
if (pallet.residualProducts.length > 0) {
onObjectEditClick(pallet, false);
} else {
createBox(pallet);
}
}}
disabled={isScanning}
mr={"sm"}
>
<IconPlus />
</ActionIcon>
)}
<ActionIcon <ActionIcon
variant={"default"} variant={"default"}
onClick={() => deletePallet(pallet.id)} onClick={() => deletePallet(pallet.id)}
disabled={isScanning}
mr={"md"} mr={"md"}
> >
<IconTrash /> <IconTrash />
@@ -55,6 +84,7 @@ const AccordionPallets = () => {
<Button <Button
variant={"default"} variant={"default"}
onClick={onClick} onClick={onClick}
disabled={isScanning}
flex={1} flex={1}
> >
<Group gap={"md"}> <Group gap={"md"}>
@@ -98,6 +128,7 @@ const AccordionPallets = () => {
products={pallet.residualProducts} products={pallet.residualProducts}
object={pallet} object={pallet}
setObjectData={object => setPalletData(object as ReceiptPallet)} setObjectData={object => setPalletData(object as ReceiptPallet)}
disabled={isScanning}
/> />
)} )}
</Accordion.Panel> </Accordion.Panel>

View File

@@ -7,7 +7,15 @@ import InlineButton from "../../../../../components/InlineButton/InlineButton.ts
import { IconPlus } from "@tabler/icons-react"; import { IconPlus } from "@tabler/icons-react";
const ReceiptEditor = () => { const ReceiptEditor = () => {
const { fixed, palletsHandlers, boxesHandlers, nextId } = useReceiptContext(); const {
fixed,
palletsHandlers,
boxesHandlers,
nextId,
scanningData,
} = useReceiptContext();
const { isScanning } = scanningData;
if (!fixed) return; if (!fixed) return;
@@ -31,11 +39,19 @@ const ReceiptEditor = () => {
return ( return (
<Stack> <Stack>
<Flex gap="md"> <Flex gap="md">
<InlineButton onClick={() => createPallet()} flex={1}> <InlineButton
onClick={() => createPallet()}
flex={1}
disabled={isScanning}
>
<IconPlus /> <IconPlus />
Паллет Паллет
</InlineButton> </InlineButton>
<InlineButton onClick={() => createBox()} flex={1}> <InlineButton
onClick={() => createBox()}
flex={1}
disabled={isScanning}
>
<IconPlus /> <IconPlus />
Короб Короб
</InlineButton> </InlineButton>

View File

@@ -6,9 +6,10 @@ type Props = {
products: ReceiptProduct[]; products: ReceiptProduct[];
object: ReceiptBox | ReceiptPallet; object: ReceiptBox | ReceiptPallet;
setObjectData: (object: ReceiptBox | ReceiptPallet) => void; setObjectData: (object: ReceiptBox | ReceiptPallet) => void;
disabled: boolean;
} }
const ReceiptProducts = ({ products, object, setObjectData }: Props) => { const ReceiptProducts = ({ products, object, setObjectData, disabled }: Props) => {
if (products.length === 0) return; if (products.length === 0) return;
const deleteProduct = (productId: number) => { const deleteProduct = (productId: number) => {
@@ -31,6 +32,7 @@ const ReceiptProducts = ({ products, object, setObjectData }: Props) => {
allowDecimal={false} allowDecimal={false}
allowNegative={false} allowNegative={false}
w={rem(100)} w={rem(100)}
disabled={disabled}
/> />
</Group> </Group>
); );
@@ -50,6 +52,7 @@ const ReceiptProducts = ({ products, object, setObjectData }: Props) => {
<ActionIcon <ActionIcon
variant={"default"} variant={"default"}
onClick={() => deleteProduct(receiptProduct.id)} onClick={() => deleteProduct(receiptProduct.id)}
disabled={disabled}
> >
<IconTrash /> <IconTrash />
</ActionIcon> </ActionIcon>

View File

@@ -4,21 +4,29 @@ import { ReceiptBox, ReceiptPallet } from "../types/types.tsx";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import nextId from "../utils/nextId.ts"; import nextId from "../utils/nextId.ts";
import { useListState, UseListStateHandlers } from "@mantine/hooks"; import { useListState, UseListStateHandlers } from "@mantine/hooks";
import useScanningMode, { ScanningData } from "../../../hooks/useScanningMode.tsx";
import useBarcodesProductsMap from "../../../hooks/useBarcodesProductsMap.tsx";
import useApplyingScanningResult from "../hooks/useApplyingScannedResult.tsx";
type ReceiptContextState = { type ReceiptContextState = {
client?: ClientSchema; client?: ClientSchema;
setClient: Dispatch<SetStateAction<ClientSchema | undefined>>; setClient: Dispatch<SetStateAction<ClientSchema | undefined>>;
fixed: boolean; fixed: boolean;
setFixed: Dispatch<SetStateAction<boolean>>; setFixed: Dispatch<SetStateAction<boolean>>;
pallets: ReceiptPallet[]; pallets: ReceiptPallet[];
palletsHandlers: UseListStateHandlers<ReceiptPallet>; palletsHandlers: UseListStateHandlers<ReceiptPallet>;
boxes: ReceiptBox[]; boxes: ReceiptBox[];
boxesHandlers: UseListStateHandlers<ReceiptBox>; boxesHandlers: UseListStateHandlers<ReceiptBox>;
nextId: () => number; nextId: () => number;
onObjectEditClick: (object: ReceiptBox | ReceiptPallet, isBox: boolean, parentPallet?: ReceiptPallet) => void; onObjectEditClick: (object: ReceiptBox | ReceiptPallet, isBox: boolean, parentPallet?: ReceiptPallet) => void;
reset: () => void; reset: () => void;
setBoxData: (box: ReceiptBox, pallet?: ReceiptPallet) => void; setBoxData: (box: ReceiptBox, pallet?: ReceiptPallet) => void;
setPalletData: (pallet: ReceiptPallet) => void; setPalletData: (pallet: ReceiptPallet) => void;
scanningData: ScanningData;
toggleScanning: (boxId?: number, palletId?: number) => void;
}; };
const ReceiptContext = createContext<ReceiptContextState | undefined>( const ReceiptContext = createContext<ReceiptContextState | undefined>(
@@ -32,6 +40,8 @@ const useReceiptContextState = () => {
const [pallets, palletsHandlers] = useListState<ReceiptPallet>([]); const [pallets, palletsHandlers] = useListState<ReceiptPallet>([]);
const [boxes, boxesHandlers] = useListState<ReceiptBox>([]); const [boxes, boxesHandlers] = useListState<ReceiptBox>([]);
const { barcodesProductsMap } = useBarcodesProductsMap({ clientId: client?.id });
const setBoxData = (box: ReceiptBox, pallet?: ReceiptPallet) => { const setBoxData = (box: ReceiptBox, pallet?: ReceiptPallet) => {
if (pallet) { if (pallet) {
setBoxOnPalletData(box, pallet); setBoxOnPalletData(box, pallet);
@@ -69,6 +79,15 @@ const useReceiptContextState = () => {
} }
}; };
const editObject = (
object: ReceiptBox | ReceiptPallet,
isBox: boolean,
parentPallet?: ReceiptPallet,
) => {
if (isBox) setBoxData(object, parentPallet);
setPalletData(object as ReceiptPallet);
};
const onObjectEditClick = ( const onObjectEditClick = (
object: ReceiptBox | ReceiptPallet, object: ReceiptBox | ReceiptPallet,
isBox: boolean, isBox: boolean,
@@ -83,13 +102,24 @@ const useReceiptContextState = () => {
clientId: client.id, clientId: client.id,
object: object, object: object,
setObjectData: (object: ReceiptBox | ReceiptPallet) => { setObjectData: (object: ReceiptBox | ReceiptPallet) => {
if (isBox) setBoxData(object, parentPallet); editObject(object, isBox, parentPallet);
setPalletData(object as ReceiptPallet);
}, },
barcodesProductsMap,
}, },
}); });
}; };
const {
onScanningFinish,
} = useApplyingScanningResult({
pallets,
boxes,
barcodesProductsMap,
editObject,
});
const { scanningData, toggleScanning } = useScanningMode({ onScanningFinish });
const reset = () => { const reset = () => {
palletsHandlers.setState([]); palletsHandlers.setState([]);
boxesHandlers.setState([]); boxesHandlers.setState([]);
@@ -111,6 +141,8 @@ const useReceiptContextState = () => {
reset, reset,
setBoxData, setBoxData,
setPalletData, setPalletData,
scanningData,
toggleScanning,
}; };
}; };

View File

@@ -0,0 +1,105 @@
import { ReceiptBox, ReceiptPallet } from "../types/types.tsx";
import { notifications } from "../../../../../shared/lib/notifications.ts";
import { ProductSchema } from "../../../../../client";
import findProductInObject from "../utils/findProductInObject.tsx";
import nextId from "../utils/nextId.ts";
import { modals } from "@mantine/modals";
type Props = {
pallets: ReceiptPallet[];
boxes: ReceiptBox[];
barcodesProductsMap: Map<string, ProductSchema[]>;
editObject: (
object: ReceiptBox | ReceiptPallet,
isBox: boolean,
parentPallet?: ReceiptPallet,
) => void;
}
const useApplyingScannedResult = ({
pallets,
boxes,
barcodesProductsMap,
editObject,
}: Props) => {
let isBox = false;
let object: ReceiptPallet | ReceiptBox;
let parentPallet: ReceiptPallet | undefined;
const onProductSelect = (product: ProductSchema) => {
const productIdx = object.residualProducts.findIndex(p => p.product.id === product.id);
if (productIdx === -1) {
object.residualProducts.unshift({
id: nextId(),
quantity: 1,
product,
});
} else {
object.residualProducts[productIdx].quantity += 1;
}
editObject(object, isBox, parentPallet);
};
const showSelectProductModal = (productsToSelect: ProductSchema[]) => {
modals.openContextModal({
modal: "selectScannedProductModal",
title: "Выберите товар для данного штрихкода",
withCloseButton: false,
innerProps: {
productsToSelect,
onProductSelect,
},
});
};
const findProductsByBarcode = (scannedValue: string) => {
const productsToSelect = barcodesProductsMap.get(scannedValue) ?? [];
if (productsToSelect?.length === 0) {
notifications.error({ message: `Товара с штрихкодом ${scannedValue} не найдено` });
return;
}
if (productsToSelect?.length === 1) {
onProductSelect(productsToSelect[0]);
return;
}
const product = findProductInObject(object, productsToSelect);
if (product) {
onProductSelect(product);
return;
}
return showSelectProductModal(productsToSelect);
};
const onScanningFinish = (
value: string,
boxId?: number,
palletId?: number,
) => {
isBox = !!boxId;
if (boxId) {
let parentPalletIdx = -1;
if (palletId) {
parentPalletIdx = pallets.findIndex(pallet => pallet.id === palletId);
}
parentPallet = parentPalletIdx === -1 ? undefined : pallets[parentPalletIdx];
const boxesToSearch = parentPallet?.boxes ?? boxes;
const boxIdx = boxesToSearch.findIndex(box => box.id === boxId);
object = boxesToSearch[boxIdx];
findProductsByBarcode(value);
return;
}
const palletIdx = pallets.findIndex(pallet => pallet.id === palletId);
object = pallets[palletIdx];
findProductsByBarcode(value);
};
return {
onScanningFinish,
};
};
export default useApplyingScannedResult;

View File

@@ -14,17 +14,19 @@ type Props = {
clientId: number; clientId: number;
object: ReceiptBox | ReceiptPallet; object: ReceiptBox | ReceiptPallet;
setObjectData: (object: ReceiptBox | ReceiptPallet) => void; setObjectData: (object: ReceiptBox | ReceiptPallet) => void;
barcodesProductsMap: Map<string, ProductSchema[]>;
} }
const NewReceiptModal = ({ const NewReceiptModal = ({
context, context,
id, id,
innerProps, innerProps,
}: ContextModalProps<Props>) => { }: ContextModalProps<Props>) => {
const { const {
clientId, clientId,
setObjectData, setObjectData,
object, object,
barcodesProductsMap,
} = innerProps; } = innerProps;
const { isScanning, setIsScanning, scannedValue, setScannedValue } = useScanning(); const { isScanning, setIsScanning, scannedValue, setScannedValue } = useScanning();
@@ -112,12 +114,12 @@ const NewReceiptModal = ({
</form> </form>
<ScanBarcode <ScanBarcode
clientId={clientId}
isScanning={isScanning} isScanning={isScanning}
setIsScanning={setIsScanning} setIsScanning={setIsScanning}
onProductSelect={onProductAfterScanningSelect} onProductSelect={onProductAfterScanningSelect}
scannedValue={scannedValue} scannedValue={scannedValue}
object={object} object={object}
barcodesProductsMap={barcodesProductsMap}
/> />
</Flex> </Flex>
); );

View File

@@ -0,0 +1,17 @@
import { ReceiptBox, ReceiptPallet } from "../types/types.tsx";
import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema } from "../../../../../client";
const findProductInObject = (
object: ReceiptPallet | ReceiptBox | ResidualPalletSchema | ResidualBoxSchema,
productsToSelect: ProductSchema[],
): ProductSchema | undefined => {
for (let i = 0; i < productsToSelect.length; i++) {
for (let j = 0; j < object.residualProducts.length; j++) {
if (productsToSelect[i].id === object.residualProducts[j].product.id) {
return productsToSelect[i];
}
}
}
};
export default findProductInObject;

View File

@@ -1,17 +1,33 @@
import { Accordion, ActionIcon, Center } from "@mantine/core"; import { Accordion, ActionIcon, Center } from "@mantine/core";
import { IconBox, IconPlus, IconTrash } from "@tabler/icons-react"; import { IconBarcode, IconBox, IconPlayerPause, IconPlus, IconTrash } from "@tabler/icons-react";
import ReceiptProducts from "./ReceiptProducts.tsx"; import ReceiptProducts from "./ReceiptProducts.tsx";
import { ResidualBoxSchema, ResidualPalletSchema, ResiduesService } from "../../../../../client"; import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema, ResiduesService } from "../../../../../client";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import { notifications } from "../../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../../shared/lib/notifications.ts";
import { ScanningData } from "../../../hooks/useScanningMode.tsx";
type Props = { type Props = {
pallet: ResidualPalletSchema; pallet: ResidualPalletSchema;
clientId: number; clientId: number;
fetchPallet: () => void; fetchPallet: () => void;
barcodesProductsMap: Map<string, ProductSchema[]>;
scanningData: ScanningData
toggleScanning: (boxId?: number, palletId?: number) => void;
} }
const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => { const AccordionBoxesOnPallet = ({
pallet,
clientId,
fetchPallet,
barcodesProductsMap,
scanningData,
toggleScanning,
}: Props) => {
const {
isScanning,
boxId,
} = scanningData;
const deleteBox = (boxId: number) => { const deleteBox = (boxId: number) => {
ResiduesService.deleteResidualBox({ ResiduesService.deleteResidualBox({
boxId, boxId,
@@ -36,6 +52,7 @@ const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => {
isBox: true, isBox: true,
object: box, object: box,
fetchObject: fetchPallet, fetchObject: fetchPallet,
barcodesProductsMap,
}, },
}); });
}; };
@@ -43,10 +60,23 @@ const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => {
const boxActions = (box: ResidualBoxSchema) => { const boxActions = (box: ResidualBoxSchema) => {
return ( return (
<> <>
<ActionIcon
variant={"default"}
onClick={() => toggleScanning(box.id, pallet?.id)}
mr={"sm"}
disabled={isScanning && box.id !== boxId}
>
{isScanning && box.id === boxId ? (
<IconPlayerPause />
) : (
<IconBarcode />
)}
</ActionIcon>
<ActionIcon <ActionIcon
variant={"default"} variant={"default"}
onClick={() => onCreateProductClick(box)} onClick={() => onCreateProductClick(box)}
mr={"sm"} mr={"sm"}
disabled={isScanning}
> >
<IconPlus /> <IconPlus />
</ActionIcon> </ActionIcon>
@@ -54,6 +84,7 @@ const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => {
variant={"default"} variant={"default"}
onClick={() => deleteBox(box.id)} onClick={() => deleteBox(box.id)}
mr={"sm"} mr={"sm"}
disabled={isScanning}
> >
<IconTrash /> <IconTrash />
</ActionIcon> </ActionIcon>
@@ -78,6 +109,7 @@ const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => {
clientId={clientId} clientId={clientId}
object={box} object={box}
updateObject={fetchPallet} updateObject={fetchPallet}
disabled={isScanning}
/> />
</Accordion.Panel> </Accordion.Panel>
</Accordion.Item> </Accordion.Item>

View File

@@ -5,9 +5,10 @@ import { notifications } from "../../../../../shared/lib/notifications.ts";
type Props = { type Props = {
residualProduct: ResidualProductSchema; residualProduct: ResidualProductSchema;
updateObject: () => void; updateObject: () => void;
disabled: boolean;
} }
const ProductQuantityField = ({ residualProduct, updateObject }: Props) => { const ProductQuantityField = ({ residualProduct, updateObject, disabled }: Props) => {
const updateProduct = (quantity: number) => { const updateProduct = (quantity: number) => {
if (residualProduct.quantity === quantity) return; if (residualProduct.quantity === quantity) return;
ResiduesService.updateResidualProduct({ ResiduesService.updateResidualProduct({
@@ -38,6 +39,7 @@ const ProductQuantityField = ({ residualProduct, updateObject }: Props) => {
allowDecimal={false} allowDecimal={false}
allowNegative={false} allowNegative={false}
w={rem(100)} w={rem(100)}
disabled={disabled}
/> />
</Group> </Group>
); );

View File

@@ -1,8 +1,8 @@
import { Button, Flex, Stack, Text, Title } from "@mantine/core"; import { ActionIcon, Button, Flex, Stack, Text, Title } from "@mantine/core";
import useReceiptBox from "../hooks/useReceiptBox.tsx"; import useReceiptBox from "../hooks/useReceiptBox.tsx";
import ReceiptProducts from "./ReceiptProducts.tsx"; import ReceiptProducts from "./ReceiptProducts.tsx";
import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx"; import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx";
import { IconArrowLeft, IconPlus } from "@tabler/icons-react"; import { IconArrowLeft, IconBarcode, IconPlayerPause, IconPlus } from "@tabler/icons-react";
type Props = { type Props = {
boxId: number; boxId: number;
@@ -14,8 +14,12 @@ const ReceiptBoxEditor = ({ boxId }: Props) => {
fetchBox, fetchBox,
clientId, clientId,
onCreateProductClick, onCreateProductClick,
scanningData,
toggleScanning,
} = useReceiptBox({ boxId }); } = useReceiptBox({ boxId });
const { isScanning } = scanningData;
if (!box) return <Text>Короб c ID K{boxId} не найден</Text>; if (!box) return <Text>Короб c ID K{boxId} не найден</Text>;
const backButton = ( const backButton = (
@@ -23,20 +27,39 @@ const ReceiptBoxEditor = ({ boxId }: Props) => {
variant={"default"} variant={"default"}
onClick={() => window.location.reload()} onClick={() => window.location.reload()}
flex={1} flex={1}
disabled={isScanning}
> >
<IconArrowLeft /> <IconArrowLeft />
</Button> </Button>
); );
const getScanningModeAction = () => {
return (
<ActionIcon
variant={"default"}
onClick={() => toggleScanning(box?.id)}
>
{isScanning ? (
<IconPlayerPause />
) : (
<IconBarcode />
)}
</ActionIcon>
);
};
return ( return (
<Stack> <Stack>
<Flex align={"center"} flex={9} gap={"md"}> <Flex align={"center"} justify={"space-between"}>
{backButton} <Flex align={"center"} flex={9} gap={"md"}>
<Title flex={8} order={3}> {backButton}
Короб ID: К{box?.id} <Title flex={8} order={3}>
</Title> Короб ID: К{box?.id}
</Title>
</Flex>
{getScanningModeAction()}
</Flex> </Flex>
<InlineButton onClick={onCreateProductClick}> <InlineButton onClick={onCreateProductClick} disabled={isScanning}>
<IconPlus /> <IconPlus />
Товар Товар
</InlineButton> </InlineButton>
@@ -44,6 +67,7 @@ const ReceiptBoxEditor = ({ boxId }: Props) => {
object={box} object={box}
clientId={clientId} clientId={clientId}
updateObject={fetchBox} updateObject={fetchBox}
disabled={isScanning}
/> />
</Stack> </Stack>
); );

View File

@@ -1,9 +1,9 @@
import useReceiptPallet from "../hooks/useReceiptPallet.tsx"; import useReceiptPallet from "../hooks/useReceiptPallet.tsx";
import { Button, Flex, Group, Stack, Text, Title } from "@mantine/core"; import { ActionIcon, Button, Flex, Group, Stack, Text, Title } from "@mantine/core";
import { IconArrowLeft, IconPlus } from "@tabler/icons-react"; import { IconArrowLeft, IconBarcode, IconPlayerPause, IconPlus } from "@tabler/icons-react";
import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx"; import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx";
import ReceiptProducts from "./ReceiptProducts.tsx"; import ReceiptProducts from "./ReceiptProducts.tsx";
import AccordionBoxesOnPallet from "./AccordionBoxes.tsx"; import AccordionBoxesOnPallet from "./AccordionBoxesOnPallet.tsx";
type Props = { type Props = {
palletId: number; palletId: number;
@@ -16,9 +16,19 @@ const ReceiptPalletEditor = ({ palletId }: Props) => {
clientId, clientId,
onCreateProductClick, onCreateProductClick,
onCreateBoxClick, onCreateBoxClick,
barcodesProductsMap,
scanningData,
toggleScanning,
} = useReceiptPallet({ palletId }); } = useReceiptPallet({ palletId });
if (!pallet) return <Text>Паллет c ID P{palletId} не найден</Text>; if (!pallet) {
return <Text>Паллет c ID P{palletId} не найден</Text>;
}
const {
isScanning,
boxId,
} = scanningData;
const createButtons = () => { const createButtons = () => {
const isBoxes = pallet.boxes.length > 0; const isBoxes = pallet.boxes.length > 0;
@@ -27,13 +37,13 @@ const ReceiptPalletEditor = ({ palletId }: Props) => {
return ( return (
<Group> <Group>
{(isBoxes || isBoth) && ( {(isBoxes || isBoth) && (
<InlineButton onClick={onCreateBoxClick} flex={1}> <InlineButton onClick={onCreateBoxClick} flex={1} disabled={isScanning}>
<IconPlus /> <IconPlus />
Короб Короб
</InlineButton> </InlineButton>
)} )}
{(isProducts || isBoth) && ( {(isProducts || isBoth) && (
<InlineButton onClick={onCreateProductClick} flex={1}> <InlineButton onClick={onCreateProductClick} flex={1} disabled={isScanning}>
<IconPlus /> <IconPlus />
Товар Товар
</InlineButton> </InlineButton>
@@ -50,6 +60,9 @@ const ReceiptPalletEditor = ({ palletId }: Props) => {
pallet={pallet} pallet={pallet}
clientId={clientId} clientId={clientId}
fetchPallet={fetchPallet} fetchPallet={fetchPallet}
barcodesProductsMap={barcodesProductsMap}
scanningData={scanningData}
toggleScanning={toggleScanning}
/> />
); );
} }
@@ -59,6 +72,7 @@ const ReceiptPalletEditor = ({ palletId }: Props) => {
clientId={clientId} clientId={clientId}
object={pallet} object={pallet}
updateObject={fetchPallet} updateObject={fetchPallet}
disabled={isScanning}
/> />
); );
} }
@@ -74,14 +88,34 @@ const ReceiptPalletEditor = ({ palletId }: Props) => {
</Button> </Button>
); );
const getScanningModeAction = () => {
if (pallet.boxes.length !== 0) return;
return (
<ActionIcon
variant={"default"}
onClick={() => toggleScanning(undefined, pallet?.id)}
disabled={isScanning && !!boxId}
>
{isScanning && !boxId ? (
<IconPlayerPause />
) : (
<IconBarcode />
)}
</ActionIcon>
);
};
return ( return (
<Stack> <Stack>
<Flex align={"center"} flex={9} gap={"md"}> <Flex align={"center"} justify={"space-between"}>
{backButton} <Flex align={"center"} flex={9} gap={"md"}>
<Title flex={8} order={3}> {backButton}
Паллет ID: П{pallet?.id} <Title flex={8} order={3}>
</Title> Паллет ID: П{pallet?.id}
</Title>
</Flex>
{getScanningModeAction()}
</Flex> </Flex>
{createButtons()} {createButtons()}
{renderPalletData()} {renderPalletData()}

View File

@@ -9,9 +9,10 @@ type Props = {
clientId: number | null; clientId: number | null;
object: ResidualBoxSchema | ResidualPalletSchema | null; object: ResidualBoxSchema | ResidualPalletSchema | null;
updateObject: () => void; updateObject: () => void;
disabled: boolean;
} }
const ReceiptProducts = ({ clientId, object, updateObject }: Props) => { const ReceiptProducts = ({ clientId, object, updateObject, disabled }: Props) => {
if (!object || !clientId) return; if (!object || !clientId) return;
const deleteProduct = (residualProductId: number) => { const deleteProduct = (residualProductId: number) => {
@@ -37,11 +38,13 @@ const ReceiptProducts = ({ clientId, object, updateObject }: Props) => {
<ProductQuantityField <ProductQuantityField
residualProduct={residualProduct} residualProduct={residualProduct}
updateObject={updateObject} updateObject={updateObject}
disabled={disabled}
/> />
</Stack> </Stack>
<ActionIcon <ActionIcon
variant={"default"} variant={"default"}
onClick={() => deleteProduct(residualProduct.id)} onClick={() => deleteProduct(residualProduct.id)}
disabled={disabled}
> >
<IconTrash /> <IconTrash />
</ActionIcon> </ActionIcon>

View File

@@ -0,0 +1,86 @@
import { notifications } from "../../../../../shared/lib/notifications.ts";
import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema, ResiduesService } from "../../../../../client";
import { modals } from "@mantine/modals";
import findProductInObject from "../../NewReceipt/utils/findProductInObject.tsx";
type Props = {
object: ResidualBoxSchema | ResidualPalletSchema | null;
barcodesProductsMap: Map<string, ProductSchema[]>;
refetch: () => void;
}
const useApplyingScannedResult = ({
object,
barcodesProductsMap,
refetch,
}: Props) => {
let boxIdValue: number | null = null;
const onProductSelect = (product: ProductSchema) => {
if (!object) return;
ResiduesService.createResidualProduct({
requestBody: {
data: {
productId: product.id,
quantity: 1,
palletId: boxIdValue ? null : object.id,
boxId: boxIdValue,
},
},
})
.then(({ ok, message }) => {
notifications.guess(ok, { message });
refetch();
})
.catch(err => console.log(err));
};
const showSelectProductModal = (productsToSelect: ProductSchema[]) => {
modals.openContextModal({
modal: "selectScannedProductModal",
title: "Выберите товар для данного штрихкода",
withCloseButton: false,
innerProps: {
productsToSelect,
onProductSelect,
},
});
};
const onScanningFinish = (
value: string,
boxId?: number,
palletId?: number,
) => {
if (!object) return;
boxIdValue = boxId ?? null;
let objectValue: ResidualPalletSchema | ResidualBoxSchema = object;
if (palletId && boxId) {
const pallet = object as ResidualPalletSchema;
const boxOnPallet = pallet.boxes.find(box => box.id === boxId);
if (!boxOnPallet) return;
objectValue = boxOnPallet;
}
const productsToSelect = barcodesProductsMap.get(value) ?? [];
if (productsToSelect?.length === 0) {
notifications.error({ message: `Товара с штрихкодом ${value} не найдено` });
return;
}
if (productsToSelect?.length === 1) {
onProductSelect(productsToSelect[0]);
return;
}
const product = findProductInObject(objectValue, productsToSelect);
if (product) {
onProductSelect(product);
return;
}
return showSelectProductModal(productsToSelect);
};
return { onScanningFinish };
};
export default useApplyingScannedResult;

View File

@@ -1,6 +1,9 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { ResidualBoxSchema, ResiduesService } from "../../../../../client"; import { ResidualBoxSchema, ResiduesService } from "../../../../../client";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import useBarcodesProductsMap from "../../../hooks/useBarcodesProductsMap.tsx";
import useScanningMode from "../../../hooks/useScanningMode.tsx";
import useApplyingScannedResult from "./useApplyingScannedResult.tsx";
type Props = { type Props = {
@@ -27,6 +30,16 @@ const useReceiptBox = ({ boxId }: Props) => {
.catch(err => console.log(err)); .catch(err => console.log(err));
}; };
const { barcodesProductsMap } = useBarcodesProductsMap({ clientId: clientId ?? -1 });
const { onScanningFinish } = useApplyingScannedResult({
object: box,
barcodesProductsMap,
refetch: fetchBox,
});
const { scanningData, toggleScanning } = useScanningMode({ onScanningFinish });
const onCreateProductClick = () => { const onCreateProductClick = () => {
if (!(box && clientId)) return; if (!(box && clientId)) return;
modals.openContextModal({ modals.openContextModal({
@@ -38,6 +51,7 @@ const useReceiptBox = ({ boxId }: Props) => {
isBox: true, isBox: true,
object: box, object: box,
fetchObject: fetchBox, fetchObject: fetchBox,
barcodesProductsMap,
}, },
}); });
}; };
@@ -47,6 +61,8 @@ const useReceiptBox = ({ boxId }: Props) => {
fetchBox, fetchBox,
clientId, clientId,
onCreateProductClick, onCreateProductClick,
scanningData,
toggleScanning,
}; };
}; };

View File

@@ -2,6 +2,9 @@ import { useEffect, useState } from "react";
import { ResidualPalletSchema, ResiduesService } from "../../../../../client"; import { ResidualPalletSchema, ResiduesService } from "../../../../../client";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import { notifications } from "../../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../../shared/lib/notifications.ts";
import useBarcodesProductsMap from "../../../hooks/useBarcodesProductsMap.tsx";
import useApplyingScannedResult from "./useApplyingScannedResult.tsx";
import useScanningMode from "../../../hooks/useScanningMode.tsx";
type Props = { type Props = {
palletId: number; palletId: number;
@@ -27,6 +30,16 @@ const useReceiptPallet = ({ palletId }: Props) => {
.catch(err => console.log(err)); .catch(err => console.log(err));
}; };
const { barcodesProductsMap } = useBarcodesProductsMap({ clientId: clientId ?? -1 });
const { onScanningFinish } = useApplyingScannedResult({
object: pallet,
barcodesProductsMap,
refetch: fetchPallet,
});
const { scanningData, toggleScanning } = useScanningMode({ onScanningFinish });
const onCreateProductClick = () => { const onCreateProductClick = () => {
if (!(pallet && clientId)) return; if (!(pallet && clientId)) return;
modals.openContextModal({ modals.openContextModal({
@@ -38,6 +51,7 @@ const useReceiptPallet = ({ palletId }: Props) => {
isBox: false, isBox: false,
object: pallet, object: pallet,
fetchObject: fetchPallet, fetchObject: fetchPallet,
barcodesProductsMap,
}, },
}); });
}; };
@@ -64,6 +78,9 @@ const useReceiptPallet = ({ palletId }: Props) => {
clientId, clientId,
onCreateProductClick, onCreateProductClick,
onCreateBoxClick, onCreateBoxClick,
barcodesProductsMap,
scanningData,
toggleScanning,
}; };
}; };

View File

@@ -14,6 +14,7 @@ type Props = {
object: ResidualBoxSchema | ResidualPalletSchema; object: ResidualBoxSchema | ResidualPalletSchema;
fetchObject: () => void; fetchObject: () => void;
isBox: boolean; isBox: boolean;
barcodesProductsMap: Map<string, ProductSchema[]>;
} }
const ReceiptModal = ({ const ReceiptModal = ({
@@ -25,7 +26,9 @@ const ReceiptModal = ({
clientId, clientId,
object, object,
fetchObject, fetchObject,
barcodesProductsMap,
} = innerProps; } = innerProps;
const { isScanning, setIsScanning, scannedValue, setScannedValue } = useScanning(); const { isScanning, setIsScanning, scannedValue, setScannedValue } = useScanning();
const initialValues = { const initialValues = {
product: null, product: null,
@@ -115,12 +118,12 @@ const ReceiptModal = ({
</form> </form>
<ScanBarcode <ScanBarcode
clientId={clientId}
isScanning={isScanning} isScanning={isScanning}
setIsScanning={setIsScanning} setIsScanning={setIsScanning}
onProductSelect={onProductAfterScanningSelect} onProductSelect={onProductAfterScanningSelect}
scannedValue={scannedValue} scannedValue={scannedValue}
object={object} object={object}
barcodesProductsMap={barcodesProductsMap}
/> />
</Flex> </Flex>
); );

View File

@@ -1,29 +1,28 @@
import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema } from "../../../../client"; import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema } from "../../../../client";
import { useEffect, useState } from "react"; import { useState } from "react";
import useProductsList from "../../../ProductsPage/hooks/useProductsList.tsx";
import { Button, Group, Radio, Stack, Text } from "@mantine/core"; import { Button, Group, Radio, Stack, Text } from "@mantine/core";
import { notifications } from "../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../shared/lib/notifications.ts";
import { ReceiptBox, ReceiptPallet } from "../NewReceipt/types/types.tsx"; import { ReceiptBox, ReceiptPallet } from "../NewReceipt/types/types.tsx";
import findProductInObject from "../NewReceipt/utils/findProductInObject.tsx";
type Props = { type Props = {
onProductSelect: (product?: ProductSchema) => void; onProductSelect: (product?: ProductSchema) => void;
clientId: number;
isScanning: boolean; isScanning: boolean;
setIsScanning: (isScanning: boolean) => void; setIsScanning: (isScanning: boolean) => void;
scannedValue: string; scannedValue: string;
object: ReceiptBox | ReceiptPallet | ResidualPalletSchema | ResidualBoxSchema; object: ReceiptBox | ReceiptPallet | ResidualPalletSchema | ResidualBoxSchema;
barcodesProductsMap: Map<string, ProductSchema[]>;
} }
const ScanBarcode = ({ const ScanBarcode = ({
onProductSelect, onProductSelect,
clientId,
isScanning, isScanning,
setIsScanning, setIsScanning,
scannedValue, scannedValue,
object, object,
barcodesProductsMap,
}: Props) => { }: Props) => {
const productsData = useProductsList({ clientId, searchInput: "" });
const [barcodesProducts, setBarcodesProducts] = useState(new Map<string, ProductSchema[]>());
let productsToSelect: ProductSchema[] = []; let productsToSelect: ProductSchema[] = [];
const [selectedProduct, setSelectedProduct] = useState<ProductSchema>(); const [selectedProduct, setSelectedProduct] = useState<ProductSchema>();
@@ -31,38 +30,8 @@ const ScanBarcode = ({
setIsScanning(!isScanning); setIsScanning(!isScanning);
}; };
const productsToBarcodesProducts = () => {
const data = new Map<string, ProductSchema[]>();
productsData.products.forEach(product => {
product.barcodes.forEach(barcode => {
if (data.has(barcode)) {
data.set(barcode, [...data.get(barcode)!, product]);
} else {
data.set(barcode, [product]);
}
});
});
setBarcodesProducts(data);
};
useEffect(() => {
if (productsData.products.length !== 0) {
productsToBarcodesProducts();
}
}, [productsData.isLoading]);
const findProductInObject = (productsToSelect: ProductSchema[]): ProductSchema | undefined => {
for (let i = 0; i < productsToSelect.length; i++) {
for (let j = 0; j < object.residualProducts.length; j++) {
if (productsToSelect[i].id === object.residualProducts[j].product.id) {
return productsToSelect[i];
}
}
}
};
const renderScanningResults = () => { const renderScanningResults = () => {
productsToSelect = barcodesProducts.get(scannedValue) ?? []; productsToSelect = barcodesProductsMap.get(scannedValue) ?? [];
if (productsToSelect?.length === 0) { if (productsToSelect?.length === 0) {
notifications.error({ message: `Товара с штрихкодом ${scannedValue} не найдено` }); notifications.error({ message: `Товара с штрихкодом ${scannedValue} не найдено` });
onProductSelect(); onProductSelect();
@@ -72,7 +41,7 @@ const ScanBarcode = ({
onProductSelect(productsToSelect[0]); onProductSelect(productsToSelect[0]);
return; return;
} }
const product = findProductInObject(productsToSelect); const product = findProductInObject(object, productsToSelect);
if (product) { if (product) {
onProductSelect(product); onProductSelect(product);
return; return;

View File

@@ -0,0 +1,42 @@
import useProductsList from "../../ProductsPage/hooks/useProductsList.tsx";
import { useEffect, useState } from "react";
import { ProductSchema } from "../../../client";
type Props = {
clientId?: number;
}
const useBarcodesProductsMap = ({ clientId }: Props) => {
const {
products,
isLoading: isProductsLoading,
} = useProductsList({ clientId: clientId ?? 0, searchInput: "" });
const [barcodesProductsMap, setBarcodesProductsMap] = useState(new Map<string, ProductSchema[]>());
const productsToBarcodesProducts = () => {
const data = new Map<string, ProductSchema[]>();
products.forEach(product => {
product.barcodes.forEach(barcode => {
if (data.has(barcode)) {
data.set(barcode, [...data.get(barcode)!, product]);
} else {
data.set(barcode, [product]);
}
});
});
setBarcodesProductsMap(data);
};
useEffect(() => {
if (products.length !== 0) {
productsToBarcodesProducts();
}
}, [isProductsLoading]);
return {
barcodesProductsMap,
};
};
export default useBarcodesProductsMap;

View File

@@ -0,0 +1,63 @@
import { useState } from "react";
import { useWindowEvent } from "@mantine/hooks";
export type ScanningData = {
boxId?: number;
palletId?: number;
isScanning: boolean;
}
type Props = {
onScanningFinish: (value: string, boxId?: number, palletId?: number) => void;
}
const useScanningMode = ({ onScanningFinish }: Props) => {
const [scanningValue, setScanningValue] = useState<string>("");
const [boxId, setBoxId] = useState<number>();
const [palletId, setPalletId] = useState<number>();
const [isScanning, setIsScanning] = useState<boolean>(false);
const setIsScanningValue = (isScanning: boolean) => {
if (!isScanning) {
setScanningValue("");
setBoxId(undefined);
setPalletId(undefined);
}
setIsScanning(isScanning);
};
useWindowEvent("keydown", (event) => {
if (!isScanning) return;
event.preventDefault();
setScanningValue(prevState => prevState + event.key);
if (["\n", "\r", "Enter"].includes(event.key)) {
onScanningFinish(scanningValue, boxId, palletId);
setScanningValue("");
}
});
const toggleScanning = (boxId?: number, palletId?: number) => {
if (isScanning) {
setBoxId(undefined);
setPalletId(undefined);
} else {
setBoxId(boxId);
setPalletId(palletId);
}
setIsScanningValue(!isScanning);
setScanningValue("");
};
const scanningData: ScanningData = {
boxId,
palletId,
isScanning,
};
return {
scanningData,
toggleScanning,
};
};
export default useScanningMode;

View File

@@ -0,0 +1,61 @@
import { ContextModalProps } from "@mantine/modals";
import { Button, Group, Radio, Stack, Text } from "@mantine/core";
import { ProductSchema } from "../../../client";
import { notifications } from "../../../shared/lib/notifications.ts";
import { useState } from "react";
type Props = {
productsToSelect: ProductSchema[];
onProductSelect: (product: ProductSchema) => void;
}
const SelectScannedProductModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const {
productsToSelect,
onProductSelect,
} = innerProps;
const [selectedProduct, setSelectedProduct] = useState<ProductSchema>();
const renderProductsToSelect = () => {
return productsToSelect.map(product => (
<Group key={product.id} wrap={"nowrap"}>
<Radio
checked={selectedProduct?.id === product.id}
onChange={() => setSelectedProduct(product)}
/>
<Stack>
<Text>{product.name}</Text>
{product.size && <Text>{product.size}</Text>}
</Stack>
</Group>
));
};
return (
<Stack>
{renderProductsToSelect()}
<Button
variant={"default"}
onClick={() => {
if (!selectedProduct) {
notifications.error({ message: "Товар не выбран" });
} else {
onProductSelect(selectedProduct);
setSelectedProduct(undefined);
context.closeContextModal(id);
}
}}
>
Выбрать
</Button>
</Stack>
);
};
export default SelectScannedProductModal;