feat: barcode templates

This commit is contained in:
2024-05-09 21:29:48 +03:00
parent 05ef4afce8
commit d82b23fbe9
19 changed files with 247 additions and 53 deletions

View File

@@ -9,6 +9,8 @@ export type { OpenAPIConfig } from './core/OpenAPI';
export type { AuthLoginRequest } from './models/AuthLoginRequest';
export type { AuthLoginResponse } from './models/AuthLoginResponse';
export type { BarcodeAttributeSchema } from './models/BarcodeAttributeSchema';
export type { BarcodeSchema } from './models/BarcodeSchema';
export type { BarcodeTemplateAdditionalAttributeSchema } from './models/BarcodeTemplateAdditionalAttributeSchema';
export type { BarcodeTemplateAttributeSchema } from './models/BarcodeTemplateAttributeSchema';
export type { BarcodeTemplateCreateRequest } from './models/BarcodeTemplateCreateRequest';
@@ -16,6 +18,7 @@ export type { BarcodeTemplateCreateResponse } from './models/BarcodeTemplateCrea
export type { BarcodeTemplateDeleteRequest } from './models/BarcodeTemplateDeleteRequest';
export type { BarcodeTemplateDeleteResponse } from './models/BarcodeTemplateDeleteResponse';
export type { BarcodeTemplateSchema } from './models/BarcodeTemplateSchema';
export type { BarcodeTemplateSizeSchema } from './models/BarcodeTemplateSizeSchema';
export type { BarcodeTemplateUpdateRequest } from './models/BarcodeTemplateUpdateRequest';
export type { BarcodeTemplateUpdateResponse } from './models/BarcodeTemplateUpdateResponse';
export type { ClientCreateRequest } from './models/ClientCreateRequest';
@@ -67,9 +70,12 @@ export type { DealUpdateProductQuantityResponse } from './models/DealUpdateProdu
export type { DealUpdateServiceQuantityRequest } from './models/DealUpdateServiceQuantityRequest';
export type { DealUpdateServiceQuantityResponse } from './models/DealUpdateServiceQuantityResponse';
export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse';
export type { GetAllBarcodeTemplateSizesResponse } from './models/GetAllBarcodeTemplateSizesResponse';
export type { GetAllBarcodeTemplatesResponse } from './models/GetAllBarcodeTemplatesResponse';
export type { GetBarcodeTemplateByIdRequest } from './models/GetBarcodeTemplateByIdRequest';
export type { GetBarcodeTemplateByIdResponse } from './models/GetBarcodeTemplateByIdResponse';
export type { GetProductBarcodeRequest } from './models/GetProductBarcodeRequest';
export type { GetProductBarcodeResponse } from './models/GetProductBarcodeResponse';
export type { HTTPValidationError } from './models/HTTPValidationError';
export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
export type { ProductAddBarcodeRequest } from './models/ProductAddBarcodeRequest';

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type BarcodeAttributeSchema = {
name: string;
value: string;
};

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { BarcodeAttributeSchema } from './BarcodeAttributeSchema';
export type BarcodeSchema = {
barcode: string;
attributes: Array<BarcodeAttributeSchema>;
};

View File

@@ -3,11 +3,11 @@
/* tslint:disable */
/* eslint-disable */
import type { BarcodeTemplateAdditionalAttributeSchema } from './BarcodeTemplateAdditionalAttributeSchema';
import type { BarcodeTemplateSizeSchema } from './BarcodeTemplateSizeSchema';
export type BarcodeTemplateCreateRequest = {
name: string;
isDefault: boolean;
width: number;
height: number;
size: BarcodeTemplateSizeSchema;
additionalAttributes: Array<BarcodeTemplateAdditionalAttributeSchema>;
attributeIds: Array<number>;
};

View File

