diff --git a/src/client/index.ts b/src/client/index.ts index b1b14b3..c436ab0 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -25,8 +25,10 @@ export type { BaseEnumListSchema } from './models/BaseEnumListSchema'; export type { BaseEnumSchema } from './models/BaseEnumSchema'; export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema'; export type { BaseShippingWarehouseSchema } from './models/BaseShippingWarehouseSchema'; +export type { BillPaymentInfo } from './models/BillPaymentInfo'; export type { BillPaymentStatus } from './models/BillPaymentStatus'; export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest'; +export type { Body_upload_product_barcode_image } from './models/Body_upload_product_barcode_image'; export type { Body_upload_product_image } from './models/Body_upload_product_image'; export type { CancelDealBillRequest } from './models/CancelDealBillRequest'; export type { CancelDealBillResponse } from './models/CancelDealBillResponse'; @@ -173,16 +175,19 @@ export type { ProductAddBarcodeRequest } from './models/ProductAddBarcodeRequest export type { ProductAddBarcodeResponse } from './models/ProductAddBarcodeResponse'; export type { ProductCreateRequest } from './models/ProductCreateRequest'; export type { ProductCreateResponse } from './models/ProductCreateResponse'; +export type { ProductDeleteBarcodeImageResponse } from './models/ProductDeleteBarcodeImageResponse'; export type { ProductDeleteRequest } from './models/ProductDeleteRequest'; export type { ProductDeleteResponse } from './models/ProductDeleteResponse'; export type { ProductExistsBarcodeResponse } from './models/ProductExistsBarcodeResponse'; export type { ProductGenerateBarcodeRequest } from './models/ProductGenerateBarcodeRequest'; export type { ProductGenerateBarcodeResponse } from './models/ProductGenerateBarcodeResponse'; +export type { ProductGetBarcodeImageResponse } from './models/ProductGetBarcodeImageResponse'; export type { ProductGetResponse } from './models/ProductGetResponse'; export type { ProductImageSchema } from './models/ProductImageSchema'; export type { ProductSchema } from './models/ProductSchema'; export type { ProductUpdateRequest } from './models/ProductUpdateRequest'; export type { ProductUpdateResponse } from './models/ProductUpdateResponse'; +export type { ProductUploadBarcodeImageResponse } from './models/ProductUploadBarcodeImageResponse'; export type { ProductUploadImageResponse } from './models/ProductUploadImageResponse'; export type { RoleSchema } from './models/RoleSchema'; export type { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema'; diff --git a/src/client/models/BillStatusUpdateRequest.ts b/src/client/models/BillStatusUpdateRequest.ts index e1a5848..e1249f4 100644 --- a/src/client/models/BillStatusUpdateRequest.ts +++ b/src/client/models/BillStatusUpdateRequest.ts @@ -2,11 +2,12 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { BillPaymentInfo } from './BillPaymentInfo'; import type { BillPaymentStatus } from './BillPaymentStatus'; import type { NotificationChannel } from './NotificationChannel'; export type BillStatusUpdateRequest = { listenerTransactionId: number; channel: NotificationChannel; - info: BillPaymentStatus; + info: (BillPaymentInfo | BillPaymentStatus); }; diff --git a/src/client/services/ProductService.ts b/src/client/services/ProductService.ts index 734bd92..5b6cd73 100644 --- a/src/client/services/ProductService.ts +++ b/src/client/services/ProductService.ts @@ -2,6 +2,7 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { Body_upload_product_barcode_image } from '../models/Body_upload_product_barcode_image'; import type { Body_upload_product_image } from '../models/Body_upload_product_image'; import type { GetProductBarcodePdfRequest } from '../models/GetProductBarcodePdfRequest'; import type { GetProductBarcodePdfResponse } from '../models/GetProductBarcodePdfResponse'; @@ -11,15 +12,18 @@ import type { ProductAddBarcodeRequest } from '../models/ProductAddBarcodeReques import type { ProductAddBarcodeResponse } from '../models/ProductAddBarcodeResponse'; import type { ProductCreateRequest } from '../models/ProductCreateRequest'; import type { ProductCreateResponse } from '../models/ProductCreateResponse'; +import type { ProductDeleteBarcodeImageResponse } from '../models/ProductDeleteBarcodeImageResponse'; import type { ProductDeleteRequest } from '../models/ProductDeleteRequest'; import type { ProductDeleteResponse } from '../models/ProductDeleteResponse'; import type { ProductExistsBarcodeResponse } from '../models/ProductExistsBarcodeResponse'; import type { ProductGenerateBarcodeRequest } from '../models/ProductGenerateBarcodeRequest'; import type { ProductGenerateBarcodeResponse } from '../models/ProductGenerateBarcodeResponse'; +import type { ProductGetBarcodeImageResponse } from '../models/ProductGetBarcodeImageResponse'; import type { ProductGetResponse } from '../models/ProductGetResponse'; import type { ProductSchema } from '../models/ProductSchema'; import type { ProductUpdateRequest } from '../models/ProductUpdateRequest'; import type { ProductUpdateResponse } from '../models/ProductUpdateResponse'; +import type { ProductUploadBarcodeImageResponse } from '../models/ProductUploadBarcodeImageResponse'; import type { ProductUploadImageResponse } from '../models/ProductUploadImageResponse'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; @@ -265,4 +269,71 @@ export class ProductService { }, }); } + /** + * Upload Product Barcode Image + * @returns ProductUploadBarcodeImageResponse Successful Response + * @throws ApiError + */ + public static uploadProductBarcodeImage({ + productId, + formData, + }: { + productId: number, + formData: Body_upload_product_barcode_image, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/product/barcode/upload-image/{product_id}', + path: { + 'product_id': productId, + }, + formData: formData, + mediaType: 'multipart/form-data', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Delete Product Barcode Image + * @returns ProductDeleteBarcodeImageResponse Successful Response + * @throws ApiError + */ + public static deleteProductBarcodeImage({ + productId, + }: { + productId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/product/barcode/delete-image/{product_id}', + path: { + 'product_id': productId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Get Product Barcode Image + * @returns ProductGetBarcodeImageResponse Successful Response + * @throws ApiError + */ + public static getProductBarcodeImage({ + productId, + }: { + productId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/product/barcode/image/{product_id}', + path: { + 'product_id': productId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } } diff --git a/src/components/BarcodeImageDropzone/BarcodeImageDropzone.tsx b/src/components/BarcodeImageDropzone/BarcodeImageDropzone.tsx new file mode 100644 index 0000000..51f59ca --- /dev/null +++ b/src/components/BarcodeImageDropzone/BarcodeImageDropzone.tsx @@ -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 & RestProps; + +const BarcodeImageDropzone: FC = (props: Props) => { + const [isLoading, setIsLoading] = useState(false); + const restProps = omit(props, [ + "productId", + ]); + const [barcodeImageUrl, setBarcodeImageUrl] = useState(); + + 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 ? ( + + ) : ( + + + + + + + + + + + + +
+ + Перенесите изображение или нажмите чтоб выбрать файл + Изображение должно быть 58 х 40 + +
+
+
+ ); + }; + + return ( + +
+ + {isLoading ? : getBody()} + +
+ {barcodeImageUrl && ( + <> + + + + )} +
+ ); +}; + +export default BarcodeImageDropzone; diff --git a/src/pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx b/src/pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx index e9b5d7e..a04b359 100644 --- a/src/pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx +++ b/src/pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx @@ -1,18 +1,12 @@ import { ContextModalProps } from "@mantine/modals"; -import { - Button, - Fieldset, - Flex, - rem, - TagsInput, - TextInput, -} from "@mantine/core"; +import { Button, Fieldset, Flex, rem, TagsInput, TextInput } from "@mantine/core"; import { useForm } from "@mantine/form"; import { BaseProduct, CreateProductRequest } from "../../types.ts"; import { ProductSchema } from "../../../../client"; import BarcodeTemplateSelect from "../../../../components/Selects/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx"; import ImageDropzone from "../../../../components/ImageDropzone/ImageDropzone.tsx"; import { BaseFormInputProps } from "../../../../types/utils.ts"; +import BarcodeImageDropzone from "../../../../components/BarcodeImageDropzone/BarcodeImageDropzone.tsx"; type CreateProps = { clientId: number; @@ -26,20 +20,20 @@ type EditProps = { type Props = CreateProps | EditProps; const CreateProductModal = ({ - context, - id, - innerProps, -}: ContextModalProps) => { + context, + id, + innerProps, + }: ContextModalProps) => { const isEditProps = "product" in innerProps; const isCreatingProps = "clientId" in innerProps; const initialValues: Omit = isEditProps ? innerProps.product : { - name: "", - article: "", - barcodes: [], - clientId: innerProps.clientId, - }; + name: "", + article: "", + barcodes: [], + clientId: innerProps.clientId, + }; const form = useForm>({ initialValues: initialValues, validate: { @@ -122,14 +116,19 @@ const CreateProductModal = ({ { isEditProps && ( //
- - } - productId={innerProps.product.id} - /> + <> + + } + productId={innerProps.product.id} + /> + + ) //
}