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,25 +33,44 @@ 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
|
||||
variant={"default"}
|
||||
onClick={() => {
|
||||
if (pallet.residualProducts.length > 0) {
|
||||
onObjectEditClick(pallet, false);
|
||||
} else {
|
||||
createBox(pallet);
|
||||
}
|
||||
}}
|
||||
mr={"sm"}
|
||||
>
|
||||
<IconPlus />
|
||||
</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) {
|
||||
onObjectEditClick(pallet, false);
|
||||
} else {
|
||||
createBox(pallet);
|
||||
}
|
||||
}}
|
||||
disabled={isScanning}
|
||||
mr={"sm"}
|
||||
>
|
||||
<IconPlus />
|
||||
</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,17 +14,19 @@ type Props = {
|
||||
clientId: number;
|
||||
object: ReceiptBox | ReceiptPallet;
|
||||
setObjectData: (object: ReceiptBox | ReceiptPallet) => void;
|
||||
barcodesProductsMap: Map<string, ProductSchema[]>;
|
||||
}
|
||||
|
||||
const NewReceiptModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const {
|
||||
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"} flex={9} gap={"md"}>
|
||||
{backButton}
|
||||
<Title flex={8} order={3}>
|
||||
Короб ID: К{box?.id}
|
||||
</Title>
|
||||
<Flex align={"center"} justify={"space-between"}>
|
||||
<Flex align={"center"} flex={9} gap={"md"}>
|
||||
{backButton}
|
||||
<Title flex={8} order={3}>
|
||||
Короб ID: К{box?.id}
|
||||
</Title>
|
||||
</Flex>
|
||||
{getScanningModeAction()}
|
||||
</Flex>
|
||||
<InlineButton onClick={onCreateProductClick}>
|
||||
<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,14 +88,34 @@ 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"} flex={9} gap={"md"}>
|
||||
{backButton}
|
||||
<Title flex={8} order={3}>
|
||||
Паллет ID: П{pallet?.id}
|
||||
</Title>
|
||||
<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()}
|
||||
|
||||
@@ -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