@@ -4,11 +4,11 @@
/* eslint-disable */
import type { BarcodeTemplateAdditionalAttributeSchema } from './BarcodeTemplateAdditionalAttributeSchema';
import type { BarcodeTemplateAttributeSchema } from './BarcodeTemplateAttributeSchema';
import type { BarcodeTemplateSizeSchema } from './BarcodeTemplateSizeSchema';
export type BarcodeTemplateSchema = {
name: string;
isDefault: boolean;
width: number;
height: number;
size: BarcodeTemplateSizeSchema;
additionalAttributes: Array<BarcodeTemplateAdditionalAttributeSchema>;
id: number;
attributes: Array<BarcodeTemplateAttributeSchema>;

View File

@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type BarcodeTemplateSizeSchema = {
id: number;
name: string;
key: string;
width: number;
height: number;
};

View File

@@ -3,11 +3,11 @@
/* tslint:disable */
/* eslint-disable */
import type { BarcodeTemplateAdditionalAttributeSchema } from './BarcodeTemplateAdditionalAttributeSchema';
import type { BarcodeTemplateSizeSchema } from './BarcodeTemplateSizeSchema';
export type BarcodeTemplateUpdateRequest = {
name: string;
isDefault: boolean;
width: number;
height: number;
size: BarcodeTemplateSizeSchema;
additionalAttributes: Array<BarcodeTemplateAdditionalAttributeSchema>;
id: number;
attributeIds: Array<number>;

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { BarcodeTemplateSizeSchema } from './BarcodeTemplateSizeSchema';
export type GetAllBarcodeTemplateSizesResponse = {
sizes: Array<BarcodeTemplateSizeSchema>;
};

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type GetProductBarcodeRequest = {
productId: number;
barcode: string;
barcodeTemplateId?: (number | null);
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { BarcodeSchema } from './BarcodeSchema';
export type GetProductBarcodeResponse = {
barcode: BarcodeSchema;
};

View File

@@ -11,6 +11,7 @@ import type { BarcodeTemplateUpdateResponse } from '../models/BarcodeTemplateUpd
import type { CreateBarcodeTemplateAttributeRequest } from '../models/CreateBarcodeTemplateAttributeRequest';
import type { CreateBarcodeTemplateAttributeResponse } from '../models/CreateBarcodeTemplateAttributeResponse';
import type { GetAllBarcodeTemplateAttributesResponse } from '../models/GetAllBarcodeTemplateAttributesResponse';
import type { GetAllBarcodeTemplateSizesResponse } from '../models/GetAllBarcodeTemplateSizesResponse';
import type { GetAllBarcodeTemplatesResponse } from '../models/GetAllBarcodeTemplatesResponse';
import type { GetBarcodeTemplateByIdRequest } from '../models/GetBarcodeTemplateByIdRequest';
import type { GetBarcodeTemplateByIdResponse } from '../models/GetBarcodeTemplateByIdResponse';
@@ -140,4 +141,15 @@ export class BarcodeService {
},
});
}
/**
* Get All Barcode Template Sizes
* @returns GetAllBarcodeTemplateSizesResponse Successful Response
* @throws ApiError
*/
public static getAllBarcodeTemplateSizes(): CancelablePromise<GetAllBarcodeTemplateSizesResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/barcode/template/size/get-all',
});
}
}

View File

@@ -2,6 +2,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GetProductBarcodeRequest } from '../models/GetProductBarcodeRequest';
import type { GetProductBarcodeResponse } from '../models/GetProductBarcodeResponse';
import type { ProductAddBarcodeRequest } from '../models/ProductAddBarcodeRequest';
import type { ProductAddBarcodeResponse } from '../models/ProductAddBarcodeResponse';
import type { ProductCreateRequest } from '../models/ProductCreateRequest';
@@ -191,4 +193,24 @@ export class ProductService {
},
});
}
/**
* Get Product Barcode
* @returns GetProductBarcodeResponse Successful Response
* @throws ApiError
*/
public static getProductBarcode({
requestBody,
}: {
requestBody: GetProductBarcodeRequest,
}): CancelablePromise<GetProductBarcodeResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/product/barcode/get',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
}

View File

@@ -0,0 +1,63 @@
import {BarcodeAttributeSchema} from "../../client";
import {forwardRef} from "react";
import styles from "./PrintBarcodeModal.module.css";
import {Flex, Text} from "@mantine/core";
import Barcode from "react-barcode";
type Props = {
attributes: BarcodeAttributeSchema[]
barcode?: string;
quantity: number;
}
type Ref = HTMLDivElement;
const PrintBarcodeContainer = forwardRef<Ref, Props>(function PrintBarcodeContainer(props: Props, ref) {
const {attributes, barcode, quantity} = props;
const MAX_ATTRIBUTES = 6;
const MIN_BARCODE_SIZE = 30;
const MAX_BARCODE_SIZE = 100;
const STEP = (MAX_BARCODE_SIZE - MIN_BARCODE_SIZE) / MAX_ATTRIBUTES;
const MAX_ATTRIBUTE_LENGTH = 35;
const getBarcodeHeight = () => {
return MIN_BARCODE_SIZE + (MAX_ATTRIBUTES - attributes.length) * STEP;
}
const getAttributeText = (attribute: BarcodeAttributeSchema) => {
let result = `${attribute.name}: ${attribute.value}`;
if (result.length > MAX_ATTRIBUTE_LENGTH) {
result = result.slice(0, MAX_ATTRIBUTE_LENGTH - 1) + ".";
}
return result;
}
return (
<div className={styles['print-only']} ref={ref}>
{barcode && Array.from({length: quantity}).map((_, index) => (
<>
<Flex
className={styles['barcode-container']}
key={index}
justify={"center"}
direction={"column"}
>
<Flex align={"center"} justify={"center"}>
<Barcode margin={0} height={getBarcodeHeight()} value={barcode}/>
</Flex>
{attributes.slice(0, MAX_ATTRIBUTES).map((attr) => (
<Text
key={attr.name}
className={styles['barcode-attribute-text']}
size={"xs"}
>
{getAttributeText(attr)}
</Text>
))}
</Flex>
</>
))}
</div>
)
});
export default PrintBarcodeContainer;

