feat: CRUD for product barcode images
This commit is contained in:
208
src/components/BarcodeImageDropzone/BarcodeImageDropzone.tsx
Normal file
208
src/components/BarcodeImageDropzone/BarcodeImageDropzone.tsx
Normal file
@@ -0,0 +1,208 @@
|
||||
import { Dropzone, DropzoneProps, FileWithPath } from "@mantine/dropzone";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { Button, Fieldset, Flex, Group, Loader, rem, Text } from "@mantine/core";
|
||||
import { IconPhoto, IconUpload, IconX } from "@tabler/icons-react";
|
||||
import { omit } from "lodash";
|
||||
import { notifications } from "../../shared/lib/notifications.ts";
|
||||
import { ProductService } from "../../client";
|
||||
|
||||
// Barcode image aspects ratio should be equal 58/40
|
||||
const BARCODE_IMAGE_RATIO = 1.45;
|
||||
|
||||
interface RestProps {
|
||||
productId?: number;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
|
||||
const BarcodeImageDropzone: FC<Props> = (props: Props) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const restProps = omit(props, [
|
||||
"productId",
|
||||
]);
|
||||
const [barcodeImageUrl, setBarcodeImageUrl] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
getBarcodeImage();
|
||||
}, []);
|
||||
|
||||
const getBarcodeImage = () => {
|
||||
if (!props.productId) return;
|
||||
|
||||
ProductService.getProductBarcodeImage({
|
||||
productId: props.productId,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.barcodeImageUrl) {
|
||||
setBarcodeImageUrl(res.barcodeImageUrl);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const showIncorrectImageSizeError = () => {
|
||||
notifications.error({
|
||||
message: "Изображение должно быть размером 58 х 40.",
|
||||
});
|
||||
};
|
||||
|
||||
const uploadImage = (productId: number, file: File) => {
|
||||
ProductService.uploadProductBarcodeImage({
|
||||
productId: productId,
|
||||
formData: {
|
||||
upload_file: file,
|
||||
},
|
||||
})
|
||||
.then(({ ok, message, barcodeImageUrl }) => {
|
||||
notifications.guess(ok, { message });
|
||||
setIsLoading(false);
|
||||
|
||||
if (ok && barcodeImageUrl) {
|
||||
setBarcodeImageUrl(barcodeImageUrl);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
notifications.error({ message: error.toString() });
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const uploadImageWrapper = (productId: number, file: File) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
const img = new Image();
|
||||
img.src = reader.result as string;
|
||||
img.onload = () => {
|
||||
const ratio = img.width / img.height;
|
||||
if (Math.abs(ratio - BARCODE_IMAGE_RATIO) > 0.01) {
|
||||
showIncorrectImageSizeError();
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
uploadImage(productId, file);
|
||||
};
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
const onDrop = (files: FileWithPath[]) => {
|
||||
if (!props.productId) return;
|
||||
if (files.length > 1) {
|
||||
notifications.error({ message: "Прикрепите одно изображение" });
|
||||
return;
|
||||
}
|
||||
const file = files[0];
|
||||
setIsLoading(true);
|
||||
uploadImageWrapper(props.productId, file);
|
||||
};
|
||||
|
||||
const deleteImage = () => {
|
||||
if (!props.productId) return;
|
||||
ProductService.deleteProductBarcodeImage({ productId: props.productId })
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
|
||||
if (!ok) return;
|
||||
setBarcodeImageUrl("");
|
||||
})
|
||||
.catch(error => {
|
||||
notifications.error({ message: error.toString() });
|
||||
});
|
||||
};
|
||||
|
||||
const getBody = () => {
|
||||
return barcodeImageUrl ? (
|
||||
<object style={{
|
||||
aspectRatio: BARCODE_IMAGE_RATIO,
|
||||
width: "240px",
|
||||
}} data={barcodeImageUrl} />
|
||||
) : (
|
||||
<Dropzone
|
||||
{...restProps}
|
||||
accept={[
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/x-icon",
|
||||
"image/webp",
|
||||
"image/svg+xml",
|
||||
"image/heic",
|
||||
]}
|
||||
multiple={false}
|
||||
onDrop={onDrop}>
|
||||
<Group
|
||||
justify="center"
|
||||
gap="xl"
|
||||
style={{ pointerEvents: "none" }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload
|
||||
style={{
|
||||
width: rem(52),
|
||||
height: rem(52),
|
||||
color: "var(--mantine-color-blue-6)",
|
||||
}}
|
||||
stroke={1.5}
|
||||
/>
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX
|
||||
style={{
|
||||
width: rem(52),
|
||||
height: rem(52),
|
||||
color: "var(--mantine-color-red-6)",
|
||||
}}
|
||||
stroke={1.5}
|
||||
/>
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconPhoto
|
||||
style={{
|
||||
width: rem(52),
|
||||
height: rem(52),
|
||||
color: "var(--mantine-color-dimmed)",
|
||||
}}
|
||||
stroke={1.5}
|
||||
/>
|
||||
</Dropzone.Idle>
|
||||
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Text
|
||||
size="xl"
|
||||
inline>
|
||||
Перенесите изображение или нажмите чтоб выбрать файл
|
||||
Изображение должно быть 58 х 40
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
gap={rem(10)}
|
||||
direction={"column"}>
|
||||
<Fieldset legend={"Штрихкод"}>
|
||||
<Flex justify={"center"}>
|
||||
{isLoading ? <Loader /> : getBody()}
|
||||
</Flex>
|
||||
</Fieldset>
|
||||
{barcodeImageUrl && (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => setBarcodeImageUrl("")}
|
||||
variant={"default"}>
|
||||
Заменить штрихкод
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => deleteImage()}
|
||||
variant={"default"}>
|
||||
Удалить штрихкод
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default BarcodeImageDropzone;
|
||||
Reference in New Issue
Block a user