feat: cards, attributes and modules
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
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) 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 onDealKitAdd = (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) 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["deal-container"]}>
|
||||
<ScrollArea offsetScrollbars>
|
||||
<Flex
|
||||
direction={"column"}
|
||||
className={styles["deal-container-wrapper"]}>
|
||||
<CardServicesTable
|
||||
onKitAdd={onDealKitAdd}
|
||||
{...cardServicesState}
|
||||
/>
|
||||
|
||||
<Divider my={rem(15)} />
|
||||
<div className={styles["deal-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["deal-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["deal-container-wrapper"]}>
|
||||
<Title order={3}>
|
||||
Общая стоимость всех услуг:{" "}
|
||||
{getTotalPrice().toLocaleString("ru")}₽
|
||||
</Title>
|
||||
</Flex>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductAndServiceTab;
|
||||
Reference in New Issue
Block a user