diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2e0fce3..0a9d350 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -74,7 +74,7 @@ export default function RootLayout({ children }: Props) { layout={"alt"} withBorder={false} navbar={{ - width: 250, + width: 220, breakpoint: "sm", collapsed: { desktop: false, diff --git a/src/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx b/src/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx index 9b0092e..bb0a94b 100644 --- a/src/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx +++ b/src/app/products/components/shared/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx @@ -15,6 +15,9 @@ const BarcodeTemplateSelect: FC = props => { return ( + `${template.name} (${template.attributes.map(a => a.name).join(", ")})` + } {...props} /> ); diff --git a/src/app/products/components/shared/ProductsTable/ProductsTable.tsx b/src/app/products/components/shared/ProductsTable/ProductsTable.tsx index a22c90a..93668b2 100644 --- a/src/app/products/components/shared/ProductsTable/ProductsTable.tsx +++ b/src/app/products/components/shared/ProductsTable/ProductsTable.tsx @@ -10,11 +10,12 @@ const ProductsTable = () => { const isMobile = useIsMobile(); const { productsCrud, products, productsFiltersForm, paginationInfo } = useProductsContext(); - const { onChangeClick } = useProductsActions(); + const { onChangeClick, onPrintBarcodeClick } = useProductsActions(); const columns = useProductsTableColumns({ onChange: onChangeClick, onDelete: productsCrud.onDelete, + onPrintBarcode: onPrintBarcodeClick, }); return ( diff --git a/src/app/products/components/shared/ProductsTable/columns.tsx b/src/app/products/components/shared/ProductsTable/columns.tsx index b8d2290..c195354 100644 --- a/src/app/products/components/shared/ProductsTable/columns.tsx +++ b/src/app/products/components/shared/ProductsTable/columns.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { IconEdit, IconTrash } from "@tabler/icons-react"; +import { IconBarcode, IconEdit, IconTrash } from "@tabler/icons-react"; import { DataTableColumn } from "mantine-datatable"; import { Center, Flex, List, Spoiler, useMantineTheme } from "@mantine/core"; import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip"; @@ -7,11 +7,16 @@ import useIsMobile from "@/hooks/utils/useIsMobile"; import { ProductSchema } from "@/lib/client"; type Props = { + onPrintBarcode: (product: ProductSchema) => void; onChange: (product: ProductSchema) => void; onDelete: (product: ProductSchema) => void; }; -export const useProductsTableColumns = ({ onChange, onDelete }: Props) => { +export const useProductsTableColumns = ({ + onChange, + onDelete, + onPrintBarcode, +}: Props) => { const theme = useMantineTheme(); const isMobile = useIsMobile(); @@ -24,11 +29,11 @@ export const useProductsTableColumns = ({ onChange, onDelete }: Props) => { width: "0%", render: product => ( - {/* onPrintBarcodeClick(product)}>*/} - {/* */} - {/**/} + onPrintBarcode(product)}> + + onChange(product)}> diff --git a/src/app/products/hooks/useProductsActions.ts b/src/app/products/hooks/useProductsActions.ts index 4a7b0d6..f15965c 100644 --- a/src/app/products/hooks/useProductsActions.ts +++ b/src/app/products/hooks/useProductsActions.ts @@ -40,11 +40,12 @@ const useProductsActions = () => { const onPrintBarcodeClick = (product: ProductSchema) => { modals.openContextModal({ - modal: "printBarcode", - title: "Печать штрихкода", // TODO + modal: "printBarcodeModal", + title: "Печать штрихкода", withCloseButton: true, innerProps: { - productId: product.id, + product, + defaultQuantity: 1, }, }); }; diff --git a/src/components/layout/Navbar/data/linksData.ts b/src/components/layout/Navbar/data/linksData.ts index a51c1f8..37688f2 100644 --- a/src/components/layout/Navbar/data/linksData.ts +++ b/src/components/layout/Navbar/data/linksData.ts @@ -35,7 +35,7 @@ const linksData: LinkData[] = [ }, { icon: IconFileBarcode, - label: "Шаблоны штрихкодов", + label: "Шаблоны ШК", href: "/barcode-templates", moduleName: ModuleNames.FULFILLMENT_BASE, }, diff --git a/src/lib/client/@tanstack/react-query.gen.ts b/src/lib/client/@tanstack/react-query.gen.ts index 8c4aac3..0fba938 100644 --- a/src/lib/client/@tanstack/react-query.gen.ts +++ b/src/lib/client/@tanstack/react-query.gen.ts @@ -47,6 +47,7 @@ import { getDealProducts, getDeals, getDealServices, + getProductBarcodePdf, getProducts, getProjects, getServiceCategories, @@ -168,6 +169,9 @@ import type { GetDealsError, GetDealServicesData, GetDealsResponse2, + GetProductBarcodePdfData, + GetProductBarcodePdfError, + GetProductBarcodePdfResponse2, GetProductsData, GetProductsError, GetProductsResponse2, @@ -1867,6 +1871,57 @@ export const updateProductMutation = ( return mutationOptions; }; +export const getProductBarcodePdfQueryKey = ( + options: Options +) => createQueryKey("getProductBarcodePdf", options); + +/** + * Get Product Barcode Pdf + */ +export const getProductBarcodePdfOptions = ( + options: Options +) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getProductBarcodePdf({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getProductBarcodePdfQueryKey(options), + }); +}; + +/** + * Get Product Barcode Pdf + */ +export const getProductBarcodePdfMutation = ( + options?: Partial> +): UseMutationOptions< + GetProductBarcodePdfResponse2, + AxiosError, + Options +> => { + const mutationOptions: UseMutationOptions< + GetProductBarcodePdfResponse2, + AxiosError, + Options + > = { + mutationFn: async localOptions => { + const { data } = await getProductBarcodePdf({ + ...options, + ...localOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + export const getServicesQueryKey = (options?: Options) => createQueryKey("getServices", options); diff --git a/src/lib/client/sdk.gen.ts b/src/lib/client/sdk.gen.ts index 39b6442..a6caa99 100644 --- a/src/lib/client/sdk.gen.ts +++ b/src/lib/client/sdk.gen.ts @@ -113,6 +113,9 @@ import type { GetDealServicesErrors, GetDealServicesResponses, GetDealsResponses, + GetProductBarcodePdfData, + GetProductBarcodePdfErrors, + GetProductBarcodePdfResponses, GetProductsData, GetProductsErrors, GetProductsResponses, @@ -247,6 +250,8 @@ import { zGetDealServicesData, zGetDealServicesResponse2, zGetDealsResponse2, + zGetProductBarcodePdfData, + zGetProductBarcodePdfResponse2, zGetProductsData, zGetProductsResponse2, zGetProjectsData, @@ -1458,6 +1463,33 @@ export const updateProduct = ( }); }; +/** + * Get Product Barcode Pdf + */ +export const getProductBarcodePdf = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GetProductBarcodePdfResponses, + GetProductBarcodePdfErrors, + ThrowOnError + >({ + requestValidator: async data => { + return await zGetProductBarcodePdfData.parseAsync(data); + }, + responseType: "json", + responseValidator: async data => { + return await zGetProductBarcodePdfResponse2.parseAsync(data); + }, + url: "/modules/fulfillment-base/product/barcode/get-pdf", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); +}; + /** * Get Services */ diff --git a/src/lib/client/types.gen.ts b/src/lib/client/types.gen.ts index b083c7e..8e9be0c 100644 --- a/src/lib/client/types.gen.ts +++ b/src/lib/client/types.gen.ts @@ -1102,6 +1102,42 @@ export type GetDealsResponse = { paginationInfo: PaginationInfoSchema; }; +/** + * GetProductBarcodePdfRequest + */ +export type GetProductBarcodePdfRequest = { + /** + * Quantity + */ + quantity: number; + /** + * Productid + */ + productId: number; + /** + * Barcode + */ + barcode: string; +}; + +/** + * GetProductBarcodePdfResponse + */ +export type GetProductBarcodePdfResponse = { + /** + * Base64String + */ + base64String: string; + /** + * Filename + */ + filename: string; + /** + * Mimetype + */ + mimeType: string; +}; + /** * GetProductsResponse */ @@ -3379,6 +3415,33 @@ export type UpdateProductResponses = { export type UpdateProductResponse2 = UpdateProductResponses[keyof UpdateProductResponses]; +export type GetProductBarcodePdfData = { + body: GetProductBarcodePdfRequest; + path?: never; + query?: never; + url: "/modules/fulfillment-base/product/barcode/get-pdf"; +}; + +export type GetProductBarcodePdfErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type GetProductBarcodePdfError = + GetProductBarcodePdfErrors[keyof GetProductBarcodePdfErrors]; + +export type GetProductBarcodePdfResponses = { + /** + * Successful Response + */ + 200: GetProductBarcodePdfResponse; +}; + +export type GetProductBarcodePdfResponse2 = + GetProductBarcodePdfResponses[keyof GetProductBarcodePdfResponses]; + export type GetServicesData = { body?: never; path?: never; diff --git a/src/lib/client/zod.gen.ts b/src/lib/client/zod.gen.ts index e7dfe7b..8c11c12 100644 --- a/src/lib/client/zod.gen.ts +++ b/src/lib/client/zod.gen.ts @@ -767,6 +767,24 @@ export const zGetDealsResponse = z.object({ paginationInfo: zPaginationInfoSchema, }); +/** + * GetProductBarcodePdfRequest + */ +export const zGetProductBarcodePdfRequest = z.object({ + quantity: z.int(), + productId: z.int(), + barcode: z.string(), +}); + +/** + * GetProductBarcodePdfResponse + */ +export const zGetProductBarcodePdfResponse = z.object({ + base64String: z.string(), + filename: z.string(), + mimeType: z.string(), +}); + /** * GetProductsResponse */ @@ -1778,6 +1796,17 @@ export const zUpdateProductData = z.object({ */ export const zUpdateProductResponse2 = zUpdateProductResponse; +export const zGetProductBarcodePdfData = z.object({ + body: zGetProductBarcodePdfRequest, + path: z.optional(z.never()), + query: z.optional(z.never()), +}); + +/** + * Successful Response + */ +export const zGetProductBarcodePdfResponse2 = zGetProductBarcodePdfResponse; + export const zGetServicesData = z.object({ body: z.optional(z.never()), path: z.optional(z.never()), diff --git a/src/modals/modals.ts b/src/modals/modals.ts index 1695416..a228ee5 100644 --- a/src/modals/modals.ts +++ b/src/modals/modals.ts @@ -13,6 +13,7 @@ import { DealProductEditorModal, DealServiceEditorModal, DuplicateServicesModal, + PrintBarcodeModal, ProductEditorModal, ProductServiceEditorModal, ServicesKitSelectModal, @@ -34,4 +35,5 @@ export const modals = { serviceEditorModal: ServiceEditorModal, barcodeTemplateEditorModal: BarcodeTemplateEditorModal, clientEditorModal: ClientEditorModal, + printBarcodeModal: PrintBarcodeModal, }; diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/PrintBarcodeModal/PrintBarcodeModal.tsx b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/PrintBarcodeModal/PrintBarcodeModal.tsx new file mode 100644 index 0000000..693249b --- /dev/null +++ b/src/modules/dealModularEditorTabs/FulfillmentBase/shared/modals/PrintBarcodeModal/PrintBarcodeModal.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Flex, NumberInput, Select } from "@mantine/core"; +import { ContextModalProps } from "@mantine/modals"; +import InlineButton from "@/components/ui/InlineButton/InlineButton"; +import { ProductSchema } from "@/lib/client"; +import { getProductBarcodePdfMutation } from "@/lib/client/@tanstack/react-query.gen"; +import { notifications } from "@/lib/notifications"; +import base64ToBlob from "@/utils/base64ToBlob"; + +type Props = { + product: ProductSchema; + defaultQuantity?: number; +}; + +const PrintBarcodeModal = ({ innerProps }: ContextModalProps) => { + const { product, defaultQuantity = 1 } = innerProps; + const [quantity, setQuantity] = useState(defaultQuantity); + const [barcode, setBarcode] = useState(null); + const [barcodeImageUrl, setBarcodeImageUrl] = useState< + string | undefined + >(); + + const getBarcodePdfMutation = useMutation({ + ...getProductBarcodePdfMutation(), + onSuccess: response => { + const pdfBlob = base64ToBlob( + response.base64String, + response.mimeType + ); + const pdfUrl = URL.createObjectURL(pdfBlob); + const pdfWindow = window.open(pdfUrl); + if (!pdfWindow) { + notifications.error({ message: "Ошибка" }); + return; + } + pdfWindow.onload = () => pdfWindow.print(); + }, + }); + + const printBarcode = () => { + if (!barcode) return; + getBarcodePdfMutation.mutate({ + body: { + barcode, + quantity, + productId: product.id, + }, + }); + }; + + return ( + + {barcodeImageUrl ? ( + + Ошибка загрузки штрихкода + + ) : ( +