feat: price by category

This commit is contained in:
2024-09-27 04:41:18 +03:00
parent f30c55456c
commit c5f839d9ef
44 changed files with 1316 additions and 681 deletions

View File

@@ -52,6 +52,8 @@ export type { CreatePayRateRequest } from './models/CreatePayRateRequest';
export type { CreatePayRateResponse } from './models/CreatePayRateResponse'; export type { CreatePayRateResponse } from './models/CreatePayRateResponse';
export type { CreatePositionRequest } from './models/CreatePositionRequest'; export type { CreatePositionRequest } from './models/CreatePositionRequest';
export type { CreatePositionResponse } from './models/CreatePositionResponse'; export type { CreatePositionResponse } from './models/CreatePositionResponse';
export type { CreatePriceCategoryRequest } from './models/CreatePriceCategoryRequest';
export type { CreatePriceCategoryResponse } from './models/CreatePriceCategoryResponse';
export type { CreateServiceKitSchema } from './models/CreateServiceKitSchema'; export type { CreateServiceKitSchema } from './models/CreateServiceKitSchema';
export type { CreateServicesKitRequest } from './models/CreateServicesKitRequest'; export type { CreateServicesKitRequest } from './models/CreateServicesKitRequest';
export type { CreateServicesKitResponse } from './models/CreateServicesKitResponse'; export type { CreateServicesKitResponse } from './models/CreateServicesKitResponse';
@@ -120,6 +122,8 @@ export type { DeletePayRateRequest } from './models/DeletePayRateRequest';
export type { DeletePayRateResponse } from './models/DeletePayRateResponse'; export type { DeletePayRateResponse } from './models/DeletePayRateResponse';
export type { DeletePositionRequest } from './models/DeletePositionRequest'; export type { DeletePositionRequest } from './models/DeletePositionRequest';
export type { DeletePositionResponse } from './models/DeletePositionResponse'; export type { DeletePositionResponse } from './models/DeletePositionResponse';
export type { DeletePriceCategoryRequest } from './models/DeletePriceCategoryRequest';
export type { DeletePriceCategoryResponse } from './models/DeletePriceCategoryResponse';
export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest'; export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest';
export type { DeleteShippingWarehouseResponse } from './models/DeleteShippingWarehouseResponse'; export type { DeleteShippingWarehouseResponse } from './models/DeleteShippingWarehouseResponse';
export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse'; export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse';
@@ -129,6 +133,7 @@ export type { GetAllBaseMarketplacesResponse } from './models/GetAllBaseMarketpl
export type { GetAllPayRatesResponse } from './models/GetAllPayRatesResponse'; export type { GetAllPayRatesResponse } from './models/GetAllPayRatesResponse';
export type { GetAllPayrollSchemeResponse } from './models/GetAllPayrollSchemeResponse'; export type { GetAllPayrollSchemeResponse } from './models/GetAllPayrollSchemeResponse';
export type { GetAllPositionsResponse } from './models/GetAllPositionsResponse'; export type { GetAllPositionsResponse } from './models/GetAllPositionsResponse';
export type { GetAllPriceCategoriesResponse } from './models/GetAllPriceCategoriesResponse';
export type { GetAllRolesResponse } from './models/GetAllRolesResponse'; export type { GetAllRolesResponse } from './models/GetAllRolesResponse';
export type { GetAllServicesKitsResponse } from './models/GetAllServicesKitsResponse'; export type { GetAllServicesKitsResponse } from './models/GetAllServicesKitsResponse';
export type { GetAllShippingWarehousesResponse } from './models/GetAllShippingWarehousesResponse'; export type { GetAllShippingWarehousesResponse } from './models/GetAllShippingWarehousesResponse';
@@ -174,6 +179,7 @@ export type { ProductUpdateRequest } from './models/ProductUpdateRequest';
export type { ProductUpdateResponse } from './models/ProductUpdateResponse'; export type { ProductUpdateResponse } from './models/ProductUpdateResponse';
export type { ProductUploadImageResponse } from './models/ProductUploadImageResponse'; export type { ProductUploadImageResponse } from './models/ProductUploadImageResponse';
export type { RoleSchema } from './models/RoleSchema'; export type { RoleSchema } from './models/RoleSchema';
export type { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema';
export type { ServiceCategorySchema } from './models/ServiceCategorySchema'; export type { ServiceCategorySchema } from './models/ServiceCategorySchema';
export type { ServiceCreateCategoryRequest } from './models/ServiceCreateCategoryRequest'; export type { ServiceCreateCategoryRequest } from './models/ServiceCreateCategoryRequest';
export type { ServiceCreateCategoryResponse } from './models/ServiceCreateCategoryResponse'; export type { ServiceCreateCategoryResponse } from './models/ServiceCreateCategoryResponse';
@@ -183,6 +189,7 @@ export type { ServiceDeleteRequest } from './models/ServiceDeleteRequest';
export type { ServiceDeleteResponse } from './models/ServiceDeleteResponse'; export type { ServiceDeleteResponse } from './models/ServiceDeleteResponse';
export type { ServiceGetAllCategoriesResponse } from './models/ServiceGetAllCategoriesResponse'; export type { ServiceGetAllCategoriesResponse } from './models/ServiceGetAllCategoriesResponse';
export type { ServiceGetAllResponse } from './models/ServiceGetAllResponse'; export type { ServiceGetAllResponse } from './models/ServiceGetAllResponse';
export type { ServicePriceCategorySchema } from './models/ServicePriceCategorySchema';
export type { ServicePriceRangeSchema } from './models/ServicePriceRangeSchema'; export type { ServicePriceRangeSchema } from './models/ServicePriceRangeSchema';
export type { ServiceSchema } from './models/ServiceSchema'; export type { ServiceSchema } from './models/ServiceSchema';
export type { ServiceUpdateRequest } from './models/ServiceUpdateRequest'; export type { ServiceUpdateRequest } from './models/ServiceUpdateRequest';
@@ -196,6 +203,8 @@ export type { UpdateMarketplaceRequest } from './models/UpdateMarketplaceRequest
export type { UpdateMarketplaceResponse } from './models/UpdateMarketplaceResponse'; export type { UpdateMarketplaceResponse } from './models/UpdateMarketplaceResponse';
export type { UpdatePayRateRequest } from './models/UpdatePayRateRequest'; export type { UpdatePayRateRequest } from './models/UpdatePayRateRequest';
export type { UpdatePayRateResponse } from './models/UpdatePayRateResponse'; export type { UpdatePayRateResponse } from './models/UpdatePayRateResponse';
export type { UpdatePriceCategoryRequest } from './models/UpdatePriceCategoryRequest';
export type { UpdatePriceCategoryResponse } from './models/UpdatePriceCategoryResponse';
export type { UpdateServiceKitSchema } from './models/UpdateServiceKitSchema'; export type { UpdateServiceKitSchema } from './models/UpdateServiceKitSchema';
export type { UpdateServicesKitRequest } from './models/UpdateServicesKitRequest'; export type { UpdateServicesKitRequest } from './models/UpdateServicesKitRequest';
export type { UpdateServicesKitResponse } from './models/UpdateServicesKitResponse'; export type { UpdateServicesKitResponse } from './models/UpdateServicesKitResponse';

View File

@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type CreatePriceCategoryRequest = {
name: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type CreatePriceCategoryResponse = {
ok: boolean;
message: string;
};

View File

@@ -3,6 +3,7 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
import type { BaseMarketplaceSchema } from './BaseMarketplaceSchema'; import type { BaseMarketplaceSchema } from './BaseMarketplaceSchema';
import type { ServicePriceCategorySchema } from './ServicePriceCategorySchema';
export type DealQuickCreateRequest = { export type DealQuickCreateRequest = {
name: string; name: string;
clientName: string; clientName: string;
@@ -10,5 +11,6 @@ export type DealQuickCreateRequest = {
acceptanceDate: string; acceptanceDate: string;
shippingWarehouse: string; shippingWarehouse: string;
baseMarketplace: BaseMarketplaceSchema; baseMarketplace: BaseMarketplaceSchema;
category?: (ServicePriceCategorySchema | null);
}; };

View File

@@ -7,6 +7,7 @@ import type { DealBillRequestSchema } from './DealBillRequestSchema';
import type { DealProductSchema } from './DealProductSchema'; import type { DealProductSchema } from './DealProductSchema';
import type { DealServiceSchema } from './DealServiceSchema'; import type { DealServiceSchema } from './DealServiceSchema';
import type { DealStatusHistorySchema } from './DealStatusHistorySchema'; import type { DealStatusHistorySchema } from './DealStatusHistorySchema';
import type { ServicePriceCategorySchema } from './ServicePriceCategorySchema';
import type { ShippingWarehouseSchema } from './ShippingWarehouseSchema'; import type { ShippingWarehouseSchema } from './ShippingWarehouseSchema';
export type DealSchema = { export type DealSchema = {
id: number; id: number;
@@ -24,5 +25,6 @@ export type DealSchema = {
comment: string; comment: string;
shippingWarehouse?: (ShippingWarehouseSchema | string | null); shippingWarehouse?: (ShippingWarehouseSchema | string | null);
billRequest?: (DealBillRequestSchema | null); billRequest?: (DealBillRequestSchema | null);
category?: (ServicePriceCategorySchema | null);
}; };

View File

@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DeletePriceCategoryRequest = {
id: number;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DeletePriceCategoryResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ServicePriceCategorySchema } from './ServicePriceCategorySchema';
export type GetAllPriceCategoriesResponse = {
priceCategories: Array<ServicePriceCategorySchema>;
};

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ServicePriceCategorySchema } from './ServicePriceCategorySchema';
export type ServiceCategoryPriceSchema = {
category: ServicePriceCategorySchema;
price: number;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ServicePriceCategorySchema = {
id: number;
name: string;
};

View File

@@ -2,6 +2,7 @@
/* istanbul ignore file */ /* istanbul ignore file */
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
import type { ServiceCategoryPriceSchema } from './ServiceCategoryPriceSchema';
import type { ServiceCategorySchema } from './ServiceCategorySchema'; import type { ServiceCategorySchema } from './ServiceCategorySchema';
import type { ServicePriceRangeSchema } from './ServicePriceRangeSchema'; import type { ServicePriceRangeSchema } from './ServicePriceRangeSchema';
export type ServiceSchema = { export type ServiceSchema = {
@@ -11,6 +12,7 @@ export type ServiceSchema = {
price: number; price: number;
serviceType: number; serviceType: number;
priceRanges: Array<ServicePriceRangeSchema>; priceRanges: Array<ServicePriceRangeSchema>;
categoryPrices: Array<ServiceCategoryPriceSchema>;
cost: (number | null); cost: (number | null);
}; };

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UpdatePriceCategoryRequest = {
id: number;
name: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UpdatePriceCategoryResponse = {
ok: boolean;
message: string;
};

View File

@@ -305,6 +305,27 @@ export class DealService {
}, },
}); });
} }
/**
* Get Detailed Deal Document
* @returns any Successful Response
* @throws ApiError
*/
public static getDealDocumentDetailed({
dealId,
}: {
dealId: number,
}): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'GET',
url: '/deal/detailedDocument/{deal_id}',
path: {
'deal_id': dealId,
},
errors: {
422: `Validation Error`,
},
});
}
/** /**
* Services Add * Services Add
* @returns DealAddServicesResponse Successful Response * @returns DealAddServicesResponse Successful Response

View File

@@ -3,8 +3,13 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
import type { BaseEnumListSchema } from '../models/BaseEnumListSchema'; import type { BaseEnumListSchema } from '../models/BaseEnumListSchema';
import type { CreatePriceCategoryRequest } from '../models/CreatePriceCategoryRequest';
import type { CreatePriceCategoryResponse } from '../models/CreatePriceCategoryResponse';
import type { CreateServicesKitRequest } from '../models/CreateServicesKitRequest'; import type { CreateServicesKitRequest } from '../models/CreateServicesKitRequest';
import type { CreateServicesKitResponse } from '../models/CreateServicesKitResponse'; import type { CreateServicesKitResponse } from '../models/CreateServicesKitResponse';
import type { DeletePriceCategoryRequest } from '../models/DeletePriceCategoryRequest';
import type { DeletePriceCategoryResponse } from '../models/DeletePriceCategoryResponse';
import type { GetAllPriceCategoriesResponse } from '../models/GetAllPriceCategoriesResponse';
import type { GetAllServicesKitsResponse } from '../models/GetAllServicesKitsResponse'; import type { GetAllServicesKitsResponse } from '../models/GetAllServicesKitsResponse';
import type { ServiceCreateCategoryRequest } from '../models/ServiceCreateCategoryRequest'; import type { ServiceCreateCategoryRequest } from '../models/ServiceCreateCategoryRequest';
import type { ServiceCreateCategoryResponse } from '../models/ServiceCreateCategoryResponse'; import type { ServiceCreateCategoryResponse } from '../models/ServiceCreateCategoryResponse';
@@ -16,6 +21,8 @@ import type { ServiceGetAllCategoriesResponse } from '../models/ServiceGetAllCat
import type { ServiceGetAllResponse } from '../models/ServiceGetAllResponse'; import type { ServiceGetAllResponse } from '../models/ServiceGetAllResponse';
import type { ServiceUpdateRequest } from '../models/ServiceUpdateRequest'; import type { ServiceUpdateRequest } from '../models/ServiceUpdateRequest';
import type { ServiceUpdateResponse } from '../models/ServiceUpdateResponse'; import type { ServiceUpdateResponse } from '../models/ServiceUpdateResponse';
import type { UpdatePriceCategoryRequest } from '../models/UpdatePriceCategoryRequest';
import type { UpdatePriceCategoryResponse } from '../models/UpdatePriceCategoryResponse';
import type { UpdateServicesKitRequest } from '../models/UpdateServicesKitRequest'; import type { UpdateServicesKitRequest } from '../models/UpdateServicesKitRequest';
import type { UpdateServicesKitResponse } from '../models/UpdateServicesKitResponse'; import type { UpdateServicesKitResponse } from '../models/UpdateServicesKitResponse';
import type { CancelablePromise } from '../core/CancelablePromise'; import type { CancelablePromise } from '../core/CancelablePromise';
@@ -186,4 +193,75 @@ export class ServiceService {
}, },
}); });
} }
/**
* Get All Price Categories
* @returns GetAllPriceCategoriesResponse Successful Response
* @throws ApiError
*/
public static getAllPriceCategories(): CancelablePromise<GetAllPriceCategoriesResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/service/price-categories/get-all',
});
}
/**
* Create Price Category
* @returns CreatePriceCategoryResponse Successful Response
* @throws ApiError
*/
public static createPriceCategory({
requestBody,
}: {
requestBody: CreatePriceCategoryRequest,
}): CancelablePromise<CreatePriceCategoryResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/service/price-categories/create',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Update Price Category
* @returns UpdatePriceCategoryResponse Successful Response
* @throws ApiError
*/
public static updatePriceCategory({
requestBody,
}: {
requestBody: UpdatePriceCategoryRequest,
}): CancelablePromise<UpdatePriceCategoryResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/service/price-categories/update',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Delete Price Category
* @returns DeletePriceCategoryResponse Successful Response
* @throws ApiError
*/
public static deletePriceCategory({
requestBody,
}: {
requestBody: DeletePriceCategoryRequest,
}): CancelablePromise<DeletePriceCategoryResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/service/price-categories/delete',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
} }

