feat: pallets and boxes for deals
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
import { Box, Drawer, rem, Tabs } from "@mantine/core";
|
||||
import { FC, useEffect } from "react";
|
||||
import { useDealPageContext } from "../../contexts/DealPageContext.tsx";
|
||||
import { IconBox, IconCalendarUser, IconSettings, IconUser } from "@tabler/icons-react";
|
||||
import { IconBox, IconCalendarUser, IconCubeSend, IconSettings, IconUser } from "@tabler/icons-react";
|
||||
import DealStatusChangeTable from "../../components/DealStatusChangeTable/DealStatusChangeTable.tsx";
|
||||
import DealEditDrawerGeneralTab from "./tabs/DealEditDrawerGeneralTab.tsx";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import ProductAndServiceTab from "../../tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
|
||||
import { motion } from "framer-motion";
|
||||
import ClientTab from "./tabs/ClientTab.tsx";
|
||||
import ShippingTab from "../../tabs/ShippingTab/ShippingTab.tsx";
|
||||
// import styles from './DealEditDrawer.module.css';
|
||||
|
||||
// const useDealServicesTableState = () => {
|
||||
@@ -348,6 +349,11 @@ const DealEditDrawer: FC = () => {
|
||||
leftSection={<IconBox />}>
|
||||
Товары и услуги
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
value={"shipment"}
|
||||
leftSection={<IconCubeSend />}>
|
||||
Отгрузка
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value={"general"}>
|
||||
<motion.div
|
||||
@@ -392,6 +398,16 @@ const DealEditDrawer: FC = () => {
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"shipment"}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<Box p={rem(10)}>
|
||||
<ShippingTab />
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</Drawer>
|
||||
);
|
||||
|
||||
51
src/pages/LeadsPage/tabs/ShippingTab/ShippingTab.tsx
Normal file
51
src/pages/LeadsPage/tabs/ShippingTab/ShippingTab.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import ShippingTree from "./components/ShippingTree.tsx";
|
||||
import { Button, Group, rem, ScrollArea, Stack } from "@mantine/core";
|
||||
import useShipping from "./hooks/useShipping.tsx";
|
||||
import useShippingQrs from "./hooks/useShippingQrs.tsx";
|
||||
import { IconPrinter, IconQrcode } from "@tabler/icons-react";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
|
||||
const ShippingTab = () => {
|
||||
const {
|
||||
onCreateBoxInDealClick,
|
||||
onCreatePalletClick,
|
||||
} = useShipping();
|
||||
|
||||
const {
|
||||
onGetDealQrPdfClick,
|
||||
onGetPalletsPdfClick,
|
||||
onGetBoxesPdfClick,
|
||||
} = useShippingQrs();
|
||||
|
||||
const getButton = (label: string, func: () => void, icon?: ReactNode) => {
|
||||
return (
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={func}
|
||||
>
|
||||
<Group gap={rem(10)}>
|
||||
{icon}
|
||||
{label}
|
||||
</Group>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack h={"94vh"}>
|
||||
<Group>
|
||||
{getButton("Добавить паллет", onCreatePalletClick)}
|
||||
{getButton("Добавить короб", onCreateBoxInDealClick)}
|
||||
{getButton("Сделка", onGetDealQrPdfClick, <IconQrcode />)}
|
||||
{getButton("Паллеты", onGetPalletsPdfClick, <IconPrinter />)}
|
||||
{getButton("Короба", onGetBoxesPdfClick, <IconPrinter />)}
|
||||
</Group>
|
||||
<ScrollArea>
|
||||
<ShippingTree />
|
||||
</ScrollArea>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShippingTab;
|
||||
@@ -0,0 +1,87 @@
|
||||
import useShippingTableColumns from "../hooks/shippingTableColumns.tsx";
|
||||
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import { BoxSchema, ShippingService } from "../../../../../client";
|
||||
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
|
||||
import useUpdateDeal from "../hooks/useUpdateDeal.tsx";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
|
||||
|
||||
type Props = {
|
||||
items: BoxSchema[];
|
||||
}
|
||||
|
||||
const BoxesTable = ({ items }: Props) => {
|
||||
const columns = useShippingTableColumns<BoxSchema>();
|
||||
const { update } = useUpdateDeal();
|
||||
const { selectedDeal: deal } = useDealPageContext();
|
||||
|
||||
const onDeleteClick = (box: BoxSchema) => {
|
||||
ShippingService.deleteBox({
|
||||
boxId: box.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
if (ok) update();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onEditClick = (box: BoxSchema) => {
|
||||
if (!deal) return;
|
||||
modals.openContextModal({
|
||||
modal: "shippingProductModal",
|
||||
title: "Редактирование короба",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
deal,
|
||||
updateOnSubmit: update,
|
||||
isBox: true,
|
||||
shippingData: {
|
||||
boxId: box.id,
|
||||
productId: box.product.id,
|
||||
quantity: box.quantity,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
restProps={
|
||||
{
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
enableRowActions: true,
|
||||
enableRowNumbers: true,
|
||||
positionActionsColumn: "last",
|
||||
renderRowActions: ({ row }) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon
|
||||
onClick={() => onDeleteClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon
|
||||
onClick={() => onEditClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<BoxSchema>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoxesTable;
|
||||
@@ -0,0 +1,87 @@
|
||||
import useShippingTableColumns from "../hooks/shippingTableColumns.tsx";
|
||||
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import { ShippingProductSchema, ShippingService } from "../../../../../client";
|
||||
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
import useUpdateDeal from "../hooks/useUpdateDeal.tsx";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
|
||||
|
||||
|
||||
type Props = {
|
||||
items: ShippingProductSchema[];
|
||||
}
|
||||
|
||||
const ShippingProductsTable = ({ items }: Props) => {
|
||||
const columns = useShippingTableColumns<ShippingProductSchema>();
|
||||
const { update } = useUpdateDeal();
|
||||
const { selectedDeal: deal } = useDealPageContext();
|
||||
|
||||
const onDeleteClick = (shippingProduct: ShippingProductSchema) => {
|
||||
ShippingService.deleteShippingProduct({
|
||||
shippingProductId: shippingProduct.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
update();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onEditClick = (shippingProduct: ShippingProductSchema) => {
|
||||
if (!deal) return;
|
||||
modals.openContextModal({
|
||||
modal: "shippingProductModal",
|
||||
title: "Редактирование товара на паллете",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
deal,
|
||||
updateOnSubmit: update,
|
||||
isBox: false,
|
||||
shippingData: {
|
||||
shippingProductId: shippingProduct.id,
|
||||
productId: shippingProduct.product.id,
|
||||
quantity: shippingProduct.quantity,
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
restProps={
|
||||
{
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
enableRowActions: true,
|
||||
enableRowNumbers: true,
|
||||
positionActionsColumn: "last",
|
||||
renderRowActions: ({ row }) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon
|
||||
onClick={() => onDeleteClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon
|
||||
onClick={() => onEditClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<ShippingProductSchema>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShippingProductsTable;
|
||||
151
src/pages/LeadsPage/tabs/ShippingTab/components/ShippingTree.tsx
Normal file
151
src/pages/LeadsPage/tabs/ShippingTab/components/ShippingTree.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
|
||||
import { Accordion, ActionIcon, Button, Center, Group, rem, Stack, Title, Tooltip } from "@mantine/core";
|
||||
import { BoxSchema, PalletSchema, ShippingProductSchema } from "../../../../../client";
|
||||
import ShippingProductsTable from "./ShippingProductsTable.tsx";
|
||||
import BoxesTable from "./BoxesTable.tsx";
|
||||
import { IconBox, IconPlus, IconSpace, IconTrash } from "@tabler/icons-react";
|
||||
import useShipping from "../hooks/useShipping.tsx";
|
||||
|
||||
const ShippingTree = () => {
|
||||
const { selectedDeal: deal } = useDealPageContext();
|
||||
|
||||
const {
|
||||
onCreateBoxInPallet,
|
||||
onCreateShippingProduct,
|
||||
onDeletePalletClick,
|
||||
palletIds,
|
||||
} = useShipping();
|
||||
|
||||
const sortById = (data?: PalletSchema[] | BoxSchema[] | ShippingProductSchema[]) => {
|
||||
return data?.sort((a, b) => a.id - b.id);
|
||||
};
|
||||
|
||||
const getPallets = () => {
|
||||
const sortedPallets = sortById(deal?.pallets) as PalletSchema[];
|
||||
const pallets = sortedPallets?.map(((pallet, index) => {
|
||||
palletIds.push(pallet.id.toString());
|
||||
return (
|
||||
<Accordion.Item key={pallet.id} value={pallet.id.toString()}>
|
||||
<Center>
|
||||
<Accordion.Control icon={<IconSpace />}>
|
||||
Паллет {index + 1}
|
||||
</Accordion.Control>
|
||||
{removePalletButton(pallet.id)}
|
||||
</Center>
|
||||
<Accordion.Panel>
|
||||
{getPalletContent(pallet)}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
);
|
||||
})) ?? [];
|
||||
|
||||
if (deal?.boxes && deal?.boxes.length > 0) {
|
||||
const boxes = deal?.boxes.sort((b1, b2) => (b1.id - b2.id));
|
||||
const itemValue = "noPallets";
|
||||
const boxesWithoutPallet = (
|
||||
<Accordion.Item key={-1} value={itemValue}>
|
||||
<Accordion.Control icon={<IconBox />}>
|
||||
Короба без паллетов
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<BoxesTable items={boxes} />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
);
|
||||
pallets.unshift(boxesWithoutPallet);
|
||||
palletIds.push(itemValue);
|
||||
}
|
||||
|
||||
return pallets;
|
||||
};
|
||||
|
||||
const removePalletButton = (palletId: number) => {
|
||||
return (
|
||||
<Tooltip label="Удалить паллет">
|
||||
<ActionIcon
|
||||
variant={"default"}
|
||||
onClick={() => onDeletePalletClick(palletId)}
|
||||
mx={"md"}
|
||||
>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const createBoxOrShippingProductButton = (palletId: number, isBox: boolean) => {
|
||||
const createButtonLabel = isBox ? "Добавить короб" : "Добавить товар";
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={() => {
|
||||
if (isBox) {
|
||||
onCreateBoxInPallet(palletId);
|
||||
} else {
|
||||
onCreateShippingProduct(palletId);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Group gap={rem(5)}>
|
||||
<IconPlus />
|
||||
{createButtonLabel}
|
||||
</Group>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const getPalletContent = (pallet: PalletSchema) => {
|
||||
const isEmpty = pallet.boxes.length === 0 && pallet.shippingProducts.length === 0;
|
||||
const isBox = pallet.boxes.length > 0;
|
||||
const title = isEmpty ? "Пустой" : isBox ? "Короба" : "Товары";
|
||||
|
||||
let palletButtons;
|
||||
if (isEmpty) {
|
||||
palletButtons = [
|
||||
createBoxOrShippingProductButton(pallet.id, true),
|
||||
createBoxOrShippingProductButton(pallet.id, false),
|
||||
];
|
||||
} else {
|
||||
palletButtons = [
|
||||
createBoxOrShippingProductButton(pallet.id, isBox),
|
||||
];
|
||||
}
|
||||
|
||||
const boxes = sortById(pallet.boxes) as BoxSchema[];
|
||||
const shippingProducts = sortById(pallet.shippingProducts) as ShippingProductSchema[];
|
||||
|
||||
let table;
|
||||
if (!isEmpty) {
|
||||
if (isBox) {
|
||||
table = (<BoxesTable items={boxes} />);
|
||||
} else {
|
||||
table = (<ShippingProductsTable items={shippingProducts} />);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={rem(5)}>
|
||||
<Group justify={"space-between"}>
|
||||
<Title order={6}>{title}</Title>
|
||||
<Group gap={rem(10)}>
|
||||
{...palletButtons}
|
||||
</Group>
|
||||
</Group>
|
||||
{table}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
multiple={true}
|
||||
defaultValue={palletIds}
|
||||
bd={"solid 1px gray"}
|
||||
>
|
||||
{getPallets()}
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShippingTree;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef, MRT_RowData } from "mantine-react-table";
|
||||
|
||||
const useShippingTableColumns = <T extends MRT_RowData>() => {
|
||||
return useMemo<MRT_ColumnDef<T>[]>(
|
||||
() => [
|
||||
{
|
||||
header: "Название",
|
||||
accessorKey: "product.name",
|
||||
},
|
||||
{
|
||||
header: "Артикул",
|
||||
accessorKey: "product.article",
|
||||
},
|
||||
{
|
||||
header: "Размер",
|
||||
accessorKey: "product.size",
|
||||
},
|
||||
{
|
||||
header: "Количество",
|
||||
accessorKey: "quantity",
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export default useShippingTableColumns;
|
||||
108
src/pages/LeadsPage/tabs/ShippingTab/hooks/useShipping.tsx
Normal file
108
src/pages/LeadsPage/tabs/ShippingTab/hooks/useShipping.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
|
||||
import { ShippingService } from "../../../../../client";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { Text } from "@mantine/core";
|
||||
import useUpdateDeal from "./useUpdateDeal.tsx";
|
||||
|
||||
const useShipping = () => {
|
||||
const { selectedDeal: deal } = useDealPageContext();
|
||||
const { update } = useUpdateDeal();
|
||||
const palletIds: string[] = [];
|
||||
|
||||
const onCreatePalletClick = () => {
|
||||
if (!deal) return;
|
||||
|
||||
ShippingService.createPallet({
|
||||
dealId: deal.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
update();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onDeletePallet = (palletId: number) => {
|
||||
ShippingService.deletePallet({
|
||||
palletId: palletId,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
update();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onDeletePalletClick = (palletId: number) => {
|
||||
if (!deal) return;
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление паллета",
|
||||
children: <Text size="sm">Вы уверены что хотите удалить паллет?</Text>,
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => onDeletePallet(palletId),
|
||||
});
|
||||
};
|
||||
|
||||
const openShippingModal = (
|
||||
title: string,
|
||||
isBox: boolean,
|
||||
palletId?: number,
|
||||
dealId?: number,
|
||||
) => {
|
||||
if (!deal) return;
|
||||
modals.openContextModal({
|
||||
modal: "shippingProductModal",
|
||||
title,
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
deal,
|
||||
updateOnSubmit: update,
|
||||
isBox,
|
||||
shippingData: {
|
||||
dealId,
|
||||
palletId,
|
||||
productId: null,
|
||||
quantity: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onCreateBoxInDealClick = () => {
|
||||
openShippingModal(
|
||||
"Добавление короба",
|
||||
true,
|
||||
undefined,
|
||||
deal?.id,
|
||||
);
|
||||
};
|
||||
|
||||
const onCreateBoxInPallet = (palletId: number) => {
|
||||
openShippingModal(
|
||||
"Добавление короба",
|
||||
true,
|
||||
palletId,
|
||||
);
|
||||
};
|
||||
|
||||
const onCreateShippingProduct = (palletId: number) => {
|
||||
openShippingModal(
|
||||
"Добавление товара на паллет",
|
||||
false,
|
||||
palletId,
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateBoxInDealClick,
|
||||
onCreateBoxInPallet,
|
||||
onCreateShippingProduct,
|
||||
onCreatePalletClick,
|
||||
onDeletePalletClick,
|
||||
palletIds,
|
||||
};
|
||||
};
|
||||
|
||||
export default useShipping;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
|
||||
|
||||
|
||||
const useShippingQrs = () => {
|
||||
const { selectedDeal: deal } = useDealPageContext();
|
||||
|
||||
const basePdfUrl = `${import.meta.env.VITE_API_URL}/shipping/pdf`;
|
||||
|
||||
const getPdf = (url: string) => {
|
||||
if (!deal) return;
|
||||
const pdfWindow = window.open(url);
|
||||
if (!pdfWindow) return;
|
||||
pdfWindow.print();
|
||||
};
|
||||
|
||||
const onGetDealQrPdfClick = () => {
|
||||
getPdf(`${basePdfUrl}/deal/${deal?.id}`);
|
||||
};
|
||||
|
||||
const onGetPalletsPdfClick = () => {
|
||||
getPdf(`${basePdfUrl}/pallets/${deal?.id}`);
|
||||
};
|
||||
|
||||
const onGetBoxesPdfClick = () => {
|
||||
getPdf(`${basePdfUrl}/boxes/${deal?.id}`);
|
||||
};
|
||||
|
||||
return {
|
||||
onGetDealQrPdfClick,
|
||||
onGetPalletsPdfClick,
|
||||
onGetBoxesPdfClick,
|
||||
};
|
||||
};
|
||||
|
||||
export default useShippingQrs;
|
||||
18
src/pages/LeadsPage/tabs/ShippingTab/hooks/useUpdateDeal.tsx
Normal file
18
src/pages/LeadsPage/tabs/ShippingTab/hooks/useUpdateDeal.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useDealPageContext } from "../../../contexts/DealPageContext.tsx";
|
||||
import { DealService } from "../../../../../client";
|
||||
|
||||
const useUpdateDeal = () => {
|
||||
const { selectedDeal: deal, setSelectedDeal } = useDealPageContext();
|
||||
|
||||
const update = () => {
|
||||
if (!deal) return;
|
||||
DealService.getDealById({ dealId: deal.id })
|
||||
.then(data => {
|
||||
setSelectedDeal(data);
|
||||
});
|
||||
};
|
||||
|
||||
return { update };
|
||||
};
|
||||
|
||||
export default useUpdateDeal;
|
||||
@@ -0,0 +1,156 @@
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import { Button, Flex, NumberInput, rem, Select, Text } from "@mantine/core";
|
||||
import getRestProducts from "../utils/getRestProducts.tsx";
|
||||
import {
|
||||
CreateBoxInDealSchema,
|
||||
CreateBoxInPalletSchema,
|
||||
CreateShippingProductSchema,
|
||||
DealProductSchema,
|
||||
DealSchema,
|
||||
ShippingService,
|
||||
UpdateBoxSchema,
|
||||
UpdateShippingProductSchema,
|
||||
} from "../../../../../client";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
import { ShippingData, ShippingModalForm, ShippingProductOption } from "../types/ShippingProductData.tsx";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
|
||||
|
||||
type Props = {
|
||||
updateOnSubmit: () => void;
|
||||
deal: DealSchema;
|
||||
isBox: boolean;
|
||||
shippingData: Partial<ShippingData>;
|
||||
}
|
||||
|
||||
const ShippingProductModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const [restProducts, setRestProducts] = useState<Map<number, DealProductSchema>>(new Map());
|
||||
const [restProductsSelectData, setRestProductSelectData] = useState<ShippingProductOption[]>([]);
|
||||
|
||||
const getRestProductQuantity = () => {
|
||||
if (form.values.productId) {
|
||||
const restProduct = restProducts.get(Number(form.values.productId));
|
||||
if (restProduct) {
|
||||
return restProduct.quantity;
|
||||
}
|
||||
}
|
||||
return 10000;
|
||||
};
|
||||
|
||||
const initialValues: ShippingModalForm = {
|
||||
quantity: innerProps.shippingData.quantity ?? 0,
|
||||
productId: innerProps.shippingData.productId,
|
||||
};
|
||||
const form = useForm<ShippingModalForm>({
|
||||
initialValues,
|
||||
validate: {
|
||||
productId: productId => !productId && "Необходимо выбрать товар",
|
||||
quantity: quantity => quantity > getRestProductQuantity() ? "Слишком много товара" :
|
||||
quantity === 0 && "Слишком мало товара",
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const data = getRestProducts({
|
||||
deal: innerProps.deal,
|
||||
unaccountedValues: innerProps.shippingData as UpdateShippingProductSchema | UpdateBoxSchema,
|
||||
});
|
||||
setRestProducts(data.restProducts);
|
||||
setRestProductSelectData(data.restProductsSelectData);
|
||||
}, [innerProps.deal]);
|
||||
|
||||
const updateBox = () => {
|
||||
const data = {
|
||||
...innerProps.shippingData,
|
||||
...form.values,
|
||||
} as CreateBoxInPalletSchema | CreateBoxInDealSchema | UpdateBoxSchema;
|
||||
|
||||
ShippingService.updateBox({
|
||||
requestBody: { data },
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
innerProps.updateOnSubmit();
|
||||
if (ok) context.closeContextModal(id);
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const updateShippingProduct = () => {
|
||||
const data = {
|
||||
...innerProps.shippingData,
|
||||
...form.values,
|
||||
} as CreateShippingProductSchema | UpdateShippingProductSchema;
|
||||
|
||||
ShippingService.updateShippingProduct({
|
||||
requestBody: { data },
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
innerProps.updateOnSubmit();
|
||||
if (ok) context.closeContextModal(id);
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
if (innerProps.isBox) {
|
||||
updateBox();
|
||||
} else {
|
||||
updateShippingProduct();
|
||||
}
|
||||
};
|
||||
|
||||
const getRestQuantityText = () => {
|
||||
if (!form.values.productId) return;
|
||||
|
||||
const restQuantityInDeal = getRestProductQuantity();
|
||||
const restQuantity = restQuantityInDeal - form.values.quantity;
|
||||
|
||||
if (restQuantity >= 0) {
|
||||
return <Text>Осталось распределить {restQuantity} шт.</Text>;
|
||||
}
|
||||
return <Text>Введено слишком большое количество.<br />Доступно {restQuantityInDeal} шт.</Text>;
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(() => onSubmit())}>
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
>
|
||||
<Select
|
||||
data={restProductsSelectData}
|
||||
label={"Товар"}
|
||||
placeholder={"Выберите товар"}
|
||||
{...form.getInputProps("productId")}
|
||||
value={form.values.productId?.toString()}
|
||||
/>
|
||||
|
||||
{getRestQuantityText()}
|
||||
|
||||
<NumberInput
|
||||
label={"Количество"}
|
||||
hideControls
|
||||
{...form.getInputProps("quantity")}
|
||||
min={0}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant={"default"}
|
||||
type={"submit"}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShippingProductModal;
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
CreateBoxInDealSchema,
|
||||
CreateBoxInPalletSchema,
|
||||
CreateShippingProductSchema,
|
||||
UpdateBoxSchema,
|
||||
UpdateShippingProductSchema,
|
||||
} from "../../../../../client";
|
||||
|
||||
export type ShippingModalForm = {
|
||||
quantity: number;
|
||||
productId?: number | null;
|
||||
}
|
||||
|
||||
export type ShippingProductOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export type ShippingData =
|
||||
CreateBoxInDealSchema
|
||||
| CreateBoxInPalletSchema
|
||||
| UpdateBoxSchema
|
||||
| CreateShippingProductSchema
|
||||
| UpdateShippingProductSchema;
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ShippingProductOption } from "../types/ShippingProductData.tsx";
|
||||
import { DealProductSchema, DealSchema, ProductSchema } from "../../../../../client";
|
||||
|
||||
type UnaccountedValues = {
|
||||
boxId?: number | null;
|
||||
shippingProductId?: number | null;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
deal?: DealSchema;
|
||||
unaccountedValues: UnaccountedValues;
|
||||
}
|
||||
|
||||
const getRestProducts = ({
|
||||
deal,
|
||||
unaccountedValues,
|
||||
}: Props) => {
|
||||
const totalProducts = new Map(
|
||||
deal?.products.map(product => [product.product.id, product]),
|
||||
);
|
||||
|
||||
const distributedProducts = new Map();
|
||||
|
||||
const accountProduct = (product: ProductSchema, quantity: number) => {
|
||||
const productId = product.id;
|
||||
if (distributedProducts.has(productId)) {
|
||||
const prodData = distributedProducts.get(productId);
|
||||
distributedProducts.set(productId, { product, quantity: quantity + prodData.quantity });
|
||||
} else {
|
||||
distributedProducts.set(productId, { product, quantity });
|
||||
}
|
||||
};
|
||||
|
||||
deal?.boxes?.forEach((box) => {
|
||||
if (box.id === unaccountedValues.boxId) return;
|
||||
accountProduct(box.product, box.quantity);
|
||||
});
|
||||
|
||||
deal?.pallets?.forEach(pallet => {
|
||||
pallet.shippingProducts.forEach(shippingProduct => {
|
||||
if (shippingProduct.id === unaccountedValues.shippingProductId) return;
|
||||
accountProduct(shippingProduct.product, shippingProduct.quantity);
|
||||
});
|
||||
pallet.boxes.forEach((box) => {
|
||||
if (box.id === unaccountedValues.boxId) return;
|
||||
accountProduct(box.product, box.quantity);
|
||||
});
|
||||
});
|
||||
|
||||
const restProducts = new Map<number, DealProductSchema>();
|
||||
|
||||
totalProducts.entries().forEach(([key, product]) => {
|
||||
const distributedProduct = distributedProducts.get(key);
|
||||
if (distributedProduct) {
|
||||
if (product.quantity > distributedProduct.quantity) {
|
||||
const restQuantity = product.quantity - distributedProduct.quantity;
|
||||
restProducts.set(key, { ...product, quantity: restQuantity });
|
||||
}
|
||||
} else {
|
||||
restProducts.set(key, product);
|
||||
}
|
||||
});
|
||||
|
||||
const restProductsSelectData: ShippingProductOption[] = [];
|
||||
|
||||
restProducts.forEach(
|
||||
(restProduct, id) => {
|
||||
restProductsSelectData.push({ value: String(id), label: restProduct.product.name });
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
restProducts,
|
||||
restProductsSelectData,
|
||||
};
|
||||
};
|
||||
|
||||
export default getRestProducts;
|
||||
Reference in New Issue
Block a user