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 { FC } from "react";
|
||||||
import { Flex, ScrollArea } from "@mantine/core";
|
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/DealInfoView/components/DealServicesTable/components/DealServicesTitle";
|
||||||
import DealServicesTitle from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/DealServicesTitle";
|
import DealServicesTotalLabel from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/DealServicesTotalLabel";
|
||||||
import DealServicesTotalLabel from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/DealServicesTotalLabel";
|
import ServicesActions from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/ServicesActions";
|
||||||
import ServicesActions from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/components/ServicesActions";
|
|
||||||
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
import { useFulfillmentBaseContext } from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||||
|
import DealServiceRow from "./components/DealServiceRow";
|
||||||
|
|
||||||
const DealServicesTable: FC = () => {
|
const DealServicesTable: FC = () => {
|
||||||
const { dealServicesList, dealServicesCrud } = useFulfillmentBaseContext();
|
const { dealServicesList, dealServicesCrud } = useFulfillmentBaseContext();
|
||||||
@ -15,7 +15,7 @@ const DealServicesTable: FC = () => {
|
|||||||
<Flex
|
<Flex
|
||||||
direction={"column"}
|
direction={"column"}
|
||||||
gap={"sm"}
|
gap={"sm"}
|
||||||
h={"88vh"}>
|
h={"78vh"}>
|
||||||
<DealServicesTitle />
|
<DealServicesTitle />
|
||||||
<ScrollArea
|
<ScrollArea
|
||||||
flex={1}
|
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 { Flex, ScrollArea, Stack } from "@mantine/core";
|
||||||
import DealServicesTable from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealServicesTable/DealServicesTable";
|
import DealInfoView from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/DealInfoView/DealInfoView";
|
||||||
import ProductsActions from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductsActions/ProductsActions";
|
|
||||||
import ProductView from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductView/ProductView";
|
import ProductView from "@/modules/dealModules/dealEditorTabs/FulfillmentBaseTab/components/ProductView/ProductView";
|
||||||
import { useFulfillmentBaseContext } from "../../contexts/FulfillmentBaseContext";
|
import { useFulfillmentBaseContext } from "../../contexts/FulfillmentBaseContext";
|
||||||
import styles from "../../FulfillmentBaseTab.module.css";
|
|
||||||
|
|
||||||
const FulfillmentBaseTabBody = () => {
|
const FulfillmentBaseTabBody = () => {
|
||||||
const { dealProductsList } = useFulfillmentBaseContext();
|
const { dealProductsList } = useFulfillmentBaseContext();
|
||||||
@ -25,13 +23,7 @@ const FulfillmentBaseTabBody = () => {
|
|||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
<Stack
|
<DealInfoView />
|
||||||
flex={2}
|
|
||||||
gap={"sm"}
|
|
||||||
className={styles.container}>
|
|
||||||
<DealServicesTable />
|
|
||||||
<ProductsActions />
|
|
||||||
</Stack>
|
|
||||||
</Flex>
|
</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