View File

@@ -1,99 +1,108 @@
import {Button, rem, Textarea, TextInput} from "@mantine/core"; import { Button, rem, Textarea, TextInput } from "@mantine/core";
import {QuickDeal} from "../../../types/QuickDeal.ts"; import { QuickDeal } from "../../../types/QuickDeal.ts";
import {FC} from "react"; import { FC } from "react";
import {useForm} from "@mantine/form"; import { useForm } from "@mantine/form";
import styles from './CreateDealForm.module.css'; import styles from "./CreateDealForm.module.css";
import ClientAutocomplete from "../../Selects/ClientAutocomplete/ClientAutocomplete.tsx"; import ClientAutocomplete from "../../Selects/ClientAutocomplete/ClientAutocomplete.tsx";
import {DateTimePicker} from "@mantine/dates"; import { DateTimePicker } from "@mantine/dates";
import ShippingWarehouseAutocomplete import ShippingWarehouseAutocomplete
from "../../Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx"; from "../../Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
import BaseMarketplaceSelect from "../../Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx"; import BaseMarketplaceSelect from "../../Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
import ServicePriceCategorySelect from "../../Selects/ServicePriceCategorySelect/ServicePriceCategorySelect.tsx";
type Props = { type Props = {
onSubmit: (quickDeal: QuickDeal) => void onSubmit: (quickDeal: QuickDeal) => void
onCancel: () => void; onCancel: () => void;
} }
const CreateDealFrom: FC<Props> = ({onSubmit, onCancel}) => { const CreateDealFrom: FC<Props> = ({ onSubmit, onCancel }) => {
const form = useForm<QuickDeal>({ const form = useForm<QuickDeal>({
initialValues: { initialValues: {
name: '', name: "",
clientName: '', clientName: "",
clientAddress: '', clientAddress: "",
comment: '', comment: "",
acceptanceDate: new Date(), acceptanceDate: new Date(),
shippingWarehouse: '', shippingWarehouse: "",
baseMarketplace: { baseMarketplace: {
key: "", key: "",
iconUrl: "", iconUrl: "",
name: "" name: "",
} },
} },
}); });
return ( return (
<form <form
style={{width: '100%'}} style={{ width: "100%" }}
onSubmit={form.onSubmit((values) => onSubmit(values))} onSubmit={form.onSubmit((values) => onSubmit(values))}
> >
<div style={{ <div style={{
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
width: '100%', width: "100%",
gap: rem(10), gap: rem(10),
padding: rem(10) padding: rem(10),
}}> }}>
<div className={styles['inputs']}> <div className={styles["inputs"]}>
<TextInput <TextInput
placeholder={'Название сделки'} placeholder={"Название сделки"}
{...form.getInputProps('name')} {...form.getInputProps("name")}
/> />
</div> </div>
<div className={styles['inputs']}> <div className={styles["inputs"]}>
<ClientAutocomplete <ClientAutocomplete
nameRestProps={form.getInputProps('clientName')} nameRestProps={form.getInputProps("clientName")}
/> />
</div> </div>
<div className={styles['inputs']}> <div className={styles["inputs"]}>
<BaseMarketplaceSelect <BaseMarketplaceSelect
rightSection={<></>} rightSection={<></>}
placeholder={"Базовый маркетплейс"} placeholder={"Базовый маркетплейс"}
{...form.getInputProps("baseMarketplace")} {...form.getInputProps("baseMarketplace")}
/> />
<ShippingWarehouseAutocomplete <ShippingWarehouseAutocomplete
{...form.getInputProps('shippingWarehouse')} {...form.getInputProps("shippingWarehouse")}
placeholder={'Склад отгрузки'} placeholder={"Склад отгрузки"}
/>
</div>
<div className={styles["inputs"]}>
<ServicePriceCategorySelect
rightSection={<></>}
placeholder={"Выберите категорию"}
{...form.getInputProps("category")}
/> />
</div> </div>
<div className={styles['inputs']}> <div className={styles["inputs"]}>
<Textarea <Textarea
autosize autosize
placeholder={'Комментарий'} placeholder={"Комментарий"}
minRows={2} minRows={2}
maxRows={4} maxRows={4}
{...form.getInputProps('comment')} {...form.getInputProps("comment")}
/> />
</div> </div>
<div className={styles['inputs']}> <div className={styles["inputs"]}>
<DateTimePicker <DateTimePicker
placeholder={'Дата приемки'} placeholder={"Дата приемки"}
{...form.getInputProps('acceptanceDate')} {...form.getInputProps("acceptanceDate")}
/> />
</div> </div>
<div className={styles['buttons']}> <div className={styles["buttons"]}>
<Button <Button
type={'submit'} type={"submit"}
>Добавить</Button> >Добавить</Button>
<Button <Button
variant={'outline'} variant={"outline"}
onClick={() => onCancel()} onClick={() => onCancel()}
>Отменить</Button> >Отменить</Button>
</div> </div>
</div> </div>
</form> </form>
) );
} };
export default CreateDealFrom; export default CreateDealFrom;

View File

@@ -0,0 +1,17 @@
import ObjectSelect, { ObjectSelectProps } from "../../ObjectSelect/ObjectSelect.tsx";
import { ServicePriceCategorySchema } from "../../../client";
import useServicePriceCategoriesList from "../../../pages/ServicesPage/hooks/useServicePriceCategoriesList.tsx";
type Props = Omit<ObjectSelectProps<ServicePriceCategorySchema>, "data">
const ServicePriceCategorySelect = (props: Props) => {
const { objects } = useServicePriceCategoriesList();
return (
<ObjectSelect
{...props}
data={objects}
/>
);
};
export default ServicePriceCategorySelect;

View File

