feat: residues accounting
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { ResidualBoxSchema } from "../../../../client";
|
||||
import { Accordion, ActionIcon, Center, Checkbox, Text, Tooltip } from "@mantine/core";
|
||||
import { IconBox, IconTrash } from "@tabler/icons-react";
|
||||
import ResidualProductsTable from "../ResidualProductsTable/ResidualProductsTable.tsx";
|
||||
import { useResiduesContext } from "../../contexts/ResiduesContext.tsx";
|
||||
|
||||
type Props = {
|
||||
boxes?: ResidualBoxSchema[];
|
||||
}
|
||||
|
||||
const ResidualBoxes = ({ boxes }: Props) => {
|
||||
const { onDeleteBoxClick, boxIdsToPrint } = useResiduesContext();
|
||||
if (!boxes || boxes.length == 0) return;
|
||||
|
||||
const boxIds = boxes.map((box) => box.id.toString());
|
||||
|
||||
const removeBoxButton = (box: ResidualBoxSchema) => {
|
||||
return (
|
||||
<Tooltip label="Удалить короб">
|
||||
<ActionIcon
|
||||
variant={"default"}
|
||||
onClick={() => onDeleteBoxClick(box)}
|
||||
mx={"md"}
|
||||
>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const checkboxToPrint = (box: ResidualBoxSchema) => {
|
||||
return (
|
||||
<Checkbox
|
||||
ml="sm"
|
||||
checked={boxIdsToPrint.has(box.id)}
|
||||
onChange={() => {
|
||||
if (boxIdsToPrint.has(box.id)) {
|
||||
boxIdsToPrint.delete(box.id);
|
||||
} else {
|
||||
boxIdsToPrint.add(box.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderBox = (box: ResidualBoxSchema) => {
|
||||
return (
|
||||
<Accordion.Item key={box.id} value={box.id.toString()}>
|
||||
<Center>
|
||||
<Accordion.Control icon={<IconBox />}>
|
||||
Короб {box.id}
|
||||
</Accordion.Control>
|
||||
{checkboxToPrint(box)}
|
||||
{removeBoxButton(box)}
|
||||
</Center>
|
||||
<Accordion.Panel>
|
||||
{
|
||||
box.residualProducts.length > 0 ? (
|
||||
<ResidualProductsTable
|
||||
items={box.residualProducts.sort((a, b) => a.id - b.id)}
|
||||
/>
|
||||
) : (
|
||||
<Text>Пустой</Text>
|
||||
)
|
||||
}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
multiple={true}
|
||||
defaultValue={boxIds}
|
||||
bd={"solid 1px gray"}
|
||||
>
|
||||
{boxes.sort((a, b) => a.id - b.id).map(box => renderBox(box))}
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResidualBoxes;
|
||||
@@ -0,0 +1,84 @@
|
||||
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import { ResidualProductSchema, ResiduesService } 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 { modals } from "@mantine/modals";
|
||||
import { useResiduesContext } from "../../contexts/ResiduesContext.tsx";
|
||||
import useResiduesTableColumns from "../../hooks/residuesTableColumns.tsx";
|
||||
|
||||
|
||||
type Props = {
|
||||
items: ResidualProductSchema[];
|
||||
}
|
||||
|
||||
const ResidualProductsTable = ({ items }: Props) => {
|
||||
const columns = useResiduesTableColumns<ResidualProductSchema>();
|
||||
const { selectedClient, refetchClient } = useResiduesContext();
|
||||
|
||||
const onDeleteClick = (residualProduct: ResidualProductSchema) => {
|
||||
ResiduesService.deleteResidualProduct({
|
||||
residualProductId: residualProduct.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
refetchClient();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onEditClick = (residualProduct: ResidualProductSchema) => {
|
||||
if (!selectedClient) return;
|
||||
modals.openContextModal({
|
||||
modal: "residualProductModal",
|
||||
title: "Редактирование товара на паллете",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
client: selectedClient,
|
||||
updateOnSubmit: refetchClient,
|
||||
residuesData: {
|
||||
residualProductId: residualProduct.id,
|
||||
product: residualProduct.product,
|
||||
quantity: residualProduct.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<ResidualProductSchema>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResidualProductsTable;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Group } from "@mantine/core";
|
||||
import { useResiduesContext } from "../../contexts/ResiduesContext.tsx";
|
||||
import useResiduesPdf from "../../hooks/useResiduesPdf.tsx";
|
||||
import { IconQrcode } from "@tabler/icons-react";
|
||||
import InlineButton from "../../../../components/InlineButton/InlineButton.tsx";
|
||||
|
||||
|
||||
const ResiduesHeader = () => {
|
||||
const {
|
||||
onCreatePalletClick,
|
||||
onCreateBoxClick,
|
||||
} = useResiduesContext();
|
||||
|
||||
const {
|
||||
onGetPalletsPdfClick,
|
||||
} = useResiduesPdf();
|
||||
|
||||
return (
|
||||
<Group>
|
||||
<InlineButton onClick={() => onCreatePalletClick()}>
|
||||
Добавить паллет
|
||||
</InlineButton>
|
||||
<InlineButton onClick={() => onCreateBoxClick()}>
|
||||
Добавить короб
|
||||
</InlineButton>
|
||||
<InlineButton onClick={() => onGetPalletsPdfClick()}>
|
||||
<IconQrcode />
|
||||
Печать
|
||||
</InlineButton>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResiduesHeader;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Group, Stack } from "@mantine/core";
|
||||
import ClientSelect from "../../../../components/Selects/ClientSelect/ClientSelect.tsx";
|
||||
import ResiduesHeader from "../ResiduesHeader/ResiduesHeader.tsx";
|
||||
import ResiduesTree from "../ResiduesTree/ResiduesTree.tsx";
|
||||
import { useResiduesContext } from "../../contexts/ResiduesContext.tsx";
|
||||
|
||||
const ResiduesPageContent = () => {
|
||||
const { selectedClient, selectClient } = useResiduesContext();
|
||||
|
||||
return (
|
||||
<Stack h="92vh">
|
||||
<Group>
|
||||
<ClientSelect onChange={selectClient} />
|
||||
{selectedClient && <ResiduesHeader />}
|
||||
</Group>
|
||||
{selectedClient && <ResiduesTree />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResiduesPageContent;
|
||||
135
src/pages/ResiduesPage/components/ResiduesTree/ResiduesTree.tsx
Normal file
135
src/pages/ResiduesPage/components/ResiduesTree/ResiduesTree.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import {
|
||||
Accordion,
|
||||
ActionIcon,
|
||||
Button,
|
||||
Center, Checkbox,
|
||||
Group,
|
||||
rem,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { IconPlus, IconSpace, IconTrash } from "@tabler/icons-react";
|
||||
import { useResiduesContext } from "../../contexts/ResiduesContext.tsx";
|
||||
import { ResidualBoxSchema, ResidualPalletSchema, ResidualProductSchema } from "../../../../client";
|
||||
import ResidualProductsTable from "../ResidualProductsTable/ResidualProductsTable.tsx";
|
||||
import ResidualBoxes from "../ResidualBoxes/ResidualBoxes.tsx";
|
||||
import residualBoxes from "../ResidualBoxes/ResidualBoxes.tsx";
|
||||
|
||||
const ResiduesTree = () => {
|
||||
const {
|
||||
selectedClient,
|
||||
onDeletePalletClick,
|
||||
onCreateBoxClick,
|
||||
palletIdsToPrint,
|
||||
} = useResiduesContext();
|
||||
const palletIds: string[] = [];
|
||||
|
||||
const sortById = (data?: ResidualPalletSchema[] | ResidualBoxSchema[] | ResidualProductSchema[]) => {
|
||||
return data?.sort((a, b) => a.id - b.id);
|
||||
};
|
||||
|
||||
const checkboxToPrint = (pallet: ResidualPalletSchema) => {
|
||||
return (
|
||||
<Checkbox
|
||||
ml="sm"
|
||||
checked={palletIdsToPrint.has(pallet.id)}
|
||||
onChange={() => {
|
||||
if (palletIdsToPrint.has(pallet.id)) {
|
||||
palletIdsToPrint.delete(pallet.id);
|
||||
} else {
|
||||
palletIdsToPrint.add(pallet.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getPallets = () => {
|
||||
const sortedPallets = sortById(selectedClient?.pallets) as ResidualPalletSchema[];
|
||||
return sortedPallets?.map((pallet => {
|
||||
palletIds.push(pallet.id.toString());
|
||||
return (
|
||||
<Accordion.Item key={pallet.id} value={pallet.id.toString()}>
|
||||
<Center>
|
||||
<Accordion.Control icon={<IconSpace />}>
|
||||
Паллет - П{pallet.id}
|
||||
</Accordion.Control>
|
||||
{checkboxToPrint(pallet)}
|
||||
{removePalletButton(pallet)}
|
||||
</Center>
|
||||
<Accordion.Panel>
|
||||
{getPalletContent(pallet)}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
);
|
||||
})) ?? [];
|
||||
};
|
||||
|
||||
const removePalletButton = (pallet: ResidualPalletSchema) => {
|
||||
return (
|
||||
<Tooltip label="Удалить паллет">
|
||||
<ActionIcon
|
||||
variant={"default"}
|
||||
onClick={() => onDeletePalletClick(pallet)}
|
||||
mx={"md"}
|
||||
>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const createBoxButton = (palletId: number) => {
|
||||
return (
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={() => onCreateBoxClick(palletId)}
|
||||
>
|
||||
<Group gap={"md"}>
|
||||
<IconPlus />
|
||||
Добавить короб
|
||||
</Group>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const getPalletContent = (pallet: ResidualPalletSchema) => {
|
||||
const isEmpty = pallet.boxes.length === 0 && pallet.residualProducts.length === 0;
|
||||
const isBox = pallet.residualProducts.length === 0;
|
||||
const title = isEmpty ? "Пустой" : isBox ? "Короба" : "Товары";
|
||||
|
||||
const residualProducts = sortById(pallet.residualProducts) as ResidualProductSchema[];
|
||||
|
||||
return (
|
||||
<Stack gap={rem(5)}>
|
||||
<Group justify={"space-between"}>
|
||||
<Stack gap={"xs"}>
|
||||
<Text>Дата добавления: {new Date(pallet.createdAt).toLocaleString("ru-RU")}</Text>
|
||||
<Title order={6}>{title}</Title>
|
||||
</Stack>
|
||||
{isBox && createBoxButton(pallet.id)}
|
||||
</Group>
|
||||
{residualProducts.length > 0 && <ResidualProductsTable items={residualProducts} />}
|
||||
{residualBoxes.length > 0 && <ResidualBoxes boxes={pallet.boxes} />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollArea>
|
||||
<Accordion
|
||||
multiple={true}
|
||||
defaultValue={palletIds}
|
||||
bd={"solid 1px gray"}
|
||||
>
|
||||
<ResidualBoxes boxes={selectedClient?.boxes} />
|
||||
{getPallets()}
|
||||
</Accordion>
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResiduesTree;
|
||||
176
src/pages/ResiduesPage/contexts/ResiduesContext.tsx
Normal file
176
src/pages/ResiduesPage/contexts/ResiduesContext.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
import { createContext, FC, useContext, useState } from "react";
|
||||
import {
|
||||
ClientDetailedSchema,
|
||||
ClientSchema,
|
||||
ClientService,
|
||||
ResidualBoxSchema,
|
||||
ResidualPalletSchema,
|
||||
ResiduesService,
|
||||
} from "../../../client";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { Text } from "@mantine/core";
|
||||
import { notifications } from "../../../shared/lib/notifications.ts";
|
||||
import { useSet } from "@mantine/hooks";
|
||||
|
||||
type ResiduesContextState = {
|
||||
selectedClient?: ClientDetailedSchema;
|
||||
selectClient: (client?: ClientSchema) => void;
|
||||
refetchClient: () => void;
|
||||
onDeletePalletClick: (pallet: ResidualPalletSchema) => void;
|
||||
onCreatePalletClick: () => void;
|
||||
onDeleteBoxClick: (box: ResidualBoxSchema) => void;
|
||||
onCreateBoxClick: (pallet?: number) => void;
|
||||
boxIdsToPrint: Set<number>;
|
||||
palletIdsToPrint: Set<number>;
|
||||
};
|
||||
|
||||
const ResiduesContext = createContext<ResiduesContextState | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const useResiduesContextState = () => {
|
||||
const [selectedClient, setSelectedClient] = useState<ClientDetailedSchema>();
|
||||
const boxIdsToPrint = useSet<number>();
|
||||
const palletIdsToPrint = useSet<number>();
|
||||
|
||||
const fetchClient = (clientId?: number) => {
|
||||
if (!clientId) {
|
||||
setSelectedClient(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
ClientService.getClient({
|
||||
clientId,
|
||||
}).then(res => {
|
||||
setSelectedClient(res.client);
|
||||
}).catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const selectClient = (client?: ClientSchema) => {
|
||||
fetchClient(client?.id);
|
||||
};
|
||||
|
||||
const refetchClient = () => {
|
||||
fetchClient(selectedClient?.id);
|
||||
};
|
||||
|
||||
const onDeletePallet = (palletId: number) => {
|
||||
ResiduesService.deleteResidualPallet({ palletId })
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
palletIdsToPrint.delete(palletId);
|
||||
refetchClient();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onDeletePalletClick = (pallet: ResidualPalletSchema) => {
|
||||
if (!selectedClient) return;
|
||||
if (pallet.boxes.length === 0 && pallet.residualProducts.length === 0) {
|
||||
onDeletePallet(pallet.id);
|
||||
return;
|
||||
}
|
||||
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление паллета",
|
||||
children: <Text size="sm">Вы уверены что хотите удалить паллет?</Text>,
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => onDeletePallet(pallet.id),
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteBox = (boxId: number) => {
|
||||
ResiduesService.deleteResidualBox({ boxId })
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
boxIdsToPrint.delete(boxId);
|
||||
refetchClient();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onDeleteBoxClick = (box: ResidualBoxSchema) => {
|
||||
if (!selectedClient) return;
|
||||
if (box.residualProducts.length === 0) {
|
||||
onDeleteBox(box.id);
|
||||
return;
|
||||
}
|
||||
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление короба",
|
||||
children: <Text size="sm">Вы уверены что хотите удалить короб?</Text>,
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => onDeleteBox(box.id),
|
||||
});
|
||||
};
|
||||
|
||||
const onCreatePalletClick = () => {
|
||||
if (!selectedClient) return;
|
||||
ResiduesService.createResidualPallet({
|
||||
requestBody: { clientId: selectedClient.id },
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) notifications.error({ message });
|
||||
refetchClient();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onCreateBoxClick = (palletId?: number) => {
|
||||
if (!selectedClient) return;
|
||||
ResiduesService.createResidualBox({
|
||||
requestBody: {
|
||||
palletId: palletId ?? null,
|
||||
clientId: palletId ? null : selectedClient.id,
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) notifications.error({ message });
|
||||
refetchClient();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
return {
|
||||
selectedClient,
|
||||
selectClient,
|
||||
refetchClient,
|
||||
onDeletePalletClick,
|
||||
onCreatePalletClick,
|
||||
onDeleteBoxClick,
|
||||
onCreateBoxClick,
|
||||
boxIdsToPrint,
|
||||
palletIdsToPrint,
|
||||
};
|
||||
};
|
||||
|
||||
type ResiduesContextProviderProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const ResiduesContextProvider: FC<ResiduesContextProviderProps> = ({ children }) => {
|
||||
const state = useResiduesContextState();
|
||||
return (
|
||||
<ResiduesContext.Provider value={state}>
|
||||
{children}
|
||||
</ResiduesContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useResiduesContext = () => {
|
||||
const context = useContext(ResiduesContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useResiduesContext must be used within a ResiduesContextProvider",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
32
src/pages/ResiduesPage/hooks/residuesTableColumns.tsx
Normal file
32
src/pages/ResiduesPage/hooks/residuesTableColumns.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef, MRT_RowData } from "mantine-react-table";
|
||||
|
||||
|
||||
const useResiduesTableColumns = <T extends MRT_RowData>() => {
|
||||
return useMemo<MRT_ColumnDef<T>[]>(
|
||||
() => [
|
||||
{
|
||||
header: "Название",
|
||||
accessorKey: "product.name",
|
||||
Cell: ({ row }) => row.original.product?.name ?? "-",
|
||||
},
|
||||
{
|
||||
header: "Артикул",
|
||||
accessorKey: "product.article",
|
||||
Cell: ({ row }) => row.original.product?.article ?? "-",
|
||||
},
|
||||
{
|
||||
header: "Размер",
|
||||
accessorKey: "product.size",
|
||||
Cell: ({ row }) => row.original.product?.size ?? "-",
|
||||
},
|
||||
{
|
||||
header: "Количество",
|
||||
accessorKey: "quantity",
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export default useResiduesTableColumns;
|
||||
31
src/pages/ResiduesPage/hooks/useResiduesPdf.tsx
Normal file
31
src/pages/ResiduesPage/hooks/useResiduesPdf.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useResiduesContext } from "../contexts/ResiduesContext.tsx";
|
||||
import { notifications } from "../../../shared/lib/notifications.ts";
|
||||
|
||||
const useResiduesPdf = () => {
|
||||
const { palletIdsToPrint, boxIdsToPrint } = useResiduesContext();
|
||||
|
||||
const basePdfUrl = `${import.meta.env.VITE_API_URL}/residues/pdf`;
|
||||
|
||||
const getPdf = (url: string) => {
|
||||
const pdfWindow = window.open(url);
|
||||
if (!pdfWindow) return;
|
||||
pdfWindow.print();
|
||||
};
|
||||
|
||||
const onGetPalletsPdfClick = () => {
|
||||
if (palletIdsToPrint.size === 0 && boxIdsToPrint.size === 0) {
|
||||
notifications.show({ message: "Не выбран ни один элемент для печати "});
|
||||
return;
|
||||
}
|
||||
|
||||
const palletIdsStr = palletIdsToPrint.values().toArray().join(",")
|
||||
const boxIdsStr = boxIdsToPrint.values().toArray().join(",")
|
||||
getPdf(`${basePdfUrl}/?pallet_ids=${palletIdsStr}&box_ids=${boxIdsStr}`);
|
||||
};
|
||||
|
||||
return {
|
||||
onGetPalletsPdfClick,
|
||||
};
|
||||
};
|
||||
|
||||
export default useResiduesPdf;
|
||||
@@ -0,0 +1,83 @@
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import { Button, Flex, NumberInput, rem } from "@mantine/core";
|
||||
import { ClientDetailedSchema, ProductSchema, ResiduesService, UpdateResidualProductSchema } from "../../../../client";
|
||||
import { ResidualModalForm, UpdateResidualProductData } from "../../types/ResidualProductData.tsx";
|
||||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||
import ProductSelect from "../../../../components/ProductSelect/ProductSelect.tsx";
|
||||
|
||||
|
||||
type Props = {
|
||||
updateOnSubmit: () => void;
|
||||
client: ClientDetailedSchema;
|
||||
residuesData: UpdateResidualProductData;
|
||||
}
|
||||
|
||||
const ResidualProductModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const initialValues: ResidualModalForm = {
|
||||
quantity: innerProps.residuesData.quantity ?? 0,
|
||||
product: innerProps.residuesData.product,
|
||||
};
|
||||
const form = useForm<ResidualModalForm>({
|
||||
initialValues,
|
||||
validate: {
|
||||
product: product => !product && "Необходимо выбрать товар",
|
||||
quantity: quantity => quantity === 0 && "Слишком мало товара",
|
||||
},
|
||||
});
|
||||
|
||||
const updateResidualProduct = () => {
|
||||
const data = {
|
||||
...form.values,
|
||||
productId: form.values.product!.id,
|
||||
} as UpdateResidualProductSchema;
|
||||
|
||||
ResiduesService.updateResidualProduct({
|
||||
requestBody: { data },
|
||||
residualProductId: innerProps.residuesData.residualProductId,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
notifications.guess(ok, { message: message });
|
||||
innerProps.updateOnSubmit();
|
||||
if (ok) context.closeContextModal(id);
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(() => updateResidualProduct())}>
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
>
|
||||
<ProductSelect
|
||||
label={"Товар"}
|
||||
placeholder={"Выберите товар"}
|
||||
{...form.getInputProps("product")}
|
||||
defaultValue={innerProps.residuesData.product as (ProductSchema & string)}
|
||||
clientId={innerProps.client.id}
|
||||
/>
|
||||
|
||||
<NumberInput
|
||||
label={"Количество"}
|
||||
hideControls
|
||||
{...form.getInputProps("quantity")}
|
||||
min={0}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant={"default"}
|
||||
type={"submit"}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResidualProductModal;
|
||||
12
src/pages/ResiduesPage/types/ResidualProductData.tsx
Normal file
12
src/pages/ResiduesPage/types/ResidualProductData.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ProductSchema } from "../../../client";
|
||||
|
||||
export type ResidualModalForm = {
|
||||
quantity: number;
|
||||
product: ProductSchema | null;
|
||||
}
|
||||
|
||||
export type UpdateResidualProductData = {
|
||||
product: ProductSchema | null;
|
||||
quantity: number;
|
||||
residualProductId: number;
|
||||
};
|
||||
19
src/pages/ResiduesPage/ui/ResiduesPage.tsx
Normal file
19
src/pages/ResiduesPage/ui/ResiduesPage.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import styles from "../../ProductsPage/ui/ProductsPage.module.css";
|
||||
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
|
||||
import { ResiduesContextProvider } from "../contexts/ResiduesContext.tsx";
|
||||
import ResiduesPageContent from "../components/ResiduesPageContent/ResiduesPageContent.tsx";
|
||||
|
||||
|
||||
const ResiduesPage = () => {
|
||||
return (
|
||||
<div className={styles["container"]}>
|
||||
<PageBlock fullHeight>
|
||||
<ResiduesContextProvider>
|
||||
<ResiduesPageContent />
|
||||
</ResiduesContextProvider>
|
||||
</PageBlock>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResiduesPage;
|
||||
Reference in New Issue
Block a user