feat: product barcode images
This commit is contained in:
@ -4,7 +4,10 @@ const path = "src/lib/client/zod.gen.ts";
|
||||
let content = fs.readFileSync(path, "utf8");
|
||||
|
||||
// Replace only for the upload schema
|
||||
content = content.replace("upload_file: z.string", "upload_file: z.any");
|
||||
const target = "upload_file: z.string";
|
||||
while (content.includes(target)) {
|
||||
content = content.replace(target, "upload_file: z.any");
|
||||
}
|
||||
|
||||
fs.writeFileSync(path, content);
|
||||
console.log("✅ Fixed zod schema for upload_file");
|
||||
|
||||
@ -18,6 +18,7 @@ export type ProductsFiltersForm = {
|
||||
|
||||
type ProductsContextState = {
|
||||
productsFiltersForm: UseFormReturnType<ProductsFiltersForm>;
|
||||
refetch: () => void;
|
||||
products: ProductSchema[];
|
||||
productsCrud: ProductsCrud;
|
||||
paginationInfo?: PaginationInfoSchema;
|
||||
@ -37,7 +38,7 @@ const useProductsContextState = (): ProductsContextState => {
|
||||
500
|
||||
);
|
||||
|
||||
const { products, paginationInfo, queryKey } = useProductsList({
|
||||
const { products, paginationInfo, queryKey, refetch } = useProductsList({
|
||||
clientId: productsFiltersForm.values.client?.id,
|
||||
searchInput: debouncedSearchInput,
|
||||
page: productsFiltersForm.values.page,
|
||||
@ -47,6 +48,7 @@ const useProductsContextState = (): ProductsContextState => {
|
||||
|
||||
return {
|
||||
productsFiltersForm,
|
||||
refetch,
|
||||
products,
|
||||
productsCrud,
|
||||
paginationInfo,
|
||||
|
||||
@ -4,7 +4,7 @@ import { ProductSchema } from "@/lib/client";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
const useProductsActions = () => {
|
||||
const { productsCrud, productsFiltersForm } = useProductsContext();
|
||||
const { productsCrud, productsFiltersForm, refetch } = useProductsContext();
|
||||
|
||||
const onCreateClick = () => {
|
||||
if (!productsFiltersForm.values.client) {
|
||||
@ -19,6 +19,7 @@ const useProductsActions = () => {
|
||||
innerProps: {
|
||||
onCreate: productsCrud.onCreate,
|
||||
clientId: productsFiltersForm.values.client.id,
|
||||
refetchProducts: refetch,
|
||||
isEditing: false,
|
||||
},
|
||||
});
|
||||
@ -33,6 +34,7 @@ const useProductsActions = () => {
|
||||
onChange: updated => productsCrud.onUpdate(product.id, updated),
|
||||
clientId: product.clientId,
|
||||
entity: product,
|
||||
refetchProducts: refetch,
|
||||
isEditing: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -39,6 +39,7 @@ import {
|
||||
deleteDealTag,
|
||||
deleteMarketplace,
|
||||
deleteProduct,
|
||||
deleteProductBarcodeImage,
|
||||
deleteProject,
|
||||
deleteService,
|
||||
deleteServiceCategory,
|
||||
@ -84,6 +85,7 @@ import {
|
||||
updateServiceCategory,
|
||||
updateServicesKit,
|
||||
updateStatus,
|
||||
uploadProductBarcodeImage,
|
||||
uploadProductImage,
|
||||
type Options,
|
||||
} from "../sdk.gen";
|
||||
@ -172,6 +174,9 @@ import type {
|
||||
DeleteMarketplaceData,
|
||||
DeleteMarketplaceError,
|
||||
DeleteMarketplaceResponse2,
|
||||
DeleteProductBarcodeImageData,
|
||||
DeleteProductBarcodeImageError,
|
||||
DeleteProductBarcodeImageResponse,
|
||||
DeleteProductData,
|
||||
DeleteProductError,
|
||||
DeleteProductResponse2,
|
||||
@ -275,6 +280,9 @@ import type {
|
||||
UpdateStatusData,
|
||||
UpdateStatusError,
|
||||
UpdateStatusResponse2,
|
||||
UploadProductBarcodeImageData,
|
||||
UploadProductBarcodeImageError,
|
||||
UploadProductBarcodeImageResponse,
|
||||
UploadProductImageData,
|
||||
UploadProductImageError,
|
||||
UploadProductImageResponse,
|
||||
@ -2292,6 +2300,84 @@ export const getProductBarcodePdfMutation = (
|
||||
return mutationOptions;
|
||||
};
|
||||
|
||||
export const uploadProductBarcodeImageQueryKey = (
|
||||
options: Options<UploadProductBarcodeImageData>
|
||||
) => createQueryKey("uploadProductBarcodeImage", options);
|
||||
|
||||
/**
|
||||
* Upload Product Barcode Image
|
||||
*/
|
||||
export const uploadProductBarcodeImageOptions = (
|
||||
options: Options<UploadProductBarcodeImageData>
|
||||
) => {
|
||||
return queryOptions({
|
||||
queryFn: async ({ queryKey, signal }) => {
|
||||
const { data } = await uploadProductBarcodeImage({
|
||||
...options,
|
||||
...queryKey[0],
|
||||
signal,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
queryKey: uploadProductBarcodeImageQueryKey(options),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload Product Barcode Image
|
||||
*/
|
||||
export const uploadProductBarcodeImageMutation = (
|
||||
options?: Partial<Options<UploadProductBarcodeImageData>>
|
||||
): UseMutationOptions<
|
||||
UploadProductBarcodeImageResponse,
|
||||
AxiosError<UploadProductBarcodeImageError>,
|
||||
Options<UploadProductBarcodeImageData>
|
||||
> => {
|
||||
const mutationOptions: UseMutationOptions<
|
||||
UploadProductBarcodeImageResponse,
|
||||
AxiosError<UploadProductBarcodeImageError>,
|
||||
Options<UploadProductBarcodeImageData>
|
||||
> = {
|
||||
mutationFn: async localOptions => {
|
||||
const { data } = await uploadProductBarcodeImage({
|
||||
...options,
|
||||
...localOptions,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
};
|
||||
return mutationOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete Product Barcode Image
|
||||
*/
|
||||
export const deleteProductBarcodeImageMutation = (
|
||||
options?: Partial<Options<DeleteProductBarcodeImageData>>
|
||||
): UseMutationOptions<
|
||||
DeleteProductBarcodeImageResponse,
|
||||
AxiosError<DeleteProductBarcodeImageError>,
|
||||
Options<DeleteProductBarcodeImageData>
|
||||
> => {
|
||||
const mutationOptions: UseMutationOptions<
|
||||
DeleteProductBarcodeImageResponse,
|
||||
AxiosError<DeleteProductBarcodeImageError>,
|
||||
Options<DeleteProductBarcodeImageData>
|
||||
> = {
|
||||
mutationFn: async localOptions => {
|
||||
const { data } = await deleteProductBarcodeImage({
|
||||
...options,
|
||||
...localOptions,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
};
|
||||
return mutationOptions;
|
||||
};
|
||||
|
||||
export const getServicesQueryKey = (options?: Options<GetServicesData>) =>
|
||||
createQueryKey("getServices", options);
|
||||
|
||||
|
||||
@ -92,6 +92,9 @@ import type {
|
||||
DeleteMarketplaceData,
|
||||
DeleteMarketplaceErrors,
|
||||
DeleteMarketplaceResponses,
|
||||
DeleteProductBarcodeImageData,
|
||||
DeleteProductBarcodeImageErrors,
|
||||
DeleteProductBarcodeImageResponses,
|
||||
DeleteProductData,
|
||||
DeleteProductErrors,
|
||||
DeleteProductResponses,
|
||||
@ -220,6 +223,9 @@ import type {
|
||||
UpdateStatusData,
|
||||
UpdateStatusErrors,
|
||||
UpdateStatusResponses,
|
||||
UploadProductBarcodeImageData,
|
||||
UploadProductBarcodeImageErrors,
|
||||
UploadProductBarcodeImageResponses,
|
||||
UploadProductImageData,
|
||||
UploadProductImageErrors,
|
||||
UploadProductImageResponses,
|
||||
@ -281,6 +287,8 @@ import {
|
||||
zDeleteDealTagResponse2,
|
||||
zDeleteMarketplaceData,
|
||||
zDeleteMarketplaceResponse2,
|
||||
zDeleteProductBarcodeImageData,
|
||||
zDeleteProductBarcodeImageResponse,
|
||||
zDeleteProductData,
|
||||
zDeleteProductResponse2,
|
||||
zDeleteProjectData,
|
||||
@ -373,6 +381,8 @@ import {
|
||||
zUpdateServicesKitResponse2,
|
||||
zUpdateStatusData,
|
||||
zUpdateStatusResponse2,
|
||||
zUploadProductBarcodeImageData,
|
||||
zUploadProductBarcodeImageResponse,
|
||||
zUploadProductImageData,
|
||||
zUploadProductImageResponse,
|
||||
} from "./zod.gen";
|
||||
@ -1719,7 +1729,7 @@ export const uploadProductImage = <ThrowOnError extends boolean = false>(
|
||||
responseValidator: async data => {
|
||||
return await zUploadProductImageResponse.parseAsync(data);
|
||||
},
|
||||
url: "/crm/v1/fulfillment-base/product/images/upload/{productId}",
|
||||
url: "/crm/v1/fulfillment-base/product{pk}/images/upload",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": null,
|
||||
@ -1755,6 +1765,57 @@ export const getProductBarcodePdf = <ThrowOnError extends boolean = false>(
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload Product Barcode Image
|
||||
*/
|
||||
export const uploadProductBarcodeImage = <ThrowOnError extends boolean = false>(
|
||||
options: Options<UploadProductBarcodeImageData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
UploadProductBarcodeImageResponses,
|
||||
UploadProductBarcodeImageErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
...formDataBodySerializer,
|
||||
requestValidator: async data => {
|
||||
return await zUploadProductBarcodeImageData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zUploadProductBarcodeImageResponse.parseAsync(data);
|
||||
},
|
||||
url: "/crm/v1/fulfillment-base/product{pk}/barcode/image/upload",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": null,
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete Product Barcode Image
|
||||
*/
|
||||
export const deleteProductBarcodeImage = <ThrowOnError extends boolean = false>(
|
||||
options: Options<DeleteProductBarcodeImageData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).delete<
|
||||
DeleteProductBarcodeImageResponses,
|
||||
DeleteProductBarcodeImageErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zDeleteProductBarcodeImageData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zDeleteProductBarcodeImageResponse.parseAsync(data);
|
||||
},
|
||||
url: "/crm/v1/fulfillment-base/product{pk}/barcode/image",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Services
|
||||
*/
|
||||
|
||||
@ -63,6 +63,20 @@ export type BarcodeTemplateSizeSchema = {
|
||||
height: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* BarcodeUploadImageResponse
|
||||
*/
|
||||
export type BarcodeUploadImageResponse = {
|
||||
/**
|
||||
* Message
|
||||
*/
|
||||
message: string;
|
||||
/**
|
||||
* Imageurl
|
||||
*/
|
||||
imageUrl?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* BaseMarketplaceSchema
|
||||
*/
|
||||
@ -103,6 +117,16 @@ export type BoardSchema = {
|
||||
projectId: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Body_upload_product_barcode_image
|
||||
*/
|
||||
export type BodyUploadProductBarcodeImage = {
|
||||
/**
|
||||
* Upload File
|
||||
*/
|
||||
upload_file: Blob | File;
|
||||
};
|
||||
|
||||
/**
|
||||
* Body_upload_product_image
|
||||
*/
|
||||
@ -639,15 +663,7 @@ export type CreateProductSchema = {
|
||||
/**
|
||||
* Barcodes
|
||||
*/
|
||||
barcodes: Array<string>;
|
||||
/**
|
||||
* Imageurl
|
||||
*/
|
||||
imageUrl?: string | null;
|
||||
/**
|
||||
* Images
|
||||
*/
|
||||
images?: Array<ProductImageSchema> | null;
|
||||
barcodes?: Array<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1090,6 +1106,16 @@ export type DealTagSchema = {
|
||||
tagColor: DealTagColorSchema;
|
||||
};
|
||||
|
||||
/**
|
||||
* DeleteBarcodeImageResponse
|
||||
*/
|
||||
export type DeleteBarcodeImageResponse = {
|
||||
/**
|
||||
* Message
|
||||
*/
|
||||
message: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* DeleteBarcodeTemplateResponse
|
||||
*/
|
||||
@ -1538,6 +1564,20 @@ export type PaginationInfoSchema = {
|
||||
totalItems: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* ProductBarcodeImageSchema
|
||||
*/
|
||||
export type ProductBarcodeImageSchema = {
|
||||
/**
|
||||
* Productid
|
||||
*/
|
||||
productId: number;
|
||||
/**
|
||||
* Imageurl
|
||||
*/
|
||||
imageUrl: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* ProductImageSchema
|
||||
*/
|
||||
@ -1603,7 +1643,7 @@ export type ProductSchema = {
|
||||
/**
|
||||
* Barcodes
|
||||
*/
|
||||
barcodes: Array<string>;
|
||||
barcodes?: Array<string>;
|
||||
/**
|
||||
* Imageurl
|
||||
*/
|
||||
@ -1612,6 +1652,11 @@ export type ProductSchema = {
|
||||
* Images
|
||||
*/
|
||||
images?: Array<ProductImageSchema> | null;
|
||||
/**
|
||||
* Barcodeimageurl
|
||||
*/
|
||||
barcodeImageUrl?: string | null;
|
||||
barcodeImage?: ProductBarcodeImageSchema | null;
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
@ -4073,12 +4118,12 @@ export type UploadProductImageData = {
|
||||
body: BodyUploadProductImage;
|
||||
path: {
|
||||
/**
|
||||
* Productid
|
||||
* Pk
|
||||
*/
|
||||
productId: number;
|
||||
pk: number;
|
||||
};
|
||||
query?: never;
|
||||
url: "/crm/v1/fulfillment-base/product/images/upload/{productId}";
|
||||
url: "/crm/v1/fulfillment-base/product{pk}/images/upload";
|
||||
};
|
||||
|
||||
export type UploadProductImageErrors = {
|
||||
@ -4128,6 +4173,70 @@ export type GetProductBarcodePdfResponses = {
|
||||
export type GetProductBarcodePdfResponse2 =
|
||||
GetProductBarcodePdfResponses[keyof GetProductBarcodePdfResponses];
|
||||
|
||||
export type UploadProductBarcodeImageData = {
|
||||
body: BodyUploadProductBarcodeImage;
|
||||
path: {
|
||||
/**
|
||||
* Pk
|
||||
*/
|
||||
pk: number;
|
||||
};
|
||||
query?: never;
|
||||
url: "/crm/v1/fulfillment-base/product{pk}/barcode/image/upload";
|
||||
};
|
||||
|
||||
export type UploadProductBarcodeImageErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type UploadProductBarcodeImageError =
|
||||
UploadProductBarcodeImageErrors[keyof UploadProductBarcodeImageErrors];
|
||||
|
||||
export type UploadProductBarcodeImageResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: BarcodeUploadImageResponse;
|
||||
};
|
||||
|
||||
export type UploadProductBarcodeImageResponse =
|
||||
UploadProductBarcodeImageResponses[keyof UploadProductBarcodeImageResponses];
|
||||
|
||||
export type DeleteProductBarcodeImageData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Pk
|
||||
*/
|
||||
pk: number;
|
||||
};
|
||||
query?: never;
|
||||
url: "/crm/v1/fulfillment-base/product{pk}/barcode/image";
|
||||
};
|
||||
|
||||
export type DeleteProductBarcodeImageErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type DeleteProductBarcodeImageError =
|
||||
DeleteProductBarcodeImageErrors[keyof DeleteProductBarcodeImageErrors];
|
||||
|
||||
export type DeleteProductBarcodeImageResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: DeleteBarcodeImageResponse;
|
||||
};
|
||||
|
||||
export type DeleteProductBarcodeImageResponse =
|
||||
DeleteProductBarcodeImageResponses[keyof DeleteProductBarcodeImageResponses];
|
||||
|
||||
export type GetServicesData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
||||
@ -32,6 +32,14 @@ export const zBarcodeTemplateSchema = z.object({
|
||||
id: z.int(),
|
||||
});
|
||||
|
||||
/**
|
||||
* BarcodeUploadImageResponse
|
||||
*/
|
||||
export const zBarcodeUploadImageResponse = z.object({
|
||||
message: z.string(),
|
||||
imageUrl: z.optional(z.union([z.string(), z.null()])),
|
||||
});
|
||||
|
||||
/**
|
||||
* BaseMarketplaceSchema
|
||||
*/
|
||||
@ -51,6 +59,13 @@ export const zBoardSchema = z.object({
|
||||
projectId: z.int(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Body_upload_product_barcode_image
|
||||
*/
|
||||
export const zBodyUploadProductBarcodeImage = z.object({
|
||||
upload_file: z.any(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Body_upload_product_image
|
||||
*/
|
||||
@ -250,6 +265,14 @@ export const zProductImageSchema = z.object({
|
||||
imageUrl: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* ProductBarcodeImageSchema
|
||||
*/
|
||||
export const zProductBarcodeImageSchema = z.object({
|
||||
productId: z.int(),
|
||||
imageUrl: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* ProductSchema
|
||||
*/
|
||||
@ -264,9 +287,11 @@ export const zProductSchema = z.object({
|
||||
composition: z.union([z.string(), z.null()]),
|
||||
size: z.union([z.string(), z.null()]),
|
||||
additionalInfo: z.union([z.string(), z.null()]),
|
||||
barcodes: z.array(z.string()),
|
||||
barcodes: z.optional(z.array(z.string())).default([]),
|
||||
imageUrl: z.optional(z.union([z.string(), z.null()])),
|
||||
images: z.optional(z.union([z.array(zProductImageSchema), z.null()])),
|
||||
barcodeImageUrl: z.optional(z.union([z.string(), z.null()])),
|
||||
barcodeImage: z.optional(z.union([zProductBarcodeImageSchema, z.null()])),
|
||||
id: z.int(),
|
||||
barcodeTemplate: zBarcodeTemplateSchema,
|
||||
});
|
||||
@ -526,9 +551,7 @@ export const zCreateProductSchema = z.object({
|
||||
composition: z.union([z.string(), z.null()]),
|
||||
size: z.union([z.string(), z.null()]),
|
||||
additionalInfo: z.union([z.string(), z.null()]),
|
||||
barcodes: z.array(z.string()),
|
||||
imageUrl: z.optional(z.union([z.string(), z.null()])),
|
||||
images: z.optional(z.union([z.array(zProductImageSchema), z.null()])),
|
||||
barcodes: z.optional(z.array(z.string())).default([]),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -746,6 +769,13 @@ export const zDealProductAddKitResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteBarcodeImageResponse
|
||||
*/
|
||||
export const zDeleteBarcodeImageResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* DeleteBarcodeTemplateResponse
|
||||
*/
|
||||
@ -2161,7 +2191,7 @@ export const zUpdateProductResponse2 = zUpdateProductResponse;
|
||||
export const zUploadProductImageData = z.object({
|
||||
body: zBodyUploadProductImage,
|
||||
path: z.object({
|
||||
productId: z.int(),
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
@ -2182,6 +2212,32 @@ export const zGetProductBarcodePdfData = z.object({
|
||||
*/
|
||||
export const zGetProductBarcodePdfResponse2 = zGetProductBarcodePdfResponse;
|
||||
|
||||
export const zUploadProductBarcodeImageData = z.object({
|
||||
body: zBodyUploadProductBarcodeImage,
|
||||
path: z.object({
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zUploadProductBarcodeImageResponse = zBarcodeUploadImageResponse;
|
||||
|
||||
export const zDeleteProductBarcodeImageData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.object({
|
||||
pk: z.int(),
|
||||
}),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zDeleteProductBarcodeImageResponse = zDeleteBarcodeImageResponse;
|
||||
|
||||
export const zGetServicesData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.optional(z.never()),
|
||||
|
||||
@ -22,6 +22,7 @@ const ProductsActions: FC = () => {
|
||||
onCreate: productsCrud.onCreate,
|
||||
isEditing: false,
|
||||
clientId: deal.client.id,
|
||||
refetchProducts: dealProductsList.refetch,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -29,6 +29,7 @@ const ProductViewActions: FC<Props> = ({ dealProduct }) => {
|
||||
entity: dealProduct.product,
|
||||
isEditing: true,
|
||||
clientId: dealProduct.product.clientId,
|
||||
refetchProducts: dealProductsList.refetch,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -0,0 +1,182 @@
|
||||
import { FC } from "react";
|
||||
import { IconPhoto, IconUpload, IconX } from "@tabler/icons-react";
|
||||
import { Fieldset, Flex, Group, Loader, rem, Text } from "@mantine/core";
|
||||
import { Dropzone, DropzoneProps, FileWithPath } from "@mantine/dropzone";
|
||||
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
||||
import { deleteProductBarcodeImage, uploadProductBarcodeImage } from "@/lib/client";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
import useImageDropzone from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/components/ProductImageDropzone/useImageDropzone";
|
||||
import BaseFormInputProps from "@/utils/baseFormInputProps";
|
||||
|
||||
|
||||
// Barcode image aspects ratio should be equal 58/40
|
||||
const BARCODE_IMAGE_RATIO = 1.45;
|
||||
|
||||
interface RestProps {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
productId?: number;
|
||||
onUploaded?: () => void;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
|
||||
const BarcodeImageDropzone: FC<Props> = ({
|
||||
productId,
|
||||
imageUrlInputProps,
|
||||
onUploaded,
|
||||
}: Props) => {
|
||||
const imageDropzoneProps = useImageDropzone({
|
||||
imageUrlInputProps,
|
||||
});
|
||||
|
||||
const onDeleteBarcodeImage = () => {
|
||||
if (!productId || !imageUrlInputProps) return;
|
||||
const { setIsLoading } = imageDropzoneProps;
|
||||
setIsLoading(true);
|
||||
|
||||
deleteProductBarcodeImage({
|
||||
path: {
|
||||
pk: productId,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
notifications.success({ message: data?.message });
|
||||
imageUrlInputProps.onChange("");
|
||||
setIsLoading(false);
|
||||
onUploaded && onUploaded();
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
notifications.error({ message: err.toString() });
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onDrop = (files: FileWithPath[]) => {
|
||||
if (!productId || !imageUrlInputProps) return;
|
||||
if (files.length > 1) {
|
||||
notifications.error({ message: "Прикрепите одно изображение" });
|
||||
return;
|
||||
}
|
||||
const { setIsLoading, setShowDropzone } = imageDropzoneProps;
|
||||
const file = files[0];
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
uploadProductBarcodeImage({
|
||||
path: {
|
||||
pk: productId,
|
||||
},
|
||||
body: {
|
||||
upload_file: file,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
notifications.success({ message: data?.message });
|
||||
setIsLoading(false);
|
||||
|
||||
if (!data?.imageUrl) {
|
||||
setShowDropzone(true);
|
||||
return;
|
||||
}
|
||||
imageUrlInputProps.onChange(data?.imageUrl);
|
||||
setShowDropzone(false);
|
||||
onUploaded && onUploaded();
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
notifications.error({ message: err.toString() });
|
||||
setShowDropzone(true);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getBody = () => {
|
||||
return imageUrlInputProps?.value ? (
|
||||
// eslint-disable-next-line jsx-a11y/alt-text
|
||||
<object
|
||||
style={{
|
||||
aspectRatio: BARCODE_IMAGE_RATIO,
|
||||
width: "240px",
|
||||
}}
|
||||
data={imageUrlInputProps?.value}
|
||||
/>
|
||||
) : (
|
||||
<Dropzone
|
||||
accept={["application/pdf"]}
|
||||
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>
|
||||
Перенесите или нажмите чтоб выбрать файл <br />
|
||||
Pdf-файл должен содержать 1 страницу размером 58 х
|
||||
40
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
gap={"xs"}
|
||||
direction={"column"}>
|
||||
<Fieldset legend={"Штрихкод"}>
|
||||
<Flex justify={"center"}>
|
||||
{imageDropzoneProps.isLoading ? <Loader /> : getBody()}
|
||||
</Flex>
|
||||
</Fieldset>
|
||||
{imageUrlInputProps?.value && (
|
||||
<>
|
||||
<InlineButton
|
||||
onClick={() => imageUrlInputProps?.onChange("")}>
|
||||
Заменить штрихкод
|
||||
</InlineButton>
|
||||
<InlineButton onClick={onDeleteBarcodeImage}>
|
||||
Удалить штрихкод
|
||||
</InlineButton>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default BarcodeImageDropzone;
|
||||
@ -9,6 +9,7 @@ import useImageDropzone from "./useImageDropzone";
|
||||
interface RestProps {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
productId?: number;
|
||||
onUploaded?: () => void;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
@ -16,6 +17,7 @@ type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
const ProductImageDropzone: FC<Props> = ({
|
||||
imageUrlInputProps,
|
||||
productId,
|
||||
onUploaded,
|
||||
}: Props) => {
|
||||
const imageDropzoneProps = useImageDropzone({
|
||||
imageUrlInputProps,
|
||||
@ -34,7 +36,7 @@ const ProductImageDropzone: FC<Props> = ({
|
||||
|
||||
uploadProductImage({
|
||||
path: {
|
||||
productId,
|
||||
pk: productId,
|
||||
},
|
||||
body: {
|
||||
upload_file: file,
|
||||
@ -50,6 +52,7 @@ const ProductImageDropzone: FC<Props> = ({
|
||||
}
|
||||
imageUrlInputProps?.onChange(data?.imageUrl);
|
||||
setShowDropzone(false);
|
||||
onUploaded && onUploaded();
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Fieldset, Flex, Stack, TagsInput, TextInput } from "@mantine/core";
|
||||
import { Flex } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import BarcodeTemplateSelect from "@/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect";
|
||||
import {
|
||||
BarcodeTemplateSchema,
|
||||
CreateProductSchema,
|
||||
@ -14,11 +13,11 @@ import {
|
||||
import BaseFormModal, {
|
||||
CreateEditFormProps,
|
||||
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||
import ProductImageDropzone from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductImageDropzone/ProductImageDropzone";
|
||||
import CharacteristicsTab from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/ProductEditorModal/components/CharacteristicsTab";
|
||||
import ImagesTab from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/ProductEditorModal/components/ImagesTab";
|
||||
import ProductEditorSegmentedControl, {
|
||||
ProductEditorTab,
|
||||
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/ProductEditorModal/components/ProductEditorSegmentedControl";
|
||||
import BaseFormInputProps from "@/utils/baseFormInputProps";
|
||||
|
||||
type Props = CreateEditFormProps<
|
||||
CreateProductSchema,
|
||||
@ -26,6 +25,7 @@ type Props = CreateEditFormProps<
|
||||
ProductSchema
|
||||
> & {
|
||||
clientId: number;
|
||||
refetchProducts: () => void;
|
||||
};
|
||||
|
||||
type ProductForm = Partial<
|
||||
@ -64,92 +64,18 @@ const ProductEditorModal = ({
|
||||
initialValues,
|
||||
validate: {
|
||||
name: name =>
|
||||
!name || name.trim() !== ""
|
||||
? null
|
||||
: "Необходимо ввести название товара",
|
||||
(!name || name.trim() === "") &&
|
||||
"Необходимо ввести название товара",
|
||||
barcodeTemplate: barcodeTemplate =>
|
||||
!barcodeTemplate && "Необходимо выбрать шаблон штрихкода",
|
||||
},
|
||||
});
|
||||
|
||||
const characteristicsTab = (
|
||||
<>
|
||||
<Fieldset legend={"Основные характеристики"}>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
placeholder={"Введите название товара"}
|
||||
label={"Название товара"}
|
||||
{...form.getInputProps("name")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите артикул"}
|
||||
label={"Артикул"}
|
||||
{...form.getInputProps("article")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите складской артикул"}
|
||||
label={"Складской артикул"}
|
||||
{...form.getInputProps("factoryArticle")}
|
||||
/>
|
||||
<BarcodeTemplateSelect
|
||||
placeholder={"Выберите шаблон штрихкода"}
|
||||
label={"Шаблон штрихкода"}
|
||||
{...form.getInputProps("barcodeTemplate")}
|
||||
onChange={template => {
|
||||
form.setFieldValue("barcodeTemplate", template);
|
||||
form.setFieldValue(
|
||||
"barcodeTemplateId",
|
||||
template?.id
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<TagsInput
|
||||
placeholder={
|
||||
!form.values.barcodes?.length
|
||||
? "Добавьте штрихкоды к товару"
|
||||
: ""
|
||||
}
|
||||
label={"Штрихкоды"}
|
||||
{...form.getInputProps("barcodes")}
|
||||
/>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Дополнительные характеристики"}>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
placeholder={"Введите бренд"}
|
||||
label={"Бренд"}
|
||||
{...form.getInputProps("brand")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите состав"}
|
||||
label={"Состав"}
|
||||
{...form.getInputProps("composition")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите цвет"}
|
||||
label={"Цвет"}
|
||||
{...form.getInputProps("color")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите размер"}
|
||||
label={"Размер"}
|
||||
{...form.getInputProps("size")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите доп. информацию"}
|
||||
label={"Доп. информация"}
|
||||
{...form.getInputProps("additionalInfo")}
|
||||
/>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
</>
|
||||
);
|
||||
|
||||
const imageTab = isEditing && (
|
||||
<ProductImageDropzone
|
||||
imageUrlInputProps={
|
||||
form.getInputProps("imageUrl") as BaseFormInputProps<string>
|
||||
}
|
||||
<ImagesTab
|
||||
form={form}
|
||||
productId={innerProps.entity.id}
|
||||
onUploaded={innerProps.refetchProducts}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -169,9 +95,11 @@ const ProductEditorModal = ({
|
||||
onChange={setEditorTab}
|
||||
/>
|
||||
)}
|
||||
{editorTab === ProductEditorTab.CHARACTERISTICS
|
||||
? characteristicsTab
|
||||
: imageTab}
|
||||
{editorTab === ProductEditorTab.CHARACTERISTICS ? (
|
||||
<CharacteristicsTab form={form} />
|
||||
) : (
|
||||
imageTab
|
||||
)}
|
||||
</Flex>
|
||||
</BaseFormModal>
|
||||
);
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
import { FC } from "react";
|
||||
import { Fieldset, Stack, TagsInput, TextInput } from "@mantine/core";
|
||||
import BarcodeTemplateSelect from "@/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect";
|
||||
import { UseFormReturnType } from "@mantine/form";
|
||||
import { ProductSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
form: UseFormReturnType<Partial<ProductSchema>>;
|
||||
}
|
||||
|
||||
const CharacteristicsTab: FC<Props> = ({ form }) => (
|
||||
<>
|
||||
<Fieldset legend={"Основные характеристики"}>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
placeholder={"Введите название товара"}
|
||||
label={"Название товара"}
|
||||
{...form.getInputProps("name")}
|
||||
withAsterisk
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите артикул"}
|
||||
label={"Артикул"}
|
||||
{...form.getInputProps("article")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите складской артикул"}
|
||||
label={"Складской артикул"}
|
||||
{...form.getInputProps("factoryArticle")}
|
||||
/>
|
||||
<BarcodeTemplateSelect
|
||||
placeholder={"Выберите шаблон штрихкода"}
|
||||
label={"Шаблон штрихкода"}
|
||||
{...form.getInputProps("barcodeTemplate")}
|
||||
onChange={template => {
|
||||
form.setFieldValue("barcodeTemplate", template);
|
||||
form.setFieldValue(
|
||||
"barcodeTemplateId",
|
||||
template?.id
|
||||
);
|
||||
}}
|
||||
withAsterisk
|
||||
/>
|
||||
<TagsInput
|
||||
placeholder={
|
||||
!form.values.barcodes?.length
|
||||
? "Добавьте штрихкоды к товару"
|
||||
: ""
|
||||
}
|
||||
label={"Штрихкоды"}
|
||||
{...form.getInputProps("barcodes")}
|
||||
/>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Дополнительные характеристики"}>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
placeholder={"Введите бренд"}
|
||||
label={"Бренд"}
|
||||
{...form.getInputProps("brand")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите состав"}
|
||||
label={"Состав"}
|
||||
{...form.getInputProps("composition")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите цвет"}
|
||||
label={"Цвет"}
|
||||
{...form.getInputProps("color")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите размер"}
|
||||
label={"Размер"}
|
||||
{...form.getInputProps("size")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите доп. информацию"}
|
||||
label={"Доп. информация"}
|
||||
{...form.getInputProps("additionalInfo")}
|
||||
/>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
</>
|
||||
)
|
||||
|
||||
export default CharacteristicsTab;
|
||||
@ -0,0 +1,36 @@
|
||||
import { FC } from "react";
|
||||
import { Stack } from "@mantine/core";
|
||||
import { UseFormReturnType } from "@mantine/form";
|
||||
import { ProductSchema } from "@/lib/client";
|
||||
import BarcodeImageDropzone from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/components/BarcodeImageDropzone/BarcodeImageDropzone";
|
||||
import ProductImageDropzone from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/components/ProductImageDropzone/ProductImageDropzone";
|
||||
import BaseFormInputProps from "@/utils/baseFormInputProps";
|
||||
|
||||
type Props = {
|
||||
form: UseFormReturnType<Partial<ProductSchema>>;
|
||||
productId: number;
|
||||
onUploaded: () => void;
|
||||
};
|
||||
|
||||
const ImagesTab: FC<Props> = ({ form, productId, onUploaded }) => (
|
||||
<Stack>
|
||||
<ProductImageDropzone
|
||||
imageUrlInputProps={
|
||||
form.getInputProps("imageUrl") as BaseFormInputProps<string>
|
||||
}
|
||||
productId={productId}
|
||||
onUploaded={onUploaded}
|
||||
/>
|
||||
<BarcodeImageDropzone
|
||||
imageUrlInputProps={
|
||||
form.getInputProps(
|
||||
"barcodeImageUrl"
|
||||
) as BaseFormInputProps<string>
|
||||
}
|
||||
productId={productId}
|
||||
onUploaded={onUploaded}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
export default ImagesTab;
|
||||
Reference in New Issue
Block a user