@@ -1,11 +1,11 @@
import {ObjectSelectProps} from "../ObjectSelect/ObjectSelect.tsx"; import { ObjectSelectProps } from "../ObjectSelect/ObjectSelect.tsx";
import {ServiceSchema} from "../../client"; import { ServicePriceCategorySchema, ServiceSchema } from "../../client";
import {Flex, FlexProps, NumberInput, NumberInputProps, rem} from "@mantine/core"; import { Flex, FlexProps, NumberInput, NumberInputProps, rem } from "@mantine/core";
import {FC, useEffect, useRef, useState} from "react"; import { FC, useEffect, useRef, useState } from "react";
import ServiceSelectNew from "../Selects/ServiceSelectNew/ServiceSelectNew.tsx"; import ServiceSelectNew from "../Selects/ServiceSelectNew/ServiceSelectNew.tsx";
import {ServiceType} from "../../shared/enums/ServiceType.ts"; import { ServiceType } from "../../shared/enums/ServiceType.ts";
type ServiceProps = Omit<ObjectSelectProps<ServiceSchema>, 'data'>; type ServiceProps = Omit<ObjectSelectProps<ServiceSchema>, "data">;
type PriceProps = NumberInputProps; type PriceProps = NumberInputProps;
type Props = { type Props = {
@@ -15,6 +15,7 @@ type Props = {
containerProps: FlexProps, containerProps: FlexProps,
filterType?: ServiceType, filterType?: ServiceType,
lockOnEdit?: boolean lockOnEdit?: boolean
category?: ServicePriceCategorySchema
} }
const ServiceWithPriceInput: FC<Props> = ({ const ServiceWithPriceInput: FC<Props> = ({
serviceProps, serviceProps,
@@ -22,10 +23,11 @@ const ServiceWithPriceInput: FC<Props> = ({
quantity, quantity,
containerProps, containerProps,
filterType = ServiceType.PRODUCT_SERVICE, filterType = ServiceType.PRODUCT_SERVICE,
lockOnEdit = true lockOnEdit = true,
category,
}) => { }) => {
const [price, setPrice] = useState<number | undefined>( const [price, setPrice] = useState<number | undefined>(
typeof priceProps.value === 'number' ? priceProps.value : undefined); typeof priceProps.value === "number" ? priceProps.value : undefined);
const [service, setService] = useState<ServiceSchema | undefined>(serviceProps.value); const [service, setService] = useState<ServiceSchema | undefined>(serviceProps.value);
const isFirstRender = useRef(true); const isFirstRender = useRef(true);
const setPriceBasedOnQuantity = (): boolean => { const setPriceBasedOnQuantity = (): boolean => {
@@ -35,29 +37,40 @@ const ServiceWithPriceInput: FC<Props> = ({
setPrice(range.price); setPrice(range.price);
return true; return true;
} };
const setPriceBasedOnCategory = () => {
if (!category || !service) return false;
const categoryPrice = service.categoryPrices.find(categoryPrice => categoryPrice.category.id === category.id);
if (!categoryPrice) return false;
setPrice(categoryPrice.price);
return true;
};
const setPriceBasedOnService = () => { const setPriceBasedOnService = () => {
if (!service) return; if (!service) return;
// if category is set, we should not set price based on service
if (setPriceBasedOnCategory()) return;
if (setPriceBasedOnQuantity()) return; if (setPriceBasedOnQuantity()) return;
setPrice(service.price); setPrice(service.price);
} };
const onServiceManualChange = (service: ServiceSchema) => { const onServiceManualChange = (service: ServiceSchema) => {
setService(service); setService(service);
} };
const onPriceManualChange = (value: number | string) => { const onPriceManualChange = (value: number | string) => {
if (typeof value !== 'number') return; if (typeof value !== "number") return;
setPrice(value); setPrice(value);
} };
useEffect(() => { useEffect(() => {
if (isFirstRender.current && lockOnEdit) return; if (isFirstRender.current && lockOnEdit) return;
// we need to set price based on quantity only if category is not set, because category has higher priority
if (category) return;
setPriceBasedOnQuantity(); setPriceBasedOnQuantity();
}, [quantity]); }, [quantity]);
useEffect(() => { useEffect(() => {
if (isFirstRender.current && lockOnEdit) return; if (isFirstRender.current && lockOnEdit) return;
if (!priceProps.onChange || typeof price === 'undefined') return; if (!priceProps.onChange || typeof price === "undefined") return;
priceProps.onChange(price); priceProps.onChange(price);
}, [price]); }, [price]);
@@ -97,7 +110,7 @@ const ServiceWithPriceInput: FC<Props> = ({
/> />
</Flex> </Flex>
) );
} };
export default ServiceWithPriceInput; export default ServiceWithPriceInput;

View File

@@ -1,35 +1,35 @@
import ReactDOM from 'react-dom/client' import ReactDOM from "react-dom/client";
import {RouterProvider, createRouter} from '@tanstack/react-router' import { RouterProvider, createRouter } from "@tanstack/react-router";
import {routeTree} from './routeTree.gen' import { routeTree } from "./routeTree.gen";
import {MantineProvider} from "@mantine/core"; import { MantineProvider } from "@mantine/core";
import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {Provider} from "react-redux"; import { Provider } from "react-redux";
import {store} from "./redux/store.ts"; import { store } from "./redux/store.ts";
import '@mantine/core/styles.css'; import "@mantine/core/styles.css";
import '@mantine/notifications/styles.css'; import "@mantine/notifications/styles.css";
import '@mantine/dates/styles.css'; import "@mantine/dates/styles.css";
import 'mantine-react-table/styles.css'; import "mantine-react-table/styles.css";
import 'dayjs/locale/ru'; import "dayjs/locale/ru";
import './main.css'; import "./main.css";
import {Notifications} from "@mantine/notifications"; import { Notifications } from "@mantine/notifications";
import {ModalsProvider} from "@mantine/modals"; import { ModalsProvider } from "@mantine/modals";
import {OpenAPI} from "./client"; import { OpenAPI } from "./client";
import {DatesProvider} from "@mantine/dates"; import { DatesProvider } from "@mantine/dates";
import {modals} from "./modals/modals.ts"; import { modals } from "./modals/modals.ts";
import TasksProvider from "./providers/TasksProvider/TasksProvider.tsx"; import TasksProvider from "./providers/TasksProvider/TasksProvider.tsx";
// Configuring router // Configuring router
const router = createRouter({routeTree}) const router = createRouter({ routeTree });
declare module '@tanstack/react-router' { declare module "@tanstack/react-router" {
interface Register { interface Register {
router: typeof router router: typeof router;
} }
} }
declare module '@mantine/modals' { declare module "@mantine/modals" {
export interface MantineModalsOverride { export interface MantineModalsOverride {
modals: typeof modals; modals: typeof modals;
} }
@@ -39,23 +39,23 @@ declare module '@mantine/modals' {
const queryClient = new QueryClient(); const queryClient = new QueryClient();
// Configuring OpenAPI // Configuring OpenAPI
OpenAPI.BASE = import.meta.env.VITE_API_URL OpenAPI.BASE = import.meta.env.VITE_API_URL;
OpenAPI.TOKEN = JSON.parse(localStorage.getItem('authState') || "{}")['accessToken']; OpenAPI.TOKEN = JSON.parse(localStorage.getItem("authState") || "{}")["accessToken"];
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<Provider store={store}> <Provider store={store}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<MantineProvider defaultColorScheme={"dark"}> <MantineProvider defaultColorScheme={"dark"}>
<ModalsProvider modals={modals}> <ModalsProvider labels={{ confirm: "Да", cancel: "Нет" }} modals={modals}>
<DatesProvider settings={{locale: 'ru'}}> <DatesProvider settings={{ locale: "ru" }}>
<TasksProvider> <TasksProvider>
<RouterProvider router={router}/> <RouterProvider router={router} />
<Notifications/> <Notifications />
</TasksProvider> </TasksProvider>
</DatesProvider> </DatesProvider>
</ModalsProvider> </ModalsProvider>
</MantineProvider> </MantineProvider>
</QueryClientProvider> </QueryClientProvider>
</Provider> </Provider>,
) );

View File

@@ -21,6 +21,7 @@ import ServicesKitSelectModal from "./ServicesKitSelectModal/ServicesKitSelectMo
import SelectDealProductsModal from "../pages/LeadsPage/modals/SelectDealProductsModal.tsx"; import SelectDealProductsModal from "../pages/LeadsPage/modals/SelectDealProductsModal.tsx";
import ShippingWarehouseForm from "../pages/ShippingWarehousesPage/modals/ShippingWarehouseForm.tsx"; import ShippingWarehouseForm from "../pages/ShippingWarehousesPage/modals/ShippingWarehouseForm.tsx";
import MarketplaceFormModal from "../pages/MarketplacesPage/modals/MarketplaceFormModal/MarketplaceFormModal.tsx"; import MarketplaceFormModal from "../pages/MarketplacesPage/modals/MarketplaceFormModal/MarketplaceFormModal.tsx";
import ServicePriceCategoryForm from "../pages/ServicesPage/modals/ServicePriceCategoryForm.tsx";
export const modals = { export const modals = {
enterDeadline: EnterDeadlineModal, enterDeadline: EnterDeadlineModal,
@@ -44,5 +45,6 @@ export const modals = {
servicesKitSelectModal: ServicesKitSelectModal, servicesKitSelectModal: ServicesKitSelectModal,
selectDealProductsModal: SelectDealProductsModal, selectDealProductsModal: SelectDealProductsModal,
shippingWarehouseForm: ShippingWarehouseForm, shippingWarehouseForm: ShippingWarehouseForm,
marketplaceFormModal: MarketplaceFormModal marketplaceFormModal: MarketplaceFormModal,
} servicePriceCategoryForm: ServicePriceCategoryForm,
};

View File

@@ -1,22 +1,22 @@
import {Box, Drawer, rem, Tabs, Text} from "@mantine/core"; import { Box, Drawer, rem, Tabs, Text } from "@mantine/core";
import {FC, useEffect, useRef} from "react"; import { FC, useEffect, useRef } from "react";
import DealServicesTable from "../../components/DealServicesTable/DealServicesTable.tsx"; import DealServicesTable from "../../components/DealServicesTable/DealServicesTable.tsx";
import {useDealPageContext} from "../../contexts/DealPageContext.tsx"; import { useDealPageContext } from "../../contexts/DealPageContext.tsx";
import {DealProductSchema, DealService, DealServiceSchema} from "../../../../client"; import { DealProductSchema, DealService, DealServiceSchema } from "../../../../client";
import {notifications} from "../../../../shared/lib/notifications.ts"; import { notifications } from "../../../../shared/lib/notifications.ts";
import {modals} from "@mantine/modals"; import { modals } from "@mantine/modals";
import {BaseTableRef} from "../../../../components/BaseTable/BaseTable.tsx"; import { BaseTableRef } from "../../../../components/BaseTable/BaseTable.tsx";
import DealProductsTable from "../../components/DealProductsTable/DealProductsTable.tsx"; import DealProductsTable from "../../components/DealProductsTable/DealProductsTable.tsx";
import {IconBox, IconCalendarUser, IconSettings} from "@tabler/icons-react"; import { IconBox, IconCalendarUser, IconSettings } from "@tabler/icons-react";
import DealStatusChangeTable from "../../components/DealStatusChangeTable/DealStatusChangeTable.tsx"; import DealStatusChangeTable from "../../components/DealStatusChangeTable/DealStatusChangeTable.tsx";
import DealEditDrawerGeneralTab from "./tabs/DealEditDrawerGeneralTab.tsx"; import DealEditDrawerGeneralTab from "./tabs/DealEditDrawerGeneralTab.tsx";
import {useQueryClient} from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import ProductAndServiceTab from "../../tabs/ProductAndServiceTab/ProductAndServiceTab.tsx"; import ProductAndServiceTab from "../../tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
import {motion} from "framer-motion"; import { motion } from "framer-motion";
// import styles from './DealEditDrawer.module.css'; // import styles from './DealEditDrawer.module.css';
const useDealServicesTableState = () => { const useDealServicesTableState = () => {
const {selectedDeal, setSelectedDeal} = useDealPageContext(); const { selectedDeal, setSelectedDeal } = useDealPageContext();
const tableRef = useRef<BaseTableRef<DealServiceSchema>>(null); const tableRef = useRef<BaseTableRef<DealServiceSchema>>(null);
const onServiceUpdate = (service: DealServiceSchema) => { const onServiceUpdate = (service: DealServiceSchema) => {
@@ -24,18 +24,18 @@ const useDealServicesTableState = () => {
DealService.updateDealService({ DealService.updateDealService({
requestBody: { requestBody: {
dealId: selectedDeal.id, dealId: selectedDeal.id,
service service,
} },
}).then(async ({ok, message}) => { }).then(async ({ ok, message }) => {
if (!ok) { if (!ok) {
notifications.guess(ok, {message}); notifications.guess(ok, { message });
return; return;
} }
await DealService.getDealById({dealId: selectedDeal.id}) await DealService.getDealById({ dealId: selectedDeal.id })
.then(setSelectedDeal) .then(setSelectedDeal);
}) });
} };
const onServiceDelete = (service: DealServiceSchema) => { const onServiceDelete = (service: DealServiceSchema) => {
if (!selectedDeal) return; if (!selectedDeal) return;
modals.openConfirmModal({ modals.openConfirmModal({
@@ -56,23 +56,23 @@ const useDealServicesTableState = () => {
DealService.deleteDealService({ DealService.deleteDealService({
requestBody: { requestBody: {
dealId: selectedDeal.id, dealId: selectedDeal.id,
serviceId: service.service.id serviceId: service.service.id,
} },
}).then(async ({ok, message}) => { }).then(async ({ ok, message }) => {
if (!ok) { if (!ok) {
notifications.guess(ok, {message}); notifications.guess(ok, { message });
return; return;
} }
await DealService.getDealById({dealId: selectedDeal.id}) await DealService.getDealById({ dealId: selectedDeal.id })
.then(setSelectedDeal) .then(setSelectedDeal);
}) });
}, },
labels: { labels: {
cancel: "Отмена", cancel: "Отмена",
confirm: "Удалить" confirm: "Удалить",
} },
}) });
} };
const onServiceCreate = (service: DealServiceSchema) => { const onServiceCreate = (service: DealServiceSchema) => {
if (!selectedDeal) return; if (!selectedDeal) return;
DealService.addDealService({ DealService.addDealService({
@@ -80,17 +80,17 @@ const useDealServicesTableState = () => {
dealId: selectedDeal.id, dealId: selectedDeal.id,
serviceId: service.service.id, serviceId: service.service.id,
quantity: service.quantity, quantity: service.quantity,
price: service.price price: service.price,
} },
}).then(async ({ok, message}) => { }).then(async ({ ok, message }) => {
if (!ok) { if (!ok) {
notifications.guess(ok, {message}); notifications.guess(ok, { message });
return; return;
} }
await DealService.getDealById({dealId: selectedDeal.id}) await DealService.getDealById({ dealId: selectedDeal.id })
.then(setSelectedDeal) .then(setSelectedDeal);
}) });
} };
const onsServiceMultipleDelete = (items: DealServiceSchema[]) => { const onsServiceMultipleDelete = (items: DealServiceSchema[]) => {
if (!selectedDeal) return; if (!selectedDeal) return;
modals.openConfirmModal({ modals.openConfirmModal({
@@ -106,23 +106,23 @@ const useDealServicesTableState = () => {
DealService.deleteMultipleDealServices({ DealService.deleteMultipleDealServices({
requestBody: { requestBody: {
dealId: selectedDeal.id, dealId: selectedDeal.id,
serviceIds: items.map(item => item.service.id) serviceIds: items.map(item => item.service.id),
} },
}).then(async ({ok, message}) => { }).then(async ({ ok, message }) => {
if (!ok) { if (!ok) {
notifications.guess(ok, {message}); notifications.guess(ok, { message });
return; return;
} }
await DealService.getDealById({dealId: selectedDeal.id}) await DealService.getDealById({ dealId: selectedDeal.id })
.then(setSelectedDeal) .then(setSelectedDeal);
}) });
}, },
labels: { labels: {
cancel: "Отмена", cancel: "Отмена",
confirm: "Удалить" confirm: "Удалить",
} },
}) });
} };
return { return {
onServiceUpdate, onServiceUpdate,
@@ -130,9 +130,9 @@ const useDealServicesTableState = () => {
onServiceCreate, onServiceCreate,
onsServiceMultipleDelete, onsServiceMultipleDelete,
tableRef, tableRef,
services: selectedDeal?.services || [] services: selectedDeal?.services || [],
} };
} };
const DealEditDrawerServicesTable = () => { const DealEditDrawerServicesTable = () => {
const { const {
services, services,
@@ -140,7 +140,7 @@ const DealEditDrawerServicesTable = () => {
onServiceCreate, onServiceCreate,
onServiceUpdate, onServiceUpdate,
onServiceDelete, onServiceDelete,
onsServiceMultipleDelete onsServiceMultipleDelete,
} = useDealServicesTableState(); } = useDealServicesTableState();
return (<DealServicesTable return (<DealServicesTable
@@ -150,26 +150,26 @@ const DealEditDrawerServicesTable = () => {
onDelete={onServiceDelete} onDelete={onServiceDelete}
onCreate={onServiceCreate} onCreate={onServiceCreate}
onMultipleDelete={onsServiceMultipleDelete} onMultipleDelete={onsServiceMultipleDelete}
/>) />);
} };
const useDealProductTableState = () => { const useDealProductTableState = () => {
const {selectedDeal, setSelectedDeal} = useDealPageContext(); const { selectedDeal, setSelectedDeal } = useDealPageContext();
const onProductUpdate = (product: DealProductSchema) => { const onProductUpdate = (product: DealProductSchema) => {
if (!selectedDeal) return; if (!selectedDeal) return;
DealService.updateDealProduct({ DealService.updateDealProduct({
requestBody: { requestBody: {
dealId: selectedDeal.id, dealId: selectedDeal.id,
product: product product: product,
} },
}).then(async ({ok, message}) => { }).then(async ({ ok, message }) => {
notifications.guess(ok, {message}); notifications.guess(ok, { message });
if (!ok) return; if (!ok) return;
await DealService.getDealById({dealId: selectedDeal.id}) await DealService.getDealById({ dealId: selectedDeal.id })
.then(setSelectedDeal) .then(setSelectedDeal);
}) });
} };
const onProductDelete = (product: DealProductSchema) => { const onProductDelete = (product: DealProductSchema) => {
if (!selectedDeal) return; if (!selectedDeal) return;
modals.openConfirmModal({ modals.openConfirmModal({
@@ -190,39 +190,39 @@ const useDealProductTableState = () => {
DealService.deleteDealProduct({ DealService.deleteDealProduct({
requestBody: { requestBody: {
dealId: selectedDeal.id, dealId: selectedDeal.id,
productId: product.product.id productId: product.product.id,
} },
}).then(async ({ok, message}) => { }).then(async ({ ok, message }) => {
if (!ok) { if (!ok) {
notifications.guess(ok, {message}); notifications.guess(ok, { message });
return; return;
} }
await DealService.getDealById({dealId: selectedDeal.id}) await DealService.getDealById({ dealId: selectedDeal.id })
.then(setSelectedDeal) .then(setSelectedDeal);
}) });
}, },
labels: { labels: {
cancel: "Отмена", cancel: "Отмена",
confirm: "Удалить" confirm: "Удалить",
} },
}) });
} };
const onProductCreate = (product: DealProductSchema) => { const onProductCreate = (product: DealProductSchema) => {
if (!selectedDeal) return; if (!selectedDeal) return;
DealService.addDealProduct({ DealService.addDealProduct({
requestBody: { requestBody: {
dealId: selectedDeal.id, dealId: selectedDeal.id,
product: product product: product,
} },
}).then(async ({ok, message}) => { }).then(async ({ ok, message }) => {
if (!ok) { if (!ok) {
notifications.guess(ok, {message}); notifications.guess(ok, { message });
return; return;
} }
await DealService.getDealById({dealId: selectedDeal.id}) await DealService.getDealById({ dealId: selectedDeal.id })
.then(setSelectedDeal) .then(setSelectedDeal);
}) });
} };
const onProductMultipleDelete = (items: DealProductSchema[]) => { const onProductMultipleDelete = (items: DealProductSchema[]) => {
if (!selectedDeal) return; if (!selectedDeal) return;
modals.openConfirmModal({ modals.openConfirmModal({
@@ -238,32 +238,32 @@ const useDealProductTableState = () => {
DealService.deleteMultipleDealProducts({ DealService.deleteMultipleDealProducts({
requestBody: { requestBody: {
dealId: selectedDeal.id, dealId: selectedDeal.id,
productIds: items.map(item => item.product.id) productIds: items.map(item => item.product.id),
} },
}).then(async ({ok, message}) => { }).then(async ({ ok, message }) => {
if (!ok) { if (!ok) {
notifications.guess(ok, {message}); notifications.guess(ok, { message });
return; return;
} }
await DealService.getDealById({dealId: selectedDeal.id}) await DealService.getDealById({ dealId: selectedDeal.id })
.then(setSelectedDeal) .then(setSelectedDeal);
}) });
}, },
labels: { labels: {
cancel: "Отмена", cancel: "Отмена",
confirm: "Удалить" confirm: "Удалить",
} },
}) });
} };
return { return {
clientId: selectedDeal?.clientId || -1, clientId: selectedDeal?.clientId || -1,
products: selectedDeal?.products || [], products: selectedDeal?.products || [],
onProductUpdate, onProductUpdate,
onProductDelete, onProductDelete,
onProductCreate, onProductCreate,
onProductMultipleDelete onProductMultipleDelete,
} };
} };
const DealEditDrawerProductsTable = () => { const DealEditDrawerProductsTable = () => {
const { const {
products, products,
@@ -283,79 +283,86 @@ const DealEditDrawerProductsTable = () => {
onCreate={onProductCreate} onCreate={onProductCreate}
/> />
) );
} };
const useDealStatusChangeState = () => { const useDealStatusChangeState = () => {
const {selectedDeal} = useDealPageContext(); const { selectedDeal } = useDealPageContext();
return { return {
statusHistory: selectedDeal?.statusHistory || [] statusHistory: selectedDeal?.statusHistory || [],
} };
} };
const DealEditDrawerStatusChangeTable = () => { const DealEditDrawerStatusChangeTable = () => {
const {statusHistory} = useDealStatusChangeState(); const { statusHistory } = useDealStatusChangeState();
return ( return (
<DealStatusChangeTable <DealStatusChangeTable
items={statusHistory} items={statusHistory}
/>) />);
} };
const useDealEditDrawerState = () => { const useDealEditDrawerState = () => {
const {selectedDeal, setSelectedDeal} = useDealPageContext(); const { selectedDeal, setSelectedDeal } = useDealPageContext();
return { return {
isVisible: selectedDeal !== undefined, isVisible: selectedDeal !== undefined,
onClose: () => setSelectedDeal(undefined) onClose: () => setSelectedDeal(undefined),
} };
} };
const DealEditDrawer: FC = () => { const DealEditDrawer: FC = () => {
const {isVisible, onClose} = useDealEditDrawerState(); const { isVisible, onClose } = useDealEditDrawerState();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
useEffect(() => { useEffect(() => {
if (isVisible) return; if (isVisible) return;
queryClient.invalidateQueries({queryKey: ["getDealSummaries"]}) queryClient.invalidateQueries({ queryKey: ["getDealSummaries"] });
}, [isVisible]); }, [isVisible]);
return ( return (
<Drawer <Drawer
size={"calc(100vw - 150px)"} size={"calc(100vw - 150px)"}
position={"right"} position={"right"}
onClose={onClose} onClose={onClose}
removeScrollProps={{allowPinchZoom: true}} removeScrollProps={{ allowPinchZoom: true }}
withCloseButton={false} withCloseButton={false}
opened={isVisible} opened={isVisible}
styles={{body: {height: '100%'}}} styles={{
body: {
height: "100%",
display: "flex",
flexDirection: "column", gap: rem(10),
},
}}
> >
<Tabs <Tabs
defaultValue={"general"} defaultValue={"general"}
h={'100%'} flex={1}
variant={"outline"} variant={"outline"}
orientation={"vertical"} orientation={"vertical"}
keepMounted={false} keepMounted={false}
> >
<Tabs.List <Tabs.List
> >
<Tabs.Tab value={"general"} leftSection={<IconSettings/>}> <Tabs.Tab value={"general"} leftSection={<IconSettings />}>
Общее Общее
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value={"history"} leftSection={<IconCalendarUser/>}> <Tabs.Tab value={"history"} leftSection={<IconCalendarUser />}>
История История
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value={"servicesAndProducts"} leftSection={<IconBox/>}> <Tabs.Tab value={"servicesAndProducts"} leftSection={<IconBox />}>
Товары и услуги Товары и услуги
</Tabs.Tab> </Tabs.Tab>
</Tabs.List> </Tabs.List>
<Tabs.Panel value={"general"}> <Tabs.Panel value={"general"}>
<motion.div <motion.div
initial={{opacity: 0}} initial={{ opacity: 0 }}
animate={{opacity: 1}} animate={{ opacity: 1 }}
transition={{duration: 0.2}} transition={{ duration: 0.2 }}
> >
<Box h={"100%"} w={"100%"} p={rem(10)}> <Box h={"100%"} w={"100%"} p={rem(10)}>
<DealEditDrawerGeneralTab/> <DealEditDrawerGeneralTab />
</Box> </Box>
</motion.div> </motion.div>
@@ -364,40 +371,40 @@ const DealEditDrawer: FC = () => {
<Tabs.Panel value={"history"}> <Tabs.Panel value={"history"}>
<motion.div <motion.div
initial={{opacity: 0}} initial={{ opacity: 0 }}
animate={{opacity: 1}} animate={{ opacity: 1 }}
transition={{duration: 0.2}} transition={{ duration: 0.2 }}
> >
<Box p={rem(10)}> <Box p={rem(10)}>
<DealEditDrawerStatusChangeTable/> <DealEditDrawerStatusChangeTable />
</Box> </Box>
</motion.div> </motion.div>
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value={"servicesAndProducts"}> <Tabs.Panel value={"servicesAndProducts"}>
<motion.div <motion.div
initial={{opacity: 0}} initial={{ opacity: 0 }}
animate={{opacity: 1}} animate={{ opacity: 1 }}
transition={{duration: 0.2}} transition={{ duration: 0.2 }}
> >
<Box p={rem(10)}> <Box p={rem(10)}>
<ProductAndServiceTab/> <ProductAndServiceTab />
</Box> </Box>
</motion.div> </motion.div>
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value={"services"}> <Tabs.Panel value={"services"}>
<Box p={rem(10)}> <Box p={rem(10)}>
<DealEditDrawerServicesTable/> <DealEditDrawerServicesTable />
</Box> </Box>
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value={"products"}> <Tabs.Panel value={"products"}>
<Box p={rem(10)}> <Box p={rem(10)}>
<DealEditDrawerProductsTable/> <DealEditDrawerProductsTable />
</Box> </Box>
</Tabs.Panel> </Tabs.Panel>
</Tabs> </Tabs>
</Drawer> </Drawer>
); );
} };
export default DealEditDrawer; export default DealEditDrawer;

View File

@@ -103,7 +103,7 @@ const Content: FC<Props> = ({ deal }) => {
}; };
return ( return (
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}> <form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
<Flex direction={"column"}> <Flex direction={"column"} justify={"space-between"} h={"100%"}>
<Fieldset legend={`Общие параметры [ID: ${deal.id}]`}> <Fieldset legend={`Общие параметры [ID: ${deal.id}]`}>
<Flex direction={"column"} gap={rem(10)}> <Flex direction={"column"} gap={rem(10)}>
<TextInput <TextInput
@@ -122,6 +122,15 @@ const Content: FC<Props> = ({ deal }) => {
placeholder={"Текущий статус"} placeholder={"Текущий статус"}
label={"Текущий статус"} label={"Текущий статус"}
value={DealStatusDictionary[deal.currentStatus as DealStatus]} /> value={DealStatusDictionary[deal.currentStatus as DealStatus]} />
{deal.category && (
<TextInput
disabled
placeholder={"Категория"}
label={"Категория"}
value={deal.category.name}
/>
)}
<Textarea <Textarea
label={"Коментарий к сделке"} label={"Коментарий к сделке"}
placeholder={"Введите коментарий к сделке"} placeholder={"Введите коментарий к сделке"}
@@ -141,34 +150,6 @@ const Content: FC<Props> = ({ deal }) => {
/> />
</Flex> </Flex>
</Fieldset> </Fieldset>
<Fieldset legend={"Клиент"}>
<TextInput
disabled
placeholder={"Название"}
label={"Название"}
value={deal.client.name}
/>
<TextInput
placeholder={"Введите телефон"}
label={"Телефон клиента"}
{...form.getInputProps("client.details.phoneNumber")}
/>
<TextInput
placeholder={"Введите email"}
label={"Email"}
{...form.getInputProps("client.details.email")}
/>
<TextInput
placeholder={"Введите телеграм"}
label={"Телеграм"}
{...form.getInputProps("client.details.telegram")}
/>
<TextInput
placeholder={"Введите ИНН"}
label={"ИНН"}
{...form.getInputProps("client.details.inn")}
/>
</Fieldset>
<Flex mt={"md"} gap={rem(10)} align={"center"} justify={"flex-end"}> <Flex mt={"md"} gap={rem(10)} align={"center"} justify={"flex-end"}>
<Flex align={"center"} gap={rem(10)} justify={"center"}> <Flex align={"center"} gap={rem(10)} justify={"center"}>
@@ -199,10 +180,7 @@ const Content: FC<Props> = ({ deal }) => {
: :
<ButtonCopyControlled <ButtonCopyControlled
onCopyClick={() => { onCopyClick={() => {
// get current datetime for filename, replaced dots with _
const date = getCurrentDateTimeForFilename(); const date = getCurrentDateTimeForFilename();
FileSaver.saveAs(`${import.meta.env.VITE_API_URL}/deal/document/${deal.id}`, FileSaver.saveAs(`${import.meta.env.VITE_API_URL}/deal/document/${deal.id}`,
`bill_${deal.id}_${date}.pdf`); `bill_${deal.id}_${date}.pdf`);
}} }}

View File

@@ -1,12 +1,10 @@
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, DealProductServiceSchema} from "../../../client"; import { DealProductSchema, DealProductServiceSchema } from "../../../client";
import {useForm} from "@mantine/form"; import { useForm } from "@mantine/form";
import {Fieldset, NumberInput} from "@mantine/core"; import { 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 {omit} from "lodash";
import {BaseFormInputProps} from "../../../types/utils.ts";
type RestProps = { type RestProps = {
clientId: number; clientId: number;
@@ -65,13 +63,13 @@ const AddDealProductModal = ({
min={1} min={1}
{...form.getInputProps('quantity')} {...form.getInputProps('quantity')}
/> />
<Fieldset legend={'Услуги'}> {/*<Fieldset legend={'Услуги'}>*/}
<DealProductServiceTable {/* <DealProductServiceTable*/}
quantity={form.values.quantity || 1} {/* quantity={form.values.quantity || 1}*/}
{...form.getInputProps('services') as {/* {...form.getInputProps('services') as*/}
BaseFormInputProps<DealProductServiceSchema[]>} {/* BaseFormInputProps<DealProductServiceSchema[]>}*/}
/> {/* />*/}
</Fieldset> {/*</Fieldset>*/}
</> </>

View File

@@ -1,6 +1,6 @@
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 {DealServiceSchema} from "../../../client"; import { DealServiceSchema, ServicePriceCategorySchema } from "../../../client";
import {useForm} from "@mantine/form"; import {useForm} from "@mantine/form";
import {ComboboxItem, ComboboxItemGroup, NumberInput, OptionsFilter} from "@mantine/core"; import {ComboboxItem, ComboboxItemGroup, NumberInput, OptionsFilter} from "@mantine/core";
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx"; import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
@@ -10,6 +10,7 @@ import {RootState} from "../../../redux/store.ts";
type RestProps = { type RestProps = {
serviceIds?: number[]; serviceIds?: number[];
category?: ServicePriceCategorySchema;
} }
type Props = CreateEditFormProps<Partial<DealServiceSchema>> & RestProps; type Props = CreateEditFormProps<Partial<DealServiceSchema>> & RestProps;
const AddDealServiceModal = ({ const AddDealServiceModal = ({
@@ -18,7 +19,7 @@ const AddDealServiceModal = ({
innerProps innerProps
}: ContextModalProps<Props>) => { }: ContextModalProps<Props>) => {
const authState = useSelector((state: RootState) => state.auth); const authState = useSelector((state: RootState) => state.auth);
console.log(innerProps.category)
const isEditing = 'element' in innerProps; const isEditing = 'element' in innerProps;
const form = useForm<Partial<DealServiceSchema>>({ const form = useForm<Partial<DealServiceSchema>>({
initialValues: isEditing ? innerProps.element : { initialValues: isEditing ? innerProps.element : {
@@ -54,6 +55,7 @@ const AddDealServiceModal = ({
<BaseFormModal.Body> <BaseFormModal.Body>
<> <>
<ServiceWithPriceInput <ServiceWithPriceInput
category={innerProps.category}
serviceProps={{ serviceProps={{
...form.getInputProps('service'), ...form.getInputProps('service'),
label: "Услуга", label: "Услуга",

View File

@@ -1,42 +1,43 @@
import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx"; import BaseFormModal, { CreateEditFormProps } from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
import {DealProductServiceSchema, ServiceSchema} from "../../../client"; import { DealProductServiceSchema, ServicePriceCategorySchema, ServiceSchema } from "../../../client";
import {ContextModalProps} from "@mantine/modals"; import { ContextModalProps } from "@mantine/modals";
import {useForm, UseFormReturnType} from "@mantine/form"; import { useForm, UseFormReturnType } from "@mantine/form";
import {isNil, isNumber} from "lodash"; import { isNil, isNumber } from "lodash";
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx"; import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
import {Flex} from "@mantine/core"; import { Flex } from "@mantine/core";
import {ServiceType} from "../../../shared/enums/ServiceType.ts"; import { ServiceType } from "../../../shared/enums/ServiceType.ts";
import {useSelector} from "react-redux"; import { useSelector } from "react-redux";
import {RootState} from "../../../redux/store.ts"; import { RootState } from "../../../redux/store.ts";
type RestProps = { type RestProps = {
quantity: number; quantity: number;
serviceIds: number[]; serviceIds: number[];
category?: ServicePriceCategorySchema;
} }
type Props = CreateEditFormProps<DealProductServiceSchema> & RestProps; type Props = CreateEditFormProps<DealProductServiceSchema> & RestProps;
const ProductServiceFormModal = ({ const ProductServiceFormModal = ({
context, context,
id, innerProps id, innerProps,
}: ContextModalProps<Props>) => { }: ContextModalProps<Props>) => {
const authState = useSelector((state: RootState) => state.auth); const authState = useSelector((state: RootState) => state.auth);
const isEditing = 'onChange' in innerProps; const isEditing = "onChange" in innerProps;
const initialValues: Partial<DealProductServiceSchema> = isEditing ? innerProps.element : { const initialValues: Partial<DealProductServiceSchema> = isEditing ? innerProps.element : {
service: undefined, service: undefined,
price: undefined, price: undefined,
employees: [] employees: [],
} };
const form = useForm<Partial<DealProductServiceSchema>>({ const form = useForm<Partial<DealProductServiceSchema>>({
initialValues, initialValues,
validate: { validate: {
service: (service?: ServiceSchema) => isNil(service) || service.id < 0 ? 'Укажите услугу' : null, service: (service?: ServiceSchema) => isNil(service) || service.id < 0 ? "Укажите услугу" : null,
price: (price?: number) => !isNumber(price) || price < 0 ? 'Укажите цену' : null price: (price?: number) => !isNumber(price) || price < 0 ? "Укажите цену" : null,
} },
}) });
const onClose = () => { const onClose = () => {
context.closeContextModal(id); context.closeContextModal(id);
} };
return ( return (
<BaseFormModal <BaseFormModal
{...innerProps} {...innerProps}
@@ -49,25 +50,26 @@ const ProductServiceFormModal = ({
<Flex w={"100%"}> <Flex w={"100%"}>
<ServiceWithPriceInput <ServiceWithPriceInput
category={innerProps.category}
serviceProps={{ serviceProps={{
...form.getInputProps('service'), ...form.getInputProps("service"),
label: "Услуга", label: "Услуга",
placeholder: "Выберите услугу", placeholder: "Выберите услугу",
disabled: isEditing, disabled: isEditing,
filterBy: (item) => !innerProps.serviceIds.includes(item.id) || isEditing, filterBy: (item) => !innerProps.serviceIds.includes(item.id) || isEditing,
style: {width: "100%"} style: { width: "100%" },
}} }}
priceProps={{ priceProps={{
...form.getInputProps('price'), ...form.getInputProps("price"),
label: "Цена", label: "Цена",
placeholder: "Введите цену", placeholder: "Введите цену",
style: {width: "100%"}, style: { width: "100%" },
disabled: authState.isGuest disabled: authState.isGuest,
}} }}
filterType={ServiceType.PRODUCT_SERVICE} filterType={ServiceType.PRODUCT_SERVICE}
containerProps={{ containerProps={{
direction: "column", direction: "column",
style: {width: "100%"} style: { width: "100%" },
}} }}
lockOnEdit={isEditing} lockOnEdit={isEditing}
@@ -79,6 +81,6 @@ const ProductServiceFormModal = ({
</BaseFormModal.Body> </BaseFormModal.Body>
</BaseFormModal> </BaseFormModal>
) );
} };
export default ProductServiceFormModal; export default ProductServiceFormModal;

View File

@@ -1,24 +1,24 @@
import {CRUDTableProps} from "../../../../../../types/CRUDTable.tsx"; import { CRUDTableProps } from "../../../../../../types/CRUDTable.tsx";
import {DealServiceSchema, GetServiceKitSchema, UserSchema} from "../../../../../../client"; import { DealServiceSchema, GetServiceKitSchema, UserSchema } from "../../../../../../client";
import {FC, useState} from "react"; import { FC, useState } from "react";
import {ActionIcon, Button, Flex, Modal, NumberInput, rem, Text, Title, Tooltip} from "@mantine/core"; import { ActionIcon, Button, Flex, Modal, NumberInput, rem, Text, Title, Tooltip } from "@mantine/core";
import {IconTrash, IconUsersGroup} from "@tabler/icons-react"; import { IconTrash, IconUsersGroup } from "@tabler/icons-react";
import {modals} from "@mantine/modals"; import { modals } from "@mantine/modals";
import {isNumber} from "lodash"; import { isNumber } from "lodash";
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx"; import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx";
import {ServiceType} from "../../../../../../shared/enums/ServiceType.ts"; import { ServiceType } from "../../../../../../shared/enums/ServiceType.ts";
import {useSelector} from "react-redux"; import { useSelector } from "react-redux";
import {RootState} from "../../../../../../redux/store.ts"; import { RootState } from "../../../../../../redux/store.ts";
import useDealProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx"; import useDealProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
type RestProps = { type RestProps = {
onKitAdd?: (kit: GetServiceKitSchema) => void onKitAdd?: (kit: GetServiceKitSchema) => void
}; };
type Props = CRUDTableProps<DealServiceSchema> & RestProps; type Props = CRUDTableProps<DealServiceSchema> & RestProps;
const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKitAdd}) => { const DealServicesTable: FC<Props> = ({ items, onDelete, onCreate, onChange, onKitAdd }) => {
const authState = useSelector((state: RootState) => state.auth); const authState = useSelector((state: RootState) => state.auth);
const {dealState} = useDealProductAndServiceTabState(); const { dealState } = useDealProductAndServiceTabState();
const isLocked = Boolean(dealState.deal?.billRequest); const isLocked = Boolean(dealState.deal?.billRequest);
const [currentService, setCurrentService] = useState<DealServiceSchema | undefined>(); const [currentService, setCurrentService] = useState<DealServiceSchema | undefined>();
@@ -27,68 +27,70 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
const onDeleteClick = (item: DealServiceSchema) => { const onDeleteClick = (item: DealServiceSchema) => {
if (!onDelete) return; if (!onDelete) return;
onDelete(item); onDelete(item);
} };
const onCreateClick = () => { const onCreateClick = () => {
if (!onCreate) return; if (!onCreate) return;
console.log("228")
const serviceIds = items.map(service => service.service.id); const serviceIds = items.map(service => service.service.id);
modals.openContextModal({ modals.openContextModal({
modal: "addDealService", modal: "addDealService",
innerProps: { innerProps: {
onCreate: onCreate, onCreate: onCreate,
serviceIds serviceIds,
category: dealState.deal?.category || undefined,
}, },
withCloseButton: false withCloseButton: false,
}) });
} };
const onQuantityChange = (item: DealServiceSchema, quantity: number) => { const onQuantityChange = (item: DealServiceSchema, quantity: number) => {
if (!onChange) return; if (!onChange) return;
onChange({ onChange({
...item, ...item,
quantity quantity,
}) });
} };
const onPriceChange = (item: DealServiceSchema, price: number) => { const onPriceChange = (item: DealServiceSchema, price: number) => {
if (!onChange) return; if (!onChange) return;
onChange({ onChange({
...item, ...item,
price price,
}) });
} };
const onEmployeeClick = (item: DealServiceSchema) => { const onEmployeeClick = (item: DealServiceSchema) => {
if (!onChange) return; if (!onChange) return;
setCurrentService(item); setCurrentService(item);
setEmployeesModalVisible(true); setEmployeesModalVisible(true);
} };
const onEmployeeModalClose = () => { const onEmployeeModalClose = () => {
setEmployeesModalVisible(false); setEmployeesModalVisible(false);
setCurrentService(undefined); setCurrentService(undefined);
} };
const getCurrentEmployees = (): UserSchema[] => { const getCurrentEmployees = (): UserSchema[] => {
if (!currentService) return []; if (!currentService) return [];
const item = items.find(i => i.service.id === currentService.service.id) const item = items.find(i => i.service.id === currentService.service.id);
if (!item) return []; if (!item) return [];
return item.employees; return item.employees;
} };
const onEmployeesChange = (items: UserSchema[]) => { const onEmployeesChange = (items: UserSchema[]) => {
if (!currentService || !onChange) return; if (!currentService || !onChange) return;
onChange({ onChange({
...currentService, ...currentService,
employees: items employees: items,
}); });
} };
const onAddKitClick = () => { const onAddKitClick = () => {
if (!onKitAdd) return; if (!onKitAdd) return;
modals.openContextModal({ modals.openContextModal({
modal: "servicesKitSelectModal", modal: "servicesKitSelectModal",
innerProps: { innerProps: {
onSelect: onKitAdd, onSelect: onKitAdd,
serviceType: ServiceType.DEAL_SERVICE serviceType: ServiceType.DEAL_SERVICE,
}, },
withCloseButton: false withCloseButton: false,
}) });
} };
return ( return (
<> <>
<Flex <Flex
@@ -103,7 +105,7 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
<Title <Title
order={3} order={3}
w={"100%"} w={"100%"}
style={{textAlign: "center"}} style={{ textAlign: "center" }}
mb={rem(10)} mb={rem(10)}
>Общие услуги</Title> >Общие услуги</Title>
@@ -125,13 +127,13 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
<ActionIcon <ActionIcon
disabled={isLocked} disabled={isLocked}
variant={"default"}> variant={"default"}>
<IconTrash/> <IconTrash />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
{!authState.isGuest && {!authState.isGuest &&
<Tooltip label="Сотрудники"> <Tooltip label="Сотрудники">
<ActionIcon onClick={() => onEmployeeClick(service)} variant={"default"}> <ActionIcon onClick={() => onEmployeeClick(service)} variant={"default"}>
<IconUsersGroup/> <IconUsersGroup />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
} }
@@ -157,7 +159,7 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
</Flex> </Flex>
<Title <Title
style={{textAlign: "end"}} style={{ textAlign: "end" }}
mt={rem(10)} mt={rem(10)}
order={3} order={3}
>Итог: {items.reduce((acc, item) => acc + (item.price * item.quantity), 0)}</Title> >Итог: {items.reduce((acc, item) => acc + (item.price * item.quantity), 0)}</Title>
@@ -196,6 +198,6 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
</Modal> </Modal>
</> </>
) );
} };
export default DealServicesTable; export default DealServicesTable;

View File

@@ -1,15 +1,15 @@
import {CRUDTableProps} from "../../../../../../types/CRUDTable.tsx"; import { CRUDTableProps } from "../../../../../../types/CRUDTable.tsx";
import {DealProductServiceSchema, UserSchema} from "../../../../../../client"; import { DealProductServiceSchema, UserSchema } from "../../../../../../client";
import {FC, useState} from "react"; import { FC, useState } from "react";
import useProductServicesTableColumns from "./columns.tsx"; import useProductServicesTableColumns from "./columns.tsx";
import {BaseTable} from "../../../../../../components/BaseTable/BaseTable.tsx"; import { BaseTable } from "../../../../../../components/BaseTable/BaseTable.tsx";
import {MRT_TableOptions} from "mantine-react-table"; import { MRT_TableOptions } from "mantine-react-table";
import {ActionIcon, Button, Flex, Modal, rem, Tooltip} from "@mantine/core"; import { ActionIcon, Button, Flex, Modal, rem, Tooltip } from "@mantine/core";
import {IconEdit, IconTrash, IconUsersGroup} from "@tabler/icons-react"; import { IconEdit, IconTrash, IconUsersGroup } from "@tabler/icons-react";
import {modals} from "@mantine/modals"; import { modals } from "@mantine/modals";
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx"; import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx";
import {useSelector} from "react-redux"; import { useSelector } from "react-redux";
import {RootState} from "../../../../../../redux/store.ts"; import { RootState } from "../../../../../../redux/store.ts";
import useDealProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx"; import useDealProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
type RestProps = { type RestProps = {
@@ -26,13 +26,13 @@ const ProductServicesTable: FC<Props> = ({
onDelete, onDelete,
onChange, onChange,
onCopyServices, onCopyServices,
onKitAdd onKitAdd,
}) => { }) => {
const {dealState} = useDealProductAndServiceTabState(); const { dealState } = useDealProductAndServiceTabState();
const isLocked = Boolean(dealState.deal?.billRequest); const isLocked = Boolean(dealState.deal?.billRequest);
const authState = useSelector((state: RootState) => state.auth); const authState = useSelector((state: RootState) => state.auth);
const columns = useProductServicesTableColumns({data: items, quantity}); const columns = useProductServicesTableColumns({ data: items, quantity });
const serviceIds = items.map(service => service.service.id); const serviceIds = items.map(service => service.service.id);
const [currentService, setCurrentService] = useState<DealProductServiceSchema | undefined>(); const [currentService, setCurrentService] = useState<DealProductServiceSchema | undefined>();
@@ -45,11 +45,12 @@ const ProductServicesTable: FC<Props> = ({
innerProps: { innerProps: {
onCreate: onCreate, onCreate: onCreate,
serviceIds, serviceIds,
quantity quantity,
category: dealState.deal?.category || undefined,
}, },
withCloseButton: false withCloseButton: false,
}) });
} };
const onChangeClick = (item: DealProductServiceSchema) => { const onChangeClick = (item: DealProductServiceSchema) => {
if (!onChange) return; if (!onChange) return;
@@ -59,33 +60,35 @@ const ProductServicesTable: FC<Props> = ({
element: item, element: item,
onChange, onChange,
serviceIds, serviceIds,
quantity quantity,
category: dealState.deal?.category || undefined,
}, },
withCloseButton: false withCloseButton: false,
}) });
} };
const onEmployeeClick = (item: DealProductServiceSchema) => { const onEmployeeClick = (item: DealProductServiceSchema) => {
if (!onChange) return; if (!onChange) return;
setCurrentService(item); setCurrentService(item);
setEmployeesModalVisible(true); setEmployeesModalVisible(true);
} };
const onEmployeeModalClose = () => { const onEmployeeModalClose = () => {
setEmployeesModalVisible(false); setEmployeesModalVisible(false);
setCurrentService(undefined); setCurrentService(undefined);
} };
const getCurrentEmployees = (): UserSchema[] => { const getCurrentEmployees = (): UserSchema[] => {
if (!currentService) return []; if (!currentService) return [];
const item = items.find(i => i.service.id === currentService.service.id) const item = items.find(i => i.service.id === currentService.service.id);
if (!item) return []; if (!item) return [];
return item.employees; return item.employees;
} };
const onEmployeesChange = (items: UserSchema[]) => { const onEmployeesChange = (items: UserSchema[]) => {
if (!currentService || !onChange) return; if (!currentService || !onChange) return;
onChange({ onChange({
...currentService, ...currentService,
employees: items employees: items,
}); });
} };
return ( return (
<> <>
<Flex <Flex
@@ -127,7 +130,7 @@ const ProductServicesTable: FC<Props> = ({
</Flex> </Flex>
), ),
renderRowActions: ({row}) => ( renderRowActions: ({ row }) => (
<Flex gap="md"> <Flex gap="md">
<Tooltip label="Удалить"> <Tooltip label="Удалить">
<ActionIcon <ActionIcon
@@ -135,7 +138,7 @@ const ProductServicesTable: FC<Props> = ({
onClick={() => { onClick={() => {
if (onDelete) onDelete(row.original); if (onDelete) onDelete(row.original);
}} variant={"default"}> }} variant={"default"}>
<IconTrash/> <IconTrash />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Tooltip <Tooltip
@@ -145,13 +148,13 @@ const ProductServicesTable: FC<Props> = ({
onClick={() => onChangeClick(row.original)} onClick={() => onChangeClick(row.original)}
variant={"default"}> variant={"default"}>
<IconEdit/> <IconEdit />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
{!authState.isGuest && {!authState.isGuest &&
<Tooltip label="Сотрудники"> <Tooltip label="Сотрудники">
<ActionIcon onClick={() => onEmployeeClick(row.original)} variant={"default"}> <ActionIcon onClick={() => onEmployeeClick(row.original)} variant={"default"}>
<IconUsersGroup/> <IconUsersGroup />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
} }
@@ -177,6 +180,6 @@ const ProductServicesTable: FC<Props> = ({
</Modal> </Modal>
</> </>
) );
} };
export default ProductServicesTable; export default ProductServicesTable;

View File

@@ -1,24 +1,24 @@
import {FC, useEffect, useState} from "react"; import { FC, useEffect, useState } from "react";
import styles from './LeadsPage.module.css'; import styles from "./LeadsPage.module.css";
import Board from "../../../components/Dnd/Board/Board.tsx"; import Board from "../../../components/Dnd/Board/Board.tsx";
import {DragDropContext, Droppable, DropResult} from "@hello-pangea/dnd"; import { DragDropContext, Droppable, DropResult } from "@hello-pangea/dnd";
import {useDealSummaries} from "../hooks/useDealSummaries.tsx"; import { useDealSummaries } from "../hooks/useDealSummaries.tsx";
import {DealStatus, getDealStatusByName} from "../../../shared/enums/DealStatus.ts"; import { DealStatus, getDealStatusByName } from "../../../shared/enums/DealStatus.ts";
import PageBlock from "../../../components/PageBlock/PageBlock.tsx"; import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
import DealEditDrawer from "../drawers/DealEditDrawer/DealEditDrawer.tsx"; import DealEditDrawer from "../drawers/DealEditDrawer/DealEditDrawer.tsx";
import {DealPageContextProvider} from "../contexts/DealPageContext.tsx"; import { DealPageContextProvider } from "../contexts/DealPageContext.tsx";
import {modals} from "@mantine/modals"; import { modals } from "@mantine/modals";
import {DealService, DealSummaryReorderRequest} from "../../../client"; import { DealService, DealSummaryReorderRequest } from "../../../client";
import {ActionIcon, Flex, rem, Text} from "@mantine/core"; import { ActionIcon, Flex, rem, Text } from "@mantine/core";
import classNames from "classnames"; import classNames from "classnames";
import {notifications} from "../../../shared/lib/notifications.ts"; import { notifications } from "../../../shared/lib/notifications.ts";
import {IconMenu2, IconMenuDeep} from "@tabler/icons-react"; import { IconMenu2, IconMenuDeep } from "@tabler/icons-react";
import useDealsPageState from "../../DealsPage/hooks/useDealsPageState.tsx"; import useDealsPageState from "../../DealsPage/hooks/useDealsPageState.tsx";
import DealStatusSelect from "../../DealsPage/components/DealStatusSelect/DealStatusSelect.tsx"; import DealStatusSelect from "../../DealsPage/components/DealStatusSelect/DealStatusSelect.tsx";
import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx"; import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx"; import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
import DealsTable from "../../DealsPage/components/DealsTable/DealsTable.tsx"; import DealsTable from "../../DealsPage/components/DealsTable/DealsTable.tsx";
import {motion} from "framer-motion"; import { motion } from "framer-motion";
enum DisplayMode { enum DisplayMode {
BOARD, BOARD,
@@ -26,9 +26,9 @@ enum DisplayMode {
} }
export const LeadsPage: FC = () => { export const LeadsPage: FC = () => {
const {data, form} = useDealsPageState(); const { data, form } = useDealsPageState();
const {summariesRaw, refetch} = useDealSummaries(); const { summariesRaw, refetch } = useDealSummaries();
const [summaries, setSummaries] = useState(summariesRaw); const [summaries, setSummaries] = useState(summariesRaw);
const [displayMode, setDisplayMode] = useState<DisplayMode>(DisplayMode.BOARD); const [displayMode, setDisplayMode] = useState<DisplayMode>(DisplayMode.BOARD);
const [isDragEnded, setIsDragEnded] = useState(true); const [isDragEnded, setIsDragEnded] = useState(true);
@@ -46,19 +46,19 @@ export const LeadsPage: FC = () => {
Вы действительно хотите удалить сделку {summary.name}? Вы действительно хотите удалить сделку {summary.name}?
</Flex>, </Flex>,
onConfirm: () => { onConfirm: () => {
DealService.deleteDeal({requestBody: {dealId: dealId}}) DealService.deleteDeal({ requestBody: { dealId: dealId } })
.then(async ({ok, message}) => { .then(async ({ ok, message }) => {
notifications.guess(ok, {message}); notifications.guess(ok, { message });
if (!ok) return; if (!ok) return;
await refetch(); await refetch();
}) });
}, },
labels: { labels: {
confirm: "Удалить", confirm: "Удалить",
cancel: "Отмена" cancel: "Отмена",
} },
}); });
} };
const onSuccess = (dealId: number) => { const onSuccess = (dealId: number) => {
const summary = summaries.find(summary => summary.id == dealId); const summary = summaries.find(summary => summary.id == dealId);
if (!summary) return; if (!summary) return;
@@ -69,19 +69,19 @@ export const LeadsPage: FC = () => {
Вы действительно хотите завершить сделку {summary.name}? Вы действительно хотите завершить сделку {summary.name}?
</Flex>, </Flex>,
onConfirm: () => { onConfirm: () => {
DealService.completeDeal({requestBody: {dealId: dealId}}) DealService.completeDeal({ requestBody: { dealId: dealId } })
.then(async ({ok, message}) => { .then(async ({ ok, message }) => {
notifications.guess(ok, {message}); notifications.guess(ok, { message });
if (!ok) return; if (!ok) return;
await refetch(); await refetch();
}) });
}, },
labels: { labels: {
confirm: "Завершить", confirm: "Завершить",
cancel: "Отмена" cancel: "Отмена",
} },
}); });
} };
const onDragEnd = async (result: DropResult) => { const onDragEnd = async (result: DropResult) => {
setIsDragEnded(true); setIsDragEnded(true);
// If there is no changes // If there is no changes
@@ -97,11 +97,11 @@ export const LeadsPage: FC = () => {
// Checking if it is custom actions // Checking if it is custom actions
const droppableId = result.destination.droppableId; const droppableId = result.destination.droppableId;
if (droppableId === 'DELETE') { if (droppableId === "DELETE") {
onDelete(dealId); onDelete(dealId);
return; return;
} }
if (droppableId === 'SUCCESS') { if (droppableId === "SUCCESS") {
onSuccess(dealId); onSuccess(dealId);
return; return;
} }
@@ -109,10 +109,10 @@ export const LeadsPage: FC = () => {
const request: Partial<DealSummaryReorderRequest> = { const request: Partial<DealSummaryReorderRequest> = {
dealId: dealId, dealId: dealId,
index: result.destination.index, index: result.destination.index,
status: status status: status,
} };
if (status == summary.status) { if (status == summary.status) {
DealService.reorderDealSummaries({requestBody: request as DealSummaryReorderRequest}) DealService.reorderDealSummaries({ requestBody: request as DealSummaryReorderRequest })
.then(async response => { .then(async response => {
setSummaries(response.summaries); setSummaries(response.summaries);
await refetch(); await refetch();
@@ -120,33 +120,33 @@ export const LeadsPage: FC = () => {
return; return;
} }
modals.openContextModal({ modals.openContextModal({
modal: 'enterDeadline', modal: "enterDeadline",
title: "Необходимо указать дедлайн", title: "Необходимо указать дедлайн",
innerProps: { innerProps: {
onSubmit: (event) => DealService.reorderDealSummaries({requestBody: event}) onSubmit: (event) => DealService.reorderDealSummaries({ requestBody: event })
.then(async response => { .then(async response => {
setSummaries(response.summaries); setSummaries(response.summaries);
await refetch(); await refetch();
}), }),
request: request request: request,
} },
}); });
} };
const getTableBody = () => { const getTableBody = () => {
return ( return (
<motion.div <motion.div
key={displayMode} key={displayMode}
initial={{opacity: 0}} initial={{ opacity: 0 }}
animate={{opacity: 1}} animate={{ opacity: 1 }}
transition={{duration: 0.2}} transition={{ duration: 0.2 }}
> >
<DealsTable items={data}/> <DealsTable items={data} />
</motion.div> </motion.div>
) );
} };
const getBoardBody = () => { const getBoardBody = () => {
return ( return (
@@ -154,12 +154,12 @@ export const LeadsPage: FC = () => {
style={{ style={{
display: "flex", display: "flex",
height: "100%", height: "100%",
flex: 1 flex: 1,
}} }}
key={displayMode} key={displayMode}
initial={{opacity: 0}} initial={{ opacity: 0 }}
animate={{opacity: 1}} animate={{ opacity: 1 }}
transition={{duration: 0.2}} transition={{ duration: 0.2 }}
> >
<DragDropContext <DragDropContext
@@ -170,24 +170,24 @@ export const LeadsPage: FC = () => {
<Flex <Flex
justify={"space-between"} justify={"space-between"}
direction={"column"} direction={"column"}
style={{flex: 1}} style={{ flex: 1 }}
> >
<div className={styles['boards']}> <div className={styles["boards"]}>
<Board <Board
withCreateButton withCreateButton
summaries={summaries summaries={summaries
.filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)} .filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
title={"Ожидает приемки"} title={"Ожидает приемки"}
droppableId={"AWAITING_ACCEPTANCE"} droppableId={"AWAITING_ACCEPTANCE"}
color={'#4A90E2'} color={"#4A90E2"}
/> />
<Board <Board
summaries={summaries summaries={summaries
.filter(summary => summary.status == DealStatus.PACKAGING)} .filter(summary => summary.status == DealStatus.PACKAGING)}
title={"Упаковка"} title={"Упаковка"}
droppableId={"PACKAGING"} droppableId={"PACKAGING"}
color={'#F5A623'} color={"#F5A623"}
/> />
<Board <Board
@@ -195,7 +195,7 @@ export const LeadsPage: FC = () => {
.filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)} .filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
title={"Ожидает отгрузки"} title={"Ожидает отгрузки"}
droppableId={"AWAITING_SHIPMENT"} droppableId={"AWAITING_SHIPMENT"}
color={'#7ED321'} color={"#7ED321"}
/> />
<Board <Board
@@ -203,7 +203,7 @@ export const LeadsPage: FC = () => {
.filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)} .filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
title={"Ожидает оплаты"} title={"Ожидает оплаты"}
droppableId={"AWAITING_PAYMENT"} droppableId={"AWAITING_PAYMENT"}
color={'#D0021B'} color={"#D0021B"}
/> />
<Board <Board
@@ -211,15 +211,15 @@ export const LeadsPage: FC = () => {
.filter(summary => summary.status == DealStatus.COMPLETED)} .filter(summary => summary.status == DealStatus.COMPLETED)}
title={"Завершена"} title={"Завершена"}
droppableId={"COMPLETED"} droppableId={"COMPLETED"}
color={'#417505'} color={"#417505"}
/> />
</div> </div>
<Flex justify={"space-between"} gap={rem(10)}> <Flex justify={"space-between"} gap={rem(10)}>
<div <div
className={ className={
classNames( classNames(
styles['delete'], styles["delete"],
isDragEnded && styles['delete-hidden'] isDragEnded && styles["delete-hidden"],
) )
} }
> >
@@ -250,8 +250,8 @@ export const LeadsPage: FC = () => {
<div <div
className={ className={
classNames( classNames(
styles['delete'], styles["delete"],
isDragEnded && styles['delete-hidden'] isDragEnded && styles["delete-hidden"],
) )
} }
> >
@@ -284,11 +284,11 @@ export const LeadsPage: FC = () => {
</DragDropContext> </DragDropContext>
</motion.div> </motion.div>
) );
} };
const getBody = () => { const getBody = () => {
return displayMode === DisplayMode.TABLE ? getTableBody() : getBoardBody(); return displayMode === DisplayMode.TABLE ? getTableBody() : getBoardBody();
} };
return ( return (
<PageBlock <PageBlock
fullHeight fullHeight
@@ -302,7 +302,7 @@ export const LeadsPage: FC = () => {
> >
<DealPageContextProvider> <DealPageContextProvider>
<PageBlock <PageBlock
style={{flex: 0}} style={{ flex: 0 }}
> >
<Flex <Flex
align={"center"} align={"center"}
@@ -326,7 +326,7 @@ export const LeadsPage: FC = () => {
}> }>
<IconMenuDeep <IconMenuDeep
style={{rotate: "-90deg"}} style={{ rotate: "-90deg" }}
/> />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
@@ -346,14 +346,14 @@ export const LeadsPage: FC = () => {
</Flex> </Flex>
<motion.div <motion.div
key={displayMode} key={displayMode}
initial={{opacity: 0}} initial={{ opacity: 0 }}
animate={{opacity: 1}} animate={{ opacity: 1 }}
transition={{duration: 0.2}} transition={{ duration: 0.2 }}
> >
<div <div
className={styles['top-panel']} className={styles["top-panel"]}
style={{display: displayMode === DisplayMode.TABLE ? "flex" : "none"}} style={{ display: displayMode === DisplayMode.TABLE ? "flex" : "none" }}
> >
<DealStatusSelect <DealStatusSelect
onClear={() => form.setFieldValue("dealStatus", null)} onClear={() => form.setFieldValue("dealStatus", null)}
@@ -384,7 +384,7 @@ export const LeadsPage: FC = () => {
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
flex: 1, flex: 1,
height: "100%" height: "100%",
}}> }}>
{getBody()} {getBody()}
</PageBlock> </PageBlock>
@@ -394,5 +394,5 @@ export const LeadsPage: FC = () => {
</PageBlock> </PageBlock>
) );
} };

View File

@@ -0,0 +1,41 @@
import { CRUDTableProps } from "../../../../types/CRUDTable.tsx";
import { ServicePriceCategorySchema } from "../../../../client";
import { FC } from "react";
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
import useServicePriceCategoryTableColumns from "./columns.tsx";
import { MRT_TableOptions } from "mantine-react-table";
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
import { IconEdit, IconTrash } from "@tabler/icons-react";
type Props = CRUDTableProps<ServicePriceCategorySchema>
const ServicePriceCategoryTable: FC<Props> = ({ items, onChange, onDelete }) => {
const columns = useServicePriceCategoryTableColumns();
return (
<BaseTable
data={items}
columns={columns}
restProps={{
enableRowActions: true,
renderRowActions: ({ row }) => (
<Flex gap="md">
<Tooltip label="Редактировать">
<ActionIcon
onClick={() => onChange && onChange(row.original)}
variant={"default"}>
<IconEdit />
</ActionIcon>
</Tooltip>
<Tooltip label="Удалить">
<ActionIcon onClick={() => onDelete && onDelete(row.original)} variant={"default"}>
<IconTrash />
</ActionIcon>
</Tooltip>
</Flex>
),
} as MRT_TableOptions<ServicePriceCategorySchema>}
/>
);
};
export default ServicePriceCategoryTable;

View File

@@ -0,0 +1,17 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { ServicePriceCategorySchema } from "../../../../client";
const useServicePriceCategoryTableColumns = () => {
return useMemo<MRT_ColumnDef<ServicePriceCategorySchema>[]>(() => [
{
accessorKey: "name",
header: "Название",
enableColumnActions: false,
enableSorting: false,
},
], []);
};
export default useServicePriceCategoryTableColumns;

View File

@@ -0,0 +1,54 @@
import { BaseFormInputProps } from "../../../../types/utils.ts";
import type { ServiceCategoryPriceSchema, ServicePriceCategorySchema } from "../../../../client";
import { FC, useEffect, useState } from "react";
import { Flex, NumberInput, rem } from "@mantine/core";
import ServicePriceCategorySelect
from "../../../../components/Selects/ServicePriceCategorySelect/ServicePriceCategorySelect.tsx";
export type PriceCategoryInputProps = BaseFormInputProps<ServiceCategoryPriceSchema[]>
const PriceCategoryInput: FC<PriceCategoryInputProps> = (props: PriceCategoryInputProps) => {
const [innerState, setInnerState] = useState<ServiceCategoryPriceSchema[]>(props.value || []);
const [category, setCategory] = useState<ServicePriceCategorySchema | undefined>(undefined);
const getValue = (): number | undefined | string => {
if (category === undefined) return undefined;
const value = innerState.find(item => item.category.id === category.id);
return value?.price || "";
};
const handleChange = (value: number | string) => {
// remove value if is string
if (typeof value === "string") {
setInnerState(innerState.filter(item => item.category.id !== category?.id));
return;
}
const newValue = {
category: category as ServicePriceCategorySchema,
price: value,
};
const newInnerState = innerState.filter(item => item.category.id !== category?.id);
setInnerState([...newInnerState, newValue]);
};
useEffect(() => {
if (props.value === innerState) return;
props.onChange(innerState);
}, [innerState]);
return (
<Flex direction={"column"} gap={rem(10)}>
<ServicePriceCategorySelect
label={"Категория цены"}
placeholder={"Выберите категорию цены"}
onChange={setCategory}
/>
<NumberInput
label={"Цена"}
placeholder={"Введите цену"}
value={getValue()}
onChange={handleChange}
/>
</Flex>
);
};
export default PriceCategoryInput;

View File

@@ -0,0 +1,49 @@
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
import { FC } from "react";
import { omit } from "lodash";
export enum ServicePriceType {
DEFAULT,
BY_RANGE,
BY_CATEGORY,
}
type RestProps = {
onChange: (value: ServicePriceType) => void;
value?: ServicePriceType;
}
type Props = Omit<SegmentedControlProps, "data" | "value" | "onChange"> & RestProps;
const ServicePriceTypeSegmentedControl: FC<Props> = (props) => {
const { onChange, value } = props;
const data = [
{
label: "По умолчанию",
value: ServicePriceType.DEFAULT.toString(),
},
{
label: "По диапазону",
value: ServicePriceType.BY_RANGE.toString(),
},
{
label: "По категории",
value: ServicePriceType.BY_CATEGORY.toString(),
},
];
const handleChange = (value: string) => {
onChange(Number(value));
};
const restProps = omit(props, ["onChange", "value"]);
return (
<SegmentedControl
data={data}
value={value?.toString()}
onChange={handleChange}
{...restProps}
/>
);
};
export default ServicePriceTypeSegmentedControl;

View File

@@ -1,27 +1,33 @@
import {FC} from "react"; import { FC } from "react";
import {SegmentedControl, SegmentedControlProps} from "@mantine/core"; import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
export enum ServicesTab { export enum ServicesTab {
DEAL_SERVICE, DEAL_SERVICE,
PRODUCT_SERVICE, PRODUCT_SERVICE,
SERVICES_KITS SERVICES_KITS,
SERVICES_PRICE_CATEGORIES
} }
type Props = Omit<SegmentedControlProps, 'data'>; type Props = Omit<SegmentedControlProps, "data">;
const data = [ const data = [
{ {
label: 'Для товара', label: "Для товара",
value: ServicesTab.PRODUCT_SERVICE.toString() value: ServicesTab.PRODUCT_SERVICE.toString(),
}, },
{ {
label: 'Для сделки', label: "Для сделки",
value: ServicesTab.DEAL_SERVICE.toString() value: ServicesTab.DEAL_SERVICE.toString(),
}, },
{ {
label: 'Наборы услуг', label: "Наборы услуг",
value: ServicesTab.SERVICES_KITS.toString() value: ServicesTab.SERVICES_KITS.toString(),
} },
] {
label: "Категории цен",
value: ServicesTab.SERVICES_PRICE_CATEGORIES.toString(),
},
];
const ServiceTypeSegmentedControl: FC<Props> = (props) => { const ServiceTypeSegmentedControl: FC<Props> = (props) => {
return ( return (
@@ -29,6 +35,6 @@ const ServiceTypeSegmentedControl: FC<Props> = (props) => {
data={data} data={data}
{...props} {...props}
/> />
) );
} };
export default ServiceTypeSegmentedControl export default ServiceTypeSegmentedControl;

View File

@@ -0,0 +1,10 @@
import ObjectList from "../../../hooks/objectList.tsx";
import { ServiceService } from "../../../client";
const useServicePriceCategoriesList = () => ObjectList({
queryFn: ServiceService.getAllPriceCategories,
getObjectsFn: (response) => response.priceCategories,
queryKey: "getAllPriceCategories",
});
export default useServicePriceCategoriesList;

View File

@@ -0,0 +1,80 @@
import UseObjectState from "../../../types/UseObjectState.ts";
import { type ServicePriceCategorySchema, ServiceService } from "../../../client";
import useServicePriceCategoriesList from "./useServicePriceCategoriesList.tsx";
import { modals } from "@mantine/modals";
import { notifications } from "../../../shared/lib/notifications.ts";
const useServicePriceCategoryState = (): UseObjectState<ServicePriceCategorySchema> => {
const { objects, refetch } = useServicePriceCategoriesList();
const onCreateClick = () => {
modals.openContextModal({
modal: "servicePriceCategoryForm",
title: "Создание категории цен",
withCloseButton: false,
innerProps: {
onCreate,
},
});
};
const onCreate = (values: ServicePriceCategorySchema) => {
console.log(ServiceService);
ServiceService.createPriceCategory({
requestBody: {
name: values.name,
},
}).then(async ({ ok, message }) => {
notifications.guess(ok, { message: message });
if (!ok) return;
await refetch();
});
};
const onDelete = (item: ServicePriceCategorySchema) => {
modals.openConfirmModal({
title: "Удаление категории",
children: "Вы уверены, что хотите удалить категорию?",
onConfirm: () => {
ServiceService.deletePriceCategory({
requestBody: {
id: item.id,
},
}).then(async ({ ok, message }) => {
notifications.guess(ok, { message: message });
if (!ok) return;
await refetch();
});
},
});
};
const onChange = (item: ServicePriceCategorySchema) => {
modals.openContextModal({
modal: "servicePriceCategoryForm",
title: "Изменение категории цен",
withCloseButton: false,
innerProps: {
onChange: (values: ServicePriceCategorySchema) => {
ServiceService.updatePriceCategory({
requestBody: {
id: item.id,
name: values.name,
},
}).then(async ({ ok, message }) => {
notifications.guess(ok, { message: message });
if (!ok) return;
await refetch();
});
},
element: item,
},
});
};
return {
onCreateClick,
onCreate,
onDelete,
onChange,
objects,
};
};
export default useServicePriceCategoryState;

View File

@@ -0,0 +1,57 @@
import { GetServiceKitSchema, ServiceService } from "../../../client";
import { omit } from "lodash";
import { notifications } from "../../../shared/lib/notifications.ts";
import { modals } from "@mantine/modals";
import useServicesKitsList from "./useServicesKitsList.tsx";
const useServicesKitsState = () => {
const { objects: servicesKits, refetch: refetchKits } = useServicesKitsList();
const onKitCreate = (kit: GetServiceKitSchema) => {
ServiceService.createServicesKit({
requestBody: {
data: {
...omit(kit, ["services", "id"]),
servicesIds: kit.services.map(service => service.id),
},
},
}).then(async ({ ok, message }) => {
notifications.guess(ok, { message: message });
if (!ok) return;
await refetchKits();
});
};
const onKitCreateClick = () => {
modals.openContextModal({
modal: "serviceKitModalForm",
title: "Создание набора услуг",
withCloseButton: false,
innerProps: {
onCreate: onKitCreate,
},
});
};
const onKitUpdate = (kit: GetServiceKitSchema) => {
ServiceService.updateServicesKit({
requestBody: {
data: {
...omit(kit, ["services"]),
servicesIds: kit.services.map(service => service.id),
},
},
}).then(async ({ ok, message }) => {
notifications.guess(ok, { message: message });
if (!ok) return;
await refetchKits();
});
};
return {
onKitCreateClick,
onKitUpdate,
servicesKits,
};
};
export default useServicesKitsState;

View File

@@ -0,0 +1,89 @@
import { modals } from "@mantine/modals";
import { ServiceCategorySchema, ServiceSchema, ServiceService } from "../../../client";
import { notifications } from "../../../shared/lib/notifications.ts";
import useServicesList from "./useServicesList.tsx";
import { Text } from "@mantine/core";
const useServicesState = () => {
const { services, refetch } = useServicesList();
const onCreateClick = () => {
modals.openContextModal({
modal: "createService",
title: "Создание услуги",
withCloseButton: false,
innerProps: {
onCreate,
},
});
};
const onCreate = (values: ServiceSchema) => {
ServiceService.createService({ requestBody: { service: values } })
.then(async ({ ok, message }) => {
notifications.guess(ok, { message: message });
if (!ok) return;
await refetch();
});
};
const onCreateCategoryClick = () => {
modals.openContextModal({
modal: "createServiceCategory",
title: "Создание категории",
withCloseButton: false,
innerProps: {
onCreate: onCategoryCreate,
},
});
};
const onCategoryCreate = (category: ServiceCategorySchema) => {
ServiceService.createServiceCategory({ requestBody: { category: category } })
.then(({ ok, message }) =>
notifications.guess(ok, { message: message }));
};
const onServiceDelete = (service: ServiceSchema) => {
modals.openConfirmModal({
title: "Удаление услуги",
children: (<Text>
Вы уверены, что хотите удалить услугу "{service.name}"?
</Text>),
onConfirm: () => {
ServiceService.deleteService({ requestBody: { serviceId: service.id } })
.then(async ({ ok, message }) => {
notifications.guess(ok, { message: message });
if (!ok) return;
await refetch();
});
},
labels: {
confirm: "Удалить",
cancel: "Отмена",
},
});
};
const onServiceUpdate = (service: ServiceSchema) => {
ServiceService
.updateService({
requestBody: {
data: service,
},
})
.then(async ({ ok, message }) => {
notifications.guess(ok, { message: message });
if (!ok) return;
await refetch();
});
};
return {
services,
onCreateClick,
onServiceDelete,
onServiceUpdate,
onCreateCategoryClick,
};
};
export default useServicesState;

View File

@@ -1,12 +1,17 @@
import {ServicePriceRangeSchema, ServiceSchema} from "../../../client"; import { ServicePriceRangeSchema, ServiceSchema } from "../../../client";
import {useForm} from "@mantine/form"; import { useForm } from "@mantine/form";
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 {NumberInput, TextInput} from "@mantine/core"; import { Fieldset, Flex, rem, TextInput } from "@mantine/core";
import ServiceCategorySelect from "../components/ServiceCategorySelect/ServiceCategorySelect.tsx"; import ServiceCategorySelect from "../components/ServiceCategorySelect/ServiceCategorySelect.tsx";
import ServiceTypeSelect from "../components/ServiceTypeSelect/ServiceTypeSelect.tsx"; import ServiceTypeSelect from "../components/ServiceTypeSelect/ServiceTypeSelect.tsx";
import ServicePriceInput from "../components/ServicePriceInput/ServicePriceInput.tsx"; import ServicePriceTypeSegmentedControl, {
import {PriceRangeInputType} from "../components/ServicePriceInput/RangePriceInput.tsx"; ServicePriceType,
} from "../components/ServicePriceTypeSegmentedControl/ServicePriceTypeSegmentedControl.tsx";
import { useEffect, useState } from "react";
import RangePriceInput, { PriceRangeInputType } from "../components/ServicePriceInput/RangePriceInput.tsx";
import SinglePriceInput from "../components/ServicePriceInput/SinglePriceInput.tsx";
import PriceCategoryInput, { PriceCategoryInputProps } from "../components/ServicePriceInput/PriceCategoryInput.tsx";
type Props = CreateEditFormProps<ServiceSchema> type Props = CreateEditFormProps<ServiceSchema>
const CreateServiceModal = ({ const CreateServiceModal = ({
@@ -14,40 +19,61 @@ const CreateServiceModal = ({
id, id,
innerProps, innerProps,
}: ContextModalProps<Props>) => { }: ContextModalProps<Props>) => {
const [priceType, setPriceType] = useState<ServicePriceType>(ServicePriceType.DEFAULT);
const isEditing = 'onChange' in innerProps; const isEditing = "onChange" in innerProps;
const initialValues: ServiceSchema = isEditing ? innerProps.element : { const initialValues: ServiceSchema = isEditing ? innerProps.element : {
id: -1, id: -1,
name: '', name: "",
price: 0, price: 0,
category: { category: {
id: -1, id: -1,
name: '' name: "",
}, },
serviceType: -1, serviceType: -1,
priceRanges: [] as ServicePriceRangeSchema[], priceRanges: [] as ServicePriceRangeSchema[],
cost: null cost: null,
} categoryPrices: [],
};
const form = useForm<ServiceSchema>({ const form = useForm<ServiceSchema>({
initialValues: initialValues, initialValues: initialValues,
validate: { validate: {
name: (name: string) => name.trim() !== '' ? null : "Необходимо ввести название услуги", name: (name: string) => name.trim() !== "" ? null : "Необходимо ввести название услуги",
category: (category: { category: (category: {
id: number, id: number,
name: string name: string
}) => category.id !== -1 ? null : "Необходимо выбрать категорию", }) => category.id !== -1 ? null : "Необходимо выбрать категорию",
serviceType: (serviceType: number) => serviceType !== -1 ? null : "Необходимо выбрать тип услуги", serviceType: (serviceType: number) => serviceType !== -1 ? null : "Необходимо выбрать тип услуги",
priceRanges: (value, values) => value.length > 0 || values.price > 0 ? null : "Необходимо добавить хотя бы один диапазон цен или указать цену за единицу услуги", priceRanges: (value, values) => value.length > 0 || values.price > 0 ? null : "Необходимо добавить хотя бы один диапазон цен или указать цену за единицу услуги",
price: (value, values) => value > 0 || values.priceRanges.length > 0 ? null : "Необходимо добавить хотя бы один диапазон цен или указать цену за единицу услуги" price: (value, values) => value > 0 || values.priceRanges.length > 0 ? null : "Необходимо добавить хотя бы один диапазон цен или указать цену за единицу услуги",
},
});
useEffect(() => {
console.log(form.values.categoryPrices);
}, [form.values]);
const getPriceBody = () => {
switch (priceType) {
case ServicePriceType.DEFAULT:
return <SinglePriceInput
placeholder={"Введите стоимость услуги"}
label={"Cтоимость услуги"}
hideControls
{...form.getInputProps("cost")}
/>;
case ServicePriceType.BY_RANGE:
return <RangePriceInput
{...form.getInputProps("priceRanges") as PriceRangeInputType}
/>;
case ServicePriceType.BY_CATEGORY:
return <PriceCategoryInput
{...form.getInputProps("categoryPrices") as PriceCategoryInputProps}
/>;
} }
}) };
const onCancelClick = () => { const onCancelClick = () => {
context.closeContextModal(id); context.closeContextModal(id);
} };
return ( return (
<BaseFormModal <BaseFormModal
{...innerProps} {...innerProps}
@@ -57,43 +83,39 @@ const CreateServiceModal = ({
> >
<BaseFormModal.Body> <BaseFormModal.Body>
<> <>
<ServiceCategorySelect <Fieldset legend={"Общие параметры"}>
placeholder={"Выберите категорию"} <ServiceCategorySelect
label={"Категория услуги"} placeholder={"Выберите категорию"}
{...form.getInputProps('category')} label={"Категория услуги"}
/> {...form.getInputProps("category")}
<TextInput />
<TextInput
placeholder={"Введите название услуги"}
label={"Название услуги"}
{...form.getInputProps("name")}
/>
<ServiceTypeSelect
placeholder={"Выберите тип услуги"}
label={"Тип услуги"}
{...form.getInputProps("serviceType")}
/>
</Fieldset>
<Fieldset legend={"Стоимость"}>
<Flex direction={"column"} gap={rem(10)} justify={"center"}>
<ServicePriceTypeSegmentedControl
value={priceType}
onChange={setPriceType}
/>
{getPriceBody()}
</Flex>
</Fieldset>
placeholder={"Введите название услуги"}
label={"Название услуги"}
{...form.getInputProps('name')}
/>
<ServiceTypeSelect
placeholder={"Выберите тип услуги"}
label={"Тип услуги"}
{...form.getInputProps('serviceType')}
/>
<NumberInput
placeholder={"Введите себестоимость услуги"}
label={"Себестоимость услуги"}
hideControls
{...form.getInputProps('cost')}
/>
<ServicePriceInput
singlePriceInputProps={{
hideControls: true,
label: "Цена за единицу услуги",
placeholder: "Введите цену за одну услугу",
...form.getInputProps('price'),
}}
priceRangeInputProps={{
...form.getInputProps('priceRanges')
} as PriceRangeInputType}
/>
</> </>
</BaseFormModal.Body> </BaseFormModal.Body>
</BaseFormModal> </BaseFormModal>
) );
} };
export default CreateServiceModal; export default CreateServiceModal;

View File

@@ -0,0 +1,38 @@
import { ServicePriceCategorySchema } from "../../../client";
import BaseFormModal, { CreateEditFormProps } from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
import { ContextModalProps } from "@mantine/modals";
import { TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
type Props = CreateEditFormProps<ServicePriceCategorySchema>;
const ServicePriceCategoryForm = ({ context, id, innerProps }: ContextModalProps<Props>) => {
const isEditing = "element" in innerProps;
const initialValues: Partial<ServicePriceCategorySchema> = isEditing ? innerProps.element : {
name: "",
};
const form = useForm<Partial<ServicePriceCategorySchema>>({
initialValues,
});
return (
<BaseFormModal
{...innerProps}
form={form}
closeOnSubmit
onClose={() => context.closeContextModal(id)}
>
<BaseFormModal.Body>
<>
<TextInput
label={"Название"}
placeholder={"Введите название категории"}
{...form.getInputProps("name")}
/>
</>
</BaseFormModal.Body>
</BaseFormModal>
);
};
export default ServicePriceCategoryForm;

View File

@@ -1,9 +1,9 @@
import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx"; import BaseFormModal, { CreateEditFormProps } from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
import {GetServiceKitSchema} from "../../../client"; import { GetServiceKitSchema } from "../../../client";
import {ContextModalProps} from "@mantine/modals"; import { ContextModalProps } from "@mantine/modals";
import {useForm} from "@mantine/form"; import { useForm } from "@mantine/form";
import {ServiceType} from "../../../shared/enums/ServiceType.ts"; import { ServiceType } from "../../../shared/enums/ServiceType.ts";
import {TextInput} from "@mantine/core"; import { TextInput } from "@mantine/core";
import ServiceTypeSelect from "../components/ServiceTypeSelect/ServiceTypeSelect.tsx"; import ServiceTypeSelect from "../components/ServiceTypeSelect/ServiceTypeSelect.tsx";
import ServicesMultiselect from "../../../components/Selects/ServicesMultiselect/ServicesMultiselect.tsx"; import ServicesMultiselect from "../../../components/Selects/ServicesMultiselect/ServicesMultiselect.tsx";
@@ -13,17 +13,17 @@ const ServiceKitModalForm = ({
id, id,
innerProps, innerProps,
}: ContextModalProps<Props>) => { }: ContextModalProps<Props>) => {
const isEditing = 'element' in innerProps; const isEditing = "element" in innerProps;
const initialValues: Partial<GetServiceKitSchema> = isEditing ? innerProps.element : { const initialValues: Partial<GetServiceKitSchema> = isEditing ? innerProps.element : {
name: "", name: "",
serviceType: ServiceType.DEAL_SERVICE, serviceType: ServiceType.DEAL_SERVICE,
services: [] services: [],
} };
const form = useForm<Partial<GetServiceKitSchema>>( const form = useForm<Partial<GetServiceKitSchema>>(
{ {
initialValues initialValues,
} },
); );
return ( return (
<BaseFormModal <BaseFormModal
@@ -54,7 +54,7 @@ const ServiceKitModalForm = ({
</> </>
</BaseFormModal.Body> </BaseFormModal.Body>
</BaseFormModal> </BaseFormModal>
) );
} };
export default ServiceKitModalForm; export default ServiceKitModalForm;

View File

@@ -1,173 +1,90 @@
import {FC, useState} from "react"; import { FC, useState } from "react";
import ServicesTable from "../components/ServicesTable/ServicesTable.tsx"; import ServicesTable from "../components/ServicesTable/ServicesTable.tsx";
import useServicesList from "../hooks/useServicesList.tsx";
import PageBlock from "../../../components/PageBlock/PageBlock.tsx"; import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
import styles from './ServicesPage.module.css'; import styles from "./ServicesPage.module.css";
import {Button, Text} from "@mantine/core"; import { Button } from "@mantine/core";
import {GetServiceKitSchema, ServiceCategorySchema, ServiceSchema, ServiceService} from "../../../client";
import {notifications} from "../../../shared/lib/notifications.ts";
import {modals} from "@mantine/modals";
import ServiceTypeSegmentedControl, { import ServiceTypeSegmentedControl, {
ServicesTab ServicesTab,
} from "../components/ServiceTypeSegmentedControl/ServiceTypeSegmentedControl.tsx"; } from "../components/ServiceTypeSegmentedControl/ServiceTypeSegmentedControl.tsx";
import useServicesKitsList from "../hooks/useServicesKitsList.tsx";
import ServicesKitsTable from "../components/ServicesKitsTable/ServicesKitsTable.tsx"; import ServicesKitsTable from "../components/ServicesKitsTable/ServicesKitsTable.tsx";
import {omit} from "lodash"; import ServicePriceCategoryTable from "../components/ServicePriceCategoryTable/ServicePriceCategoryTable.tsx";
import useServicesState from "../hooks/useServicesState.tsx";
import useServicesKitsState from "../hooks/useServicesKitsState.tsx";
import useServicePriceCategoryState from "../hooks/useServicePriceCategoryState.tsx";
import { ObjectStateToTableProps } from "../../../types/utils.ts";
export const ServicesPage: FC = () => { export const ServicesPage: FC = () => {
const {services, refetch} = useServicesList(); const [serviceType, setServiceType] = useState(ServicesTab.DEAL_SERVICE);
const {objects: servicesKits, refetch: refetchKits} = useServicesKitsList(); const { services, onServiceDelete, onServiceUpdate, onCreateClick, onCreateCategoryClick } = useServicesState();
const [serviceType, setServiceType] = useState(ServicesTab.DEAL_SERVICE) const { servicesKits, onKitUpdate, onKitCreateClick } = useServicesKitsState();
// region Service create const { onCreateClick: onCreatePriceCategoryClick, ...priceCategoryRestProps } = useServicePriceCategoryState();
const onCreateClick = () => { const getBody = () => {
modals.openContextModal({ switch (serviceType) {
modal: 'createService', case ServicesTab.SERVICES_KITS:
title: 'Создание услуги', return (
withCloseButton: false, <ServicesKitsTable
innerProps: { items={servicesKits}
onCreate onChange={onKitUpdate}
} />
}) );
} case ServicesTab.DEAL_SERVICE:
const onCreate = (values: ServiceSchema) => { case ServicesTab.PRODUCT_SERVICE:
ServiceService.createService({requestBody: {service: values}}) return (
.then(async ({ok, message}) => { <ServicesTable
notifications.guess(ok, {message: message}); onDelete={onServiceDelete}
if (!ok) return; onChange={onServiceUpdate}
await refetch(); items={services.filter(service => service.serviceType == serviceType)}
}) />
} );
// endregion case ServicesTab.SERVICES_PRICE_CATEGORIES:
return (
<ServicePriceCategoryTable
{...ObjectStateToTableProps(priceCategoryRestProps)}
/>
);
}
};
// region Category create const getControls = () => {
const onCreateCategoryClick = () => { switch (serviceType) {
modals.openContextModal({ case ServicesTab.SERVICES_KITS:
modal: "createServiceCategory", return (
title: 'Создание категории', <Button onClick={onKitCreateClick} variant={"default"}>Создать набор</Button>
withCloseButton: false, );
innerProps: { case ServicesTab.DEAL_SERVICE:
onCreate: onCategoryCreate case ServicesTab.PRODUCT_SERVICE:
} return (
}) <>
} <Button onClick={onCreateClick} variant={"default"}>Создать услугу</Button>
const onCategoryCreate = (category: ServiceCategorySchema) => { <Button onClick={onCreateCategoryClick} variant={"default"}>Создать категорию</Button>
ServiceService.createServiceCategory({requestBody: {category: category}}) </>
.then(({ok, message}) => );
notifications.guess(ok, {message: message})) case ServicesTab.SERVICES_PRICE_CATEGORIES:
} return (
// endregion <Button variant={"default"} onClick={() => {
if (onCreatePriceCategoryClick) {
const onServiceDelete = (service: ServiceSchema) => { onCreatePriceCategoryClick();
modals.openConfirmModal({ }
title: 'Удаление услуги', }}>Создать категорию</Button>
children: (<Text> );
Вы уверены, что хотите удалить услугу "{service.name}"? }
</Text>), };
onConfirm: () => {
ServiceService.deleteService({requestBody: {serviceId: service.id}})
.then(async ({ok, message}) => {
notifications.guess(ok, {message: message});
if (!ok) return;
await refetch();
})
},
labels: {
confirm: 'Удалить',
cancel: 'Отмена'
}
});
}
const onServiceUpdate = (service: ServiceSchema) => {
ServiceService
.updateService({
requestBody: {
data: service
}
})
.then(async ({ok, message}) => {
notifications.guess(ok, {message: message});
if (!ok) return;
await refetch();
})
}
const onKitCreate = (kit: GetServiceKitSchema) => {
ServiceService.createServicesKit({
requestBody: {
data: {
...omit(kit, ["services", "id"]),
servicesIds: kit.services.map(service => service.id)
}
}
}).then(async ({ok, message}) => {
notifications.guess(ok, {message: message});
if (!ok) return;
await refetchKits();
})
}
const onKitCreateClick = () => {
modals.openContextModal({
modal: 'serviceKitModalForm',
title: 'Создание набора услуг',
withCloseButton: false,
innerProps: {
onCreate: onKitCreate
}
})
}
const onKitUpdate = (kit: GetServiceKitSchema) => {
ServiceService.updateServicesKit({
requestBody: {
data: {
...omit(kit, ["services"]),
servicesIds: kit.services.map(service => service.id)
}
}
}).then(async ({ok, message}) => {
notifications.guess(ok, {message: message});
if (!ok) return;
await refetchKits();
})
}
return ( return (
<div className={styles['container']}> <div className={styles["container"]}>
<PageBlock> <PageBlock>
<div className={styles['top-panel']}> <div className={styles["top-panel"]}>
{ {getControls()}
serviceType === ServicesTab.SERVICES_KITS ?
<Button onClick={onKitCreateClick} variant={"default"}>Создать набор</Button> :
<>
<Button onClick={onCreateClick} variant={"default"}>Создать услугу</Button>
<Button onClick={onCreateCategoryClick} variant={"default"}>Создать
категорию</Button>
</>
}
<ServiceTypeSegmentedControl <ServiceTypeSegmentedControl
className={styles['top-panel-last-item']} className={styles["top-panel-last-item"]}
value={serviceType.toString()} value={serviceType.toString()}
onChange={(event) => setServiceType(parseInt(event))} onChange={(event) => setServiceType(parseInt(event))}
/> />
</div> </div>
</PageBlock> </PageBlock>
<PageBlock> <PageBlock>
{ {getBody()}
serviceType === ServicesTab.SERVICES_KITS ?
<ServicesKitsTable
items={servicesKits}
onChange={onKitUpdate}
/>
:
<ServicesTable
onDelete={onServiceDelete}
onChange={onServiceUpdate}
items={services.filter(service => service.serviceType == serviceType)}
/>
}
</PageBlock> </PageBlock>
</div> </div>
) );
} };

View File

@@ -1,4 +1,4 @@
import {BaseMarketplaceSchema} from "../client"; import { BaseMarketplaceSchema, ServicePriceCategorySchema } from "../client";
export type QuickDeal = { export type QuickDeal = {
name: string; name: string;
@@ -7,5 +7,6 @@ export type QuickDeal = {
comment: string; comment: string;
acceptanceDate: Date; acceptanceDate: Date;
shippingWarehouse: string; shippingWarehouse: string;
baseMarketplace: BaseMarketplaceSchema baseMarketplace: BaseMarketplaceSchema;
category?: ServicePriceCategorySchema;
} }

View File

@@ -0,0 +1,14 @@
type UseObjectState<T> = {
onCreateClick?: () => void;
onCreate: (values: T) => void;
onDeleteClick?: (item: T) => void;
onDelete: (item: T) => void;
onChangeClick?: (item: T) => void;
onChange: (item: T) => void;
objects: T[];
}
export default UseObjectState;

View File

@@ -1,6 +1,9 @@
import {ProductSchema} from "../client"; import { ProductSchema } from "../client";
import {isNil} from "lodash"; import { isNil } from "lodash";
import {ProductFieldNames} from "../pages/LeadsPage/tabs/ProductAndServiceTab/components/ProductView/ProductView.tsx"; import { ProductFieldNames } from "../pages/LeadsPage/tabs/ProductAndServiceTab/components/ProductView/ProductView.tsx";
import UseObjectState from "./UseObjectState.ts";
import { CRUDTableProps } from "./CRUDTable.tsx";
import { MRT_RowData } from "mantine-react-table";
export type ObjectWithNameAndId = { export type ObjectWithNameAndId = {
id: number; id: number;
@@ -14,12 +17,22 @@ export type BaseFormInputProps<T> = {
} }
export const formatDate = (date: string) => { export const formatDate = (date: string) => {
return new Date(date).toLocaleDateString("ru-RU"); return new Date(date).toLocaleDateString("ru-RU");
} };
export const getProductFields = (product: ProductSchema) => { export const getProductFields = (product: ProductSchema) => {
return Object.entries(product).map(([key, value]) => { return Object.entries(product).map(([key, value]) => {
const fieldName = ProductFieldNames[key as keyof ProductSchema]; const fieldName = ProductFieldNames[key as keyof ProductSchema];
if (!fieldName || isNil(value) || value === '' || !value) return; if (!fieldName || isNil(value) || value === "" || !value) return;
return [fieldName.toString(), value.toString()]; return [fieldName.toString(), value.toString()];
}).filter(obj => obj !== undefined) || []; }).filter(obj => obj !== undefined) || [];
};
export function ObjectStateToTableProps<T extends MRT_RowData>(state: UseObjectState<T>): CRUDTableProps<T> {
return {
items: state.objects,
onDelete: state.onDelete,
onChange: state.onChange,
onCreate: state.onCreate,
};
} }