fix: total price for deal
This commit is contained in:
@ -0,0 +1,22 @@
|
||||
import { Flex, Stack } from "@mantine/core";
|
||||
import DealServicesTable from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/DealServicesTable";
|
||||
import ProductsActions from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/ProductsActions/ProductsActions";
|
||||
import TotalPriceLabel from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/TotalPriceLabel/TotalPriceLabel";
|
||||
import styles from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/FulfillmentBaseTab.module.css";
|
||||
|
||||
const DealInfoView = () => (
|
||||
<Stack
|
||||
flex={2}
|
||||
gap={"sm"}>
|
||||
<Flex
|
||||
gap={"sm"}
|
||||
direction={"column"}
|
||||
className={styles.container}>
|
||||
<DealServicesTable />
|
||||
<ProductsActions />
|
||||
</Flex>
|
||||
<TotalPriceLabel />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
export default DealInfoView;
|
||||
@ -1,10 +1,10 @@
|
||||
import { FC } from "react";
|
||||
import { Flex, ScrollArea } from "@mantine/core";
|
||||
import DealServiceRow from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/DealServiceRow";
|
||||
import DealServicesTitle from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/DealServicesTitle";
|
||||
import DealServicesTotalLabel from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/DealServicesTotalLabel";
|
||||
import ServicesActions from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/ServicesActions";
|
||||
import DealServicesTitle from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/DealServicesTitle";
|
||||
import DealServicesTotalLabel from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/DealServicesTotalLabel";
|
||||
import ServicesActions from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/ServicesActions";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
import DealServiceRow from "./components/DealServiceRow";
|
||||
|
||||
const DealServicesTable: FC = () => {
|
||||
const { dealServicesList, dealServicesCrud } = useFulfillmentBaseContext();
|
||||
@ -15,7 +15,7 @@ const DealServicesTable: FC = () => {
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={"sm"}
|
||||
h={"88vh"}>
|
||||
h={"78vh"}>
|
||||
<DealServicesTitle />
|
||||
<ScrollArea
|
||||
flex={1}
|
||||
@ -0,0 +1,40 @@
|
||||
import { useMemo } from "react";
|
||||
import { Flex, Title } from "@mantine/core";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
import styles from "../../../../FulfillmentBaseTab.module.css";
|
||||
|
||||
const TotalPriceLabel = () => {
|
||||
const {
|
||||
dealServicesList: { dealServices },
|
||||
dealProductsList: { dealProducts },
|
||||
} = useFulfillmentBaseContext();
|
||||
|
||||
const totalPrice = useMemo(() => {
|
||||
const productServicesPrice = dealProducts.reduce(
|
||||
(acc, row) =>
|
||||
acc +
|
||||
row.productServices.reduce(
|
||||
(acc2, row2) => acc2 + row2.price * row.quantity,
|
||||
0
|
||||
),
|
||||
0
|
||||
);
|
||||
const cardServicesPrice = dealServices.reduce(
|
||||
(acc, row) => acc + row.price * row.quantity,
|
||||
0
|
||||
);
|
||||
return cardServicesPrice + productServicesPrice;
|
||||
}, [dealServices, dealProducts]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
className={styles.container}>
|
||||
<Title order={3}>
|
||||
Общая стоимость всех услуг: {totalPrice.toLocaleString("ru")}₽
|
||||
</Title>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default TotalPriceLabel;
|
||||
@ -1,9 +1,7 @@
|
||||
import { Flex, ScrollArea, Stack } from "@mantine/core";
|
||||
import DealServicesTable from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/DealServicesTable";
|
||||
import ProductsActions from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductsActions/ProductsActions";
|
||||
import DealInfoView from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealInfoView/DealInfoView";
|
||||
import ProductView from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductView/ProductView";
|
||||
import { useFulfillmentBaseContext } from "../../contexts/FulfillmentBaseContext";
|
||||
import styles from "../../FulfillmentBaseTab.module.css";
|
||||
|
||||
const FulfillmentBaseTabBody = () => {
|
||||
const { dealProductsList } = useFulfillmentBaseContext();
|
||||
@ -25,13 +23,7 @@ const FulfillmentBaseTabBody = () => {
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
<Stack
|
||||
flex={2}
|
||||
gap={"sm"}
|
||||
className={styles.container}>
|
||||
<DealServicesTable />
|
||||
<ProductsActions />
|
||||
</Stack>
|
||||
<DealInfoView />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,115 +0,0 @@
|
||||
import ShippingWarehouseAutocomplete
|
||||
from "../../../../../../components/Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
|
||||
import { CardService, ShippingWarehouseSchema } from "../../../../../../client";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useCardPageContext } from "../../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
|
||||
import { Button, Checkbox, Stack } from "@mantine/core";
|
||||
import { notifications } from "../../../../../../shared/lib/notifications.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
import { isEqual } from "lodash";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../../../../../redux/store.ts";
|
||||
|
||||
|
||||
type GeneralDataFormType = {
|
||||
shippingWarehouse?: ShippingWarehouseSchema | null | string;
|
||||
isServicesProfitAccounted: boolean;
|
||||
}
|
||||
|
||||
const GeneralDataForm = () => {
|
||||
const { selectedCard: card, refetchCard } = useCardPageContext();
|
||||
const { isDealsViewer } = useSelector((state: RootState) => state.auth);
|
||||
if (!card) return <></>;
|
||||
|
||||
const [initialValues, setInitialValues] = useState<GeneralDataFormType>(card);
|
||||
|
||||
const form = useForm<GeneralDataFormType>({
|
||||
initialValues,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const data = card ?? {};
|
||||
setInitialValues(data);
|
||||
form.setValues(data);
|
||||
}, [card]);
|
||||
|
||||
const isShippingWarehouse = (
|
||||
value: ShippingWarehouseSchema | string | null | undefined,
|
||||
): value is ShippingWarehouseSchema => {
|
||||
return !!value && !["string"].includes(typeof value);
|
||||
};
|
||||
|
||||
const onSubmit = (values: GeneralDataFormType) => {
|
||||
if (!card) return;
|
||||
|
||||
const shippingWarehouse = isShippingWarehouse(values.shippingWarehouse)
|
||||
? values.shippingWarehouse.name
|
||||
: values.shippingWarehouse;
|
||||
|
||||
CardService.updateProductsAndServicesGeneralInfo({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
data: {
|
||||
...values,
|
||||
shippingWarehouse,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
|
||||
refetchCard();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(values => onSubmit(values))}>
|
||||
<Stack>
|
||||
<ShippingWarehouseAutocomplete
|
||||
placeholder={isDealsViewer ? "" : "Введите склад отгрузки"}
|
||||
label={"Склад отгрузки"}
|
||||
value={
|
||||
isShippingWarehouse(
|
||||
form.values.shippingWarehouse,
|
||||
)
|
||||
? form.values.shippingWarehouse
|
||||
: undefined
|
||||
}
|
||||
onChange={event => {
|
||||
if (isShippingWarehouse(event)) {
|
||||
form.getInputProps(
|
||||
"shippingWarehouse",
|
||||
).onChange(event.name);
|
||||
return;
|
||||
}
|
||||
form.getInputProps(
|
||||
"shippingWarehouse",
|
||||
).onChange(event);
|
||||
}}
|
||||
readOnly={isDealsViewer}
|
||||
/>
|
||||
{!isDealsViewer && (
|
||||
<>
|
||||
<Checkbox
|
||||
label={"Учет выручки в статистике"}
|
||||
{...form.getInputProps("isServicesProfitAccounted", { type: "checkbox" })}
|
||||
/>
|
||||
<Button
|
||||
type={"submit"}
|
||||
variant={"default"}
|
||||
disabled={isEqual(initialValues, form.values)}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralDataForm;
|
||||
@ -1,68 +0,0 @@
|
||||
import { CardSchema } from "../../../../../../client";
|
||||
import ButtonCopy from "../../../../../../components/ButtonCopy/ButtonCopy.tsx";
|
||||
import { ButtonCopyControlled } from "../../../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
|
||||
import { getCurrentDateTimeForFilename } from "../../../../../../shared/lib/date.ts";
|
||||
import FileSaver from "file-saver";
|
||||
import { Button, Popover, Stack } from "@mantine/core";
|
||||
|
||||
type Props = {
|
||||
card: CardSchema;
|
||||
}
|
||||
|
||||
const PaymentLinkButton = ({ card }: Props) => {
|
||||
if ((!card.billRequests || card.billRequests.length === 0) && (!card?.group?.billRequests || card?.group?.billRequests.length === 0)) {
|
||||
return (
|
||||
<ButtonCopyControlled
|
||||
onCopyClick={() => {
|
||||
const date =
|
||||
getCurrentDateTimeForFilename();
|
||||
FileSaver.saveAs(
|
||||
`${import.meta.env.VITE_API_URL}/card/billing-document/${card.id}`,
|
||||
`bill_${card.id}_${date}.pdf`,
|
||||
);
|
||||
}}
|
||||
copied={false}
|
||||
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||
>
|
||||
Ссылка на оплату (PDF)
|
||||
</ButtonCopyControlled>
|
||||
);
|
||||
}
|
||||
|
||||
const requests = (card?.group ? card?.group?.billRequests : card.billRequests) ?? [];
|
||||
const urls = requests.map(request => request.pdfUrl).filter(url => url !== null);
|
||||
|
||||
if (urls.length === 1) {
|
||||
return (
|
||||
<ButtonCopy
|
||||
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||
value={urls[0]}
|
||||
>
|
||||
Ссылка на оплату
|
||||
</ButtonCopy>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover width={380} position="bottom" withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Button variant={"default"}>Ссылки на оплату</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Stack gap={"md"}>
|
||||
{urls.map((url, i) => (
|
||||
<ButtonCopy
|
||||
key={i}
|
||||
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||
value={url}
|
||||
>
|
||||
{`Ссылка на оплату (часть ${String(i + 1)})`}
|
||||
</ButtonCopy>
|
||||
))}
|
||||
</Stack>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentLinkButton;
|
||||
@ -1,63 +0,0 @@
|
||||
import { ActionIcon, Group, Tooltip } from "@mantine/core";
|
||||
import styles from "../../../../../../pages/CardsPage/ui/CardsPage.module.css";
|
||||
import { CardSchema, CardService } from "../../../../../../client";
|
||||
import { base64ToBlob } from "../../../../../../shared/lib/utils.ts";
|
||||
import { notifications } from "../../../../../../shared/lib/notifications.ts";
|
||||
import { IconBarcode, IconPrinter } from "@tabler/icons-react";
|
||||
|
||||
|
||||
type Props = {
|
||||
card: CardSchema;
|
||||
}
|
||||
|
||||
const PrintDealBarcodesButton = ({ card }: Props) => {
|
||||
return (
|
||||
<Group wrap={"nowrap"}>
|
||||
<Tooltip
|
||||
className={styles["print-deals-button"]}
|
||||
label={"Распечатать штрихкоды сделки"}
|
||||
>
|
||||
<ActionIcon
|
||||
onClick={async () => {
|
||||
const response =
|
||||
await CardService.getCardProductsBarcodesPdf({
|
||||
requestBody: {
|
||||
cardId: card.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>
|
||||
<Tooltip label={"Распечатать сделку"}>
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
const pdfWindow = window.open(
|
||||
`${import.meta.env.VITE_API_URL}/card/tech-spec/${card.id}`,
|
||||
);
|
||||
if (!pdfWindow) return;
|
||||
pdfWindow.print();
|
||||
}}
|
||||
variant={"default"}>
|
||||
<IconPrinter />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrintDealBarcodesButton;
|
||||
Reference in New Issue
Block a user