feat: deal product services
This commit is contained in:
		@@ -2,9 +2,9 @@
 | 
				
			|||||||
/* istanbul ignore file */
 | 
					/* istanbul ignore file */
 | 
				
			||||||
/* tslint:disable */
 | 
					/* tslint:disable */
 | 
				
			||||||
/* eslint-disable */
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					import type { DealProductSchema } from './DealProductSchema';
 | 
				
			||||||
export type DealAddProductRequest = {
 | 
					export type DealAddProductRequest = {
 | 
				
			||||||
    dealId: number;
 | 
					    dealId: number;
 | 
				
			||||||
    productId: number;
 | 
					    product: DealProductSchema;
 | 
				
			||||||
    quantity: number;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@
 | 
				
			|||||||
import type { ServiceSchema } from './ServiceSchema';
 | 
					import type { ServiceSchema } from './ServiceSchema';
 | 
				
			||||||
export type DealProductServiceSchema = {
 | 
					export type DealProductServiceSchema = {
 | 
				
			||||||
    service: ServiceSchema;
 | 
					    service: ServiceSchema;
 | 
				
			||||||
    quantity: number;
 | 
					 | 
				
			||||||
    price: number;
 | 
					    price: number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,5 +9,10 @@ export type ProductCreateRequest = {
 | 
				
			|||||||
    clientId: number;
 | 
					    clientId: number;
 | 
				
			||||||
    barcodes: Array<string>;
 | 
					    barcodes: Array<string>;
 | 
				
			||||||
    barcodeTemplate?: (BarcodeTemplateSchema | null);
 | 
					    barcodeTemplate?: (BarcodeTemplateSchema | null);
 | 
				
			||||||
 | 
					    brand?: (string | null);
 | 
				
			||||||
 | 
					    color?: (string | null);
 | 
				
			||||||
 | 
					    composition?: (string | null);
 | 
				
			||||||
 | 
					    size?: (string | null);
 | 
				
			||||||
 | 
					    additionalInfo?: (string | null);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,6 @@
 | 
				
			|||||||
/* eslint-disable */
 | 
					/* eslint-disable */
 | 
				
			||||||
import type { BarcodeTemplateSchema } from './BarcodeTemplateSchema';
 | 
					import type { BarcodeTemplateSchema } from './BarcodeTemplateSchema';
 | 
				
			||||||
export type ProductSchema = {
 | 
					export type ProductSchema = {
 | 
				
			||||||
    id: number;
 | 
					 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
    article: string;
 | 
					    article: string;
 | 
				
			||||||
    clientId: number;
 | 
					    clientId: number;
 | 
				
			||||||
@@ -15,5 +14,6 @@ export type ProductSchema = {
 | 
				
			|||||||
    composition?: (string | null);
 | 
					    composition?: (string | null);
 | 
				
			||||||
    size?: (string | null);
 | 
					    size?: (string | null);
 | 
				
			||||||
    additionalInfo?: (string | null);
 | 
					    additionalInfo?: (string | null);
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import {Select, SelectProps} from "@mantine/core";
 | 
					import {Select, SelectProps} from "@mantine/core";
 | 
				
			||||||
import {useEffect, useMemo, useState} from "react";
 | 
					import {useEffect, useMemo, useState} from "react";
 | 
				
			||||||
import {ObjectWithNameAndId} from "../../types/utils.ts";
 | 
					import {ObjectWithNameAndId} from "../../types/utils.ts";
 | 
				
			||||||
 | 
					import {groupBy} from "lodash";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SelectObjectType<T extends ObjectWithNameAndId> = T;
 | 
					export type SelectObjectType<T extends ObjectWithNameAndId> = T;
 | 
				
			||||||
@@ -14,6 +15,7 @@ type RestProps<T extends ObjectWithNameAndId> = {
 | 
				
			|||||||
    defaultValue?: SelectObjectType<T>
 | 
					    defaultValue?: SelectObjectType<T>
 | 
				
			||||||
    onChange: (value: SelectObjectType<T>) => void;
 | 
					    onChange: (value: SelectObjectType<T>) => void;
 | 
				
			||||||
    data: SelectObjectType<T>[];
 | 
					    data: SelectObjectType<T>[];
 | 
				
			||||||
 | 
					    groupBy?: (item: SelectObjectType<T>) => string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ObjectSelectProps<T extends ObjectWithNameAndId> =
 | 
					export type ObjectSelectProps<T extends ObjectWithNameAndId> =
 | 
				
			||||||
@@ -27,14 +29,23 @@ const ObjectSelect = <T extends ObjectWithNameAndId, >(props: ObjectSelectProps<
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const value = isControlled ? props.value : internalValue;
 | 
					    const value = isControlled ? props.value : internalValue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const data = useMemo(() => props.data.reduce((acc, item) => {
 | 
					    const data = useMemo(() => {
 | 
				
			||||||
        acc.push({
 | 
					        if (props.groupBy) {
 | 
				
			||||||
 | 
					            const groupedData = groupBy(props.data, props.groupBy);
 | 
				
			||||||
 | 
					            return Object.entries(groupedData).map(([group, items]) => ({
 | 
				
			||||||
 | 
					                group,
 | 
				
			||||||
 | 
					                items: items.map(item => ({
 | 
				
			||||||
                    label: item.name,
 | 
					                    label: item.name,
 | 
				
			||||||
                    value: item.id.toString()
 | 
					                    value: item.id.toString()
 | 
				
			||||||
        });
 | 
					                }))
 | 
				
			||||||
        return acc;
 | 
					            }));
 | 
				
			||||||
    }, [] as { label: string, value: string }[]), [props.data]);
 | 
					        } else {
 | 
				
			||||||
 | 
					            return props.data.map(item => ({
 | 
				
			||||||
 | 
					                label: item.name,
 | 
				
			||||||
 | 
					                value: item.id.toString()
 | 
				
			||||||
 | 
					            }));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [props.data, props.groupBy]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleOnChange = (event: string | null) => {
 | 
					    const handleOnChange = (event: string | null) => {
 | 
				
			||||||
        if (!event) return;
 | 
					        if (!event) return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,57 +2,75 @@ import {ProductSchema} from "../../client";
 | 
				
			|||||||
import {Select, SelectProps} from "@mantine/core";
 | 
					import {Select, SelectProps} from "@mantine/core";
 | 
				
			||||||
import {FC, useEffect, useMemo, useState} from "react";
 | 
					import {FC, useEffect, useMemo, useState} from "react";
 | 
				
			||||||
import useProductsList from "../../pages/ProductsPage/hooks/useProductsList.tsx";
 | 
					import useProductsList from "../../pages/ProductsPage/hooks/useProductsList.tsx";
 | 
				
			||||||
 | 
					import {omit} from "lodash";
 | 
				
			||||||
 | 
					import ObjectSelect, {ObjectSelectProps} from "../ObjectSelect/ObjectSelect.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ControlledValueProps = {
 | 
					 | 
				
			||||||
    value: ProductSchema;
 | 
					 | 
				
			||||||
    onChange: (value: ProductSchema) => void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
type RestProps = {
 | 
					type RestProps = {
 | 
				
			||||||
    defaultValue?: ProductSchema;
 | 
					 | 
				
			||||||
    onChange: (value: ProductSchema) => void;
 | 
					 | 
				
			||||||
    clientId: number;
 | 
					    clientId: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
type Props = (RestProps & Partial<ControlledValueProps>) & Omit<SelectProps, 'value' | 'onChange'>;
 | 
					type Props = Omit<ObjectSelectProps<ProductSchema>, 'data'> & RestProps;
 | 
				
			||||||
 | 
					const ProductSelect: FC<Props> = (props: Props) => {
 | 
				
			||||||
const ProductSelect: FC<Props> = (props) => {
 | 
					 | 
				
			||||||
    const isControlled = 'value' in props;
 | 
					 | 
				
			||||||
    const [intertalValue, setInternalValue] = useState<ProductSchema | undefined>(props.defaultValue);
 | 
					 | 
				
			||||||
    const value = isControlled ? props.value : intertalValue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const {products} = useProductsList({clientId: props.clientId});
 | 
					    const {products} = useProductsList({clientId: props.clientId});
 | 
				
			||||||
 | 
					    const restProps = omit(props, ['clientId']);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const data = useMemo(() => products.reduce((acc, product) => {
 | 
					 | 
				
			||||||
        acc.push({
 | 
					 | 
				
			||||||
            label: product.name,
 | 
					 | 
				
			||||||
            value: product.id.toString()
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        return acc;
 | 
					 | 
				
			||||||
    }, [] as { label: string, value: string }[]), [products]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const handleOnChange = (event: string | null) => {
 | 
					 | 
				
			||||||
        if (!event) return;
 | 
					 | 
				
			||||||
        const product = products.find(product => parseInt(event) == product.id);
 | 
					 | 
				
			||||||
        if (!product) return;
 | 
					 | 
				
			||||||
        if (isControlled) {
 | 
					 | 
				
			||||||
            props.onChange(product);
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        setInternalValue(product);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    useEffect(() => {
 | 
					 | 
				
			||||||
        if (isControlled || !intertalValue) return;
 | 
					 | 
				
			||||||
        props.onChange(intertalValue);
 | 
					 | 
				
			||||||
    }, [intertalValue]);
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Select
 | 
					        <ObjectSelect
 | 
				
			||||||
            {...props}
 | 
					            {...restProps}
 | 
				
			||||||
            withCheckIcon={false}
 | 
					            data={products}
 | 
				
			||||||
            searchable
 | 
					 | 
				
			||||||
            value={value?.id.toString()}
 | 
					 | 
				
			||||||
            onChange={handleOnChange}
 | 
					 | 
				
			||||||
            data={data}
 | 
					 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export default ProductSelect;
 | 
					export default ProductSelect;
 | 
				
			||||||
 | 
					// type ControlledValueProps = {
 | 
				
			||||||
 | 
					//     value: ProductSchema;
 | 
				
			||||||
 | 
					//     onChange: (value: ProductSchema) => void;
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					// type RestProps = {
 | 
				
			||||||
 | 
					//     defaultValue?: ProductSchema;
 | 
				
			||||||
 | 
					//     onChange: (value: ProductSchema) => void;
 | 
				
			||||||
 | 
					//     clientId: number;
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					// type Props = (RestProps & Partial<ControlledValueProps>) & Omit<SelectProps, 'value' | 'onChange'>;
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// const ProductSelect: FC<Props> = (props) => {
 | 
				
			||||||
 | 
					//     const isControlled = 'value' in props;
 | 
				
			||||||
 | 
					//     const [intertalValue, setInternalValue] = useState<ProductSchema | undefined>(props.defaultValue);
 | 
				
			||||||
 | 
					//     const value = isControlled ? props.value : intertalValue
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     const {products} = useProductsList({clientId: props.clientId});
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     const data = useMemo(() => products.reduce((acc, product) => {
 | 
				
			||||||
 | 
					//         acc.push({
 | 
				
			||||||
 | 
					//             label: product.name,
 | 
				
			||||||
 | 
					//             value: product.id.toString()
 | 
				
			||||||
 | 
					//         });
 | 
				
			||||||
 | 
					//         return acc;
 | 
				
			||||||
 | 
					//     }, [] as { label: string, value: string }[]), [products]);
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     const handleOnChange = (event: string | null) => {
 | 
				
			||||||
 | 
					//         if (!event) return;
 | 
				
			||||||
 | 
					//         const product = products.find(product => parseInt(event) == product.id);
 | 
				
			||||||
 | 
					//         if (!product) return;
 | 
				
			||||||
 | 
					//         if (isControlled) {
 | 
				
			||||||
 | 
					//             props.onChange(product);
 | 
				
			||||||
 | 
					//             return;
 | 
				
			||||||
 | 
					//         }
 | 
				
			||||||
 | 
					//         setInternalValue(product);
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//     useEffect(() => {
 | 
				
			||||||
 | 
					//         if (isControlled || !intertalValue) return;
 | 
				
			||||||
 | 
					//         props.onChange(intertalValue);
 | 
				
			||||||
 | 
					//     }, [intertalValue]);
 | 
				
			||||||
 | 
					//     const restProps = omit(props, ['clientId'])
 | 
				
			||||||
 | 
					//     return (
 | 
				
			||||||
 | 
					//         <Select
 | 
				
			||||||
 | 
					//             {...restProps}
 | 
				
			||||||
 | 
					//             withCheckIcon={false}
 | 
				
			||||||
 | 
					//             searchable
 | 
				
			||||||
 | 
					//             value={value?.id.toString()}
 | 
				
			||||||
 | 
					//             onChange={handleOnChange}
 | 
				
			||||||
 | 
					//             data={data}
 | 
				
			||||||
 | 
					//         />
 | 
				
			||||||
 | 
					//     )
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					// export default ProductSelect;
 | 
				
			||||||
@@ -13,12 +13,14 @@ type Props = Omit<ObjectSelectProps<ServiceSchema>, 'data'> & RestProps;
 | 
				
			|||||||
const ServiceSelectNew: FC<Props> = (props: Props) => {
 | 
					const ServiceSelectNew: FC<Props> = (props: Props) => {
 | 
				
			||||||
    const {services} = useServicesList();
 | 
					    const {services} = useServicesList();
 | 
				
			||||||
    const data = props.filterType ? services.filter(service => service.serviceType === props.filterType) : services;
 | 
					    const data = props.filterType ? services.filter(service => service.serviceType === props.filterType) : services;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const restProps = omit(props, ['filterType']);
 | 
					    const restProps = omit(props, ['filterType']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <ObjectSelect
 | 
					        <ObjectSelect
 | 
				
			||||||
            {...restProps}
 | 
					            {...restProps}
 | 
				
			||||||
            data={data}
 | 
					            data={data}
 | 
				
			||||||
 | 
					            groupBy={item => item.category.name}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					import {ObjectSelectProps} from "../ObjectSelect/ObjectSelect.tsx";
 | 
				
			||||||
 | 
					import {ServiceSchema} from "../../client";
 | 
				
			||||||
 | 
					import {Flex, FlexProps, NumberInput, NumberInputProps, rem} from "@mantine/core";
 | 
				
			||||||
 | 
					import {FC, useEffect, useState} from "react";
 | 
				
			||||||
 | 
					import ServiceSelectNew from "../Selects/ServiceSelectNew/ServiceSelectNew.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ServiceProps = Omit<ObjectSelectProps<ServiceSchema>, 'data'>;
 | 
				
			||||||
 | 
					type PriceProps = NumberInputProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = {
 | 
				
			||||||
 | 
					    serviceProps: ServiceProps,
 | 
				
			||||||
 | 
					    priceProps: PriceProps
 | 
				
			||||||
 | 
					    quantity: number;
 | 
				
			||||||
 | 
					    containerProps: FlexProps
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const ServiceWithPriceInput: FC<Props> = ({serviceProps, priceProps, quantity, containerProps}) => {
 | 
				
			||||||
 | 
					    const [price, setPrice] = useState<number | undefined>(
 | 
				
			||||||
 | 
					        typeof priceProps.value === 'number' ? priceProps.value : undefined);
 | 
				
			||||||
 | 
					    const [service, setService] = useState<ServiceSchema | undefined>(serviceProps.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const setPriceBasedOnQuantity = (): boolean => {
 | 
				
			||||||
 | 
					        if (!service || !service.priceRanges.length) return false;
 | 
				
			||||||
 | 
					        const range = service.priceRanges.find(priceRange =>
 | 
				
			||||||
 | 
					            quantity >= priceRange.fromQuantity && quantity <= priceRange.toQuantity) || service.priceRanges[0];
 | 
				
			||||||
 | 
					        setPrice(range.price);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const setPriceBasedOnService = () => {
 | 
				
			||||||
 | 
					        if (!service) return;
 | 
				
			||||||
 | 
					        if (setPriceBasedOnQuantity()) return;
 | 
				
			||||||
 | 
					        setPrice(service.price);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const onServiceManualChange = (service: ServiceSchema) => {
 | 
				
			||||||
 | 
					        setService(service);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const onPriceManualChange = (value: number | string) => {
 | 
				
			||||||
 | 
					        if (typeof value !== 'number') return;
 | 
				
			||||||
 | 
					        setPrice(value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        setPriceBasedOnQuantity();
 | 
				
			||||||
 | 
					    }, [quantity]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (!priceProps.onChange || !price) return;
 | 
				
			||||||
 | 
					        priceProps.onChange(price);
 | 
				
			||||||
 | 
					    }, [price]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (!serviceProps.onChange || !service) return;
 | 
				
			||||||
 | 
					        setPriceBasedOnService();
 | 
				
			||||||
 | 
					        serviceProps.onChange(service);
 | 
				
			||||||
 | 
					    }, [service]);
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Flex
 | 
				
			||||||
 | 
					            {...containerProps}
 | 
				
			||||||
 | 
					            gap={rem(10)}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <ServiceSelectNew
 | 
				
			||||||
 | 
					                {...serviceProps}
 | 
				
			||||||
 | 
					                value={service}
 | 
				
			||||||
 | 
					                onChange={onServiceManualChange}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <NumberInput
 | 
				
			||||||
 | 
					                {...priceProps}
 | 
				
			||||||
 | 
					                onChange={onPriceManualChange}
 | 
				
			||||||
 | 
					                value={price}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </Flex>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ServiceWithPriceInput;
 | 
				
			||||||
@@ -1,30 +1,73 @@
 | 
				
			|||||||
import {Checkbox, Flex, NumberInput} from "@mantine/core";
 | 
					import {ActionIcon, Button, Flex, Input, rem} from "@mantine/core";
 | 
				
			||||||
import ServiceSelectNew from "../../../../components/Selects/ServiceSelectNew/ServiceSelectNew.tsx";
 | 
					import {BaseFormInputProps} from "../../../../types/utils.ts";
 | 
				
			||||||
import {ServiceType} from "../../../../shared/enums/ServiceType.ts";
 | 
					import {DealProductServiceSchema, ServiceSchema} from "../../../../client";
 | 
				
			||||||
import {useForm} from "@mantine/form";
 | 
					import {FC, useEffect, useState} from "react";
 | 
				
			||||||
import {DealProductServiceSchema} from "../../../../client";
 | 
					import ServiceWithPriceInput from "../../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
 | 
				
			||||||
 | 
					import {isNumber} from "lodash";
 | 
				
			||||||
 | 
					import {notifications} from "../../../../shared/lib/notifications.ts";
 | 
				
			||||||
 | 
					import {IconTrash} from "@tabler/icons-react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DealProductServiceTable = () => {
 | 
					type RestProps = {
 | 
				
			||||||
    const initialValues: Partial<DealProductServiceSchema> = {}
 | 
					    quantity: number;
 | 
				
			||||||
    const form = useForm<Partial<DealProductServiceSchema>>(
 | 
					}
 | 
				
			||||||
    );
 | 
					type Props = BaseFormInputProps<DealProductServiceSchema[]> & RestProps;
 | 
				
			||||||
 | 
					const DealProductServiceTable: FC<Props> = (props: Props) => {
 | 
				
			||||||
 | 
					    const {value, onChange, quantity, error} = props;
 | 
				
			||||||
 | 
					    const [innerValue, setInnerValue] = useState<Partial<DealProductServiceSchema>[]>(value || []);
 | 
				
			||||||
 | 
					    const onServiceChange = (idx: number, value: ServiceSchema) => {
 | 
				
			||||||
 | 
					        setInnerValue(oldValue => oldValue.map((item, i) => i === idx ? {...item, service: value} : item));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const onQuantityChange = (idx: number, value: string | number) => {
 | 
				
			||||||
 | 
					        if (!isNumber(value)) return;
 | 
				
			||||||
 | 
					        setInnerValue(oldValue => oldValue.map((item, i) => i === idx ? {...item, price: value} : item));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const onCreate = () => {
 | 
				
			||||||
 | 
					        if (innerValue.length > 0 && !innerValue.at(-1)?.service) {
 | 
				
			||||||
 | 
					            notifications.error({message: "Заполните последнюю услугу"})
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        setInnerValue(prevState => [...prevState, {service: undefined, quantity: 1}])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const onDelete = (idx: number) => {
 | 
				
			||||||
 | 
					        setInnerValue(oldValue => oldValue.filter((_, i) => i !== idx));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        onChange(innerValue as DealProductServiceSchema[]);
 | 
				
			||||||
 | 
					    }, [innerValue]);
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Flex direction={"column"}>
 | 
					        <Input.Wrapper error={error}>
 | 
				
			||||||
            <ServiceSelectNew
 | 
					            <Flex direction={"column"} gap={rem(10)}>
 | 
				
			||||||
                filterType={ServiceType.PRODUCT_SERVICE}
 | 
					                {innerValue.map((service, idx) => (
 | 
				
			||||||
                onChange={() => {
 | 
					                    <Flex key={idx} direction={"row"} gap={rem(10)} align={"center"} justify={"stretch"}>
 | 
				
			||||||
                }}/>
 | 
					                        <ActionIcon onClick={() => onDelete(idx)} variant={"default"}>
 | 
				
			||||||
            <NumberInput
 | 
					                            <IconTrash/>
 | 
				
			||||||
                label={"Количество"}
 | 
					                        </ActionIcon>
 | 
				
			||||||
            />
 | 
					                        <ServiceWithPriceInput
 | 
				
			||||||
            <Checkbox
 | 
					                            serviceProps={{
 | 
				
			||||||
                label={"Привязать количество услуг к количеству товара"}
 | 
					                                onChange: (event) => onServiceChange(idx, event),
 | 
				
			||||||
            />
 | 
					                                value: service.service,
 | 
				
			||||||
            <NumberInput
 | 
					                                placeholder: "Выберите услугу",
 | 
				
			||||||
                label={"Цена за единицу"}
 | 
					                                style: {width: "100%"}
 | 
				
			||||||
            />
 | 
					                            }}
 | 
				
			||||||
 | 
					                            priceProps={{
 | 
				
			||||||
 | 
					                                onChange: (event) => onQuantityChange(idx, event),
 | 
				
			||||||
 | 
					                                value: service.price,
 | 
				
			||||||
 | 
					                                placeholder: "Введите стоимость",
 | 
				
			||||||
 | 
					                                hideControls: true,
 | 
				
			||||||
 | 
					                                style: {width: "100%"},
 | 
				
			||||||
 | 
					                                suffix: "₽"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            }}
 | 
				
			||||||
 | 
					                            containerProps={{w: "100%"}}
 | 
				
			||||||
 | 
					                            quantity={quantity}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
                    </Flex>
 | 
					                    </Flex>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ))}
 | 
				
			||||||
 | 
					                <Button onClick={onCreate} variant={"default"}>Добавить услугу</Button>
 | 
				
			||||||
 | 
					            </Flex>
 | 
				
			||||||
 | 
					        </Input.Wrapper>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export default DealProductServiceTable;
 | 
					export default DealProductServiceTable;
 | 
				
			||||||
@@ -6,7 +6,7 @@ import {DealProductSchema, ProductService} from "../../../../client";
 | 
				
			|||||||
import {ActionIcon, Button, Flex, rem, Tooltip} from "@mantine/core";
 | 
					import {ActionIcon, Button, Flex, rem, Tooltip} from "@mantine/core";
 | 
				
			||||||
import {MRT_TableOptions} from "mantine-react-table";
 | 
					import {MRT_TableOptions} from "mantine-react-table";
 | 
				
			||||||
import {modals} from "@mantine/modals";
 | 
					import {modals} from "@mantine/modals";
 | 
				
			||||||
import {IconBarcode, IconTrash} from "@tabler/icons-react";
 | 
					import {IconBarcode, IconEdit, IconTrash} from "@tabler/icons-react";
 | 
				
			||||||
import {notifications} from "../../../../shared/lib/notifications.ts";
 | 
					import {notifications} from "../../../../shared/lib/notifications.ts";
 | 
				
			||||||
import {CreateProductRequest} from "../../../ProductsPage/types.ts";
 | 
					import {CreateProductRequest} from "../../../ProductsPage/types.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,7 +39,8 @@ const DealProductsTable: FC<Props> = (props: Props) => {
 | 
				
			|||||||
            innerProps: {
 | 
					            innerProps: {
 | 
				
			||||||
                onCreate: (product) => onCreate(product as DealProductSchema),
 | 
					                onCreate: (product) => onCreate(product as DealProductSchema),
 | 
				
			||||||
                clientId
 | 
					                clientId
 | 
				
			||||||
            }
 | 
					            },
 | 
				
			||||||
 | 
					            size:"lg"
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const onPrintBarcodeClick = (product: DealProductSchema) => {
 | 
					    const onPrintBarcodeClick = (product: DealProductSchema) => {
 | 
				
			||||||
@@ -71,6 +72,21 @@ const DealProductsTable: FC<Props> = (props: Props) => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    const onEditClick = (product: DealProductSchema) => {
 | 
				
			||||||
 | 
					        modals.openContextModal({
 | 
				
			||||||
 | 
					            modal: "addDealProduct",
 | 
				
			||||||
 | 
					            title: 'Создание товара',
 | 
				
			||||||
 | 
					            withCloseButton: false,
 | 
				
			||||||
 | 
					            innerProps: {
 | 
				
			||||||
 | 
					                clientId: clientId,
 | 
				
			||||||
 | 
					                element: product,
 | 
				
			||||||
 | 
					                onChange: () => {
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            size:"lg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <BaseTable
 | 
					        <BaseTable
 | 
				
			||||||
            data={items}
 | 
					            data={items}
 | 
				
			||||||
@@ -91,6 +107,11 @@ const DealProductsTable: FC<Props> = (props: Props) => {
 | 
				
			|||||||
                                <IconBarcode/>
 | 
					                                <IconBarcode/>
 | 
				
			||||||
                            </ActionIcon>
 | 
					                            </ActionIcon>
 | 
				
			||||||
                        </Tooltip>
 | 
					                        </Tooltip>
 | 
				
			||||||
 | 
					                        <Tooltip label="Редактировать">
 | 
				
			||||||
 | 
					                            <ActionIcon onClick={() => onEditClick(row.original)} variant={"default"}>
 | 
				
			||||||
 | 
					                                <IconEdit/>
 | 
				
			||||||
 | 
					                            </ActionIcon>
 | 
				
			||||||
 | 
					                        </Tooltip>
 | 
				
			||||||
                    </Flex>
 | 
					                    </Flex>
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                renderBottomToolbar: ({table}) => (
 | 
					                renderBottomToolbar: ({table}) => (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -214,8 +214,7 @@ const useDealProductTableState = () => {
 | 
				
			|||||||
        DealService.addDealProduct({
 | 
					        DealService.addDealProduct({
 | 
				
			||||||
            requestBody: {
 | 
					            requestBody: {
 | 
				
			||||||
                dealId: selectedDeal.id,
 | 
					                dealId: selectedDeal.id,
 | 
				
			||||||
                productId: product.product.id,
 | 
					                product: product
 | 
				
			||||||
                quantity: product.quantity
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }).then(async ({ok, message}) => {
 | 
					        }).then(async ({ok, message}) => {
 | 
				
			||||||
            if (!ok) {
 | 
					            if (!ok) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,37 +1,53 @@
 | 
				
			|||||||
import {ContextModalProps} from "@mantine/modals";
 | 
					import {ContextModalProps} from "@mantine/modals";
 | 
				
			||||||
import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
 | 
					import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
 | 
				
			||||||
import {DealProductSchema} from "../../../client";
 | 
					import {DealProductSchema, DealProductServiceSchema} from "../../../client";
 | 
				
			||||||
import {useForm} from "@mantine/form";
 | 
					import {useForm} from "@mantine/form";
 | 
				
			||||||
import {NumberInput} from "@mantine/core";
 | 
					import {Fieldset, NumberInput} from "@mantine/core";
 | 
				
			||||||
import ProductSelect from "../../../components/ProductSelect/ProductSelect.tsx";
 | 
					import ProductSelect from "../../../components/ProductSelect/ProductSelect.tsx";
 | 
				
			||||||
 | 
					import DealProductServiceTable from "../components/DealProductsTable/DealProductServiceTable.tsx";
 | 
				
			||||||
 | 
					import {omit} from "lodash";
 | 
				
			||||||
 | 
					import {BaseFormInputProps} from "../../../types/utils.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type RestProps = {
 | 
					type RestProps = {
 | 
				
			||||||
    clientId: number
 | 
					    clientId: number
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = CreateEditFormProps<Partial<DealProductSchema>> & RestProps;
 | 
					type Props = CreateEditFormProps<DealProductSchema> & RestProps;
 | 
				
			||||||
const AddDealProductModal = ({
 | 
					const AddDealProductModal = ({
 | 
				
			||||||
                                 context,
 | 
					                                 context,
 | 
				
			||||||
                                 id,
 | 
					                                 id,
 | 
				
			||||||
                                 innerProps
 | 
					                                 innerProps
 | 
				
			||||||
                             }: ContextModalProps<Props>) => {
 | 
					                             }: ContextModalProps<Props>) => {
 | 
				
			||||||
 | 
					    const isEditing = 'element' in innerProps;
 | 
				
			||||||
 | 
					    const restProps = omit(innerProps, ['clientId']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const validateServices = (services?: DealProductServiceSchema[]) => {
 | 
				
			||||||
 | 
					        if (!services || services.length == 0) return null;
 | 
				
			||||||
 | 
					        console.log("validating...");
 | 
				
			||||||
 | 
					        console.log( services.filter(service => service.service === undefined))
 | 
				
			||||||
 | 
					        return services.find(service => service.service === undefined) ? "Удалите пустые услуги" : null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    const form = useForm<Partial<DealProductSchema>>({
 | 
					    const form = useForm<Partial<DealProductSchema>>({
 | 
				
			||||||
        initialValues: {
 | 
					        initialValues: isEditing ? innerProps.element : {
 | 
				
			||||||
            product: undefined,
 | 
					            product: undefined,
 | 
				
			||||||
            quantity: 0
 | 
					            services: [],
 | 
				
			||||||
 | 
					            quantity: 1
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        validate: {
 | 
					        validate: {
 | 
				
			||||||
            product: (product?: DealProductSchema['product']) => product !== undefined ? null : "Необходимо выбрать товар",
 | 
					            product: (product?: DealProductSchema['product']) => product !== undefined ? null : "Необходимо выбрать товар",
 | 
				
			||||||
            quantity: (quantity?: number) => (quantity && quantity > 0) ? null : "Количество должно быть больше 0"
 | 
					            quantity: (quantity?: number) => (quantity && quantity > 0) ? null : "Количество должно быть больше 0",
 | 
				
			||||||
 | 
					            services: validateServices
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onClose = () => {
 | 
					    const onClose = () => {
 | 
				
			||||||
        context.closeContextModal(id);
 | 
					        context.closeContextModal(id);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    console.log(form.values)
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <BaseFormModal
 | 
					        <BaseFormModal
 | 
				
			||||||
            {...innerProps}
 | 
					            {...restProps as CreateEditFormProps<DealProductSchema>}
 | 
				
			||||||
            form={form}
 | 
					            form={form}
 | 
				
			||||||
            closeOnSubmit
 | 
					            closeOnSubmit
 | 
				
			||||||
            onClose={onClose}>
 | 
					            onClose={onClose}>
 | 
				
			||||||
@@ -49,10 +65,19 @@ const AddDealProductModal = ({
 | 
				
			|||||||
                        min={1}
 | 
					                        min={1}
 | 
				
			||||||
                        {...form.getInputProps('quantity')}
 | 
					                        {...form.getInputProps('quantity')}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
 | 
					                    <Fieldset legend={'Услуги'}>
 | 
				
			||||||
 | 
					                        <DealProductServiceTable
 | 
				
			||||||
 | 
					                            quantity={form.values.quantity || 1}
 | 
				
			||||||
 | 
					                            {...form.getInputProps('services') as
 | 
				
			||||||
 | 
					                                BaseFormInputProps<DealProductServiceSchema[]>}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    </Fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                </>
 | 
					                </>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            </BaseFormModal.Body>
 | 
					            </BaseFormModal.Body>
 | 
				
			||||||
        </BaseFormModal>
 | 
					        </BaseFormModal>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,12 @@
 | 
				
			|||||||
import {createLazyFileRoute} from "@tanstack/react-router";
 | 
					import {createLazyFileRoute} from "@tanstack/react-router";
 | 
				
			||||||
import ServiceSelectNew from "../components/Selects/ServiceSelectNew/ServiceSelectNew.tsx";
 | 
					import ServiceSelectNew from "../components/Selects/ServiceSelectNew/ServiceSelectNew.tsx";
 | 
				
			||||||
import {ServiceType} from "../shared/enums/ServiceType.ts";
 | 
					import {ServiceType} from "../shared/enums/ServiceType.ts";
 | 
				
			||||||
 | 
					import ServiceWithPriceInput from "../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
 | 
				
			||||||
 | 
					import {useEffect, useState} from "react";
 | 
				
			||||||
 | 
					import {ServiceSchema, ServiceService} from "../client";
 | 
				
			||||||
 | 
					import {NumberInput} from "@mantine/core";
 | 
				
			||||||
 | 
					import {isNumber} from "lodash";
 | 
				
			||||||
 | 
					import useServicesList from "../pages/ServicesPage/hooks/useServicesList.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Route = createLazyFileRoute('/test')({
 | 
					export const Route = createLazyFileRoute('/test')({
 | 
				
			||||||
    component: TestPage
 | 
					    component: TestPage
 | 
				
			||||||
@@ -18,12 +24,48 @@ const data = [{
 | 
				
			|||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function TestPage() {
 | 
					function TestPage() {
 | 
				
			||||||
 | 
					    const [service, setService] = useState<ServiceSchema | undefined>({
 | 
				
			||||||
 | 
					        "id": 96,
 | 
				
			||||||
 | 
					        "name": "123",
 | 
				
			||||||
 | 
					        "category": {"id": 1, "name": "Услуги по работе с товаром с учетом суммы всех сторон в см."},
 | 
				
			||||||
 | 
					        "price": 0,
 | 
				
			||||||
 | 
					        "serviceType": 1,
 | 
				
			||||||
 | 
					        "priceRanges": [{"id": 4, "fromQuantity": 1, "toQuantity": 200, "price": 35}, {
 | 
				
			||||||
 | 
					            "id": 3,
 | 
				
			||||||
 | 
					            "fromQuantity": 201,
 | 
				
			||||||
 | 
					            "toQuantity": 300,
 | 
				
			||||||
 | 
					            "price": 24
 | 
				
			||||||
 | 
					        }]
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const [price, setPrice] = useState<number | string>();
 | 
				
			||||||
 | 
					    const [q, setQ] = useState(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log('service:---------');
 | 
				
			||||||
 | 
					    console.log(service);
 | 
				
			||||||
 | 
					    console.log('price:---------');
 | 
				
			||||||
 | 
					    console.log(price);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <ServiceSelectNew
 | 
					            <NumberInput
 | 
				
			||||||
                filterType={ServiceType.PRODUCT_SERVICE}
 | 
					                value={q}
 | 
				
			||||||
                onChange={() => {
 | 
					                onChange={event => {
 | 
				
			||||||
 | 
					                    if (!isNumber(event)) return;
 | 
				
			||||||
 | 
					                    setQ(event);
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <ServiceWithPriceInput
 | 
				
			||||||
 | 
					                priceProps={{
 | 
				
			||||||
 | 
					                    onChange: setPrice,
 | 
				
			||||||
 | 
					                    value: price,
 | 
				
			||||||
 | 
					                    label: "Цена"
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                quantity={q}
 | 
				
			||||||
 | 
					                serviceProps={{
 | 
				
			||||||
 | 
					                    onChange: setService,
 | 
				
			||||||
 | 
					                    value: service,
 | 
				
			||||||
 | 
					                    label: "Услуга"
 | 
				
			||||||
                }}
 | 
					                }}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user