feat: print all barcodes for selected deal

This commit is contained in:
2024-09-28 19:47:15 +04:00
parent 196b4103e3
commit 943291c7d1
6 changed files with 88 additions and 20 deletions

View File

@@ -0,0 +1,3 @@
export type GetDealProductsBarcodesPdfRequest = {
dealId: number;
};

View File

@@ -0,0 +1,5 @@
export type GetDealProductsBarcodesPdfResponse = {
base64String: string;
filename: string;
mimeType: string;
};

View File

@@ -47,6 +47,8 @@ import type { DealUpdateServiceQuantityRequest } from '../models/DealUpdateServi
import type { DealUpdateServiceQuantityResponse } from '../models/DealUpdateServiceQuantityResponse'; import type { DealUpdateServiceQuantityResponse } from '../models/DealUpdateServiceQuantityResponse';
import type { DealUpdateServiceRequest } from '../models/DealUpdateServiceRequest'; import type { DealUpdateServiceRequest } from '../models/DealUpdateServiceRequest';
import type { DealUpdateServiceResponse } from '../models/DealUpdateServiceResponse'; import type { DealUpdateServiceResponse } from '../models/DealUpdateServiceResponse';
import type { GetDealProductsBarcodesPdfRequest } from '../models/GetDealProductsBarcodesPdfRequest.ts';
import type { GetDealProductsBarcodesPdfResponse } from '../models/GetDealProductsBarcodesPdfResponse.ts';
import type { CancelablePromise } from '../core/CancelablePromise'; import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI'; import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request'; import { request as __request } from '../core/request';
@@ -586,4 +588,24 @@ export class DealService {
}, },
}); });
} }
/**
* Get Deal Products Barcodes Pdf
* @returns GetProductBarcodePdfResponse Successful Response
* @throws ApiError
*/
public static getDealProductsBarcodesPdf({
requestBody,
}: {
requestBody: GetDealProductsBarcodesPdfRequest,
}): CancelablePromise<GetDealProductsBarcodesPdfResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/deal/barcodes/get-pdf',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
} }

View File

