298 lines
11 KiB
TypeScript
298 lines
11 KiB
TypeScript
import { FC } from "react";
|
||
import styles from "./ProductAndServiceTab.module.css";
|
||
import ProductView from "./components/ProductView/ProductView.tsx";
|
||
import {
|
||
Button,
|
||
Divider,
|
||
Flex,
|
||
rem,
|
||
ScrollArea,
|
||
Text,
|
||
Title,
|
||
} from "@mantine/core";
|
||
import CardServicesTable from "./components/DealServicesTable/CardServicesTable.tsx";
|
||
import useCardProductAndServiceTabState from "./hooks/useProductAndServiceTabState.tsx";
|
||
import { modals } from "@mantine/modals";
|
||
import {
|
||
BillingService,
|
||
CardProductSchema,
|
||
CardService,
|
||
GetServiceKitSchema,
|
||
ProductSchema,
|
||
ProductService,
|
||
} from "../../../../client";
|
||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||
import { CreateProductRequest } from "../../../ProductsPage/types.ts";
|
||
import classNames from "classnames";
|
||
|
||
const ProductAndServiceTab: FC = () => {
|
||
const { cardState, cardServicesState, cardProductsState } =
|
||
useCardProductAndServiceTabState();
|
||
const isLocked = Boolean(cardState.card?.billRequest || cardState.card?.group?.billRequest);
|
||
const onAddProductClick = () => {
|
||
if (!cardProductsState.onCreate || !cardState.card || !cardState.card.clientId) return;
|
||
const productIds = cardState.card.products.map(
|
||
product => product.product.id
|
||
);
|
||
modals.openContextModal({
|
||
modal: "addCardProduct",
|
||
innerProps: {
|
||
onCreate: cardProductsState.onCreate,
|
||
clientId: cardState.card.clientId,
|
||
productIds: productIds,
|
||
},
|
||
withCloseButton: false,
|
||
});
|
||
};
|
||
const getTotalPrice = () => {
|
||
if (!cardState.card) return 0;
|
||
const productServicesPrice = cardState.card.products.reduce(
|
||
(acc, row) =>
|
||
acc +
|
||
row.services.reduce(
|
||
(acc2, row2) => acc2 + row2.price * row.quantity,
|
||
0
|
||
),
|
||
0
|
||
);
|
||
const cardServicesPrice = cardState.card.services.reduce(
|
||
(acc, row) => acc + row.price * row.quantity,
|
||
0
|
||
);
|
||
return cardServicesPrice + productServicesPrice;
|
||
};
|
||
const onCopyServices = (
|
||
sourceProduct: CardProductSchema,
|
||
destinationProducts: CardProductSchema[]
|
||
) => {
|
||
if (!cardState.card) return;
|
||
CardService.copyProductServices({
|
||
requestBody: {
|
||
cardId: cardState.card.id,
|
||
destinationProductIds: destinationProducts.map(
|
||
product => product.product.id
|
||
),
|
||
sourceProductId: sourceProduct.product.id,
|
||
},
|
||
}).then(async ({ ok, message }) => {
|
||
notifications.guess(ok, { message });
|
||
if (!ok) return;
|
||
await cardState.refetch();
|
||
});
|
||
};
|
||
const onCopyServicesClick = (product: CardProductSchema) => {
|
||
modals.openContextModal({
|
||
modal: "selectCardProductsModal",
|
||
title: "Дублирование услуг",
|
||
size: "lg",
|
||
innerProps: {
|
||
cardProducts: cardState.card?.products || [],
|
||
cardProduct: product,
|
||
onSelect: onCopyServices,
|
||
},
|
||
withCloseButton: false,
|
||
});
|
||
};
|
||
|
||
const onKitAdd = (item: CardProductSchema, kit: GetServiceKitSchema) => {
|
||
if (!cardState.card) return;
|
||
CardService.addKitToCardProduct({
|
||
requestBody: {
|
||
cardId: cardState.card.id,
|
||
kitId: kit.id,
|
||
productId: item.product.id,
|
||
},
|
||
}).then(async ({ ok, message }) => {
|
||
notifications.guess(ok, { message });
|
||
if (!ok) return;
|
||
await cardState.refetch();
|
||
});
|
||
};
|
||
const onCardKitAdd = (kit: GetServiceKitSchema) => {
|
||
if (!cardState.card) return;
|
||
CardService.addKitToCard({
|
||
requestBody: {
|
||
cardId: cardState.card.id,
|
||
kitId: kit.id,
|
||
},
|
||
}).then(async ({ ok, message }) => {
|
||
notifications.guess(ok, { message });
|
||
if (!ok) return;
|
||
await cardState.refetch();
|
||
});
|
||
};
|
||
|
||
const onCreateProduct = (newProduct: CreateProductRequest) => {
|
||
ProductService.createProduct({
|
||
requestBody: newProduct,
|
||
}).then(({ ok, message }) => {
|
||
notifications.guess(ok, { message: message });
|
||
});
|
||
};
|
||
const onCreateProductClick = () => {
|
||
if (!cardState.card || !cardState.card.clientId) return;
|
||
modals.openContextModal({
|
||
modal: "createProduct",
|
||
title: "Создание товара",
|
||
withCloseButton: false,
|
||
innerProps: {
|
||
clientId: cardState.card.clientId,
|
||
onCreate: onCreateProduct,
|
||
},
|
||
});
|
||
};
|
||
const onProductEdit = (product: ProductSchema) => {
|
||
ProductService.updateProduct({ requestBody: { product } }).then(
|
||
async ({ ok, message }) => {
|
||
notifications.guess(ok, { message });
|
||
if (!ok) return;
|
||
await cardState.refetch();
|
||
}
|
||
);
|
||
};
|
||
|
||
const onCreateBillClick = () => {
|
||
if (!cardState.card) return;
|
||
const cardId = cardState.card.id;
|
||
modals.openConfirmModal({
|
||
withCloseButton: false,
|
||
size: "xl",
|
||
children: (
|
||
<Text style={{ textAlign: "justify" }}>
|
||
Создание заявки на выставление счета, подтвержденное
|
||
нажатием кнопки "Выставить", заблокирует возможность
|
||
редактирования товаров и услуг сделки. Пожалуйста, проверьте
|
||
всю информацию на точность и полноту перед подтверждением.
|
||
</Text>
|
||
),
|
||
onConfirm: () => {
|
||
BillingService.createDealBill({
|
||
requestBody: {
|
||
cardId,
|
||
},
|
||
}).then(async ({ ok, message }) => {
|
||
notifications.guess(ok, { message });
|
||
if (ok)
|
||
notifications.success({
|
||
message:
|
||
"Ссылка на оплату доступна во вкладе общее",
|
||
});
|
||
await cardState.refetch();
|
||
});
|
||
},
|
||
labels: {
|
||
confirm: "Выставить",
|
||
cancel: "Отмена",
|
||
},
|
||
});
|
||
};
|
||
const onCancelBillClick = () => {
|
||
if (!cardState.card) return;
|
||
const cardId = cardState.card.id;
|
||
modals.openConfirmModal({
|
||
withCloseButton: false,
|
||
children: (
|
||
<Text style={{ textAlign: "justify" }}>
|
||
Вы уверены что хотите отозвать заявку на оплату?
|
||
</Text>
|
||
),
|
||
onConfirm: () => {
|
||
BillingService.cancelDealBill({
|
||
requestBody: {
|
||
cardId,
|
||
},
|
||
}).then(async ({ ok, message }) => {
|
||
notifications.guess(ok, { message });
|
||
await cardState.refetch();
|
||
});
|
||
},
|
||
labels: {
|
||
confirm: "Отозвать",
|
||
cancel: "Отмена",
|
||
},
|
||
});
|
||
};
|
||
return (
|
||
<div
|
||
className={classNames(
|
||
styles["container"],
|
||
cardState.card?.billRequest && styles["container-disabled"]
|
||
)}>
|
||
<div className={styles["products-list"]}>
|
||
<ScrollArea offsetScrollbars>
|
||
{cardState.card?.products.map(product => (
|
||
<ProductView
|
||
onProductEdit={onProductEdit}
|
||
onKitAdd={onKitAdd}
|
||
onCopyServices={onCopyServicesClick}
|
||
key={product.product.id}
|
||
product={product}
|
||
onChange={cardProductsState.onChange}
|
||
onDelete={cardProductsState.onDelete}
|
||
/>
|
||
))}
|
||
</ScrollArea>
|
||
</div>
|
||
|
||
<div className={styles["card-container"]}>
|
||
<ScrollArea offsetScrollbars>
|
||
<Flex
|
||
direction={"column"}
|
||
className={styles["card-container-wrapper"]}>
|
||
<CardServicesTable
|
||
onKitAdd={onCardKitAdd}
|
||
{...cardServicesState}
|
||
/>
|
||
|
||
<Divider my={rem(15)} />
|
||
<div className={styles["card-container-buttons"]}>
|
||
<Button
|
||
disabled={isLocked}
|
||
variant={"default"}
|
||
fullWidth
|
||
onClick={onCreateProductClick}>
|
||
Создать товар
|
||
</Button>
|
||
<Button
|
||
disabled={isLocked}
|
||
onClick={onAddProductClick}
|
||
variant={"default"}
|
||
fullWidth>
|
||
Добавить товар
|
||
</Button>
|
||
</div>
|
||
<Divider my={rem(15)} />
|
||
<div className={styles["card-container-buttons"]}>
|
||
{isLocked ? (
|
||
<Button
|
||
onClick={onCancelBillClick}
|
||
color={"red"}>
|
||
Отозвать счет
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
disabled={isLocked}
|
||
onClick={onCreateBillClick}
|
||
variant={"default"}
|
||
fullWidth>
|
||
Выставить счет
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</Flex>
|
||
<Flex
|
||
direction={"column"}
|
||
className={styles["card-container-wrapper"]}>
|
||
<Title order={3}>
|
||
Общая стоимость всех услуг:{" "}
|
||
{getTotalPrice().toLocaleString("ru")}₽
|
||
</Title>
|
||
</Flex>
|
||
</ScrollArea>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ProductAndServiceTab;
|