feat: scanning mode on receipt page
This commit is contained in:
		@@ -33,6 +33,7 @@ import AssignUserModal from "../pages/LeadsPage/tabs/EmployeesTab/modals/AssignU
 | 
			
		||||
import ResidualProductModal from "../pages/ResiduesPage/modals/ResidualProductModal/ResidualProductModal.tsx";
 | 
			
		||||
import NewReceiptModal from "../pages/ReceiptPage/components/NewReceipt/modals/NewReceiptModal.tsx";
 | 
			
		||||
import ReceiptModal from "../pages/ReceiptPage/components/ReceiptEditing/modals/ReceiptModal.tsx";
 | 
			
		||||
import SelectScannedProductModal from "../pages/ReceiptPage/modals/SelectScannedProductModal.tsx";
 | 
			
		||||
 | 
			
		||||
export const modals = {
 | 
			
		||||
    enterDeadline: EnterDeadlineModal,
 | 
			
		||||
@@ -63,6 +64,7 @@ export const modals = {
 | 
			
		||||
    transactionTagsModal: TransactionTagsModal,
 | 
			
		||||
    shippingProductModal: ShippingProductModal,
 | 
			
		||||
    residualProductModal: ResidualProductModal,
 | 
			
		||||
    selectScannedProductModal: SelectScannedProductModal,
 | 
			
		||||
    newReceiptModal: NewReceiptModal,
 | 
			
		||||
    receiptModal: ReceiptModal,
 | 
			
		||||
    departmentModal: DepartmentModal,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
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 { useReceiptContext } from "../contexts/ReceiptContext.tsx";
 | 
			
		||||
import ReceiptProducts from "./ReceiptProducts.tsx";
 | 
			
		||||
@@ -10,7 +10,17 @@ type 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) => {
 | 
			
		||||
        if (palletIdx && pallet) {
 | 
			
		||||
@@ -23,9 +33,22 @@ const AccordionBoxes = ({ pallet, palletIdx }: Props) => {
 | 
			
		||||
    const boxActions = (box: ReceiptBox) => {
 | 
			
		||||
        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
 | 
			
		||||
                    variant={"default"}
 | 
			
		||||
                    onClick={() => onObjectEditClick(box, true, pallet)}
 | 
			
		||||
                    disabled={isScanning}
 | 
			
		||||
                    mr={"sm"}
 | 
			
		||||
                >
 | 
			
		||||
                    <IconPlus />
 | 
			
		||||
@@ -33,6 +56,7 @@ const AccordionBoxes = ({ pallet, palletIdx }: Props) => {
 | 
			
		||||
                <ActionIcon
 | 
			
		||||
                    variant={"default"}
 | 
			
		||||
                    onClick={() => deleteBox(box.id)}
 | 
			
		||||
                    disabled={isScanning}
 | 
			
		||||
                    mr={"sm"}
 | 
			
		||||
                >
 | 
			
		||||
                    <IconTrash />
 | 
			
		||||
@@ -60,6 +84,7 @@ const AccordionBoxes = ({ pallet, palletIdx }: Props) => {
 | 
			
		||||
                            products={box.residualProducts}
 | 
			
		||||
                            object={box}
 | 
			
		||||
                            setObjectData={(box: ReceiptBox) => setBoxData(box, pallet)}
 | 
			
		||||
                            disabled={isScanning}
 | 
			
		||||
                        />
 | 
			
		||||
                    </Accordion.Panel>
 | 
			
		||||
                </Accordion.Item>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,22 @@
 | 
			
		||||
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 { useReceiptContext } from "../contexts/ReceiptContext.tsx";
 | 
			
		||||
import AccordionBoxes from "./AccordionBoxes.tsx";
 | 
			
		||||
import ReceiptProducts from "./ReceiptProducts.tsx";
 | 
			
		||||
 | 
			
		||||
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) => {
 | 
			
		||||
        palletsHandlers.filter(item => item.id !== palletId);
 | 
			
		||||
@@ -23,10 +33,26 @@ const AccordionPallets = () => {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const palletActions = (pallet: ReceiptPallet) => {
 | 
			
		||||
        const isScanModeVisible = pallet.boxes.length === 0;
 | 
			
		||||
        const isCreateButtonVisible = pallet.boxes.length > 0 || pallet.residualProducts.length > 0;
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                {isCreateButtonVisible && <ActionIcon
 | 
			
		||||
                {isScanModeVisible && (
 | 
			
		||||
                    <ActionIcon
 | 
			
		||||
                        variant={"default"}
 | 
			
		||||
                        onClick={() => toggleScanning(undefined, pallet.id)}
 | 
			
		||||
                        mr={"sm"}
 | 
			
		||||
                        disabled={isScanning && (pallet.id !== palletId || !!boxId)}
 | 
			
		||||
                    >
 | 
			
		||||
                        {isScanning && pallet.id === palletId && !boxId ? (
 | 
			
		||||
                            <IconPlayerPause />
 | 
			
		||||
                        ) : (
 | 
			
		||||
                            <IconBarcode />
 | 
			
		||||
                        )}
 | 
			
		||||
                    </ActionIcon>
 | 
			
		||||
                )}
 | 
			
		||||
                {isCreateButtonVisible && (
 | 
			
		||||
                    <ActionIcon
 | 
			
		||||
                        variant={"default"}
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                            if (pallet.residualProducts.length > 0) {
 | 
			
		||||
@@ -35,13 +61,16 @@ const AccordionPallets = () => {
 | 
			
		||||
                                createBox(pallet);
 | 
			
		||||
                            }
 | 
			
		||||
                        }}
 | 
			
		||||
                        disabled={isScanning}
 | 
			
		||||
                        mr={"sm"}
 | 
			
		||||
                    >
 | 
			
		||||
                        <IconPlus />
 | 
			
		||||
                </ActionIcon>}
 | 
			
		||||
                    </ActionIcon>
 | 
			
		||||
                )}
 | 
			
		||||
                <ActionIcon
 | 
			
		||||
                    variant={"default"}
 | 
			
		||||
                    onClick={() => deletePallet(pallet.id)}
 | 
			
		||||
                    disabled={isScanning}
 | 
			
		||||
                    mr={"md"}
 | 
			
		||||
                >
 | 
			
		||||
                    <IconTrash />
 | 
			
		||||
@@ -55,6 +84,7 @@ const AccordionPallets = () => {
 | 
			
		||||
            <Button
 | 
			
		||||
                variant={"default"}
 | 
			
		||||
                onClick={onClick}
 | 
			
		||||
                disabled={isScanning}
 | 
			
		||||
                flex={1}
 | 
			
		||||
            >
 | 
			
		||||
                <Group gap={"md"}>
 | 
			
		||||
@@ -98,6 +128,7 @@ const AccordionPallets = () => {
 | 
			
		||||
                            products={pallet.residualProducts}
 | 
			
		||||
                            object={pallet}
 | 
			
		||||
                            setObjectData={object => setPalletData(object as ReceiptPallet)}
 | 
			
		||||
                            disabled={isScanning}
 | 
			
		||||
                        />
 | 
			
		||||
                    )}
 | 
			
		||||
                </Accordion.Panel>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,15 @@ import InlineButton from "../../../../../components/InlineButton/InlineButton.ts
 | 
			
		||||
import { IconPlus } from "@tabler/icons-react";
 | 
			
		||||
 | 
			
		||||
const ReceiptEditor = () => {
 | 
			
		||||
    const { fixed, palletsHandlers, boxesHandlers, nextId } = useReceiptContext();
 | 
			
		||||
    const {
 | 
			
		||||
        fixed,
 | 
			
		||||
        palletsHandlers,
 | 
			
		||||
        boxesHandlers,
 | 
			
		||||
        nextId,
 | 
			
		||||
        scanningData,
 | 
			
		||||
    } = useReceiptContext();
 | 
			
		||||
 | 
			
		||||
    const { isScanning } = scanningData;
 | 
			
		||||
 | 
			
		||||
    if (!fixed) return;
 | 
			
		||||
 | 
			
		||||
@@ -31,11 +39,19 @@ const ReceiptEditor = () => {
 | 
			
		||||
    return (
 | 
			
		||||
        <Stack>
 | 
			
		||||
            <Flex gap="md">
 | 
			
		||||
                <InlineButton onClick={() => createPallet()} flex={1}>
 | 
			
		||||
                <InlineButton
 | 
			
		||||
                    onClick={() => createPallet()}
 | 
			
		||||
                    flex={1}
 | 
			
		||||
                    disabled={isScanning}
 | 
			
		||||
                >
 | 
			
		||||
                    <IconPlus />
 | 
			
		||||
                    Паллет
 | 
			
		||||
                </InlineButton>
 | 
			
		||||
                <InlineButton onClick={() => createBox()} flex={1}>
 | 
			
		||||
                <InlineButton
 | 
			
		||||
                    onClick={() => createBox()}
 | 
			
		||||
                    flex={1}
 | 
			
		||||
                    disabled={isScanning}
 | 
			
		||||
                >
 | 
			
		||||
                    <IconPlus />
 | 
			
		||||
                    Короб
 | 
			
		||||
                </InlineButton>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,10 @@ type Props = {
 | 
			
		||||
    products: ReceiptProduct[];
 | 
			
		||||
    object: ReceiptBox | ReceiptPallet;
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
    const deleteProduct = (productId: number) => {
 | 
			
		||||
@@ -31,6 +32,7 @@ const ReceiptProducts = ({ products, object, setObjectData }: Props) => {
 | 
			
		||||
                    allowDecimal={false}
 | 
			
		||||
                    allowNegative={false}
 | 
			
		||||
                    w={rem(100)}
 | 
			
		||||
                    disabled={disabled}
 | 
			
		||||
                />
 | 
			
		||||
            </Group>
 | 
			
		||||
        );
 | 
			
		||||
@@ -50,6 +52,7 @@ const ReceiptProducts = ({ products, object, setObjectData }: Props) => {
 | 
			
		||||
                        <ActionIcon
 | 
			
		||||
                            variant={"default"}
 | 
			
		||||
                            onClick={() => deleteProduct(receiptProduct.id)}
 | 
			
		||||
                            disabled={disabled}
 | 
			
		||||
                        >
 | 
			
		||||
                            <IconTrash />
 | 
			
		||||
                        </ActionIcon>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,21 +4,29 @@ import { ReceiptBox, ReceiptPallet } from "../types/types.tsx";
 | 
			
		||||
import { modals } from "@mantine/modals";
 | 
			
		||||
import nextId from "../utils/nextId.ts";
 | 
			
		||||
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 = {
 | 
			
		||||
    client?: ClientSchema;
 | 
			
		||||
    setClient: Dispatch<SetStateAction<ClientSchema | undefined>>;
 | 
			
		||||
    fixed: boolean;
 | 
			
		||||
    setFixed: Dispatch<SetStateAction<boolean>>;
 | 
			
		||||
 | 
			
		||||
    pallets: ReceiptPallet[];
 | 
			
		||||
    palletsHandlers: UseListStateHandlers<ReceiptPallet>;
 | 
			
		||||
    boxes: ReceiptBox[];
 | 
			
		||||
    boxesHandlers: UseListStateHandlers<ReceiptBox>;
 | 
			
		||||
 | 
			
		||||
    nextId: () => number;
 | 
			
		||||
    onObjectEditClick: (object: ReceiptBox | ReceiptPallet, isBox: boolean, parentPallet?: ReceiptPallet) => void;
 | 
			
		||||
    reset: () => void;
 | 
			
		||||
    setBoxData: (box: ReceiptBox, pallet?: ReceiptPallet) => void;
 | 
			
		||||
    setPalletData: (pallet: ReceiptPallet) => void;
 | 
			
		||||
 | 
			
		||||
    scanningData: ScanningData;
 | 
			
		||||
    toggleScanning: (boxId?: number, palletId?: number) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ReceiptContext = createContext<ReceiptContextState | undefined>(
 | 
			
		||||
@@ -32,6 +40,8 @@ const useReceiptContextState = () => {
 | 
			
		||||
    const [pallets, palletsHandlers] = useListState<ReceiptPallet>([]);
 | 
			
		||||
    const [boxes, boxesHandlers] = useListState<ReceiptBox>([]);
 | 
			
		||||
 | 
			
		||||
    const { barcodesProductsMap } = useBarcodesProductsMap({ clientId: client?.id });
 | 
			
		||||
 | 
			
		||||
    const setBoxData = (box: ReceiptBox, pallet?: ReceiptPallet) => {
 | 
			
		||||
        if (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 = (
 | 
			
		||||
        object: ReceiptBox | ReceiptPallet,
 | 
			
		||||
        isBox: boolean,
 | 
			
		||||
@@ -83,13 +102,24 @@ const useReceiptContextState = () => {
 | 
			
		||||
                clientId: client.id,
 | 
			
		||||
                object: object,
 | 
			
		||||
                setObjectData: (object: ReceiptBox | ReceiptPallet) => {
 | 
			
		||||
                    if (isBox) setBoxData(object, parentPallet);
 | 
			
		||||
                    setPalletData(object as ReceiptPallet);
 | 
			
		||||
                    editObject(object, isBox, parentPallet);
 | 
			
		||||
                },
 | 
			
		||||
                barcodesProductsMap,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const {
 | 
			
		||||
        onScanningFinish,
 | 
			
		||||
    } = useApplyingScanningResult({
 | 
			
		||||
        pallets,
 | 
			
		||||
        boxes,
 | 
			
		||||
        barcodesProductsMap,
 | 
			
		||||
        editObject,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const { scanningData, toggleScanning } = useScanningMode({ onScanningFinish });
 | 
			
		||||
 | 
			
		||||
    const reset = () => {
 | 
			
		||||
        palletsHandlers.setState([]);
 | 
			
		||||
        boxesHandlers.setState([]);
 | 
			
		||||
@@ -111,6 +141,8 @@ const useReceiptContextState = () => {
 | 
			
		||||
        reset,
 | 
			
		||||
        setBoxData,
 | 
			
		||||
        setPalletData,
 | 
			
		||||
        scanningData,
 | 
			
		||||
        toggleScanning,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -14,6 +14,7 @@ type Props = {
 | 
			
		||||
    clientId: number;
 | 
			
		||||
    object: ReceiptBox | ReceiptPallet;
 | 
			
		||||
    setObjectData: (object: ReceiptBox | ReceiptPallet) => void;
 | 
			
		||||
    barcodesProductsMap: Map<string, ProductSchema[]>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const NewReceiptModal = ({
 | 
			
		||||
@@ -25,6 +26,7 @@ const NewReceiptModal = ({
 | 
			
		||||
        clientId,
 | 
			
		||||
        setObjectData,
 | 
			
		||||
        object,
 | 
			
		||||
        barcodesProductsMap,
 | 
			
		||||
    } = innerProps;
 | 
			
		||||
    const { isScanning, setIsScanning, scannedValue, setScannedValue } = useScanning();
 | 
			
		||||
 | 
			
		||||
@@ -112,12 +114,12 @@ const NewReceiptModal = ({
 | 
			
		||||
            </form>
 | 
			
		||||
 | 
			
		||||
            <ScanBarcode
 | 
			
		||||
                clientId={clientId}
 | 
			
		||||
                isScanning={isScanning}
 | 
			
		||||
                setIsScanning={setIsScanning}
 | 
			
		||||
                onProductSelect={onProductAfterScanningSelect}
 | 
			
		||||
                scannedValue={scannedValue}
 | 
			
		||||
                object={object}
 | 
			
		||||
                barcodesProductsMap={barcodesProductsMap}
 | 
			
		||||
            />
 | 
			
		||||
        </Flex>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -1,17 +1,33 @@
 | 
			
		||||
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 { ResidualBoxSchema, ResidualPalletSchema, ResiduesService } from "../../../../../client";
 | 
			
		||||
import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema, ResiduesService } from "../../../../../client";
 | 
			
		||||
import { modals } from "@mantine/modals";
 | 
			
		||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
 | 
			
		||||
import { ScanningData } from "../../../hooks/useScanningMode.tsx";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    pallet: ResidualPalletSchema;
 | 
			
		||||
    clientId: number;
 | 
			
		||||
    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) => {
 | 
			
		||||
        ResiduesService.deleteResidualBox({
 | 
			
		||||
            boxId,
 | 
			
		||||
@@ -36,6 +52,7 @@ const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => {
 | 
			
		||||
                isBox: true,
 | 
			
		||||
                object: box,
 | 
			
		||||
                fetchObject: fetchPallet,
 | 
			
		||||
                barcodesProductsMap,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
@@ -43,10 +60,23 @@ const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => {
 | 
			
		||||
    const boxActions = (box: ResidualBoxSchema) => {
 | 
			
		||||
        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
 | 
			
		||||
                    variant={"default"}
 | 
			
		||||
                    onClick={() => onCreateProductClick(box)}
 | 
			
		||||
                    mr={"sm"}
 | 
			
		||||
                    disabled={isScanning}
 | 
			
		||||
                >
 | 
			
		||||
                    <IconPlus />
 | 
			
		||||
                </ActionIcon>
 | 
			
		||||
@@ -54,6 +84,7 @@ const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => {
 | 
			
		||||
                    variant={"default"}
 | 
			
		||||
                    onClick={() => deleteBox(box.id)}
 | 
			
		||||
                    mr={"sm"}
 | 
			
		||||
                    disabled={isScanning}
 | 
			
		||||
                >
 | 
			
		||||
                    <IconTrash />
 | 
			
		||||
                </ActionIcon>
 | 
			
		||||
@@ -78,6 +109,7 @@ const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => {
 | 
			
		||||
                            clientId={clientId}
 | 
			
		||||
                            object={box}
 | 
			
		||||
                            updateObject={fetchPallet}
 | 
			
		||||
                            disabled={isScanning}
 | 
			
		||||
                        />
 | 
			
		||||
                    </Accordion.Panel>
 | 
			
		||||
                </Accordion.Item>
 | 
			
		||||
@@ -5,9 +5,10 @@ import { notifications } from "../../../../../shared/lib/notifications.ts";
 | 
			
		||||
type Props = {
 | 
			
		||||
    residualProduct: ResidualProductSchema;
 | 
			
		||||
    updateObject: () => void;
 | 
			
		||||
    disabled: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ProductQuantityField = ({ residualProduct, updateObject }: Props) => {
 | 
			
		||||
const ProductQuantityField = ({ residualProduct, updateObject, disabled }: Props) => {
 | 
			
		||||
    const updateProduct = (quantity: number) => {
 | 
			
		||||
        if (residualProduct.quantity === quantity) return;
 | 
			
		||||
        ResiduesService.updateResidualProduct({
 | 
			
		||||
@@ -38,6 +39,7 @@ const ProductQuantityField = ({ residualProduct, updateObject }: Props) => {
 | 
			
		||||
                allowDecimal={false}
 | 
			
		||||
                allowNegative={false}
 | 
			
		||||
                w={rem(100)}
 | 
			
		||||
                disabled={disabled}
 | 
			
		||||
            />
 | 
			
		||||
        </Group>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -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 ReceiptProducts from "./ReceiptProducts.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 = {
 | 
			
		||||
    boxId: number;
 | 
			
		||||
@@ -14,8 +14,12 @@ const ReceiptBoxEditor = ({ boxId }: Props) => {
 | 
			
		||||
        fetchBox,
 | 
			
		||||
        clientId,
 | 
			
		||||
        onCreateProductClick,
 | 
			
		||||
        scanningData,
 | 
			
		||||
        toggleScanning,
 | 
			
		||||
    } = useReceiptBox({ boxId });
 | 
			
		||||
 | 
			
		||||
    const { isScanning } = scanningData;
 | 
			
		||||
 | 
			
		||||
    if (!box) return <Text>Короб c ID K{boxId} не найден</Text>;
 | 
			
		||||
 | 
			
		||||
    const backButton = (
 | 
			
		||||
@@ -23,20 +27,39 @@ const ReceiptBoxEditor = ({ boxId }: Props) => {
 | 
			
		||||
            variant={"default"}
 | 
			
		||||
            onClick={() => window.location.reload()}
 | 
			
		||||
            flex={1}
 | 
			
		||||
            disabled={isScanning}
 | 
			
		||||
        >
 | 
			
		||||
            <IconArrowLeft />
 | 
			
		||||
        </Button>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const getScanningModeAction = () => {
 | 
			
		||||
        return (
 | 
			
		||||
            <ActionIcon
 | 
			
		||||
                variant={"default"}
 | 
			
		||||
                onClick={() => toggleScanning(box?.id)}
 | 
			
		||||
            >
 | 
			
		||||
                {isScanning ? (
 | 
			
		||||
                    <IconPlayerPause />
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <IconBarcode />
 | 
			
		||||
                )}
 | 
			
		||||
            </ActionIcon>
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Stack>
 | 
			
		||||
            <Flex align={"center"} justify={"space-between"}>
 | 
			
		||||
                <Flex align={"center"} flex={9} gap={"md"}>
 | 
			
		||||
                    {backButton}
 | 
			
		||||
                    <Title flex={8} order={3}>
 | 
			
		||||
                        Короб ID: К{box?.id}
 | 
			
		||||
                    </Title>
 | 
			
		||||
                </Flex>
 | 
			
		||||
            <InlineButton onClick={onCreateProductClick}>
 | 
			
		||||
                {getScanningModeAction()}
 | 
			
		||||
            </Flex>
 | 
			
		||||
            <InlineButton onClick={onCreateProductClick} disabled={isScanning}>
 | 
			
		||||
                <IconPlus />
 | 
			
		||||
                Товар
 | 
			
		||||
            </InlineButton>
 | 
			
		||||
@@ -44,6 +67,7 @@ const ReceiptBoxEditor = ({ boxId }: Props) => {
 | 
			
		||||
                object={box}
 | 
			
		||||
                clientId={clientId}
 | 
			
		||||
                updateObject={fetchBox}
 | 
			
		||||
                disabled={isScanning}
 | 
			
		||||
            />
 | 
			
		||||
        </Stack>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import useReceiptPallet from "../hooks/useReceiptPallet.tsx";
 | 
			
		||||
import { Button, Flex, Group, Stack, Text, Title } from "@mantine/core";
 | 
			
		||||
import { IconArrowLeft, IconPlus } from "@tabler/icons-react";
 | 
			
		||||
import { ActionIcon, Button, Flex, Group, Stack, Text, Title } from "@mantine/core";
 | 
			
		||||
import { IconArrowLeft, IconBarcode, IconPlayerPause, IconPlus } from "@tabler/icons-react";
 | 
			
		||||
import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx";
 | 
			
		||||
import ReceiptProducts from "./ReceiptProducts.tsx";
 | 
			
		||||
import AccordionBoxesOnPallet from "./AccordionBoxes.tsx";
 | 
			
		||||
import AccordionBoxesOnPallet from "./AccordionBoxesOnPallet.tsx";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    palletId: number;
 | 
			
		||||
@@ -16,9 +16,19 @@ const ReceiptPalletEditor = ({ palletId }: Props) => {
 | 
			
		||||
        clientId,
 | 
			
		||||
        onCreateProductClick,
 | 
			
		||||
        onCreateBoxClick,
 | 
			
		||||
        barcodesProductsMap,
 | 
			
		||||
        scanningData,
 | 
			
		||||
        toggleScanning,
 | 
			
		||||
    } = 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 isBoxes = pallet.boxes.length > 0;
 | 
			
		||||
@@ -27,13 +37,13 @@ const ReceiptPalletEditor = ({ palletId }: Props) => {
 | 
			
		||||
        return (
 | 
			
		||||
            <Group>
 | 
			
		||||
                {(isBoxes || isBoth) && (
 | 
			
		||||
                    <InlineButton onClick={onCreateBoxClick} flex={1}>
 | 
			
		||||
                    <InlineButton onClick={onCreateBoxClick} flex={1} disabled={isScanning}>
 | 
			
		||||
                        <IconPlus />
 | 
			
		||||
                        Короб
 | 
			
		||||
                    </InlineButton>
 | 
			
		||||
                )}
 | 
			
		||||
                {(isProducts || isBoth) && (
 | 
			
		||||
                    <InlineButton onClick={onCreateProductClick} flex={1}>
 | 
			
		||||
                    <InlineButton onClick={onCreateProductClick} flex={1} disabled={isScanning}>
 | 
			
		||||
                        <IconPlus />
 | 
			
		||||
                        Товар
 | 
			
		||||
                    </InlineButton>
 | 
			
		||||
@@ -50,6 +60,9 @@ const ReceiptPalletEditor = ({ palletId }: Props) => {
 | 
			
		||||
                    pallet={pallet}
 | 
			
		||||
                    clientId={clientId}
 | 
			
		||||
                    fetchPallet={fetchPallet}
 | 
			
		||||
                    barcodesProductsMap={barcodesProductsMap}
 | 
			
		||||
                    scanningData={scanningData}
 | 
			
		||||
                    toggleScanning={toggleScanning}
 | 
			
		||||
                />
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
@@ -59,6 +72,7 @@ const ReceiptPalletEditor = ({ palletId }: Props) => {
 | 
			
		||||
                    clientId={clientId}
 | 
			
		||||
                    object={pallet}
 | 
			
		||||
                    updateObject={fetchPallet}
 | 
			
		||||
                    disabled={isScanning}
 | 
			
		||||
                />
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
@@ -74,15 +88,35 @@ const ReceiptPalletEditor = ({ palletId }: Props) => {
 | 
			
		||||
        </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 (
 | 
			
		||||
        <Stack>
 | 
			
		||||
            <Flex align={"center"} justify={"space-between"}>
 | 
			
		||||
                <Flex align={"center"} flex={9} gap={"md"}>
 | 
			
		||||
                    {backButton}
 | 
			
		||||
                    <Title flex={8} order={3}>
 | 
			
		||||
                        Паллет ID: П{pallet?.id}
 | 
			
		||||
                    </Title>
 | 
			
		||||
                </Flex>
 | 
			
		||||
                {getScanningModeAction()}
 | 
			
		||||
            </Flex>
 | 
			
		||||
            {createButtons()}
 | 
			
		||||
            {renderPalletData()}
 | 
			
		||||
        </Stack>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,10 @@ type Props = {
 | 
			
		||||
    clientId: number | null;
 | 
			
		||||
    object: ResidualBoxSchema | ResidualPalletSchema | null;
 | 
			
		||||
    updateObject: () => void;
 | 
			
		||||
    disabled: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ReceiptProducts = ({ clientId, object, updateObject }: Props) => {
 | 
			
		||||
const ReceiptProducts = ({ clientId, object, updateObject, disabled }: Props) => {
 | 
			
		||||
    if (!object || !clientId) return;
 | 
			
		||||
 | 
			
		||||
    const deleteProduct = (residualProductId: number) => {
 | 
			
		||||
@@ -37,11 +38,13 @@ const ReceiptProducts = ({ clientId, object, updateObject }: Props) => {
 | 
			
		||||
                            <ProductQuantityField
 | 
			
		||||
                                residualProduct={residualProduct}
 | 
			
		||||
                                updateObject={updateObject}
 | 
			
		||||
                                disabled={disabled}
 | 
			
		||||
                            />
 | 
			
		||||
                        </Stack>
 | 
			
		||||
                        <ActionIcon
 | 
			
		||||
                            variant={"default"}
 | 
			
		||||
                            onClick={() => deleteProduct(residualProduct.id)}
 | 
			
		||||
                            disabled={disabled}
 | 
			
		||||
                        >
 | 
			
		||||
                            <IconTrash />
 | 
			
		||||
                        </ActionIcon>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { ResidualBoxSchema, ResiduesService } from "../../../../../client";
 | 
			
		||||
import { modals } from "@mantine/modals";
 | 
			
		||||
import useBarcodesProductsMap from "../../../hooks/useBarcodesProductsMap.tsx";
 | 
			
		||||
import useScanningMode from "../../../hooks/useScanningMode.tsx";
 | 
			
		||||
import useApplyingScannedResult from "./useApplyingScannedResult.tsx";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
@@ -27,6 +30,16 @@ const useReceiptBox = ({ boxId }: Props) => {
 | 
			
		||||
            .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 = () => {
 | 
			
		||||
        if (!(box && clientId)) return;
 | 
			
		||||
        modals.openContextModal({
 | 
			
		||||
@@ -38,6 +51,7 @@ const useReceiptBox = ({ boxId }: Props) => {
 | 
			
		||||
                isBox: true,
 | 
			
		||||
                object: box,
 | 
			
		||||
                fetchObject: fetchBox,
 | 
			
		||||
                barcodesProductsMap,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
@@ -47,6 +61,8 @@ const useReceiptBox = ({ boxId }: Props) => {
 | 
			
		||||
        fetchBox,
 | 
			
		||||
        clientId,
 | 
			
		||||
        onCreateProductClick,
 | 
			
		||||
        scanningData,
 | 
			
		||||
        toggleScanning,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,9 @@ import { useEffect, useState } from "react";
 | 
			
		||||
import { ResidualPalletSchema, ResiduesService } from "../../../../../client";
 | 
			
		||||
import { modals } from "@mantine/modals";
 | 
			
		||||
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 = {
 | 
			
		||||
    palletId: number;
 | 
			
		||||
@@ -27,6 +30,16 @@ const useReceiptPallet = ({ palletId }: Props) => {
 | 
			
		||||
            .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 = () => {
 | 
			
		||||
        if (!(pallet && clientId)) return;
 | 
			
		||||
        modals.openContextModal({
 | 
			
		||||
@@ -38,6 +51,7 @@ const useReceiptPallet = ({ palletId }: Props) => {
 | 
			
		||||
                isBox: false,
 | 
			
		||||
                object: pallet,
 | 
			
		||||
                fetchObject: fetchPallet,
 | 
			
		||||
                barcodesProductsMap,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
@@ -64,6 +78,9 @@ const useReceiptPallet = ({ palletId }: Props) => {
 | 
			
		||||
        clientId,
 | 
			
		||||
        onCreateProductClick,
 | 
			
		||||
        onCreateBoxClick,
 | 
			
		||||
        barcodesProductsMap,
 | 
			
		||||
        scanningData,
 | 
			
		||||
        toggleScanning,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ type Props = {
 | 
			
		||||
    object: ResidualBoxSchema | ResidualPalletSchema;
 | 
			
		||||
    fetchObject: () => void;
 | 
			
		||||
    isBox: boolean;
 | 
			
		||||
    barcodesProductsMap: Map<string, ProductSchema[]>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ReceiptModal = ({
 | 
			
		||||
@@ -25,7 +26,9 @@ const ReceiptModal = ({
 | 
			
		||||
        clientId,
 | 
			
		||||
        object,
 | 
			
		||||
        fetchObject,
 | 
			
		||||
        barcodesProductsMap,
 | 
			
		||||
    } = innerProps;
 | 
			
		||||
 | 
			
		||||
    const { isScanning, setIsScanning, scannedValue, setScannedValue } = useScanning();
 | 
			
		||||
    const initialValues = {
 | 
			
		||||
        product: null,
 | 
			
		||||
@@ -115,12 +118,12 @@ const ReceiptModal = ({
 | 
			
		||||
            </form>
 | 
			
		||||
 | 
			
		||||
            <ScanBarcode
 | 
			
		||||
                clientId={clientId}
 | 
			
		||||
                isScanning={isScanning}
 | 
			
		||||
                setIsScanning={setIsScanning}
 | 
			
		||||
                onProductSelect={onProductAfterScanningSelect}
 | 
			
		||||
                scannedValue={scannedValue}
 | 
			
		||||
                object={object}
 | 
			
		||||
                barcodesProductsMap={barcodesProductsMap}
 | 
			
		||||
            />
 | 
			
		||||
        </Flex>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,28 @@
 | 
			
		||||
import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema } from "../../../../client";
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import useProductsList from "../../../ProductsPage/hooks/useProductsList.tsx";
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import { Button, Group, Radio, Stack, Text } from "@mantine/core";
 | 
			
		||||
import { notifications } from "../../../../shared/lib/notifications.ts";
 | 
			
		||||
import { ReceiptBox, ReceiptPallet } from "../NewReceipt/types/types.tsx";
 | 
			
		||||
import findProductInObject from "../NewReceipt/utils/findProductInObject.tsx";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    onProductSelect: (product?: ProductSchema) => void;
 | 
			
		||||
    clientId: number;
 | 
			
		||||
    isScanning: boolean;
 | 
			
		||||
    setIsScanning: (isScanning: boolean) => void;
 | 
			
		||||
    scannedValue: string;
 | 
			
		||||
    object: ReceiptBox | ReceiptPallet | ResidualPalletSchema | ResidualBoxSchema;
 | 
			
		||||
    barcodesProductsMap: Map<string, ProductSchema[]>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ScanBarcode = ({
 | 
			
		||||
                         onProductSelect,
 | 
			
		||||
                         clientId,
 | 
			
		||||
                         isScanning,
 | 
			
		||||
                         setIsScanning,
 | 
			
		||||
                         scannedValue,
 | 
			
		||||
                         object,
 | 
			
		||||
                         barcodesProductsMap,
 | 
			
		||||
                     }: Props) => {
 | 
			
		||||
    const productsData = useProductsList({ clientId, searchInput: "" });
 | 
			
		||||
    const [barcodesProducts, setBarcodesProducts] = useState(new Map<string, ProductSchema[]>());
 | 
			
		||||
 | 
			
		||||
    let productsToSelect: ProductSchema[] = [];
 | 
			
		||||
    const [selectedProduct, setSelectedProduct] = useState<ProductSchema>();
 | 
			
		||||
 | 
			
		||||
@@ -31,38 +30,8 @@ const ScanBarcode = ({
 | 
			
		||||
        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 = () => {
 | 
			
		||||
        productsToSelect = barcodesProducts.get(scannedValue) ?? [];
 | 
			
		||||
        productsToSelect = barcodesProductsMap.get(scannedValue) ?? [];
 | 
			
		||||
        if (productsToSelect?.length === 0) {
 | 
			
		||||
            notifications.error({ message: `Товара с штрихкодом ${scannedValue} не найдено` });
 | 
			
		||||
            onProductSelect();
 | 
			
		||||
@@ -72,7 +41,7 @@ const ScanBarcode = ({
 | 
			
		||||
            onProductSelect(productsToSelect[0]);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const product = findProductInObject(productsToSelect);
 | 
			
		||||
        const product = findProductInObject(object, productsToSelect);
 | 
			
		||||
        if (product) {
 | 
			
		||||
            onProductSelect(product);
 | 
			
		||||
            return;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								src/pages/ReceiptPage/hooks/useBarcodesProductsMap.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/pages/ReceiptPage/hooks/useBarcodesProductsMap.tsx
									
									
									
									
									
										Normal 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;
 | 
			
		||||
							
								
								
									
										63
									
								
								src/pages/ReceiptPage/hooks/useScanningMode.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/pages/ReceiptPage/hooks/useScanningMode.tsx
									
									
									
									
									
										Normal 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;
 | 
			
		||||
							
								
								
									
										61
									
								
								src/pages/ReceiptPage/modals/SelectScannedProductModal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/pages/ReceiptPage/modals/SelectScannedProductModal.tsx
									
									
									
									
									
										Normal 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;
 | 
			
		||||
		Reference in New Issue
	
	Block a user