feat: cards, attributes and modules

This commit is contained in:
2025-02-19 14:46:13 +04:00
parent cc3e72bf94
commit dc9455966e
286 changed files with 2355 additions and 2168 deletions

View File

@@ -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;