@@ -13,7 +13,7 @@ type RestProps = {
}; };
type Props = CRUDTableProps<DealSummary> & RestProps; type Props = CRUDTableProps<DealSummary> & RestProps;
const DealsTable: FC<Props> = ({ items, viewOnly = false }) => { const DealsTable: FC<Props> = ({ items, onSelectionChange, viewOnly = false }) => {
const columns = useDealsTableColumns(); const columns = useDealsTableColumns();
const { setSelectedDeal } = useDealPageContext(); const { setSelectedDeal } = useDealPageContext();
const onEditClick = (dealSummary: DealSummary) => { const onEditClick = (dealSummary: DealSummary) => {
@@ -21,10 +21,12 @@ const DealsTable: FC<Props> = ({ items, viewOnly = false }) => {
setSelectedDeal(deal); setSelectedDeal(deal);
}); });
}; };
return ( return (
<BaseTable <BaseTable
data={items} data={items}
columns={columns} columns={columns}
onSelectionChange={onSelectionChange}
restProps={ restProps={
{ {
enableSorting: true, enableSorting: true,
@@ -33,6 +35,7 @@ const DealsTable: FC<Props> = ({ items, viewOnly = false }) => {
enableBottomToolbar: !viewOnly, enableBottomToolbar: !viewOnly,
paginationDisplayMode: "pages", paginationDisplayMode: "pages",
enableRowActions: true, enableRowActions: true,
enableRowSelection: true,
renderRowActions: ({ row }) => ( renderRowActions: ({ row }) => (
<Flex gap="md"> <Flex gap="md">
<Tooltip label="Редактировать"> <Tooltip label="Редактировать">

View File

@@ -5,9 +5,6 @@
height: 100%; height: 100%;
} }
.search-input {
}
.boards { .boards {
margin-top: 1rem; margin-top: 1rem;
flex: 1; flex: 1;
@@ -41,3 +38,7 @@
gap: rem(10); gap: rem(10);
display: flex; display: flex;
} }
.print-deals-button {
align-self: center;
}

View File

@@ -3,25 +3,23 @@ import styles from "./LeadsPage.module.css";
import Board from "../../../components/Dnd/Board/Board.tsx"; import Board from "../../../components/Dnd/Board/Board.tsx";
import { DragDropContext, Droppable, DropResult } from "@hello-pangea/dnd"; import { DragDropContext, Droppable, DropResult } from "@hello-pangea/dnd";
import { useDealSummaries } from "../hooks/useDealSummaries.tsx"; import { useDealSummaries } from "../hooks/useDealSummaries.tsx";
import { import { DealStatus, getDealStatusByName } from "../../../shared/enums/DealStatus.ts";
DealStatus,
getDealStatusByName,
} from "../../../shared/enums/DealStatus.ts";
import PageBlock from "../../../components/PageBlock/PageBlock.tsx"; import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
import DealEditDrawer from "../drawers/DealEditDrawer/DealEditDrawer.tsx"; import DealEditDrawer from "../drawers/DealEditDrawer/DealEditDrawer.tsx";
import { DealPageContextProvider } from "../contexts/DealPageContext.tsx"; import { DealPageContextProvider } from "../contexts/DealPageContext.tsx";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import { DealService, DealSummaryReorderRequest } from "../../../client"; import { DealService, DealSummary, DealSummaryReorderRequest } from "../../../client";
import { ActionIcon, Flex, NumberInput, rem, Text } from "@mantine/core"; import { ActionIcon, Flex, NumberInput, rem, Text, Tooltip } from "@mantine/core";
import classNames from "classnames"; import classNames from "classnames";
import { notifications } from "../../../shared/lib/notifications.ts"; import { notifications } from "../../../shared/lib/notifications.ts";
import { IconMenu2, IconMenuDeep } from "@tabler/icons-react"; import { IconBarcode, IconMenu2, IconMenuDeep } from "@tabler/icons-react";
import useDealsPageState from "../../DealsPage/hooks/useDealsPageState.tsx"; import useDealsPageState from "../../DealsPage/hooks/useDealsPageState.tsx";
import DealStatusSelect from "../../DealsPage/components/DealStatusSelect/DealStatusSelect.tsx"; import DealStatusSelect from "../../DealsPage/components/DealStatusSelect/DealStatusSelect.tsx";
import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx"; import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx"; import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
import DealsTable from "../../DealsPage/components/DealsTable/DealsTable.tsx"; import DealsTable from "../../DealsPage/components/DealsTable/DealsTable.tsx";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { base64ToBlob } from "../../../shared/lib/utils.ts";
enum DisplayMode { enum DisplayMode {
BOARD, BOARD,
@@ -37,6 +35,9 @@ export const LeadsPage: FC = () => {
DisplayMode.BOARD DisplayMode.BOARD
); );
const [isDragEnded, setIsDragEnded] = useState(true); const [isDragEnded, setIsDragEnded] = useState(true);
const [selectedDeals, setSelectedDeals] = useState<DealSummary[]>([]);
useEffect(() => { useEffect(() => {
setSummaries(summariesRaw); setSummaries(summariesRaw);
}, [summariesRaw]); }, [summariesRaw]);
@@ -151,7 +152,7 @@ export const LeadsPage: FC = () => {
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}> transition={{ duration: 0.2 }}>
<DealsTable items={data} /> <DealsTable items={data} onSelectionChange={setSelectedDeals} />
</motion.div> </motion.div>
); );
}; };
@@ -182,7 +183,7 @@ export const LeadsPage: FC = () => {
summaries={summaries.filter( summaries={summaries.filter(
summary => summary =>
summary.status == summary.status ==
DealStatus.AWAITING_ACCEPTANCE DealStatus.AWAITING_ACCEPTANCE,
)} )}
title={"Ожидает приемки"} title={"Ожидает приемки"}
droppableId={"AWAITING_ACCEPTANCE"} droppableId={"AWAITING_ACCEPTANCE"}
@@ -191,7 +192,7 @@ export const LeadsPage: FC = () => {
<Board <Board
summaries={summaries.filter( summaries={summaries.filter(
summary => summary =>
summary.status == DealStatus.PACKAGING summary.status == DealStatus.PACKAGING,
)} )}
title={"Упаковка"} title={"Упаковка"}
droppableId={"PACKAGING"} droppableId={"PACKAGING"}
@@ -201,7 +202,7 @@ export const LeadsPage: FC = () => {
summaries={summaries.filter( summaries={summaries.filter(
summary => summary =>
summary.status == summary.status ==
DealStatus.AWAITING_SHIPMENT DealStatus.AWAITING_SHIPMENT,
)} )}
title={"Ожидает отгрузки"} title={"Ожидает отгрузки"}
droppableId={"AWAITING_SHIPMENT"} droppableId={"AWAITING_SHIPMENT"}
@@ -211,7 +212,7 @@ export const LeadsPage: FC = () => {
summaries={summaries.filter( summaries={summaries.filter(
summary => summary =>
summary.status == summary.status ==
DealStatus.AWAITING_PAYMENT DealStatus.AWAITING_PAYMENT,
)} )}
title={"Ожидает оплаты"} title={"Ожидает оплаты"}
droppableId={"AWAITING_PAYMENT"} droppableId={"AWAITING_PAYMENT"}
@@ -220,7 +221,7 @@ export const LeadsPage: FC = () => {
<Board <Board
summaries={summaries.filter( summaries={summaries.filter(
summary => summary =>
summary.status == DealStatus.COMPLETED summary.status == DealStatus.COMPLETED,
)} )}
title={"Завершена"} title={"Завершена"}
droppableId={"COMPLETED"} droppableId={"COMPLETED"}
@@ -233,7 +234,7 @@ export const LeadsPage: FC = () => {
<div <div
className={classNames( className={classNames(
styles["delete"], styles["delete"],
isDragEnded && styles["delete-hidden"] isDragEnded && styles["delete-hidden"],
)}> )}>
<Droppable droppableId={"DELETE"}> <Droppable droppableId={"DELETE"}>
{(provided, snapshot) => ( {(provided, snapshot) => (
@@ -254,7 +255,7 @@ export const LeadsPage: FC = () => {
<div <div
className={classNames( className={classNames(
styles["delete"], styles["delete"],
isDragEnded && styles["delete-hidden"] isDragEnded && styles["delete-hidden"],
)}> )}>
<Droppable droppableId={"SUCCESS"}> <Droppable droppableId={"SUCCESS"}>
{(provided, snapshot) => ( {(provided, snapshot) => (
@@ -345,11 +346,44 @@ export const LeadsPage: FC = () => {
? "flex" ? "flex"
: "none", : "none",
}}> }}>
{
selectedDeals.length === 1 &&
<Tooltip
className={styles["print-deals-button"]}
label={"Распечатать штрихкоды сделки"}
>
<ActionIcon
onClick={async () => {
const response =
await DealService.getDealProductsBarcodesPdf({
requestBody: {
dealId: selectedDeals[0].id,
},
});
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();
};
}}
variant={"default"}>
<IconBarcode />
</ActionIcon>
</Tooltip>
}
<NumberInput <NumberInput
min={1} min={1}
step={1}
placeholder={"Введите номер"} placeholder={"Введите номер"}
{...form.getInputProps("id")} {...form.getInputProps("id")}
hideControls
/> />
<DealStatusSelect <DealStatusSelect
onClear={() => onClear={() =>