refactor: removed extra folder from modules
This commit is contained in:
@ -0,0 +1,12 @@
|
||||
.container {
|
||||
padding: var(--mantine-spacing-sm);
|
||||
border: dashed 1px var(--mantine-color-default-border);
|
||||
border-radius: var(--mantine-radius-lg);
|
||||
}
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
max-height: rem(250);
|
||||
max-width: rem(250);
|
||||
height: 100%;
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import { FC } from "react";
|
||||
import { DealSchema } from "@/lib/client";
|
||||
import FulfillmentBaseTabBody from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/FulfillmentBaseTabBody/FulfillmentBaseTabBody";
|
||||
import { FulfillmentBaseContextProvider } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
type Props = {
|
||||
value: DealSchema;
|
||||
};
|
||||
|
||||
const FulfillmentBaseTab: FC<Props> = ({ value }) => {
|
||||
return (
|
||||
<FulfillmentBaseContextProvider deal={value}>
|
||||
<FulfillmentBaseTabBody />
|
||||
</FulfillmentBaseContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FulfillmentBaseTab;
|
||||
@ -0,0 +1,22 @@
|
||||
import { Flex, Stack } from "@mantine/core";
|
||||
import DealServicesTable from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/DealServicesTable";
|
||||
import ProductsActions from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/ProductsActions/ProductsActions";
|
||||
import TotalPriceLabel from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/TotalPriceLabel/TotalPriceLabel";
|
||||
import styles from "@/modules/dealModularEditorTabs/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;
|
||||
@ -0,0 +1,42 @@
|
||||
import { FC } from "react";
|
||||
import { Flex, ScrollArea } from "@mantine/core";
|
||||
import DealServicesTitle from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/DealServicesTitle";
|
||||
import DealServicesTotalLabel from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/DealServicesTotalLabel";
|
||||
import ServicesActions from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/components/DealServicesTable/components/ServicesActions";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
import DealServiceRow from "./components/DealServiceRow";
|
||||
|
||||
const DealServicesTable: FC = () => {
|
||||
const { dealServicesList, dealServicesCrud } = useFulfillmentBaseContext();
|
||||
|
||||
// const isLocked = isDealLocked(deal); // TODO bills
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={"sm"}
|
||||
h={"78vh"}>
|
||||
<DealServicesTitle />
|
||||
<ScrollArea
|
||||
flex={1}
|
||||
scrollbars={"y"}
|
||||
offsetScrollbars={"y"}>
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={"sm"}>
|
||||
{dealServicesList.dealServices.map(dealService => (
|
||||
<DealServiceRow
|
||||
key={dealService.service.id}
|
||||
value={dealService}
|
||||
onDelete={dealServicesCrud.onDelete}
|
||||
onChange={dealServicesCrud.onUpdate}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</ScrollArea>
|
||||
<DealServicesTotalLabel />
|
||||
<ServicesActions />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
export default DealServicesTable;
|
||||
@ -0,0 +1,88 @@
|
||||
import { FC } from "react";
|
||||
import { IconTrash } from "@tabler/icons-react";
|
||||
import { isNumber } from "lodash";
|
||||
import { Divider, Group, NumberInput, Stack, Text } from "@mantine/core";
|
||||
import { useDebouncedCallback } from "@mantine/hooks";
|
||||
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
||||
import { DealServiceSchema } from "@/lib/client";
|
||||
import LockCheckbox from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/LockCheckbox/LockCheckbox";
|
||||
|
||||
type Props = {
|
||||
value: DealServiceSchema;
|
||||
onChange: (
|
||||
dealId: number,
|
||||
serviceId: number,
|
||||
value: DealServiceSchema
|
||||
) => void;
|
||||
onDelete: (value: DealServiceSchema) => void;
|
||||
};
|
||||
|
||||
const DealServiceRow: FC<Props> = ({ value, onChange, onDelete }) => {
|
||||
const debouncedOnChange = useDebouncedCallback(
|
||||
async (item: DealServiceSchema) => {
|
||||
onChange(item.dealId, item.serviceId, item);
|
||||
},
|
||||
200
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
w={"100%"}
|
||||
gap={"xs"}>
|
||||
<Divider />
|
||||
<Text>{value.service.name}</Text>
|
||||
<Group>
|
||||
<ActionIconWithTip
|
||||
onClick={() => onDelete(value)}
|
||||
tipLabel={"Удалить услугу"}>
|
||||
<IconTrash />
|
||||
</ActionIconWithTip>
|
||||
<NumberInput
|
||||
flex={1}
|
||||
suffix={" шт."}
|
||||
onChange={quantity =>
|
||||
isNumber(quantity) &&
|
||||
debouncedOnChange({ ...value, quantity })
|
||||
}
|
||||
value={value.quantity}
|
||||
min={1}
|
||||
allowNegative={false}
|
||||
/>
|
||||
<NumberInput
|
||||
flex={1}
|
||||
onChange={price =>
|
||||
isNumber(price) &&
|
||||
debouncedOnChange({ ...value, price })
|
||||
}
|
||||
suffix={"₽"}
|
||||
value={value.price}
|
||||
disabled={value.isFixedPrice}
|
||||
min={1}
|
||||
allowNegative={false}
|
||||
rightSectionProps={{
|
||||
style: {
|
||||
display: "flex",
|
||||
cursor: "pointer",
|
||||
pointerEvents: "auto",
|
||||
},
|
||||
}}
|
||||
rightSection={
|
||||
<LockCheckbox
|
||||
label={"Зафиксировать цену"}
|
||||
variant={"default"}
|
||||
value={value.isFixedPrice}
|
||||
onChange={isFixedPrice =>
|
||||
debouncedOnChange({
|
||||
...value,
|
||||
isFixedPrice,
|
||||
})
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealServiceRow;
|
||||
@ -0,0 +1,11 @@
|
||||
import { Title } from "@mantine/core";
|
||||
|
||||
const DealServicesTitle = () => (
|
||||
<Title
|
||||
order={3}
|
||||
w={"100%"}>
|
||||
Общие услуги
|
||||
</Title>
|
||||
);
|
||||
|
||||
export default DealServicesTitle;
|
||||
@ -0,0 +1,26 @@
|
||||
import { useMemo } from "react";
|
||||
import { Title } from "@mantine/core";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
const DealServicesTotalLabel = () => {
|
||||
const { dealServicesList } = useFulfillmentBaseContext();
|
||||
const total = useMemo(
|
||||
() =>
|
||||
dealServicesList.dealServices.reduce(
|
||||
(acc, item) => acc + item.price * item.quantity,
|
||||
0
|
||||
),
|
||||
[dealServicesList.dealServices]
|
||||
);
|
||||
|
||||
return (
|
||||
<Title
|
||||
style={{ textAlign: "end" }}
|
||||
mt={"sm"}
|
||||
order={3}>
|
||||
Итог: {total.toFixed(2)}₽
|
||||
</Title>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealServicesTotalLabel;
|
||||
@ -0,0 +1,69 @@
|
||||
import { Button, Flex } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { addKitToDeal, ServicesKitSchema } from "@/lib/client";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
|
||||
|
||||
const ServicesActions = () => {
|
||||
const { dealServicesList, dealServicesCrud, deal } =
|
||||
useFulfillmentBaseContext();
|
||||
|
||||
const onCreateClick = () => {
|
||||
const serviceIdsToExclude = dealServicesList.dealServices.map(
|
||||
service => service.service.id
|
||||
);
|
||||
modals.openContextModal({
|
||||
modal: "dealServiceEditorModal",
|
||||
innerProps: {
|
||||
onCreate: values =>
|
||||
dealServicesCrud.onCreate({ ...values, dealId: deal.id }),
|
||||
serviceIdsToExclude,
|
||||
isEditing: false,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
const onServicesKitAdd = (servicesKit: ServicesKitSchema) => {
|
||||
addKitToDeal({
|
||||
body: {
|
||||
dealId: deal.id,
|
||||
kitId: servicesKit.id,
|
||||
},
|
||||
})
|
||||
.then(() => dealServicesList.refetch())
|
||||
.catch(err => console.error(err));
|
||||
};
|
||||
|
||||
const onAddKitClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "servicesKitSelectModal",
|
||||
innerProps: {
|
||||
onSelect: onServicesKitAdd,
|
||||
serviceType: ServiceType.DEAL_SERVICE,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
gap={"sm"}
|
||||
mt={"auto"}>
|
||||
<Button
|
||||
onClick={onCreateClick}
|
||||
fullWidth
|
||||
variant={"default"}>
|
||||
Добавить услугу
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onAddKitClick}
|
||||
fullWidth
|
||||
variant={"default"}>
|
||||
Добавить набор услуг
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicesActions;
|
||||
@ -0,0 +1,61 @@
|
||||
import { FC } from "react";
|
||||
import { Button, Flex } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
const ProductsActions: FC = () => {
|
||||
const { deal, dealProductsList, productsCrud, dealProductsCrud } =
|
||||
useFulfillmentBaseContext();
|
||||
|
||||
const onCreateProductClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "productEditorModal",
|
||||
title: "Создание товара",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate: productsCrud.onCreate,
|
||||
isEditing: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onCreateDealProductClick = () => {
|
||||
const productIdsToExclude = dealProductsList.dealProducts.map(
|
||||
product => product.product.id
|
||||
);
|
||||
|
||||
modals.openContextModal({
|
||||
modal: "dealProductEditorModal",
|
||||
title: "Добавление товара",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate: values =>
|
||||
dealProductsCrud.onCreate({ ...values, dealId: deal.id }),
|
||||
productIdsToExclude,
|
||||
isEditing: false,
|
||||
clientId: 0, // TODO add clients
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
w={"100%"}
|
||||
gap={"sm"}>
|
||||
<Button
|
||||
variant={"default"}
|
||||
fullWidth
|
||||
onClick={onCreateProductClick}>
|
||||
Создать товар
|
||||
</Button>
|
||||
<Button
|
||||
variant={"default"}
|
||||
fullWidth
|
||||
onClick={onCreateDealProductClick}>
|
||||
Добавить товар
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductsActions;
|
||||
@ -0,0 +1,40 @@
|
||||
import { useMemo } from "react";
|
||||
import { Flex, Title } from "@mantine/core";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/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;
|
||||
@ -0,0 +1,31 @@
|
||||
import { Flex, ScrollArea, Stack } from "@mantine/core";
|
||||
import DealInfoView from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/DealInfoView/DealInfoView";
|
||||
import ProductView from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductView/ProductView";
|
||||
import { useFulfillmentBaseContext } from "../../contexts/FulfillmentBaseContext";
|
||||
|
||||
const FulfillmentBaseTabBody = () => {
|
||||
const { dealProductsList } = useFulfillmentBaseContext();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
mah={"96vh"}
|
||||
mx={"md"}
|
||||
gap={"xs"}>
|
||||
<ScrollArea
|
||||
offsetScrollbars={"y"}
|
||||
flex={4}>
|
||||
<Stack>
|
||||
{dealProductsList.dealProducts.map((dealProduct, index) => (
|
||||
<ProductView
|
||||
dealProduct={dealProduct}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
<DealInfoView />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default FulfillmentBaseTabBody;
|
||||
@ -0,0 +1,27 @@
|
||||
import { FC } from "react";
|
||||
import { IconLock, IconLockOpen } from "@tabler/icons-react";
|
||||
import { CheckboxProps } from "@mantine/core";
|
||||
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
||||
|
||||
type RestProps = {
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
};
|
||||
|
||||
type Props = Omit<CheckboxProps, "value" | "onChange"> & RestProps;
|
||||
|
||||
const LockCheckbox: FC<Props> = props => {
|
||||
const getIcon = () => (props.value ? <IconLock /> : <IconLockOpen />);
|
||||
|
||||
const handleChange = () => props.onChange(!props.value);
|
||||
|
||||
return (
|
||||
<ActionIconWithTip
|
||||
onClick={handleChange}
|
||||
variant={props.variant}>
|
||||
{getIcon()}
|
||||
</ActionIconWithTip>
|
||||
);
|
||||
};
|
||||
|
||||
export default LockCheckbox;
|
||||
@ -0,0 +1,68 @@
|
||||
import { FC } from "react";
|
||||
import { DropzoneProps, FileWithPath } from "@mantine/dropzone";
|
||||
import ImageDropzone from "@/components/ui/ImageDropzone/ImageDropzone";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
import BaseFormInputProps from "@/utils/baseFormInputProps";
|
||||
import useImageDropzone from "./useImageDropzone";
|
||||
|
||||
interface RestProps {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
productId?: number;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
|
||||
const ProductImageDropzone: FC<Props> = ({
|
||||
imageUrlInputProps,
|
||||
productId,
|
||||
}: Props) => {
|
||||
const imageDropzoneProps = useImageDropzone({
|
||||
imageUrlInputProps,
|
||||
});
|
||||
|
||||
const onDrop = (files: FileWithPath[]) => {
|
||||
if (!productId || !imageUrlInputProps) return;
|
||||
if (files.length > 1) {
|
||||
notifications.error({ message: "Прикрепите одно изображение" });
|
||||
return;
|
||||
}
|
||||
const { setIsLoading, setShowDropzone } = imageDropzoneProps;
|
||||
const file = files[0];
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
// TODO SEND REQUEST
|
||||
|
||||
// ProductService.uploadProductImage({
|
||||
// productId,
|
||||
// formData: {
|
||||
// upload_file: file,
|
||||
// },
|
||||
// })
|
||||
// .then(({ ok, message, imageUrl }) => {
|
||||
// notifications.guess(ok, { message });
|
||||
// setIsLoading(false);
|
||||
//
|
||||
// if (!ok || !imageUrl) {
|
||||
// setShowDropzone(true);
|
||||
// return;
|
||||
// }
|
||||
// imageUrlInputProps?.onChange(imageUrl);
|
||||
// setShowDropzone(false);
|
||||
// })
|
||||
// .catch(error => {
|
||||
// notifications.error({ message: error.toString() });
|
||||
// setShowDropzone(true);
|
||||
// setIsLoading(false);
|
||||
// });
|
||||
};
|
||||
|
||||
return (
|
||||
<ImageDropzone
|
||||
onDrop={onDrop}
|
||||
imageDropzone={imageDropzoneProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductImageDropzone;
|
||||
@ -0,0 +1,27 @@
|
||||
import { useState } from "react";
|
||||
import BaseFormInputProps from "@/utils/baseFormInputProps";
|
||||
|
||||
type Props = {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
};
|
||||
|
||||
const useImageDropzone = ({ imageUrlInputProps }: Props) => {
|
||||
const [showDropzone, setShowDropzone] = useState(
|
||||
!(
|
||||
typeof imageUrlInputProps?.value === "string" &&
|
||||
imageUrlInputProps.value.trim() !== ""
|
||||
)
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
return {
|
||||
showDropzone,
|
||||
setShowDropzone,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
imageUrlInputProps,
|
||||
};
|
||||
};
|
||||
|
||||
export default useImageDropzone;
|
||||
@ -0,0 +1,55 @@
|
||||
import { FC, useState } from "react";
|
||||
import { omit } from "lodash";
|
||||
import { Loader, OptionsFilter } from "@mantine/core";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import ObjectSelect, {
|
||||
ObjectSelectProps,
|
||||
} from "@/components/selects/ObjectSelect/ObjectSelect";
|
||||
import { ProductSchema } from "@/lib/client";
|
||||
import useProductsList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/lists/useProductsList";
|
||||
import renderProductOption from "./utils/renderProductOption";
|
||||
|
||||
type RestProps = {
|
||||
clientId: number;
|
||||
};
|
||||
|
||||
const MAX_PRODUCTS = 200;
|
||||
|
||||
type Props = Omit<ObjectSelectProps<ProductSchema>, "data"> & RestProps;
|
||||
|
||||
const ProductSelect: FC<Props> = (props: Props) => {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [debounced] = useDebouncedValue(searchValue, 500);
|
||||
const { products, isLoading } = useProductsList({
|
||||
// clientId: props.clientId,
|
||||
searchInput: debounced,
|
||||
page: 0,
|
||||
itemsPerPage: MAX_PRODUCTS,
|
||||
});
|
||||
const restProps = omit(props, ["clientId"]);
|
||||
|
||||
const optionsFilter: OptionsFilter = ({ options }) => options;
|
||||
const setSearchValueImpl = (value: string) => {
|
||||
const names = products.map(product => product.name);
|
||||
if (names.includes(value)) return;
|
||||
setSearchValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<ObjectSelect
|
||||
rightSection={
|
||||
isLoading || searchValue !== debounced ? (
|
||||
<Loader size={"sm"} />
|
||||
) : null
|
||||
}
|
||||
onSearchChange={setSearchValueImpl}
|
||||
renderOption={renderProductOption(products)}
|
||||
searchable
|
||||
{...restProps}
|
||||
data={products}
|
||||
filter={optionsFilter}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductSelect;
|
||||
@ -0,0 +1,51 @@
|
||||
import {
|
||||
ComboboxItem,
|
||||
ComboboxLikeRenderOptionInput,
|
||||
SelectProps,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { ProductSchema } from "@/lib/client";
|
||||
import ProductFieldsList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductView/components/ProductFieldsList";
|
||||
|
||||
const renderProductOption = (
|
||||
products: ProductSchema[]
|
||||
): SelectProps["renderOption"] => {
|
||||
return (item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
||||
const product = products.find(
|
||||
product => product.id === Number(item.option.value)
|
||||
);
|
||||
if (!product) return item.option.label;
|
||||
// const imageUrl =
|
||||
// product.images && product.images[0]
|
||||
// ? product.images[0].imageUrl
|
||||
// : undefined;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
multiline
|
||||
label={
|
||||
<>
|
||||
<ProductFieldsList product={product} />
|
||||
{/*{imageUrl && (*/}
|
||||
{/* <Image*/}
|
||||
{/* src={imageUrl}*/}
|
||||
{/* alt={product.name}*/}
|
||||
{/* maw={rem(250)}*/}
|
||||
{/* />*/}
|
||||
{/*)}*/}
|
||||
</>
|
||||
}>
|
||||
<div>
|
||||
{product.name}
|
||||
<br />
|
||||
{/*{product.barcodes && (*/}
|
||||
{/* <Text size={"xs"}>{product.barcodes[0]}</Text>*/}
|
||||
{/*)}*/}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default renderProductOption;
|
||||
@ -0,0 +1,156 @@
|
||||
import { FC } from "react";
|
||||
import { isNumber } from "lodash";
|
||||
import {
|
||||
Flex,
|
||||
Image,
|
||||
NumberInput,
|
||||
Stack,
|
||||
Textarea,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useDebouncedCallback } from "@mantine/hooks";
|
||||
import { modals } from "@mantine/modals";
|
||||
import {
|
||||
addKitToDealProduct,
|
||||
DealProductSchema,
|
||||
duplicateProductServices,
|
||||
ServicesKitSchema,
|
||||
} from "@/lib/client";
|
||||
import ProductFieldsList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductView/components/ProductFieldsList";
|
||||
import ProductViewActions from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductView/components/ProductViewActions";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
|
||||
import ProductServicesTable from "./components/ProductServicesTable";
|
||||
import styles from "../../FulfillmentBaseTab.module.css";
|
||||
|
||||
type Props = {
|
||||
dealProduct: DealProductSchema;
|
||||
};
|
||||
|
||||
const ProductView: FC<Props> = ({ dealProduct }) => {
|
||||
const { dealProductsCrud, deal, dealProductsList } =
|
||||
useFulfillmentBaseContext();
|
||||
|
||||
const debouncedOnChange = useDebouncedCallback(
|
||||
(newValues: Partial<DealProductSchema>) => {
|
||||
dealProductsCrud.onUpdate(
|
||||
dealProduct.dealId,
|
||||
dealProduct.productId,
|
||||
{
|
||||
...dealProduct,
|
||||
...newValues,
|
||||
}
|
||||
);
|
||||
},
|
||||
200
|
||||
);
|
||||
|
||||
const duplicateServices = (
|
||||
sourceDealProduct: DealProductSchema,
|
||||
targetDealProducts: DealProductSchema[]
|
||||
) => {
|
||||
duplicateProductServices({
|
||||
body: {
|
||||
dealId: deal.id,
|
||||
sourceDealProductId: sourceDealProduct.productId,
|
||||
targetDealProductIds: targetDealProducts.map(p => p.productId),
|
||||
},
|
||||
})
|
||||
.then(() => dealProductsList.refetch())
|
||||
.catch(err => console.error(err));
|
||||
};
|
||||
|
||||
const onDuplicateServices = (sourceDealProduct: DealProductSchema) => {
|
||||
modals.openContextModal({
|
||||
modal: "duplicateServicesModal",
|
||||
title: "Дублирование услуг",
|
||||
size: "lg",
|
||||
innerProps: {
|
||||
dealProducts: dealProductsList.dealProducts,
|
||||
sourceDealProduct,
|
||||
duplicateServices,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
const onServicesKitAdd = (servicesKit: ServicesKitSchema) => {
|
||||
addKitToDealProduct({
|
||||
body: {
|
||||
dealId: dealProduct.dealId,
|
||||
productId: dealProduct.productId,
|
||||
kitId: servicesKit.id,
|
||||
},
|
||||
})
|
||||
.then(() => dealProductsList.refetch())
|
||||
.catch(err => console.error(err));
|
||||
};
|
||||
|
||||
const onAddKitClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "servicesKitSelectModal",
|
||||
innerProps: {
|
||||
onSelect: onServicesKitAdd,
|
||||
serviceType: ServiceType.PRODUCT_SERVICE,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
align={"start"}
|
||||
direction={"row"}
|
||||
className={styles.container}
|
||||
gap={"sm"}>
|
||||
<Stack
|
||||
flex={2}
|
||||
gap={"sm"}>
|
||||
{!dealProduct.product && (
|
||||
<Image
|
||||
flex={1}
|
||||
radius={"md"}
|
||||
fit={"cover"}
|
||||
// src={dealProduct.product.imageUrl}
|
||||
/>
|
||||
)}
|
||||
<Title order={3}>{dealProduct.product.name}</Title>
|
||||
<ProductFieldsList product={dealProduct.product} />
|
||||
{/*<Text>*/}
|
||||
{/* Штрихкоды:*/}
|
||||
{/*{value.product.barcodes.join(", ")}*/}
|
||||
{/*</Text>*/}
|
||||
<NumberInput
|
||||
suffix={" шт."}
|
||||
value={dealProduct.quantity}
|
||||
onChange={quantity =>
|
||||
isNumber(quantity) && debouncedOnChange({ quantity })
|
||||
}
|
||||
placeholder={"Введите количество товара"}
|
||||
min={1}
|
||||
allowNegative={false}
|
||||
/>
|
||||
<Textarea
|
||||
defaultValue={dealProduct.comment}
|
||||
onChange={event =>
|
||||
debouncedOnChange({
|
||||
comment: event.currentTarget.value,
|
||||
})
|
||||
}
|
||||
rows={4}
|
||||
placeholder={"Комментарий"}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack flex={5}>
|
||||
<ProductServicesTable
|
||||
dealProduct={dealProduct}
|
||||
onDuplicateServices={() => onDuplicateServices(dealProduct)}
|
||||
onKitAdd={onAddKitClick}
|
||||
/>
|
||||
<ProductViewActions dealProduct={dealProduct} />
|
||||
</Stack>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductView;
|
||||
@ -0,0 +1,37 @@
|
||||
import { FC } from "react";
|
||||
import { isNil } from "lodash";
|
||||
import { Text } from "@mantine/core";
|
||||
import { ProductSchema } from "@/lib/client";
|
||||
|
||||
type ProductFieldNames = {
|
||||
[K in keyof ProductSchema]: string;
|
||||
};
|
||||
|
||||
export const ProductFieldNames: Partial<ProductFieldNames> = {
|
||||
color: "Цвет",
|
||||
article: "Артикул",
|
||||
size: "Размер",
|
||||
brand: "Бренд",
|
||||
composition: "Состав",
|
||||
additionalInfo: "Доп. информация",
|
||||
};
|
||||
|
||||
type Props = {
|
||||
product: ProductSchema;
|
||||
};
|
||||
|
||||
const ProductFieldsList: FC<Props> = ({ product }) => {
|
||||
const fieldList = Object.entries(product).map(([key, value]) => {
|
||||
const fieldName = ProductFieldNames[key as keyof ProductSchema];
|
||||
if (!fieldName || isNil(value) || value === "") return null;
|
||||
return (
|
||||
<Text key={fieldName}>
|
||||
{fieldName}: {value.toString()}{" "}
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
return <>{fieldList}</>;
|
||||
};
|
||||
|
||||
export default ProductFieldsList;
|
||||
@ -0,0 +1,127 @@
|
||||
import { FC } from "react";
|
||||
import { IconCopy, IconMoodSad } from "@tabler/icons-react";
|
||||
import { Button, Flex, Group, Stack, Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
||||
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||
import { DealProductSchema, ProductServiceSchema } from "@/lib/client";
|
||||
import useProductServicesTableColumns from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductView/hooks/useProductServicesTableColumns";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
type Props = {
|
||||
dealProduct: DealProductSchema;
|
||||
onDuplicateServices?: () => void;
|
||||
onKitAdd?: () => void;
|
||||
};
|
||||
|
||||
const ProductServicesTable: FC<Props> = ({
|
||||
dealProduct,
|
||||
onDuplicateServices,
|
||||
onKitAdd,
|
||||
}) => {
|
||||
const { productServiceCrud, dealProductsList } =
|
||||
useFulfillmentBaseContext();
|
||||
|
||||
const onChange = (item: ProductServiceSchema) => {
|
||||
const excludeServiceIds = dealProduct.productServices.map(
|
||||
productService => productService.service.id
|
||||
);
|
||||
const totalQuantity = dealProductsList.dealProducts.reduce(
|
||||
(sum, prod) => prod.quantity + sum,
|
||||
0
|
||||
);
|
||||
|
||||
modals.openContextModal({
|
||||
modal: "productServiceEditorModal",
|
||||
innerProps: {
|
||||
entity: item,
|
||||
onChange: values =>
|
||||
productServiceCrud.onUpdate(
|
||||
item.dealId,
|
||||
item.productId,
|
||||
item.serviceId,
|
||||
values
|
||||
),
|
||||
excludeServiceIds,
|
||||
quantity: totalQuantity,
|
||||
isEditing: true,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
const columns = useProductServicesTableColumns({
|
||||
data: dealProduct.productServices,
|
||||
quantity: dealProduct.quantity,
|
||||
onDelete: productServiceCrud.onDelete,
|
||||
onChange,
|
||||
});
|
||||
|
||||
const onCreateClick = () => {
|
||||
const excludeServiceIds = dealProduct.productServices.map(
|
||||
productService => productService.service.id
|
||||
);
|
||||
|
||||
modals.openContextModal({
|
||||
modal: "productServiceEditorModal",
|
||||
innerProps: {
|
||||
onCreate: values =>
|
||||
productServiceCrud.onCreate({ ...dealProduct, ...values }),
|
||||
excludeServiceIds,
|
||||
quantity: dealProduct.quantity,
|
||||
isEditing: false,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
const isEmptyTable = dealProduct.productServices.length === 0;
|
||||
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
<BaseTable
|
||||
records={dealProduct.productServices}
|
||||
columns={columns}
|
||||
groups={undefined}
|
||||
idAccessor={"serviceId"}
|
||||
withTableBorder
|
||||
style={{
|
||||
height: isEmptyTable ? "8rem" : "auto",
|
||||
}}
|
||||
emptyState={
|
||||
<Group
|
||||
gap={"xs"}
|
||||
mt={isEmptyTable ? "xl" : 0}>
|
||||
<Text>Нет услуг</Text>
|
||||
<IconMoodSad />
|
||||
</Group>
|
||||
}
|
||||
/>
|
||||
<Flex
|
||||
justify={"flex-end"}
|
||||
gap={"xs"}
|
||||
pt={"xs"}>
|
||||
{onDuplicateServices && (
|
||||
<ActionIconWithTip
|
||||
tipLabel={"Продублировать услуги"}
|
||||
onClick={onDuplicateServices}>
|
||||
<IconCopy />
|
||||
</ActionIconWithTip>
|
||||
)}
|
||||
{onKitAdd && (
|
||||
<Button
|
||||
onClick={onKitAdd}
|
||||
variant={"default"}>
|
||||
Добавить набор услуг
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={onCreateClick}
|
||||
variant={"default"}>
|
||||
Добавить услугу
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default ProductServicesTable;
|
||||
@ -0,0 +1,61 @@
|
||||
import { FC } from "react";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { Flex } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
||||
import { DealProductSchema } from "@/lib/client";
|
||||
import { useFulfillmentBaseContext } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/contexts/FulfillmentBaseContext";
|
||||
|
||||
type Props = {
|
||||
dealProduct: DealProductSchema;
|
||||
};
|
||||
|
||||
const ProductViewActions: FC<Props> = ({ dealProduct }) => {
|
||||
const { dealProductsCrud, dealProductsList, productsCrud } =
|
||||
useFulfillmentBaseContext();
|
||||
|
||||
const onProductEditClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "productEditorModal",
|
||||
title: "Редактирование товара",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onChange: values =>
|
||||
productsCrud.onUpdate(
|
||||
dealProduct.productId,
|
||||
values,
|
||||
dealProductsList.refetch
|
||||
),
|
||||
entity: dealProduct.product,
|
||||
isEditing: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
mt={"auto"}
|
||||
ml={"auto"}
|
||||
gap={"sm"}>
|
||||
{/*<Tooltip*/}
|
||||
{/* onClick={onPrintBarcodeClick}*/}
|
||||
{/* label="Печать штрихкода">*/}
|
||||
{/* <ActionIcon variant={"default"}>*/}
|
||||
{/* <IconBarcode />*/}
|
||||
{/* </ActionIcon>*/}
|
||||
{/*</Tooltip>*/}
|
||||
<ActionIconWithTip
|
||||
onClick={onProductEditClick}
|
||||
tipLabel="Редактировать товар">
|
||||
<IconEdit />
|
||||
</ActionIconWithTip>
|
||||
<ActionIconWithTip
|
||||
onClick={() => dealProductsCrud.onDelete(dealProduct)}
|
||||
tipLabel="Удалить товар">
|
||||
<IconTrash />
|
||||
</ActionIconWithTip>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductViewActions;
|
||||
@ -0,0 +1,72 @@
|
||||
import { useMemo } from "react";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { DataTableColumn } from "mantine-datatable";
|
||||
import { Box, Flex, Text } from "@mantine/core";
|
||||
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
|
||||
import { ProductServiceSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
data: ProductServiceSchema[];
|
||||
quantity: number;
|
||||
onChange: (dealProductService: ProductServiceSchema) => void;
|
||||
onDelete: (dealProductService: ProductServiceSchema) => void;
|
||||
};
|
||||
|
||||
const useProductServicesTableColumns = ({
|
||||
data,
|
||||
quantity,
|
||||
onChange,
|
||||
onDelete,
|
||||
}: Props) => {
|
||||
const totalPrice = useMemo(
|
||||
() => data.reduce((acc, row) => acc + row.price * quantity, 0),
|
||||
[data, quantity]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
accessor: "actions",
|
||||
title: "Действия",
|
||||
textAlign: "center",
|
||||
width: "0%",
|
||||
render: dealProductService => (
|
||||
<Flex
|
||||
gap="md"
|
||||
px={"sm"}
|
||||
my={"sm"}>
|
||||
<ActionIconWithTip
|
||||
tipLabel={"Удалить услугу"}
|
||||
onClick={() => onDelete(dealProductService)}>
|
||||
<IconTrash />
|
||||
</ActionIconWithTip>
|
||||
<ActionIconWithTip
|
||||
tipLabel="Редактировать"
|
||||
onClick={() => onChange(dealProductService)}>
|
||||
<IconEdit />
|
||||
</ActionIconWithTip>
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessor: "service.name",
|
||||
title: "Услуга",
|
||||
},
|
||||
{
|
||||
accessor: "price",
|
||||
title: "Цена",
|
||||
footer: data.length > 0 && (
|
||||
<Box my={"sm"}>
|
||||
<Text fw={700}>
|
||||
Итог: {totalPrice.toLocaleString("ru")}₽
|
||||
</Text>
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
] as DataTableColumn<ProductServiceSchema>[],
|
||||
[totalPrice]
|
||||
);
|
||||
};
|
||||
|
||||
export default useProductServicesTableColumns;
|
||||
@ -0,0 +1,54 @@
|
||||
import { FC } from "react";
|
||||
import { omit } from "lodash";
|
||||
import {
|
||||
ComboboxItem,
|
||||
ComboboxParsedItemGroup,
|
||||
OptionsFilter,
|
||||
} from "@mantine/core";
|
||||
import ObjectSelect, {
|
||||
ObjectSelectProps,
|
||||
} from "@/components/selects/ObjectSelect/ObjectSelect";
|
||||
import { ServiceSchema } from "@/lib/client";
|
||||
import useServicesList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/lists/useServicesList";
|
||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
|
||||
|
||||
type RestProps = {
|
||||
filterType?: ServiceType;
|
||||
};
|
||||
|
||||
type Props = Omit<ObjectSelectProps<ServiceSchema>, "data"> & RestProps;
|
||||
|
||||
const ServiceSelect: FC<Props> = props => {
|
||||
const { services } = useServicesList();
|
||||
const data =
|
||||
props.filterType !== undefined
|
||||
? services.filter(
|
||||
service => service.serviceType === props.filterType
|
||||
)
|
||||
: services;
|
||||
|
||||
const restProps = omit(props, ["filterType"]);
|
||||
|
||||
const optionsFilter: OptionsFilter = ({ options, search }) => {
|
||||
return (options as ComboboxParsedItemGroup[]).map(option => {
|
||||
return {
|
||||
...option,
|
||||
items: option.items.filter((item: ComboboxItem) =>
|
||||
item.label.toLowerCase().includes(search.toLowerCase())
|
||||
),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ObjectSelect
|
||||
{...restProps}
|
||||
data={data}
|
||||
searchable
|
||||
groupBy={service => service.category.name}
|
||||
filter={optionsFilter}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceSelect;
|
||||
@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { DealSchema } from "@/lib/client";
|
||||
import { getProductsQueryKey } from "@/lib/client/@tanstack/react-query.gen";
|
||||
import makeContext from "@/lib/contextFactory/contextFactory";
|
||||
import useDealServicesCrud, {
|
||||
DealServicesCrud,
|
||||
} from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/cruds/useDealServiceCrud";
|
||||
import useProductServiceCrud, {
|
||||
DealProductServicesCrud,
|
||||
} from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/cruds/useProductServiceCrud";
|
||||
import useDealServicesList, {
|
||||
DealServicesList,
|
||||
} from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/lists/useDealServicesList";
|
||||
import useDealProductCrud, {
|
||||
DealProductsCrud,
|
||||
} from "../hooks/cruds/useDealProductCrud";
|
||||
import { ProductsCrud, useProductsCrud } from "../hooks/cruds/useProductsCrud";
|
||||
import useDealProductsList, {
|
||||
DealProductsList,
|
||||
} from "../hooks/lists/useDealProductsList";
|
||||
|
||||
type FulfillmentBaseContextState = {
|
||||
deal: DealSchema;
|
||||
productsCrud: ProductsCrud;
|
||||
dealProductsList: DealProductsList;
|
||||
dealProductsCrud: DealProductsCrud;
|
||||
dealServicesList: DealServicesList;
|
||||
dealServicesCrud: DealServicesCrud;
|
||||
productServiceCrud: DealProductServicesCrud;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
deal: DealSchema;
|
||||
};
|
||||
|
||||
const useFulfillmentBaseContextState = ({
|
||||
deal,
|
||||
}: Props): FulfillmentBaseContextState => {
|
||||
const productQueryKey = getProductsQueryKey();
|
||||
const productsCrud = useProductsCrud({ queryKey: productQueryKey });
|
||||
|
||||
const dealProductsList = useDealProductsList({ dealId: deal.id });
|
||||
const dealProductsCrud = useDealProductCrud({ dealId: deal.id });
|
||||
|
||||
const dealServicesList = useDealServicesList({ dealId: deal.id });
|
||||
const dealServicesCrud = useDealServicesCrud({ dealId: deal.id });
|
||||
|
||||
const productServiceCrud = useProductServiceCrud({
|
||||
refetchDealProducts: dealProductsList.refetch,
|
||||
});
|
||||
|
||||
return {
|
||||
deal,
|
||||
productsCrud,
|
||||
dealProductsList,
|
||||
dealProductsCrud,
|
||||
dealServicesList,
|
||||
dealServicesCrud,
|
||||
productServiceCrud,
|
||||
};
|
||||
};
|
||||
|
||||
export const [FulfillmentBaseContextProvider, useFulfillmentBaseContext] =
|
||||
makeContext<FulfillmentBaseContextState, Props>(
|
||||
useFulfillmentBaseContextState,
|
||||
"FulfillmentBase"
|
||||
);
|
||||
@ -0,0 +1,153 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import getCommonQueryClient from "@/hooks/cruds/baseCrud/getCommonQueryClient";
|
||||
import {
|
||||
CreateDealProductSchema,
|
||||
DealProductSchema,
|
||||
UpdateDealProductSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createDealProductMutation,
|
||||
deleteDealProductMutation,
|
||||
getDealProductsQueryKey,
|
||||
updateDealProductMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type Props = {
|
||||
dealId: number;
|
||||
};
|
||||
|
||||
export type DealProductsCrud = {
|
||||
onCreate: (data: CreateDealProductSchema) => void;
|
||||
onUpdate: (
|
||||
dealId: number,
|
||||
serviceId: number,
|
||||
data: UpdateDealProductSchema
|
||||
) => void;
|
||||
onDelete: (data: DealProductSchema, onSuccess?: () => void) => void;
|
||||
};
|
||||
|
||||
const useDealProductCrud = ({ dealId }: Props): DealProductsCrud => {
|
||||
const queryKey = getDealProductsQueryKey({ path: { dealId } });
|
||||
const key = "getDealProducts";
|
||||
const { queryClient, onError, onSettled } = getCommonQueryClient({
|
||||
queryKey,
|
||||
key,
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
...createDealProductMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
...updateDealProductMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
onMutate: async ({
|
||||
body: { entity: update },
|
||||
path: { dealId, productId },
|
||||
}) => {
|
||||
await queryClient.cancelQueries({ queryKey: [key] });
|
||||
|
||||
const previous = queryClient.getQueryData(queryKey);
|
||||
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealProductSchema[] }) => {
|
||||
const updated: DealProductSchema[] = old.items.map(
|
||||
(entity: DealProductSchema) =>
|
||||
entity.dealId === dealId &&
|
||||
entity.productId === productId
|
||||
? { ...entity, ...update }
|
||||
: entity
|
||||
);
|
||||
|
||||
return {
|
||||
...old,
|
||||
items: updated,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
...deleteDealProductMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
onMutate: async ({ path: { dealId, productId } }) => {
|
||||
await queryClient.cancelQueries({ queryKey: [key] });
|
||||
|
||||
const previous = queryClient.getQueryData(queryKey);
|
||||
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealProductSchema[] }) => {
|
||||
const filtered = old.items.filter(
|
||||
e => e.dealId !== dealId && e.productId !== productId
|
||||
);
|
||||
return {
|
||||
...old,
|
||||
items: filtered,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
});
|
||||
|
||||
const onCreate = (entity: CreateDealProductSchema) => {
|
||||
createMutation.mutate({
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdate = (
|
||||
dealId: number,
|
||||
productId: number,
|
||||
entity: UpdateDealProductSchema
|
||||
) => {
|
||||
updateMutation.mutate({
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
path: {
|
||||
dealId,
|
||||
productId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = (entity: DealProductSchema, onSuccess?: () => void) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление товара из сделки",
|
||||
children: (
|
||||
<Text>
|
||||
Вы уверены, что хотите удалить "{entity.product.name}" из
|
||||
сделки?
|
||||
</Text>
|
||||
),
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => {
|
||||
deleteMutation.mutate({ path: entity }, { onSuccess });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onCreate,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDealProductCrud;
|
||||
@ -0,0 +1,153 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { Text } from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import getCommonQueryClient from "@/hooks/cruds/baseCrud/getCommonQueryClient";
|
||||
import {
|
||||
CreateDealServiceSchema,
|
||||
DealServiceSchema,
|
||||
UpdateDealServiceSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createDealServiceMutation,
|
||||
deleteDealServiceMutation,
|
||||
getDealServicesQueryKey,
|
||||
updateDealServiceMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type Props = {
|
||||
dealId: number;
|
||||
};
|
||||
|
||||
export type DealServicesCrud = {
|
||||
onCreate: (data: CreateDealServiceSchema) => void;
|
||||
onUpdate: (
|
||||
dealId: number,
|
||||
serviceId: number,
|
||||
data: UpdateDealServiceSchema
|
||||
) => void;
|
||||
onDelete: (data: DealServiceSchema, onSuccess?: () => void) => void;
|
||||
};
|
||||
|
||||
const useDealServiceCrud = ({ dealId }: Props): DealServicesCrud => {
|
||||
const queryKey = getDealServicesQueryKey({ path: { dealId } });
|
||||
const key = "getDealServices";
|
||||
const { queryClient, onError, onSettled } = getCommonQueryClient({
|
||||
queryKey,
|
||||
key,
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
...createDealServiceMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
...updateDealServiceMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
onMutate: async ({
|
||||
body: { entity: update },
|
||||
path: { dealId, serviceId },
|
||||
}) => {
|
||||
await queryClient.cancelQueries({ queryKey: [key] });
|
||||
|
||||
const previous = queryClient.getQueryData(queryKey);
|
||||
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealServiceSchema[] }) => {
|
||||
const updated: DealServiceSchema[] = old.items.map(
|
||||
(entity: DealServiceSchema) =>
|
||||
entity.dealId === dealId &&
|
||||
entity.serviceId === serviceId
|
||||
? { ...entity, ...update }
|
||||
: entity
|
||||
);
|
||||
|
||||
return {
|
||||
...old,
|
||||
items: updated,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
...deleteDealServiceMutation(),
|
||||
onError,
|
||||
onSettled,
|
||||
onMutate: async ({ path: { dealId, serviceId } }) => {
|
||||
await queryClient.cancelQueries({ queryKey: [key] });
|
||||
|
||||
const previous = queryClient.getQueryData(queryKey);
|
||||
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealServiceSchema[] }) => {
|
||||
const filtered = old.items.filter(
|
||||
e => e.dealId !== dealId && e.serviceId !== serviceId
|
||||
);
|
||||
return {
|
||||
...old,
|
||||
items: filtered,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
});
|
||||
|
||||
const onCreate = (entity: CreateDealServiceSchema) => {
|
||||
createMutation.mutate({
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdate = (
|
||||
dealId: number,
|
||||
serviceId: number,
|
||||
entity: UpdateDealServiceSchema
|
||||
) => {
|
||||
updateMutation.mutate({
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
path: {
|
||||
dealId,
|
||||
serviceId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = (entity: DealServiceSchema, onSuccess?: () => void) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление услуги из сделки",
|
||||
children: (
|
||||
<Text>
|
||||
Вы уверены, что хотите удалить "{entity.service.name}" из
|
||||
сделки?
|
||||
</Text>
|
||||
),
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => {
|
||||
deleteMutation.mutate({ path: entity }, { onSuccess });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onCreate,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDealServiceCrud;
|
||||
@ -0,0 +1,102 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import {
|
||||
CreateProductServiceSchema,
|
||||
ProductServiceSchema,
|
||||
HttpValidationError,
|
||||
UpdateProductServiceSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createDealProductServiceMutation,
|
||||
deleteDealProductServiceMutation,
|
||||
updateDealProductServiceMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
export type DealProductServicesCrud = {
|
||||
onCreate: (data: CreateProductServiceSchema) => void;
|
||||
onUpdate: (
|
||||
dealId: number,
|
||||
productId: number,
|
||||
serviceId: number,
|
||||
data: UpdateProductServiceSchema
|
||||
) => void;
|
||||
onDelete: (data: ProductServiceSchema) => void;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
refetchDealProducts: () => void;
|
||||
};
|
||||
|
||||
const useProductServiceCrud = ({
|
||||
refetchDealProducts,
|
||||
}: Props): DealProductServicesCrud => {
|
||||
const onError = (error: AxiosError<HttpValidationError>, _: any) => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const createMutation = useMutation({
|
||||
...createDealProductServiceMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
...updateDealProductServiceMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
...deleteDealProductServiceMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const onCreate = (entity: CreateProductServiceSchema) => {
|
||||
createMutation.mutate(
|
||||
{
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
},
|
||||
{ onSuccess: refetchDealProducts }
|
||||
);
|
||||
};
|
||||
|
||||
const onUpdate = (
|
||||
dealId: number,
|
||||
productId: number,
|
||||
serviceId: number,
|
||||
entity: UpdateProductServiceSchema
|
||||
) => {
|
||||
updateMutation.mutate(
|
||||
{
|
||||
body: {
|
||||
entity,
|
||||
},
|
||||
path: {
|
||||
dealId,
|
||||
productId,
|
||||
serviceId,
|
||||
},
|
||||
},
|
||||
{ onSuccess: refetchDealProducts }
|
||||
);
|
||||
};
|
||||
|
||||
const onDelete = (entity: ProductServiceSchema) => {
|
||||
deleteMutation.mutate(
|
||||
{ path: entity },
|
||||
{ onSuccess: refetchDealProducts }
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
onCreate,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
};
|
||||
};
|
||||
|
||||
export default useProductServiceCrud;
|
||||
@ -0,0 +1,49 @@
|
||||
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
|
||||
import {
|
||||
CreateProductSchema,
|
||||
ProductSchema,
|
||||
UpdateProductSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createProductMutation,
|
||||
deleteProductMutation,
|
||||
updateProductMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type UseProductsProps = {
|
||||
queryKey: any[];
|
||||
};
|
||||
|
||||
export type ProductsCrud = {
|
||||
onCreate: (product: CreateProductSchema) => void;
|
||||
onUpdate: (
|
||||
productId: number,
|
||||
product: UpdateProductSchema,
|
||||
onSuccess?: () => void
|
||||
) => void;
|
||||
onDelete: (product: ProductSchema) => void;
|
||||
};
|
||||
|
||||
export const useProductsCrud = ({
|
||||
queryKey,
|
||||
}: UseProductsProps): ProductsCrud => {
|
||||
return useCrudOperations<
|
||||
ProductSchema,
|
||||
UpdateProductSchema,
|
||||
CreateProductSchema
|
||||
>({
|
||||
key: "getProducts",
|
||||
queryKey,
|
||||
mutations: {
|
||||
create: createProductMutation(),
|
||||
update: updateProductMutation(),
|
||||
delete: deleteProductMutation(),
|
||||
},
|
||||
getUpdateEntity: (old, update) =>
|
||||
({
|
||||
...old,
|
||||
...update,
|
||||
}) as ProductSchema,
|
||||
getDeleteConfirmTitle: () => "Удаление товара",
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,49 @@
|
||||
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
|
||||
import {
|
||||
CreateServiceSchema,
|
||||
ServiceSchema,
|
||||
UpdateServiceSchema,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
createServiceMutation,
|
||||
deleteServiceMutation,
|
||||
updateServiceMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type UseServicesProps = {
|
||||
queryKey: any[];
|
||||
};
|
||||
|
||||
export type ServicesCrud = {
|
||||
onCreate: (service: CreateServiceSchema) => void;
|
||||
onUpdate: (
|
||||
serviceId: number,
|
||||
service: UpdateServiceSchema,
|
||||
onSuccess?: () => void
|
||||
) => void;
|
||||
onDelete: (service: ServiceSchema) => void;
|
||||
};
|
||||
|
||||
export const useServicesCrud = ({
|
||||
queryKey,
|
||||
}: UseServicesProps): ServicesCrud => {
|
||||
return useCrudOperations<
|
||||
ServiceSchema,
|
||||
UpdateServiceSchema,
|
||||
CreateServiceSchema
|
||||
>({
|
||||
key: "getServices",
|
||||
queryKey,
|
||||
mutations: {
|
||||
create: createServiceMutation(),
|
||||
update: updateServiceMutation(),
|
||||
delete: deleteServiceMutation(),
|
||||
},
|
||||
getUpdateEntity: (old, update) =>
|
||||
({
|
||||
...old,
|
||||
...update,
|
||||
}) as ServiceSchema,
|
||||
getDeleteConfirmTitle: () => "Удаление услуги",
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,49 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { DealProductSchema } from "@/lib/client";
|
||||
import {
|
||||
getDealProductsOptions,
|
||||
getDealProductsQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type Props = {
|
||||
dealId: number;
|
||||
};
|
||||
|
||||
export type DealProductsList = {
|
||||
dealProducts: DealProductSchema[];
|
||||
setDealProducts: (dealProducts: DealProductSchema[]) => void;
|
||||
refetch: () => void;
|
||||
queryKey: any[];
|
||||
};
|
||||
|
||||
const useDealProductsList = ({ dealId }: Props): DealProductsList => {
|
||||
const queryClient = useQueryClient();
|
||||
const options = {
|
||||
path: { dealId },
|
||||
};
|
||||
const { data, refetch } = useQuery({
|
||||
...getDealProductsOptions(options),
|
||||
enabled: !!dealId,
|
||||
});
|
||||
|
||||
const queryKey = getDealProductsQueryKey(options);
|
||||
|
||||
const setDealProducts = (dealProducts: DealProductSchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealProductSchema[] }) => ({
|
||||
...old,
|
||||
items: dealProducts,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
dealProducts: data?.items ?? [],
|
||||
setDealProducts,
|
||||
refetch,
|
||||
queryKey,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDealProductsList;
|
||||
@ -0,0 +1,49 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { DealServiceSchema } from "@/lib/client";
|
||||
import {
|
||||
getDealServicesOptions,
|
||||
getDealServicesQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type Props = {
|
||||
dealId: number;
|
||||
};
|
||||
|
||||
export type DealServicesList = {
|
||||
dealServices: DealServiceSchema[];
|
||||
setDealServices: (dealServices: DealServiceSchema[]) => void;
|
||||
refetch: () => void;
|
||||
queryKey: any[];
|
||||
};
|
||||
|
||||
const useDealServicesList = ({ dealId }: Props): DealServicesList => {
|
||||
const queryClient = useQueryClient();
|
||||
const options = {
|
||||
path: { dealId },
|
||||
};
|
||||
const { data, refetch } = useQuery({
|
||||
...getDealServicesOptions(options),
|
||||
enabled: !!dealId,
|
||||
});
|
||||
|
||||
const queryKey = getDealServicesQueryKey(options);
|
||||
|
||||
const setDealServices = (dealServices: DealServiceSchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: DealServiceSchema[] }) => ({
|
||||
...old,
|
||||
items: dealServices,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
dealServices: data?.items ?? [],
|
||||
setDealServices,
|
||||
refetch,
|
||||
queryKey,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDealServicesList;
|
||||
@ -0,0 +1,42 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ProductSchema } from "@/lib/client";
|
||||
import {
|
||||
getProductsOptions,
|
||||
getProductsQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
type Props = {
|
||||
searchInput: string;
|
||||
page?: number;
|
||||
itemsPerPage?: number;
|
||||
};
|
||||
|
||||
const useProductsList = ({ searchInput, page, itemsPerPage }: Props) => {
|
||||
const queryClient = useQueryClient();
|
||||
const options = {
|
||||
query: { searchInput, page, itemsPerPage },
|
||||
};
|
||||
const { data, refetch, isLoading } = useQuery(getProductsOptions(options));
|
||||
|
||||
const queryKey = getProductsQueryKey(options);
|
||||
|
||||
const setProducts = (products: ProductSchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: ProductSchema[] }) => ({
|
||||
...old,
|
||||
items: products,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
products: data?.items ?? [],
|
||||
setProducts,
|
||||
refetch,
|
||||
queryKey,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export default useProductsList;
|
||||
@ -0,0 +1,33 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ServicesKitSchema } from "@/lib/client";
|
||||
import {
|
||||
getServicesKitsOptions,
|
||||
getServicesKitsQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
const useServicesKitsList = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { data, refetch, isLoading } = useQuery(getServicesKitsOptions());
|
||||
|
||||
const queryKey = getServicesKitsQueryKey();
|
||||
|
||||
const setServicesKits = (servicesKits: ServicesKitSchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: ServicesKitSchema[] }) => ({
|
||||
...old,
|
||||
items: servicesKits,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
servicesKits: data?.items ?? [],
|
||||
setServicesKits,
|
||||
refetch,
|
||||
queryKey,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export default useServicesKitsList;
|
||||
@ -0,0 +1,34 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ServiceSchema } from "@/lib/client";
|
||||
import {
|
||||
getServicesOptions,
|
||||
getServicesQueryKey,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
|
||||
|
||||
const useServicesList = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { data, refetch, isLoading } = useQuery(getServicesOptions());
|
||||
|
||||
const queryKey = getServicesQueryKey();
|
||||
|
||||
const setServices = (services: ServiceSchema[]) => {
|
||||
queryClient.setQueryData(
|
||||
queryKey,
|
||||
(old: { items: ServiceSchema[] }) => ({
|
||||
...old,
|
||||
items: services,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
services: data?.items ?? [],
|
||||
setServices,
|
||||
refetch,
|
||||
queryKey,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export default useServicesList;
|
||||
@ -0,0 +1,148 @@
|
||||
import { CRUDTableProps } from "../../../../../types/CRUDTable.tsx";
|
||||
import { CardService, CardServiceSchema, CardProductSchema } from "../../../../../client";
|
||||
import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
|
||||
const useCardState = () => {
|
||||
|
||||
const { selectedCard, setSelectedCard } = useCardPageContext();
|
||||
const recalculate = async () => {
|
||||
return CardService.recalculateCardPrice({
|
||||
requestBody: {
|
||||
cardId: selectedCard?.id || -1,
|
||||
},
|
||||
});
|
||||
};
|
||||
const refetchCard = async () => {
|
||||
if (!selectedCard) return;
|
||||
|
||||
return CardService.getCardById({ cardId: selectedCard.id }).then(
|
||||
async card => {
|
||||
setSelectedCard(card);
|
||||
},
|
||||
);
|
||||
};
|
||||
const refetch = async () => {
|
||||
if (!selectedCard) return;
|
||||
await refetchCard();
|
||||
const { ok, message } = await recalculate();
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
|
||||
await refetchCard();
|
||||
};
|
||||
return {
|
||||
card: selectedCard,
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
|
||||
const useCardServicesState = (): CRUDTableProps<CardServiceSchema> => {
|
||||
const { card, refetch } = useCardState();
|
||||
const refetchAndRecalculate = async () => {
|
||||
await refetch();
|
||||
};
|
||||
const onCreate = (item: CardServiceSchema) => {
|
||||
if (!card) return;
|
||||
CardService.addCardService({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
serviceId: item.service.id,
|
||||
quantity: item.quantity,
|
||||
price: item.price,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
const onDelete = (item: CardServiceSchema) => {
|
||||
if (!card) return;
|
||||
CardService.deleteCardService({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
serviceId: item.service.id,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
const onChange = (item: CardServiceSchema) => {
|
||||
if (!card) return;
|
||||
CardService.updateCardService({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
service: item,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
return {
|
||||
items: card?.services || [],
|
||||
onCreate,
|
||||
onDelete,
|
||||
onChange,
|
||||
};
|
||||
};
|
||||
|
||||
const useDealProductsState = (): CRUDTableProps<CardProductSchema> => {
|
||||
const { card, refetch } = useCardState();
|
||||
const refetchAndRecalculate = async () => {
|
||||
await refetch();
|
||||
};
|
||||
const onCreate = (item: CardProductSchema) => {
|
||||
if (!card) return;
|
||||
CardService.addCardProduct({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
product: item,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
const onDelete = (item: CardProductSchema) => {
|
||||
if (!card) return;
|
||||
CardService.deleteCardProduct({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
productId: item.product.id,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
const onChange = (item: CardProductSchema) => {
|
||||
if (!card) return;
|
||||
CardService.updateCardProduct({
|
||||
requestBody: {
|
||||
cardId: card.id,
|
||||
product: item,
|
||||
},
|
||||
}).then(async ({ ok, message }) => {
|
||||
if (!ok) notifications.guess(ok, { message });
|
||||
if (ok) await refetchAndRecalculate();
|
||||
});
|
||||
};
|
||||
return {
|
||||
items: card?.products || [],
|
||||
onCreate,
|
||||
onDelete,
|
||||
onChange,
|
||||
};
|
||||
};
|
||||
const useCardProductAndServiceTabState = () => {
|
||||
const cardState = useCardState();
|
||||
const cardProductsState = useDealProductsState();
|
||||
const cardServicesState = useCardServicesState();
|
||||
return {
|
||||
cardState,
|
||||
cardProductsState,
|
||||
cardServicesState,
|
||||
};
|
||||
};
|
||||
export default useCardProductAndServiceTabState;
|
||||
@ -0,0 +1,87 @@
|
||||
"use client";
|
||||
|
||||
import { NumberInput, Textarea } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import {
|
||||
CreateDealProductSchema,
|
||||
DealProductSchema,
|
||||
UpdateDealProductSchema,
|
||||
} from "@/lib/client";
|
||||
import BaseFormModal, {
|
||||
CreateEditFormProps,
|
||||
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||
import ProductSelect from "../../components/ProductSelect/ProductSelect";
|
||||
|
||||
type RestProps = {
|
||||
clientId: number;
|
||||
productIdsToExclude?: number[];
|
||||
};
|
||||
|
||||
type Props = CreateEditFormProps<
|
||||
CreateDealProductSchema,
|
||||
UpdateDealProductSchema,
|
||||
DealProductSchema
|
||||
> &
|
||||
RestProps;
|
||||
|
||||
const DealProductEditorModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const form = useForm<Partial<DealProductSchema>>({
|
||||
initialValues: innerProps.isEditing
|
||||
? innerProps.entity
|
||||
: {
|
||||
quantity: 1,
|
||||
comment: "",
|
||||
},
|
||||
validate: {
|
||||
product: product => !product && "Необходимо выбрать товар",
|
||||
quantity: quantity =>
|
||||
(!quantity || quantity === 0) &&
|
||||
"Количество должно быть больше 0",
|
||||
},
|
||||
});
|
||||
|
||||
const onClose = () => {
|
||||
context.closeContextModal(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
form={form}
|
||||
closeOnSubmit
|
||||
onClose={onClose}>
|
||||
<ProductSelect
|
||||
placeholder={"Выберите товар"}
|
||||
label={"Товар"}
|
||||
clientId={innerProps.clientId}
|
||||
disabled={innerProps.isEditing}
|
||||
filterBy={item =>
|
||||
!(innerProps.productIdsToExclude || []).includes(item.id)
|
||||
}
|
||||
{...form.getInputProps("product")}
|
||||
onChange={product => {
|
||||
form.setFieldValue("product", product);
|
||||
form.setFieldValue("productId", product.id);
|
||||
}}
|
||||
/>
|
||||
<NumberInput
|
||||
placeholder={"Введите количество"}
|
||||
label={"Количество"}
|
||||
min={1}
|
||||
{...form.getInputProps("quantity")}
|
||||
/>
|
||||
<Textarea
|
||||
placeholder={"Введите комментарий"}
|
||||
label={"Комментарий"}
|
||||
{...form.getInputProps("comment")}
|
||||
/>
|
||||
</BaseFormModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealProductEditorModal;
|
||||
@ -0,0 +1,91 @@
|
||||
"use client";
|
||||
|
||||
import { NumberInput } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import {
|
||||
CreateDealServiceSchema,
|
||||
DealServiceSchema,
|
||||
UpdateDealServiceSchema,
|
||||
} from "@/lib/client";
|
||||
import BaseFormModal, {
|
||||
CreateEditFormProps,
|
||||
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
|
||||
import ServiceWithPriceInput from "./components/ServiceWithPriceInput";
|
||||
|
||||
type RestProps = {
|
||||
serviceIdsToExclude?: number[];
|
||||
};
|
||||
|
||||
type Props = CreateEditFormProps<
|
||||
CreateDealServiceSchema,
|
||||
UpdateDealServiceSchema,
|
||||
DealServiceSchema
|
||||
> &
|
||||
RestProps;
|
||||
|
||||
const DealServiceEditorModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const form = useForm<Partial<DealServiceSchema>>({
|
||||
initialValues: innerProps.isEditing
|
||||
? innerProps.entity
|
||||
: {
|
||||
service: undefined,
|
||||
serviceId: undefined,
|
||||
quantity: 1,
|
||||
isFixedPrice: false,
|
||||
},
|
||||
validate: {
|
||||
service: service => !service && "Необходимо выбрать услугу",
|
||||
quantity: quantity =>
|
||||
(!quantity || quantity === 0) &&
|
||||
"Количество должно быть больше 0",
|
||||
},
|
||||
});
|
||||
|
||||
const onClose = () => context.closeContextModal(id);
|
||||
|
||||
return (
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
form={form}
|
||||
closeOnSubmit
|
||||
onClose={onClose}>
|
||||
<ServiceWithPriceInput
|
||||
serviceProps={{
|
||||
...form.getInputProps("service"),
|
||||
onChange: service => {
|
||||
form.setFieldValue("service", service);
|
||||
form.setFieldValue("serviceId", service.id);
|
||||
},
|
||||
label: "Услуга",
|
||||
placeholder: "Выберите услугу",
|
||||
disabled: innerProps.isEditing,
|
||||
filterBy: item =>
|
||||
!innerProps.serviceIdsToExclude?.includes(item.id) ||
|
||||
innerProps.isEditing,
|
||||
}}
|
||||
priceProps={{
|
||||
...form.getInputProps("price"),
|
||||
label: "Цена",
|
||||
placeholder: "Введите цену",
|
||||
}}
|
||||
quantity={form.values.quantity || 1}
|
||||
filterType={ServiceType.DEAL_SERVICE}
|
||||
lockOnEdit={innerProps.isEditing}
|
||||
/>
|
||||
<NumberInput
|
||||
placeholder={"Введите количество"}
|
||||
label={"Количество"}
|
||||
min={1}
|
||||
{...form.getInputProps("quantity")}
|
||||
/>
|
||||
</BaseFormModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealServiceEditorModal;
|
||||
@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import { FC, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
NumberInput,
|
||||
NumberInputProps,
|
||||
Stack,
|
||||
StackProps,
|
||||
} from "@mantine/core";
|
||||
import { ObjectSelectProps } from "@/components/selects/ObjectSelect/ObjectSelect";
|
||||
import { ServiceSchema } from "@/lib/client";
|
||||
import ServiceSelect from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ServiceSelect/ServiceSelect";
|
||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
|
||||
|
||||
type ServiceProps = Omit<ObjectSelectProps<ServiceSchema>, "data">;
|
||||
type PriceProps = NumberInputProps;
|
||||
|
||||
type Props = {
|
||||
serviceProps: ServiceProps;
|
||||
priceProps: PriceProps;
|
||||
quantity: number;
|
||||
containerProps?: StackProps;
|
||||
filterType?: ServiceType;
|
||||
lockOnEdit?: boolean;
|
||||
};
|
||||
|
||||
const ServiceWithPriceInput: FC<Props> = ({
|
||||
serviceProps,
|
||||
priceProps,
|
||||
quantity,
|
||||
containerProps,
|
||||
filterType = ServiceType.PRODUCT_SERVICE,
|
||||
lockOnEdit = true,
|
||||
}) => {
|
||||
const [price, setPrice] = useState<number | undefined>(
|
||||
typeof priceProps.value === "number" ? priceProps.value : undefined
|
||||
);
|
||||
const [service, setService] = useState<ServiceSchema | undefined>(
|
||||
serviceProps.value
|
||||
);
|
||||
const isFirstRender = useRef(true);
|
||||
|
||||
const getPriceBasedOnQuantity = (): number | null => {
|
||||
if (!service || !service.priceRanges.length) return null;
|
||||
const range =
|
||||
service.priceRanges.find(
|
||||
priceRange =>
|
||||
quantity >= priceRange.fromQuantity &&
|
||||
quantity <= priceRange.toQuantity
|
||||
) || service.priceRanges[0];
|
||||
|
||||
return range.price;
|
||||
};
|
||||
|
||||
const setPriceBasedOnService = () => {
|
||||
if (!service) return;
|
||||
const rangePrice = getPriceBasedOnQuantity();
|
||||
setPrice(rangePrice || service.price);
|
||||
};
|
||||
|
||||
const onPriceManualChange = (value: number | string) => {
|
||||
if (typeof value !== "number") return;
|
||||
setPrice(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isFirstRender.current && lockOnEdit) return;
|
||||
const price = getPriceBasedOnQuantity();
|
||||
if (price) setPrice(price);
|
||||
}, [quantity]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFirstRender.current && lockOnEdit) return;
|
||||
if (!priceProps.onChange || price === undefined) return;
|
||||
priceProps.onChange(price);
|
||||
}, [price]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!serviceProps.onChange ||
|
||||
!service ||
|
||||
(price && isFirstRender.current && lockOnEdit)
|
||||
)
|
||||
return;
|
||||
setPriceBasedOnService();
|
||||
serviceProps.onChange(service);
|
||||
}, [service]);
|
||||
|
||||
useEffect(() => {
|
||||
isFirstRender.current = false;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap={"xs"}
|
||||
{...containerProps}>
|
||||
<ServiceSelect
|
||||
{...serviceProps}
|
||||
value={service}
|
||||
onChange={setService}
|
||||
filterType={filterType}
|
||||
disabled={lockOnEdit}
|
||||
/>
|
||||
<NumberInput
|
||||
{...priceProps}
|
||||
onChange={onPriceManualChange}
|
||||
defaultValue={priceProps.value}
|
||||
min={1}
|
||||
allowNegative={false}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceWithPriceInput;
|
||||
@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button, Flex, Text } from "@mantine/core";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import ObjectMultiSelect from "@/components/selects/ObjectMultiSelect/ObjectMultiSelect";
|
||||
import { DealProductSchema } from "@/lib/client";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
type Props = {
|
||||
dealProducts: DealProductSchema[];
|
||||
sourceDealProduct: DealProductSchema;
|
||||
duplicateServices: (
|
||||
sourceDealProduct: DealProductSchema,
|
||||
targetDealProducts: DealProductSchema[]
|
||||
) => void;
|
||||
};
|
||||
|
||||
const DuplicateServicesModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const [selectedDealProducts, setSelectedDealProducts] = useState<
|
||||
DealProductSchema[]
|
||||
>([]);
|
||||
|
||||
const onDealProductSelect = () => {
|
||||
if (!selectedDealProducts) {
|
||||
notifications.error({
|
||||
message:
|
||||
"Выберите товары на которые необходимо продублировать услуги",
|
||||
});
|
||||
return;
|
||||
}
|
||||
innerProps.duplicateServices(
|
||||
innerProps.sourceDealProduct,
|
||||
selectedDealProducts
|
||||
);
|
||||
context.closeContextModal(id);
|
||||
};
|
||||
|
||||
const onDuplicateAllClick = () => {
|
||||
innerProps.duplicateServices(
|
||||
innerProps.sourceDealProduct,
|
||||
innerProps.dealProducts.filter(
|
||||
item => item !== innerProps.sourceDealProduct
|
||||
)
|
||||
);
|
||||
context.closeContextModal(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={"xs"}>
|
||||
<ObjectMultiSelect
|
||||
w={"100%"}
|
||||
label={"Товары"}
|
||||
placeholder={
|
||||
"Выберите товары на которые нужно продублировать услуги"
|
||||
}
|
||||
onChange={setSelectedDealProducts}
|
||||
value={selectedDealProducts}
|
||||
data={innerProps.dealProducts}
|
||||
getLabelFn={item => item.product.name}
|
||||
getValueFn={item => item.product.id.toString()}
|
||||
filterBy={item => item !== innerProps.sourceDealProduct}
|
||||
/>
|
||||
<Flex
|
||||
gap={"xs"}
|
||||
justify={"flex-end"}>
|
||||
<Button
|
||||
variant={"subtle"}
|
||||
onClick={() => context.closeContextModal(id)}>
|
||||
<Text>Отменить</Text>
|
||||
</Button>
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={onDuplicateAllClick}>
|
||||
<Text>Продублировать на все товары</Text>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onDealProductSelect}
|
||||
variant={"default"}>
|
||||
<Text>Продублировать</Text>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default DuplicateServicesModal;
|
||||
@ -0,0 +1,122 @@
|
||||
"use client";
|
||||
|
||||
import { Fieldset, Flex, rem, TextInput } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import {
|
||||
CreateProductSchema,
|
||||
ProductSchema,
|
||||
UpdateProductSchema,
|
||||
} from "@/lib/client";
|
||||
import BaseFormModal, {
|
||||
CreateEditFormProps,
|
||||
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||
import ProductImageDropzone from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/components/ProductImageDropzone/ProductImageDropzone";
|
||||
import BaseFormInputProps from "@/utils/baseFormInputProps";
|
||||
|
||||
type Props = CreateEditFormProps<
|
||||
CreateProductSchema,
|
||||
UpdateProductSchema,
|
||||
ProductSchema
|
||||
>;
|
||||
|
||||
const ProductEditorModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const isEditing = "entity" in innerProps;
|
||||
|
||||
const initialValues: Partial<ProductSchema> = isEditing
|
||||
? innerProps.entity!
|
||||
: {
|
||||
name: "",
|
||||
article: "",
|
||||
factoryArticle: "",
|
||||
brand: "",
|
||||
composition: "",
|
||||
color: "",
|
||||
size: "",
|
||||
additionalInfo: "",
|
||||
};
|
||||
|
||||
const form = useForm<Partial<ProductSchema>>({
|
||||
initialValues,
|
||||
validate: {
|
||||
name: name =>
|
||||
!name || name.trim() !== ""
|
||||
? null
|
||||
: "Необходимо ввести название товара",
|
||||
},
|
||||
});
|
||||
|
||||
const onClose = () => context.closeContextModal(id);
|
||||
|
||||
return (
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
form={form}
|
||||
closeOnSubmit
|
||||
onClose={onClose}>
|
||||
<Flex
|
||||
gap={rem(10)}
|
||||
direction={"column"}>
|
||||
<Fieldset legend={"Основные характеристики"}>
|
||||
<TextInput
|
||||
placeholder={"Введите название товара"}
|
||||
label={"Название товара"}
|
||||
{...form.getInputProps("name")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите артикул"}
|
||||
label={"Артикул"}
|
||||
{...form.getInputProps("article")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите складской артикул"}
|
||||
label={"Складской артикул"}
|
||||
{...form.getInputProps("factoryArticle")}
|
||||
/>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Дополнительные характеристики"}>
|
||||
<TextInput
|
||||
placeholder={"Введите бренд"}
|
||||
label={"Бренд"}
|
||||
{...form.getInputProps("brand")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите состав"}
|
||||
label={"Состав"}
|
||||
{...form.getInputProps("composition")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите цвет"}
|
||||
label={"Цвет"}
|
||||
{...form.getInputProps("color")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите размер"}
|
||||
label={"Размер"}
|
||||
{...form.getInputProps("size")}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={"Введите доп. информацию"}
|
||||
label={"Доп. информация"}
|
||||
{...form.getInputProps("additionalInfo")}
|
||||
/>
|
||||
</Fieldset>
|
||||
{isEditing && (
|
||||
<ProductImageDropzone
|
||||
imageUrlInputProps={
|
||||
form.getInputProps(
|
||||
"imageUrl"
|
||||
) as BaseFormInputProps<string>
|
||||
}
|
||||
productId={innerProps.entity.id}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</BaseFormModal>
|
||||
);
|
||||
};
|
||||
export default ProductEditorModal;
|
||||
@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import { isNil, isNumber } from "lodash";
|
||||
import { Checkbox, Flex } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import {
|
||||
CreateProductServiceSchema,
|
||||
ProductServiceSchema,
|
||||
UpdateProductServiceSchema,
|
||||
} from "@/lib/client/index.js";
|
||||
import BaseFormModal, {
|
||||
CreateEditFormProps,
|
||||
} from "@/modals/base/BaseFormModal/BaseFormModal";
|
||||
import ServiceWithPriceInput from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/DealServiceEditorModal/components/ServiceWithPriceInput";
|
||||
import { ServiceType } from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/types/service";
|
||||
|
||||
type RestProps = {
|
||||
quantity: number;
|
||||
excludeServiceIds: number[];
|
||||
};
|
||||
|
||||
type Props = CreateEditFormProps<
|
||||
CreateProductServiceSchema,
|
||||
UpdateProductServiceSchema,
|
||||
ProductServiceSchema
|
||||
> &
|
||||
RestProps;
|
||||
|
||||
const ProductServiceEditorModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const initialValues: Partial<ProductServiceSchema> = innerProps.isEditing
|
||||
? innerProps.entity
|
||||
: {
|
||||
service: undefined,
|
||||
serviceId: undefined,
|
||||
price: undefined,
|
||||
isFixedPrice: false,
|
||||
};
|
||||
|
||||
const form = useForm<Partial<ProductServiceSchema>>({
|
||||
initialValues,
|
||||
validate: {
|
||||
service: service =>
|
||||
(isNil(service) || service.id < 0) && "Укажите услугу",
|
||||
price: price => (!isNumber(price) || price < 0) && "Укажите цену",
|
||||
},
|
||||
});
|
||||
|
||||
const onClose = () => context.closeContextModal(id);
|
||||
|
||||
return (
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
form={form}
|
||||
onClose={onClose}
|
||||
closeOnSubmit>
|
||||
<Flex
|
||||
w={"100%"}
|
||||
direction={"column"}
|
||||
gap={"xs"}>
|
||||
<ServiceWithPriceInput
|
||||
serviceProps={{
|
||||
...form.getInputProps("service"),
|
||||
onChange: value => {
|
||||
form.setFieldValue("service", value);
|
||||
form.setFieldValue("serviceId", value.id);
|
||||
},
|
||||
label: "Услуга",
|
||||
placeholder: "Выберите услугу",
|
||||
disabled: innerProps.isEditing,
|
||||
filterBy: item =>
|
||||
!innerProps.excludeServiceIds.includes(item.id) ||
|
||||
innerProps.isEditing,
|
||||
}}
|
||||
priceProps={{
|
||||
...form.getInputProps("price"),
|
||||
label: "Цена",
|
||||
placeholder: "Введите цену",
|
||||
}}
|
||||
filterType={ServiceType.PRODUCT_SERVICE}
|
||||
lockOnEdit={innerProps.isEditing}
|
||||
quantity={innerProps.quantity}
|
||||
/>
|
||||
<Checkbox
|
||||
{...form.getInputProps("isFixedPrice", {
|
||||
type: "checkbox",
|
||||
})}
|
||||
label={"Зафиксировать цену"}
|
||||
placeholder={"Зафиксировать цену"}
|
||||
/>
|
||||
</Flex>
|
||||
</BaseFormModal>
|
||||
);
|
||||
};
|
||||
export default ProductServiceEditorModal;
|
||||
@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { Flex } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import { ServicesKitSchema } from "@/lib/client";
|
||||
import BaseFormModalActions from "@/modals/base/BaseFormModal/BaseFormModalActions";
|
||||
import ServicesKitSelect from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/modals/ServicesKitSelectModal/components/ServicesKitSelect";
|
||||
|
||||
type Props = {
|
||||
onSelect: (kit: ServicesKitSchema) => void;
|
||||
serviceType: number;
|
||||
};
|
||||
|
||||
type ServicesKitForm = {
|
||||
servicesKit: ServicesKitSchema;
|
||||
};
|
||||
|
||||
const ServicesKitSelectModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const form = useForm<ServicesKitForm>({
|
||||
validate: {
|
||||
servicesKit: servicesKit => !servicesKit && "Выберите сервис",
|
||||
},
|
||||
});
|
||||
|
||||
const onClose = () => context.closeContextModal(id);
|
||||
|
||||
const onSubmit = (values: ServicesKitForm) => {
|
||||
innerProps.onSelect(values.servicesKit);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<Flex
|
||||
gap={"xs"}
|
||||
direction={"column"}>
|
||||
<Flex>
|
||||
<ServicesKitSelect
|
||||
w={"100%"}
|
||||
{...form.getInputProps("servicesKit")}
|
||||
filterBy={item =>
|
||||
item.serviceType === innerProps.serviceType
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
<BaseFormModalActions onClose={onClose} />
|
||||
</Flex>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicesKitSelectModal;
|
||||
@ -0,0 +1,23 @@
|
||||
import { FC } from "react";
|
||||
import ObjectSelect, {
|
||||
ObjectSelectProps,
|
||||
} from "@/components/selects/ObjectSelect/ObjectSelect";
|
||||
import { ServicesKitSchema } from "@/lib/client";
|
||||
import useServicesKitsList from "@/modules/dealModularEditorTabs/FulfillmentBaseTab/hooks/lists/useServicesKitsList";
|
||||
|
||||
type Props = Omit<ObjectSelectProps<ServicesKitSchema>, "data">;
|
||||
|
||||
const ServicesKitSelect: FC<Props> = props => {
|
||||
const { servicesKits } = useServicesKitsList();
|
||||
|
||||
return (
|
||||
<ObjectSelect
|
||||
label={"Набор услуг"}
|
||||
placeholder={"Выберите набор услуг"}
|
||||
data={servicesKits}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicesKitSelect;
|
||||
@ -0,0 +1,4 @@
|
||||
export enum ServiceType {
|
||||
DEAL_SERVICE,
|
||||
PRODUCT_SERVICE,
|
||||
}
|
||||
Reference in New Issue
Block a user