feat: product images upload and display

This commit is contained in:
2025-10-20 16:13:05 +04:00
parent 8cc11bca67
commit 82f08b4f83
12 changed files with 380 additions and 226 deletions

10
fix-zod.ts Normal file
View File

@ -0,0 +1,10 @@
import * as fs from "fs";
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");
fs.writeFileSync(path, content);
console.log("✅ Fixed zod schema for upload_file");

View File

@ -7,7 +7,7 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"generate-client": "openapi-ts && prettier --write ./src/lib/client/**/*.ts && git add ./src/lib/client", "generate-client": "openapi-ts && prettier --write ./src/lib/client/**/*.ts && git add ./src/lib/client & sudo npx tsc ./fix-zod.ts && mv -f ./fix-zod.js ./fix-zod.cjs && sudo node ./fix-zod.cjs",
"generate-modules": "sudo npx tsc ./src/modules/modulesFileGen/modulesFileGen.ts && mv -f ./src/modules/modulesFileGen/modulesFileGen.js ./src/modules/modulesFileGen/modulesFileGen.cjs && sudo node ./src/modules/modulesFileGen/modulesFileGen.cjs" "generate-modules": "sudo npx tsc ./src/modules/modulesFileGen/modulesFileGen.ts && mv -f ./src/modules/modulesFileGen/modulesFileGen.js ./src/modules/modulesFileGen/modulesFileGen.cjs && sudo node ./src/modules/modulesFileGen/modulesFileGen.cjs"
}, },
"dependencies": { "dependencies": {

View File

@ -47,7 +47,7 @@ const ServicesKitEditorModal = ({
{...form.getInputProps("name")} {...form.getInputProps("name")}
/> />
<ServiceTypeSegmentedControl <ServiceTypeSegmentedControl
value={form.values.serviceType?.toString()} value={form.values.serviceType}
onChange={tab => { onChange={tab => {
form.setFieldValue("serviceType", Number(tab)); form.setFieldValue("serviceType", Number(tab));
form.setFieldValue("services", []); form.setFieldValue("services", []);

View File

@ -60,7 +60,6 @@ import {
getMarketplaces, getMarketplaces,
getProductBarcodePdf, getProductBarcodePdf,
getProducts, getProducts,
getProject,
getProjects, getProjects,
getServiceCategories, getServiceCategories,
getServices, getServices,
@ -85,6 +84,7 @@ import {
updateServiceCategory, updateServiceCategory,
updateServicesKit, updateServicesKit,
updateStatus, updateStatus,
uploadProductImage,
type Options, type Options,
} from "../sdk.gen"; } from "../sdk.gen";
import type { import type {
@ -215,7 +215,6 @@ import type {
GetProductsData, GetProductsData,
GetProductsError, GetProductsError,
GetProductsResponse2, GetProductsResponse2,
GetProjectData,
GetProjectsData, GetProjectsData,
GetServiceCategoriesData, GetServiceCategoriesData,
GetServicesData, GetServicesData,
@ -276,6 +275,9 @@ import type {
UpdateStatusData, UpdateStatusData,
UpdateStatusError, UpdateStatusError,
UpdateStatusResponse2, UpdateStatusResponse2,
UploadProductImageData,
UploadProductImageError,
UploadProductImageResponse,
} from "../types.gen"; } from "../types.gen";
export type QueryKey<TOptions extends Options> = [ export type QueryKey<TOptions extends Options> = [
@ -2188,6 +2190,57 @@ export const updateProductMutation = (
return mutationOptions; return mutationOptions;
}; };
export const uploadProductImageQueryKey = (
options: Options<UploadProductImageData>
) => createQueryKey("uploadProductImage", options);
/**
* Upload Product Image
*/
export const uploadProductImageOptions = (
options: Options<UploadProductImageData>
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await uploadProductImage({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: uploadProductImageQueryKey(options),
});
};
/**
* Upload Product Image
*/
export const uploadProductImageMutation = (
options?: Partial<Options<UploadProductImageData>>
): UseMutationOptions<
UploadProductImageResponse,
AxiosError<UploadProductImageError>,
Options<UploadProductImageData>
> => {
const mutationOptions: UseMutationOptions<
UploadProductImageResponse,
AxiosError<UploadProductImageError>,
Options<UploadProductImageData>
> = {
mutationFn: async localOptions => {
const { data } = await uploadProductImage({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const getProductBarcodePdfQueryKey = ( export const getProductBarcodePdfQueryKey = (
options: Options<GetProductBarcodePdfData> options: Options<GetProductBarcodePdfData>
) => createQueryKey("getProductBarcodePdf", options); ) => createQueryKey("getProductBarcodePdf", options);
@ -2716,27 +2769,6 @@ export const deleteProjectMutation = (
return mutationOptions; return mutationOptions;
}; };
export const getProjectQueryKey = (options: Options<GetProjectData>) =>
createQueryKey("getProject", options);
/**
* Get Project
*/
export const getProjectOptions = (options: Options<GetProjectData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await getProject({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: getProjectQueryKey(options),
});
};
/** /**
* Update Project * Update Project
*/ */

View File

@ -1,6 +1,11 @@
// This file is auto-generated by @hey-api/openapi-ts // This file is auto-generated by @hey-api/openapi-ts
import type { Client, Options as ClientOptions, TDataShape } from "./client"; import {
formDataBodySerializer,
type Client,
type Options as ClientOptions,
type TDataShape,
} from "./client";
import { client as _heyApiClient } from "./client.gen"; import { client as _heyApiClient } from "./client.gen";
import type { import type {
AddKitToDealData, AddKitToDealData,
@ -147,9 +152,6 @@ import type {
GetProductsData, GetProductsData,
GetProductsErrors, GetProductsErrors,
GetProductsResponses, GetProductsResponses,
GetProjectData,
GetProjectErrors,
GetProjectResponses,
GetProjectsData, GetProjectsData,
GetProjectsResponses, GetProjectsResponses,
GetServiceCategoriesData, GetServiceCategoriesData,
@ -218,6 +220,9 @@ import type {
UpdateStatusData, UpdateStatusData,
UpdateStatusErrors, UpdateStatusErrors,
UpdateStatusResponses, UpdateStatusResponses,
UploadProductImageData,
UploadProductImageErrors,
UploadProductImageResponses,
} from "./types.gen"; } from "./types.gen";
import { import {
zAddKitToDealData, zAddKitToDealData,
@ -320,8 +325,6 @@ import {
zGetProductBarcodePdfResponse2, zGetProductBarcodePdfResponse2,
zGetProductsData, zGetProductsData,
zGetProductsResponse2, zGetProductsResponse2,
zGetProjectData,
zGetProjectResponse2,
zGetProjectsData, zGetProjectsData,
zGetProjectsResponse2, zGetProjectsResponse2,
zGetServiceCategoriesData, zGetServiceCategoriesData,
@ -370,6 +373,8 @@ import {
zUpdateServicesKitResponse2, zUpdateServicesKitResponse2,
zUpdateStatusData, zUpdateStatusData,
zUpdateStatusResponse2, zUpdateStatusResponse2,
zUploadProductImageData,
zUploadProductImageResponse,
} from "./zod.gen"; } from "./zod.gen";
export type Options< export type Options<
@ -1695,6 +1700,34 @@ export const updateProduct = <ThrowOnError extends boolean = false>(
}); });
}; };
/**
* Upload Product Image
*/
export const uploadProductImage = <ThrowOnError extends boolean = false>(
options: Options<UploadProductImageData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).post<
UploadProductImageResponses,
UploadProductImageErrors,
ThrowOnError
>({
...formDataBodySerializer,
requestValidator: async data => {
return await zUploadProductImageData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zUploadProductImageResponse.parseAsync(data);
},
url: "/crm/v1/fulfillment-base/product/images/upload/{productId}",
...options,
headers: {
"Content-Type": null,
...options.headers,
},
});
};
/** /**
* Get Product Barcode Pdf * Get Product Barcode Pdf
*/ */
@ -2095,29 +2128,6 @@ export const deleteProject = <ThrowOnError extends boolean = false>(
}); });
}; };
/**
* Get Project
*/
export const getProject = <ThrowOnError extends boolean = false>(
options: Options<GetProjectData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).get<
GetProjectResponses,
GetProjectErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zGetProjectData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zGetProjectResponse2.parseAsync(data);
},
url: "/crm/v1/project/{pk}",
...options,
});
};
/** /**
* Update Project * Update Project
*/ */

View File

@ -103,6 +103,16 @@ export type BoardSchema = {
projectId: number; projectId: number;
}; };
/**
* Body_upload_product_image
*/
export type BodyUploadProductImage = {
/**
* Upload File
*/
upload_file: Blob | File;
};
/** /**
* BuiltInModuleSchema * BuiltInModuleSchema
*/ */
@ -630,6 +640,14 @@ export type CreateProductSchema = {
* Barcodes * Barcodes
*/ */
barcodes: Array<string>; barcodes: Array<string>;
/**
* Imageurl
*/
imageUrl?: string | null;
/**
* Images
*/
images?: Array<ProductImageSchema> | null;
}; };
/** /**
@ -1400,13 +1418,6 @@ export type GetProductsResponse = {
paginationInfo: PaginationInfoSchema; paginationInfo: PaginationInfoSchema;
}; };
/**
* GetProjectResponse
*/
export type GetProjectResponse = {
entity: ProjectSchema;
};
/** /**
* GetProjectsResponse * GetProjectsResponse
*/ */
@ -1593,6 +1604,14 @@ export type ProductSchema = {
* Barcodes * Barcodes
*/ */
barcodes: Array<string>; barcodes: Array<string>;
/**
* Imageurl
*/
imageUrl?: string | null;
/**
* Images
*/
images?: Array<ProductImageSchema> | null;
/** /**
* Id * Id
*/ */
@ -1655,6 +1674,20 @@ export type ProductServicesDuplicateResponse = {
message: string; message: string;
}; };
/**
* ProductUploadImageResponse
*/
export type ProductUploadImageResponse = {
/**
* Message
*/
message: string;
/**
* Imageurl
*/
imageUrl?: string | null;
};
/** /**
* ProjectSchema * ProjectSchema
*/ */
@ -4036,6 +4069,38 @@ export type UpdateProductResponses = {
export type UpdateProductResponse2 = export type UpdateProductResponse2 =
UpdateProductResponses[keyof UpdateProductResponses]; UpdateProductResponses[keyof UpdateProductResponses];
export type UploadProductImageData = {
body: BodyUploadProductImage;
path: {
/**
* Productid
*/
productId: number;
};
query?: never;
url: "/crm/v1/fulfillment-base/product/images/upload/{productId}";
};
export type UploadProductImageErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type UploadProductImageError =
UploadProductImageErrors[keyof UploadProductImageErrors];
export type UploadProductImageResponses = {
/**
* Successful Response
*/
200: ProductUploadImageResponse;
};
export type UploadProductImageResponse =
UploadProductImageResponses[keyof UploadProductImageResponses];
export type GetProductBarcodePdfData = { export type GetProductBarcodePdfData = {
body: GetProductBarcodePdfRequest; body: GetProductBarcodePdfRequest;
path?: never; path?: never;
@ -4458,37 +4523,6 @@ export type DeleteProjectResponses = {
export type DeleteProjectResponse2 = export type DeleteProjectResponse2 =
DeleteProjectResponses[keyof DeleteProjectResponses]; DeleteProjectResponses[keyof DeleteProjectResponses];
export type GetProjectData = {
body?: never;
path: {
/**
* Pk
*/
pk: number;
};
query?: never;
url: "/crm/v1/project/{pk}";
};
export type GetProjectErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type GetProjectError = GetProjectErrors[keyof GetProjectErrors];
export type GetProjectResponses = {
/**
* Successful Response
*/
200: GetProjectResponse;
};
export type GetProjectResponse2 =
GetProjectResponses[keyof GetProjectResponses];
export type UpdateProjectData = { export type UpdateProjectData = {
body: UpdateProjectRequest; body: UpdateProjectRequest;
path: { path: {

View File

@ -51,6 +51,13 @@ export const zBoardSchema = z.object({
projectId: z.int(), projectId: z.int(),
}); });
/**
* Body_upload_product_image
*/
export const zBodyUploadProductImage = z.object({
upload_file: z.any(),
});
/** /**
* BuiltInModuleTabSchema * BuiltInModuleTabSchema
*/ */
@ -234,6 +241,15 @@ export const zCreateDealProductRequest = z.object({
entity: zCreateDealProductSchema, entity: zCreateDealProductSchema,
}); });
/**
* ProductImageSchema
*/
export const zProductImageSchema = z.object({
id: z.int(),
productId: z.int(),
imageUrl: z.string(),
});
/** /**
* ProductSchema * ProductSchema
*/ */
@ -249,6 +265,8 @@ export const zProductSchema = z.object({
size: z.union([z.string(), z.null()]), size: z.union([z.string(), z.null()]),
additionalInfo: z.union([z.string(), z.null()]), additionalInfo: z.union([z.string(), z.null()]),
barcodes: z.array(z.string()), barcodes: z.array(z.string()),
imageUrl: z.optional(z.union([z.string(), z.null()])),
images: z.optional(z.union([z.array(zProductImageSchema), z.null()])),
id: z.int(), id: z.int(),
barcodeTemplate: zBarcodeTemplateSchema, barcodeTemplate: zBarcodeTemplateSchema,
}); });
@ -509,6 +527,8 @@ export const zCreateProductSchema = z.object({
size: z.union([z.string(), z.null()]), size: z.union([z.string(), z.null()]),
additionalInfo: z.union([z.string(), z.null()]), additionalInfo: z.union([z.string(), z.null()]),
barcodes: z.array(z.string()), barcodes: z.array(z.string()),
imageUrl: z.optional(z.union([z.string(), z.null()])),
images: z.optional(z.union([z.array(zProductImageSchema), z.null()])),
}); });
/** /**
@ -957,13 +977,6 @@ export const zGetProductsResponse = z.object({
paginationInfo: zPaginationInfoSchema, paginationInfo: zPaginationInfoSchema,
}); });
/**
* GetProjectResponse
*/
export const zGetProjectResponse = z.object({
entity: zProjectSchema,
});
/** /**
* GetProjectsResponse * GetProjectsResponse
*/ */
@ -1042,15 +1055,6 @@ export const zHttpValidationError = z.object({
detail: z.optional(z.array(zValidationError)), detail: z.optional(z.array(zValidationError)),
}); });
/**
* ProductImageSchema
*/
export const zProductImageSchema = z.object({
id: z.int(),
productId: z.int(),
imageUrl: z.string(),
});
/** /**
* ProductServicesDuplicateRequest * ProductServicesDuplicateRequest
*/ */
@ -1067,6 +1071,14 @@ export const zProductServicesDuplicateResponse = z.object({
message: z.string(), message: z.string(),
}); });
/**
* ProductUploadImageResponse
*/
export const zProductUploadImageResponse = z.object({
message: z.string(),
imageUrl: z.optional(z.union([z.string(), z.null()])),
});
export const zSortDir = z.enum(["asc", "desc"]); export const zSortDir = z.enum(["asc", "desc"]);
/** /**
@ -2146,6 +2158,19 @@ export const zUpdateProductData = z.object({
*/ */
export const zUpdateProductResponse2 = zUpdateProductResponse; export const zUpdateProductResponse2 = zUpdateProductResponse;
export const zUploadProductImageData = z.object({
body: zBodyUploadProductImage,
path: z.object({
productId: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zUploadProductImageResponse = zProductUploadImageResponse;
export const zGetProductBarcodePdfData = z.object({ export const zGetProductBarcodePdfData = z.object({
body: zGetProductBarcodePdfRequest, body: zGetProductBarcodePdfRequest,
path: z.optional(z.never()), path: z.optional(z.never()),
@ -2336,19 +2361,6 @@ export const zDeleteProjectData = z.object({
*/ */
export const zDeleteProjectResponse2 = zDeleteProjectResponse; export const zDeleteProjectResponse2 = zDeleteProjectResponse;
export const zGetProjectData = z.object({
body: z.optional(z.never()),
path: z.object({
pk: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zGetProjectResponse2 = zGetProjectResponse;
export const zUpdateProjectData = z.object({ export const zUpdateProjectData = z.object({
body: zUpdateProjectRequest, body: zUpdateProjectRequest,
path: z.object({ path: z.object({

View File

@ -25,14 +25,16 @@ export type BaseFormProps<T> = {
onClose: () => void; onClose: () => void;
closeOnSubmit?: boolean; closeOnSubmit?: boolean;
children: ReactNode; children: ReactNode;
actionsEnabled?: boolean;
}; };
type Props<TCreate, TUpdate, TEntity> = BaseFormProps<TEntity> & type Props<TCreate, TUpdate, TEntity> = BaseFormProps<TEntity> &
CreateEditFormProps<TCreate, TUpdate, TEntity>; CreateEditFormProps<TCreate, TUpdate, TEntity>;
const BaseFormModal = <TCreate, TUpdate = TCreate, TEntity = TUpdate>( const BaseFormModal = <TCreate, TUpdate = TCreate, TEntity = TUpdate>({
props: Props<TCreate, TUpdate, TEntity> actionsEnabled = true,
) => { ...props
}: Props<TCreate, TUpdate, TEntity>) => {
const { closeOnSubmit = false } = props; const { closeOnSubmit = false } = props;
const onSubmit = (values: Partial<TEntity>) => { const onSubmit = (values: Partial<TEntity>) => {
@ -54,7 +56,7 @@ const BaseFormModal = <TCreate, TUpdate = TCreate, TEntity = TUpdate>(
gap={"xs"} gap={"xs"}
direction={"column"}> direction={"column"}>
{props.children} {props.children}
<BaseFormModalActions {...props} /> {actionsEnabled && <BaseFormModalActions {...props} />}
</Flex> </Flex>
</form> </form>
); );

View File

@ -1,6 +1,7 @@
import { FC } from "react"; import { FC } from "react";
import { DropzoneProps, FileWithPath } from "@mantine/dropzone"; import { DropzoneProps, FileWithPath } from "@mantine/dropzone";
import ImageDropzone from "@/components/ui/ImageDropzone/ImageDropzone"; import ImageDropzone from "@/components/ui/ImageDropzone/ImageDropzone";
import { uploadProductImage } from "@/lib/client";
import { notifications } from "@/lib/notifications"; import { notifications } from "@/lib/notifications";
import BaseFormInputProps from "@/utils/baseFormInputProps"; import BaseFormInputProps from "@/utils/baseFormInputProps";
import useImageDropzone from "./useImageDropzone"; import useImageDropzone from "./useImageDropzone";
@ -31,30 +32,31 @@ const ProductImageDropzone: FC<Props> = ({
setIsLoading(true); setIsLoading(true);
// TODO SEND REQUEST uploadProductImage({
path: {
productId,
},
body: {
upload_file: file,
},
})
.then(({ data }) => {
notifications.success({ message: data?.message });
setIsLoading(false);
// ProductService.uploadProductImage({ if (!data?.imageUrl) {
// productId, setShowDropzone(true);
// formData: { return;
// upload_file: file, }
// }, imageUrlInputProps?.onChange(data?.imageUrl);
// }) setShowDropzone(false);
// .then(({ ok, message, imageUrl }) => { })
// notifications.guess(ok, { message }); .catch(err => {
// setIsLoading(false); console.log(err);
// notifications.error({ message: err.toString() });
// if (!ok || !imageUrl) { setShowDropzone(true);
// setShowDropzone(true); setIsLoading(false);
// return; });
// }
// imageUrlInputProps?.onChange(imageUrl);
// setShowDropzone(false);
// })
// .catch(error => {
// notifications.error({ message: error.toString() });
// setShowDropzone(true);
// setIsLoading(false);
// });
}; };
return ( return (

View File

@ -107,12 +107,12 @@ const ProductView: FC<Props> = ({ dealProduct }) => {
<Stack <Stack
flex={2} flex={2}
gap={"sm"}> gap={"sm"}>
{!dealProduct.product && ( {dealProduct.product?.imageUrl && (
<Image <Image
flex={1} flex={1}
radius={"md"} radius={"md"}
fit={"cover"} fit={"cover"}
// src={dealProduct.product.imageUrl} src={dealProduct.product.imageUrl}
/> />
)} )}
<Title order={3}>{dealProduct.product.name}</Title> <Title order={3}>{dealProduct.product.name}</Title>

View File

@ -1,5 +1,6 @@
"use client"; "use client";
import { useState } from "react";
import { Fieldset, Flex, Stack, TagsInput, TextInput } from "@mantine/core"; import { Fieldset, Flex, Stack, TagsInput, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form"; import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals"; import { ContextModalProps } from "@mantine/modals";
@ -14,6 +15,9 @@ import BaseFormModal, {
CreateEditFormProps, CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal"; } from "@/modals/base/BaseFormModal/BaseFormModal";
import ProductImageDropzone from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductImageDropzone/ProductImageDropzone"; import ProductImageDropzone from "@/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductImageDropzone/ProductImageDropzone";
import ProductEditorSegmentedControl, {
ProductEditorTab,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/ProductEditorModal/components/ProductEditorSegmentedControl";
import BaseFormInputProps from "@/utils/baseFormInputProps"; import BaseFormInputProps from "@/utils/baseFormInputProps";
type Props = CreateEditFormProps< type Props = CreateEditFormProps<
@ -35,6 +39,9 @@ const ProductEditorModal = ({
id, id,
innerProps, innerProps,
}: ContextModalProps<Props>) => { }: ContextModalProps<Props>) => {
const [editorTab, setEditorTab] = useState<ProductEditorTab>(
ProductEditorTab.CHARACTERISTICS
);
const isEditing = "entity" in innerProps; const isEditing = "entity" in innerProps;
const initialValues: ProductForm = isEditing const initialValues: ProductForm = isEditing
@ -63,15 +70,8 @@ const ProductEditorModal = ({
}, },
}); });
return ( const characteristicsTab = (
<BaseFormModal <>
{...innerProps}
form={form}
closeOnSubmit
onClose={() => context.closeContextModal(id)}>
<Flex
gap={"xs"}
direction={"column"}>
<Fieldset legend={"Основные характеристики"}> <Fieldset legend={"Основные характеристики"}>
<Stack gap={"xs"}> <Stack gap={"xs"}>
<TextInput <TextInput
@ -141,16 +141,37 @@ const ProductEditorModal = ({
/> />
</Stack> </Stack>
</Fieldset> </Fieldset>
{isEditing && ( </>
);
const imageTab = isEditing && (
<ProductImageDropzone <ProductImageDropzone
imageUrlInputProps={ imageUrlInputProps={
form.getInputProps( form.getInputProps("imageUrl") as BaseFormInputProps<string>
"imageUrl"
) as BaseFormInputProps<string>
} }
productId={innerProps.entity.id} productId={innerProps.entity.id}
/> />
);
return (
<BaseFormModal
{...innerProps}
form={form}
closeOnSubmit
actionsEnabled={editorTab === ProductEditorTab.CHARACTERISTICS}
onClose={() => context.closeContextModal(id)}>
<Flex
gap={"xs"}
direction={"column"}>
{isEditing && (
<ProductEditorSegmentedControl
value={editorTab}
onChange={setEditorTab}
/>
)} )}
{editorTab === ProductEditorTab.CHARACTERISTICS
? characteristicsTab
: imageTab}
</Flex> </Flex>
</BaseFormModal> </BaseFormModal>
); );

View File

@ -0,0 +1,31 @@
import { FC } from "react";
import BaseSegmentedControl, {
BaseSegmentedControlProps,
} from "@/components/ui/BaseSegmentedControl/BaseSegmentedControl";
export enum ProductEditorTab {
CHARACTERISTICS,
IMAGES,
}
type Props = Omit<BaseSegmentedControlProps<ProductEditorTab>, "data">;
const data = [
{
label: "Характеристики",
value: ProductEditorTab.CHARACTERISTICS,
},
{
label: "Изображение",
value: ProductEditorTab.IMAGES,
},
];
const ProductEditorSegmentedControl: FC<Props> = props => (
<BaseSegmentedControl
data={data}
{...props}
/>
);
export default ProductEditorSegmentedControl;