View File

@@ -6,4 +6,16 @@
.print-only {
display: block;
}
.barcode-container {
text-align: left;
margin: 1.25mm;
padding: 1.25mm;
background-color: white;
}
.barcode-attribute-text {
line-height: 1;
}
}

View File

@@ -1,12 +1,11 @@
import {ContextModalProps, modals} from "@mantine/modals";
import {Button, Divider, Flex, NumberInput, rem, Select, Spoiler} from "@mantine/core";
import Barcode from "react-barcode";
import {Button, Divider, Flex, NumberInput, rem, Select} from "@mantine/core";
import {useEffect, useRef, useState} from "react";
import {useReactToPrint} from "react-to-print";
import {ProductService} from "../../client";
import styles from "./PrintBarcodeModal.module.css";
import {BarcodeSchema, ProductService} from "../../client";
import {useGetProductById} from "../../api/product/useGetProductById.tsx";
import {notifications} from "../../shared/lib/notifications.ts";
import PrintBarcodeContainer from "./PrintBarcodeContainer.tsx";
type Props = {
productId: number;
@@ -18,6 +17,7 @@ const PrintBarcodeModal = ({
const {productId, defaultQuantity = 1} = innerProps;
const [quantity, setQuantity] = useState(defaultQuantity);
const [barcode, setBarcode] = useState<string | undefined>()
const [barcodeData, setBarcodeData] = useState<BarcodeSchema | undefined>();
const {product, refetch} = useGetProductById(productId);
@@ -55,11 +55,26 @@ const PrintBarcodeModal = ({
setBarcode(barcode);
})
}
const fetchBarcodeData = () => {
if (!barcode) return;
ProductService.getProductBarcode({
requestBody:
{barcode, productId}
}).then(({barcode}) => {
setBarcodeData(barcode);
})
}
useEffect(() => {
if (!product) return;
if (product.barcodes.length === 1)
setBarcode(product.barcodes[0]);
}, [product]);
useEffect(() => {
if (!barcode) return;
fetchBarcodeData();
}, [barcode]);
return (
<>
<Flex
@@ -80,21 +95,7 @@ const PrintBarcodeModal = ({
onChange={(value) => typeof value === "number" && setQuantity(value)}
min={1}
/>
<Spoiler
w={"100%"}
style={{
textAlign: "center",
}}
styles={{control: {width: "100%"}}}
showLabel={"Показать предпросмотр"}
hideLabel={"Скрыть предпросмотр"}
maxHeight={0}>
<div>
{barcode &&
<Barcode value={barcode}/>
}
</div>
</Spoiler>
<Divider
my={rem(10)}
/>
@@ -120,11 +121,13 @@ const PrintBarcodeModal = ({
>Печать</Button>
</Flex>
</Flex>
<div className={styles['print-only']} ref={barcodeRef}>
{barcode && Array.from({length: quantity}).map((_, index) => (
<Barcode key={index} value={barcode}/>
))}
</div>
<PrintBarcodeContainer
barcode={barcode}
quantity={quantity}
ref={barcodeRef}
attributes={barcodeData?.attributes || []}
/>
</>
)
}

View File

@@ -0,0 +1,16 @@
import {BarcodeTemplateSizeSchema} from "../../../../client";
import ObjectSelect, {ObjectSelectProps} from "../../../../components/ObjectSelect/ObjectSelect.tsx";
import useBarcodeTemplateSizesList from "../../hooks/useBarcodeTemplateSizesList.tsx";
type Props = Omit<ObjectSelectProps<BarcodeTemplateSizeSchema>, 'data'>
const BarcodeTemplateSizeSelect = (props: Props) => {
const {barcodeTemplateSizes} = useBarcodeTemplateSizesList();
return (
<ObjectSelect
data={barcodeTemplateSizes}
{...props}
/>
)
}
export default BarcodeTemplateSizeSelect;

View File

@@ -15,8 +15,8 @@ export const useBarcodeTemplatesTableColumns = () => {
Cell: ({row}) => <>{row.original.attributes.map(attr => attr.name).join(', ')}</>
},
{
accessorKey: "size.name",
header: "Размер",
Cell: ({row}) => <>{row.original.width}x{row.original.height}</>
},
{
accessorKey: "isDefault",

View File

@@ -0,0 +1,14 @@
import {BarcodeService} from "../../../client";
import {useQuery} from "@tanstack/react-query";
const useBarcodeTemplateSizesList = () => {
const {isLoading, data, error, refetch} = useQuery({
queryKey: ['getAllBarcodeTemplateSizes'],
queryFn: BarcodeService.getAllBarcodeTemplateSizes
});
const barcodeTemplateSizes = isLoading || error || !data ? [] : data.sizes;
return {barcodeTemplateSizes, refetch}
}
export default useBarcodeTemplateSizesList;

View File

@@ -6,12 +6,12 @@ import {
} from "../../../../client";
import {ContextModalProps} from "@mantine/modals";
import {useForm} from "@mantine/form";
import {Checkbox, Fieldset, Flex, NumberInput, rem, TextInput} from "@mantine/core";
import {IconX} from "@tabler/icons-react";
import {Checkbox, Fieldset, Flex, rem, TextInput} from "@mantine/core";
import BarcodeTemplateAttributeMultiselect
from "../../components/BarcodeTemplateAttributeMultiselect/BarcodeTemplateAttributeMultiselect.tsx";
import BarcodeTemplateAdditionalFieldTable
from "../../components/BarcodeTemplateAdditionalFieldTable/BarcodeTemplateAdditionalFieldTable.tsx";
import BarcodeTemplateSizeSelect from "../../components/BarcodeTemplateSizeSelect/BarcodeTemplateSizeSelect.tsx";
type Props = CreateEditFormProps<BarcodeTemplateSchema>
const BarcodeTemplateFormModal = ({
@@ -22,8 +22,6 @@ const BarcodeTemplateFormModal = ({
const isEditing = 'onChange' in innerProps;
const initialValues = isEditing ? innerProps.element : {
name: "",
width: undefined,
height: undefined,
isDefault: false,
attributes: [] as Array<BarcodeTemplateAttributeSchema>,
additionalAttributes: [] as Array<BarcodeTemplateAdditionalAttributeSchema>
@@ -31,9 +29,7 @@ const BarcodeTemplateFormModal = ({
const form = useForm<Partial<BarcodeTemplateSchema>>({
initialValues: initialValues,
validate: {
width: (width: number | undefined) => width && width > 0 ? null : "Ширина должна быть больше 0",
height: (height: number | undefined) => height && height > 0 ? null : "Высота должна быть больше 0",
attributes: (attributes: Array<BarcodeTemplateAttributeSchema> | undefined) => attributes && attributes.length > 0 ? null : "Необходимо добавить хотя бы один атрибут",
attributes: (attributes: Array<BarcodeTemplateAttributeSchema> | undefined) => attributes ? null : "Необходимо добавить хотя бы один атрибут",
name: (name: string | undefined) => name && name.trim() !== '' ? null : "Необходимо ввести название шаблона",
}
})
@@ -55,21 +51,12 @@ const BarcodeTemplateFormModal = ({
placeholder={"Введите название шаблона"}
{...form.getInputProps('name')}
/>
<Flex gap={10} align={"center"}>
<NumberInput
hideControls
label={'Ширина'}
placeholder={"Ширина в мм"}
{...form.getInputProps('width')}
<BarcodeTemplateSizeSelect
label={"Размер"}
placeholder={"Выберите размер шаблона"}
{...form.getInputProps('size')}
/>
<IconX/>
<NumberInput
hideControls
label={'Высота'}
placeholder={"Высота в мм"}
{...form.getInputProps('height')}
/>
</Flex>
<BarcodeTemplateAttributeMultiselect
label={"Стандартные атрибуты"}
placeholder={!form.values.attributes?.length ? "Выберите атрибуты" : undefined}