crap
This commit is contained in:
@@ -19,16 +19,26 @@ export type { ClientSchema } from './models/ClientSchema';
|
|||||||
export type { ClientUpdateDetailsRequest } from './models/ClientUpdateDetailsRequest';
|
export type { ClientUpdateDetailsRequest } from './models/ClientUpdateDetailsRequest';
|
||||||
export type { ClientUpdateRequest } from './models/ClientUpdateRequest';
|
export type { ClientUpdateRequest } from './models/ClientUpdateRequest';
|
||||||
export type { ClientUpdateResponse } from './models/ClientUpdateResponse';
|
export type { ClientUpdateResponse } from './models/ClientUpdateResponse';
|
||||||
|
export type { DealAddServiceRequest } from './models/DealAddServiceRequest';
|
||||||
|
export type { DealAddServiceResponse } from './models/DealAddServiceResponse';
|
||||||
export type { DealAddServicesRequest } from './models/DealAddServicesRequest';
|
export type { DealAddServicesRequest } from './models/DealAddServicesRequest';
|
||||||
export type { DealAddServicesResponse } from './models/DealAddServicesResponse';
|
export type { DealAddServicesResponse } from './models/DealAddServicesResponse';
|
||||||
export type { DealChangeStatusRequest } from './models/DealChangeStatusRequest';
|
export type { DealChangeStatusRequest } from './models/DealChangeStatusRequest';
|
||||||
export type { DealChangeStatusResponse } from './models/DealChangeStatusResponse';
|
export type { DealChangeStatusResponse } from './models/DealChangeStatusResponse';
|
||||||
export type { DealCreateRequest } from './models/DealCreateRequest';
|
export type { DealCreateRequest } from './models/DealCreateRequest';
|
||||||
|
export type { DealDeleteServiceRequest } from './models/DealDeleteServiceRequest';
|
||||||
|
export type { DealDeleteServiceResponse } from './models/DealDeleteServiceResponse';
|
||||||
|
export type { DealDeleteServicesRequest } from './models/DealDeleteServicesRequest';
|
||||||
|
export type { DealDeleteServicesResponse } from './models/DealDeleteServicesResponse';
|
||||||
|
export type { DealGetAllResponse } from './models/DealGetAllResponse';
|
||||||
export type { DealQuickCreateRequest } from './models/DealQuickCreateRequest';
|
export type { DealQuickCreateRequest } from './models/DealQuickCreateRequest';
|
||||||
export type { DealQuickCreateResponse } from './models/DealQuickCreateResponse';
|
export type { DealQuickCreateResponse } from './models/DealQuickCreateResponse';
|
||||||
|
export type { DealSchema } from './models/DealSchema';
|
||||||
export type { DealServiceSchema } from './models/DealServiceSchema';
|
export type { DealServiceSchema } from './models/DealServiceSchema';
|
||||||
export type { DealSummary } from './models/DealSummary';
|
export type { DealSummary } from './models/DealSummary';
|
||||||
export type { DealSummaryResponse } from './models/DealSummaryResponse';
|
export type { DealSummaryResponse } from './models/DealSummaryResponse';
|
||||||
|
export type { DealUpdateServiceQuantityRequest } from './models/DealUpdateServiceQuantityRequest';
|
||||||
|
export type { DealUpdateServiceQuantityResponse } from './models/DealUpdateServiceQuantityResponse';
|
||||||
export type { HTTPValidationError } from './models/HTTPValidationError';
|
export type { HTTPValidationError } from './models/HTTPValidationError';
|
||||||
export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
|
export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
|
||||||
export type { ProductCreateRequest } from './models/ProductCreateRequest';
|
export type { ProductCreateRequest } from './models/ProductCreateRequest';
|
||||||
|
|||||||
10
src/client/models/DealAddServiceRequest.ts
Normal file
10
src/client/models/DealAddServiceRequest.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealAddServiceRequest = {
|
||||||
|
dealId: number;
|
||||||
|
serviceId: number;
|
||||||
|
quantity: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DealAddServiceResponse.ts
Normal file
9
src/client/models/DealAddServiceResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealAddServiceResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DealDeleteServiceRequest.ts
Normal file
9
src/client/models/DealDeleteServiceRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealDeleteServiceRequest = {
|
||||||
|
dealId: number;
|
||||||
|
serviceId: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DealDeleteServiceResponse.ts
Normal file
9
src/client/models/DealDeleteServiceResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealDeleteServiceResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DealDeleteServicesRequest.ts
Normal file
9
src/client/models/DealDeleteServicesRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealDeleteServicesRequest = {
|
||||||
|
dealId: number;
|
||||||
|
serviceIds: Array<number>;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DealDeleteServicesResponse.ts
Normal file
9
src/client/models/DealDeleteServicesResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealDeleteServicesResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DealGetAllResponse.ts
Normal file
9
src/client/models/DealGetAllResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { DealSchema } from './DealSchema';
|
||||||
|
export type DealGetAllResponse = {
|
||||||
|
deals: Array<DealSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
14
src/client/models/DealSchema.ts
Normal file
14
src/client/models/DealSchema.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { DealServiceSchema } from './DealServiceSchema';
|
||||||
|
export type DealSchema = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
clientId: number;
|
||||||
|
createdAt: string;
|
||||||
|
currentStatus: number;
|
||||||
|
services: Array<DealServiceSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
import type { ServiceSchema } from './ServiceSchema';
|
||||||
export type DealServiceSchema = {
|
export type DealServiceSchema = {
|
||||||
id: number;
|
service: ServiceSchema;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
10
src/client/models/DealUpdateServiceQuantityRequest.ts
Normal file
10
src/client/models/DealUpdateServiceQuantityRequest.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealUpdateServiceQuantityRequest = {
|
||||||
|
dealId: number;
|
||||||
|
serviceId: number;
|
||||||
|
quantity: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DealUpdateServiceQuantityResponse.ts
Normal file
9
src/client/models/DealUpdateServiceQuantityResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealUpdateServiceQuantityResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -2,14 +2,24 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
import type { DealAddServiceRequest } from '../models/DealAddServiceRequest';
|
||||||
|
import type { DealAddServiceResponse } from '../models/DealAddServiceResponse';
|
||||||
import type { DealAddServicesRequest } from '../models/DealAddServicesRequest';
|
import type { DealAddServicesRequest } from '../models/DealAddServicesRequest';
|
||||||
import type { DealAddServicesResponse } from '../models/DealAddServicesResponse';
|
import type { DealAddServicesResponse } from '../models/DealAddServicesResponse';
|
||||||
import type { DealChangeStatusRequest } from '../models/DealChangeStatusRequest';
|
import type { DealChangeStatusRequest } from '../models/DealChangeStatusRequest';
|
||||||
import type { DealChangeStatusResponse } from '../models/DealChangeStatusResponse';
|
import type { DealChangeStatusResponse } from '../models/DealChangeStatusResponse';
|
||||||
import type { DealCreateRequest } from '../models/DealCreateRequest';
|
import type { DealCreateRequest } from '../models/DealCreateRequest';
|
||||||
|
import type { DealDeleteServiceRequest } from '../models/DealDeleteServiceRequest';
|
||||||
|
import type { DealDeleteServiceResponse } from '../models/DealDeleteServiceResponse';
|
||||||
|
import type { DealDeleteServicesRequest } from '../models/DealDeleteServicesRequest';
|
||||||
|
import type { DealDeleteServicesResponse } from '../models/DealDeleteServicesResponse';
|
||||||
|
import type { DealGetAllResponse } from '../models/DealGetAllResponse';
|
||||||
import type { DealQuickCreateRequest } from '../models/DealQuickCreateRequest';
|
import type { DealQuickCreateRequest } from '../models/DealQuickCreateRequest';
|
||||||
import type { DealQuickCreateResponse } from '../models/DealQuickCreateResponse';
|
import type { DealQuickCreateResponse } from '../models/DealQuickCreateResponse';
|
||||||
|
import type { DealSchema } from '../models/DealSchema';
|
||||||
import type { DealSummaryResponse } from '../models/DealSummaryResponse';
|
import type { DealSummaryResponse } from '../models/DealSummaryResponse';
|
||||||
|
import type { DealUpdateServiceQuantityRequest } from '../models/DealUpdateServiceQuantityRequest';
|
||||||
|
import type { DealUpdateServiceQuantityResponse } from '../models/DealUpdateServiceQuantityResponse';
|
||||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||||
import { OpenAPI } from '../core/OpenAPI';
|
import { OpenAPI } from '../core/OpenAPI';
|
||||||
import { request as __request } from '../core/request';
|
import { request as __request } from '../core/request';
|
||||||
@@ -90,11 +100,31 @@ export class DealService {
|
|||||||
* @returns DealAddServicesResponse Successful Response
|
* @returns DealAddServicesResponse Successful Response
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static servicesAddDealServicesAddPost({
|
public static addMultipleDealServices({
|
||||||
requestBody,
|
requestBody,
|
||||||
}: {
|
}: {
|
||||||
requestBody: DealAddServicesRequest,
|
requestBody: DealAddServicesRequest,
|
||||||
}): CancelablePromise<DealAddServicesResponse> {
|
}): CancelablePromise<DealAddServicesResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/deal/services/add/multiple',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Services Add
|
||||||
|
* @returns DealAddServiceResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static addDealService({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: DealAddServiceRequest,
|
||||||
|
}): CancelablePromise<DealAddServiceResponse> {
|
||||||
return __request(OpenAPI, {
|
return __request(OpenAPI, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/deal/services/add',
|
url: '/deal/services/add',
|
||||||
@@ -105,4 +135,96 @@ export class DealService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Services Update
|
||||||
|
* @returns DealUpdateServiceQuantityResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static updateDealServiceQuantity({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: DealUpdateServiceQuantityRequest,
|
||||||
|
}): CancelablePromise<DealUpdateServiceQuantityResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/deal/services/update-quantity',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Services Delete
|
||||||
|
* @returns DealDeleteServiceResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static deleteDealService({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: DealDeleteServiceRequest,
|
||||||
|
}): CancelablePromise<DealDeleteServiceResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/deal/services/delete',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Services Delete
|
||||||
|
* @returns DealDeleteServicesResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static deleteMultipleDealServices({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: DealDeleteServicesRequest,
|
||||||
|
}): CancelablePromise<DealDeleteServicesResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/deal/services/delete/multiple',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get All
|
||||||
|
* @returns DealGetAllResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getAllDeals(): CancelablePromise<DealGetAllResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/deal/get-all',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get Deal By Id
|
||||||
|
* @returns DealSchema Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getDealById({
|
||||||
|
dealId,
|
||||||
|
}: {
|
||||||
|
dealId: number,
|
||||||
|
}): CancelablePromise<DealSchema> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/deal/get/{deal_id}',
|
||||||
|
path: {
|
||||||
|
'deal_id': dealId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import {
|
|||||||
useMantineReactTable
|
useMantineReactTable
|
||||||
} from "mantine-react-table";
|
} from "mantine-react-table";
|
||||||
import {MRT_Localization_RU} from "mantine-react-table/locales/ru/index.cjs";
|
import {MRT_Localization_RU} from "mantine-react-table/locales/ru/index.cjs";
|
||||||
import {forwardRef, useImperativeHandle} from 'react';
|
import {forwardRef, useEffect, useImperativeHandle} from 'react';
|
||||||
|
|
||||||
type Props<T extends Record<string, any>, K extends keyof T> = {
|
type Props<T extends Record<string, any>, K extends keyof T> = {
|
||||||
data: T[],
|
data: T[],
|
||||||
|
onSelectionChange?: (selectedRows: T[]) => void,
|
||||||
columns: MRT_ColumnDef<T>[],
|
columns: MRT_ColumnDef<T>[],
|
||||||
restProps?: MRT_TableOptions<T>,
|
restProps?: MRT_TableOptions<T>,
|
||||||
striped?: boolean
|
striped?: boolean
|
||||||
@@ -23,7 +24,7 @@ export type BaseTableRef<T extends MRT_RowData> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const BaseTable = forwardRef<BaseTableRef<any>, Props<any>>((props, ref) => {
|
export const BaseTable = forwardRef<BaseTableRef<any>, Props<any>>((props, ref) => {
|
||||||
const {data, columns, restProps, striped} = props;
|
const {data, columns, restProps, striped, onSelectionChange} = props;
|
||||||
|
|
||||||
const table = useMantineReactTable({
|
const table = useMantineReactTable({
|
||||||
localization: MRT_Localization_RU,
|
localization: MRT_Localization_RU,
|
||||||
@@ -34,8 +35,14 @@ export const BaseTable = forwardRef<BaseTableRef<any>, Props<any>>((props, ref)
|
|||||||
striped: striped
|
striped: striped
|
||||||
},
|
},
|
||||||
enableTopToolbar: false,
|
enableTopToolbar: false,
|
||||||
|
enableBottomToolbar: false,
|
||||||
|
enableRowSelection: onSelectionChange !== undefined,
|
||||||
...restProps,
|
...restProps,
|
||||||
});
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (!onSelectionChange) return;
|
||||||
|
onSelectionChange(table.getSelectedRowModel().rows.map(row => row.original))
|
||||||
|
}, [table.getState().rowSelection]);
|
||||||
|
|
||||||
// Используем useImperativeHandle для определения, что будет доступно через ref
|
// Используем useImperativeHandle для определения, что будет доступно через ref
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import {FC} from "react";
|
import {FC} from "react";
|
||||||
import {DealSummary} from "../../../client";
|
import {DealService, DealSummary} from "../../../client";
|
||||||
import styles from './DealSummaryCard.module.css';
|
import styles from './DealSummaryCard.module.css';
|
||||||
|
|
||||||
import {Text} from '@mantine/core';
|
import {Text} from '@mantine/core';
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import {useDealPageContext} from "../../../pages/LeadsPage/contexts/DealPageContext.tsx";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
dealSummary: DealSummary
|
dealSummary: DealSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
const DealSummaryCard: FC<Props> = ({dealSummary}) => {
|
const DealSummaryCard: FC<Props> = ({dealSummary}) => {
|
||||||
|
const {setSelectedDeal} = useDealPageContext();
|
||||||
|
const onDealSummaryClick = () => {
|
||||||
|
DealService.getDealById({dealId: dealSummary.id})
|
||||||
|
.then((deal) => {
|
||||||
|
setSelectedDeal(deal);
|
||||||
|
})
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className={styles['container']}>
|
<div onClick={() => onDealSummaryClick()} className={styles['container']}>
|
||||||
<div className={styles['flex-row']}>
|
<div className={styles['flex-row']}>
|
||||||
<div className={styles['flex-item']}>
|
<div className={styles['flex-item']}>
|
||||||
<Text size={"sm"} c={"gray.6"}>
|
<Text size={"sm"} c={"gray.6"}>
|
||||||
|
|||||||
3
src/components/PlusMinusInput/PlusMinusInput.module.css
Normal file
3
src/components/PlusMinusInput/PlusMinusInput.module.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.number-input {
|
||||||
|
width: rem(50);
|
||||||
|
}
|
||||||
97
src/components/PlusMinusInput/PlusMinusInput.tsx
Normal file
97
src/components/PlusMinusInput/PlusMinusInput.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import {ActionIcon, Flex, NumberInput, rem} from "@mantine/core";
|
||||||
|
import {IconMinus, IconPlus} from "@tabler/icons-react";
|
||||||
|
import styles from './PlusMinusInput.module.css';
|
||||||
|
import {FC, useEffect, useState} from "react";
|
||||||
|
|
||||||
|
type ControlledValueProps = {
|
||||||
|
value: number;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RestProps = {
|
||||||
|
defaultValue?: number;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = RestProps & Partial<ControlledValueProps>;
|
||||||
|
|
||||||
|
const PlusMinusInput: FC<Props> = (props: Props) => {
|
||||||
|
const isControlled = props.value !== undefined;
|
||||||
|
const [internalValue, setInternalValue] = useState(props.defaultValue || 0);
|
||||||
|
|
||||||
|
const value = isControlled ? props.value : internalValue;
|
||||||
|
|
||||||
|
const onMinusClick = () => {
|
||||||
|
|
||||||
|
const newValue = (value || 0) - 1;
|
||||||
|
if (newValue < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isControlled) {
|
||||||
|
props.onChange(newValue);
|
||||||
|
} else {
|
||||||
|
setInternalValue(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPlusClick = () => {
|
||||||
|
const newValue = (value || 0) + 1;
|
||||||
|
if (isControlled) {
|
||||||
|
props.onChange(newValue);
|
||||||
|
} else {
|
||||||
|
setInternalValue(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInputChange = (event: number | string) => {
|
||||||
|
let newValue = typeof event === "string" ? 0 : event;
|
||||||
|
if (isNaN(newValue) || newValue < 0) {
|
||||||
|
newValue = 0;
|
||||||
|
}
|
||||||
|
if (isControlled) {
|
||||||
|
props.onChange(newValue);
|
||||||
|
} else {
|
||||||
|
setInternalValue(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isControlled) {
|
||||||
|
props.onChange(internalValue);
|
||||||
|
}
|
||||||
|
}, [internalValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
align={"center"}
|
||||||
|
gap={rem(10)}
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
disabled={value === 0}
|
||||||
|
onClick={onMinusClick}
|
||||||
|
variant={"default"}>
|
||||||
|
<IconMinus/>
|
||||||
|
</ActionIcon>
|
||||||
|
<NumberInput
|
||||||
|
min={0}
|
||||||
|
styles={{
|
||||||
|
input: {
|
||||||
|
textAlign: "center"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
allowNegative={false}
|
||||||
|
hideControls
|
||||||
|
value={value}
|
||||||
|
className={styles['number-input']}
|
||||||
|
onChange={(event) => handleInputChange(event)}
|
||||||
|
/>
|
||||||
|
<ActionIcon
|
||||||
|
onClick={onPlusClick}
|
||||||
|
variant={"default"}>
|
||||||
|
<IconPlus/>
|
||||||
|
</ActionIcon>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlusMinusInput;
|
||||||
63
src/components/ServiceSelect/ServiceSelect.tsx
Normal file
63
src/components/ServiceSelect/ServiceSelect.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import {ServiceSchema} from "../../client";
|
||||||
|
import {FC, useEffect, useMemo, useState} from "react";
|
||||||
|
import {Select, SelectProps} from "@mantine/core";
|
||||||
|
import useServicesList from "../../pages/ServicesPage/hooks/useServicesList.tsx";
|
||||||
|
|
||||||
|
type ControlledValueProps = {
|
||||||
|
value: ServiceSchema;
|
||||||
|
onChange: (value: ServiceSchema) => void;
|
||||||
|
}
|
||||||
|
type RestProps = {
|
||||||
|
defaultValue?: ServiceSchema;
|
||||||
|
onChange: (value: ServiceSchema) => void;
|
||||||
|
}
|
||||||
|
type Props = (RestProps & Partial<ControlledValueProps>) & Omit<SelectProps, 'value' | 'onChange'>;
|
||||||
|
|
||||||
|
|
||||||
|
const ServiceSelect: FC<Props> = (props) => {
|
||||||
|
const isControlled = props.value !== undefined;
|
||||||
|
const [internalValue, setInternalValue] = useState<ServiceSchema | undefined>(props.defaultValue);
|
||||||
|
const value = isControlled ? props.value : internalValue;
|
||||||
|
const {services} = useServicesList();
|
||||||
|
const categories = useMemo(() => services.reduce((acc, service) => {
|
||||||
|
if (!acc.includes(service.category.name)) {
|
||||||
|
acc.push(service.category.name);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, [] as string[]), [services]);
|
||||||
|
|
||||||
|
const data = useMemo(() => categories.map(category => ({
|
||||||
|
group: category,
|
||||||
|
items: services.filter(service => service.category.name === category)
|
||||||
|
.map(service => ({
|
||||||
|
value: service.id.toString(),
|
||||||
|
label: service.name
|
||||||
|
}))
|
||||||
|
})), [services, categories]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleOnChange = (value: string) => {
|
||||||
|
if (isControlled) {
|
||||||
|
props.onChange(services.find(service => service.id.toString() === value) as ServiceSchema);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInternalValue(services.find(service => service.id.toString() === value) as ServiceSchema);
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isControlled) {
|
||||||
|
props.onChange(internalValue as ServiceSchema);
|
||||||
|
}
|
||||||
|
}, [internalValue]);
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
{...props}
|
||||||
|
value={value?.id.toString()}
|
||||||
|
withCheckIcon={false}
|
||||||
|
searchable
|
||||||
|
onChange={event => event && handleOnChange(event)}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceSelect;
|
||||||
@@ -3,6 +3,7 @@ import CreateServiceCategoryModal from "../pages/ServicesPage/modals/CreateServi
|
|||||||
import CreateServiceModal from "../pages/ServicesPage/modals/CreateServiceModal.tsx";
|
import CreateServiceModal from "../pages/ServicesPage/modals/CreateServiceModal.tsx";
|
||||||
import createProductModal from "../pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx";
|
import createProductModal from "../pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx";
|
||||||
import ProductFormModal from "../pages/ClientsPage/modals/ClientFormModal/ClientFormModal.tsx";
|
import ProductFormModal from "../pages/ClientsPage/modals/ClientFormModal/ClientFormModal.tsx";
|
||||||
|
import AddDealServiceModal from "../pages/LeadsPage/modals/AddDealServiceModal.tsx";
|
||||||
|
|
||||||
export const modals = {
|
export const modals = {
|
||||||
enterDeadline: EnterDeadlineModal,
|
enterDeadline: EnterDeadlineModal,
|
||||||
@@ -10,4 +11,5 @@ export const modals = {
|
|||||||
createService: CreateServiceModal,
|
createService: CreateServiceModal,
|
||||||
createProduct: createProductModal,
|
createProduct: createProductModal,
|
||||||
productFormModal: ProductFormModal,
|
productFormModal: ProductFormModal,
|
||||||
|
addDealService: AddDealServiceModal
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {FC} from "react";
|
import {FC, useState} from "react";
|
||||||
import ClientsTable from "./components/ClientsTable/ClientsTable.tsx";
|
import ClientsTable from "./components/ClientsTable/ClientsTable.tsx";
|
||||||
import useClientsList from "./hooks/useClientsList.tsx";
|
import useClientsList from "./hooks/useClientsList.tsx";
|
||||||
import PageBlock from "../../components/PageBlock/PageBlock.tsx";
|
import PageBlock from "../../components/PageBlock/PageBlock.tsx";
|
||||||
@@ -7,6 +7,7 @@ import {Button} from "@mantine/core";
|
|||||||
import {modals} from "@mantine/modals";
|
import {modals} from "@mantine/modals";
|
||||||
import {ClientSchema, ClientService} from "../../client";
|
import {ClientSchema, ClientService} from "../../client";
|
||||||
import {notifications} from "../../shared/lib/notifications.ts";
|
import {notifications} from "../../shared/lib/notifications.ts";
|
||||||
|
import PlusMinusInput from "../../components/PlusMinusInput/PlusMinusInput.tsx";
|
||||||
|
|
||||||
const ClientsPage: FC = () => {
|
const ClientsPage: FC = () => {
|
||||||
const {clients, refetch} = useClientsList();
|
const {clients, refetch} = useClientsList();
|
||||||
@@ -58,6 +59,8 @@ const ClientsPage: FC = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [a, setA] = useState(5);
|
||||||
return (
|
return (
|
||||||
<div className={styles['container']}>
|
<div className={styles['container']}>
|
||||||
<PageBlock>
|
<PageBlock>
|
||||||
@@ -68,7 +71,14 @@ const ClientsPage: FC = () => {
|
|||||||
>
|
>
|
||||||
Создать клиента
|
Создать клиента
|
||||||
</Button>
|
</Button>
|
||||||
|
<PlusMinusInput onChange={event=>{
|
||||||
|
console.log(event);
|
||||||
|
setA(event)
|
||||||
|
}}
|
||||||
|
value={a}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</PageBlock>
|
</PageBlock>
|
||||||
<PageBlock>
|
<PageBlock>
|
||||||
<ClientsTable
|
<ClientsTable
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const BaseFormModal = <T, >(props: Props<T>) => {
|
|||||||
const isEditing = 'onChange' in props;
|
const isEditing = 'onChange' in props;
|
||||||
|
|
||||||
const onSubmit = (values: T) => {
|
const onSubmit = (values: T) => {
|
||||||
|
console.log('----values');
|
||||||
|
console.log(values)
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
props.onChange(values);
|
props.onChange(values);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import {FC} from "react";
|
||||||
|
import {useDealServicesTableColumns} from "./columns.tsx";
|
||||||
|
import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
|
||||||
|
import {DealServiceSchema} from "../../../../client";
|
||||||
|
import {CRUDTableProps} from "../../../../types/CRUDTable.tsx";
|
||||||
|
import {MRT_TableOptions} from "mantine-react-table";
|
||||||
|
import {ActionIcon, Box, Button, Flex, rem, Tooltip} from "@mantine/core";
|
||||||
|
import {openContextModal} from "@mantine/modals";
|
||||||
|
import {IconTrash} from "@tabler/icons-react";
|
||||||
|
|
||||||
|
type RestProps = {
|
||||||
|
onMultipleDelete?: (items: DealServiceSchema[]) => void;
|
||||||
|
}
|
||||||
|
type Props = CRUDTableProps<DealServiceSchema> & RestProps;
|
||||||
|
const DealServicesTable: FC<Props> = (
|
||||||
|
{
|
||||||
|
items,
|
||||||
|
onChange,
|
||||||
|
onDelete,
|
||||||
|
onCreate,
|
||||||
|
onSelectionChange,
|
||||||
|
onMultipleDelete,
|
||||||
|
tableRef
|
||||||
|
}) => {
|
||||||
|
const onQuantityChange = (service: DealServiceSchema, quantity: number) => {
|
||||||
|
if (!onChange) return;
|
||||||
|
if (quantity <= 0 && onDelete) {
|
||||||
|
onDelete(service);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChange({...service, quantity});
|
||||||
|
}
|
||||||
|
const columns = useDealServicesTableColumns({
|
||||||
|
onChange: onQuantityChange,
|
||||||
|
data: items
|
||||||
|
});
|
||||||
|
const onCreateClick = () => {
|
||||||
|
if (!onCreate) return;
|
||||||
|
openContextModal({
|
||||||
|
title: "Добавление услуги",
|
||||||
|
modal: "addDealService",
|
||||||
|
innerProps: {
|
||||||
|
onCreate: (event) => onCreate(event as DealServiceSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<BaseTable
|
||||||
|
ref={tableRef}
|
||||||
|
data={items}
|
||||||
|
columns={columns}
|
||||||
|
onSelectionChange={onSelectionChange}
|
||||||
|
restProps={{
|
||||||
|
enableGrouping: true,
|
||||||
|
initialState: {grouping: ["service.category"]},
|
||||||
|
enableColumnActions: false,
|
||||||
|
enableSorting: false,
|
||||||
|
enableBottomToolbar: true,
|
||||||
|
enableRowActions: true,
|
||||||
|
enableRowSelection: true,
|
||||||
|
renderBottomToolbar: ({table}) => (
|
||||||
|
<Flex justify={"flex-end"} gap={rem(10)} p={rem(10)}>
|
||||||
|
{(onMultipleDelete && table.getSelectedRowModel().rows.length > 0) && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onMultipleDelete(table.getSelectedRowModel().rows.map(row => row.original))
|
||||||
|
}}
|
||||||
|
variant={"filled"}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
Удалить выбранные
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button onClick={onCreateClick} variant={"default"}>
|
||||||
|
Добавить услугу
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
renderRowActions: ({row}) => (
|
||||||
|
<Flex gap="md">
|
||||||
|
|
||||||
|
<Tooltip label="Удалить">
|
||||||
|
<ActionIcon onClick={() => {
|
||||||
|
if (onDelete) onDelete(row.original);
|
||||||
|
}} variant={"default"}>
|
||||||
|
<IconTrash/>
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
} as MRT_TableOptions<DealServiceSchema>}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DealServicesTable;
|
||||||
66
src/pages/LeadsPage/components/DealServicesTable/columns.tsx
Normal file
66
src/pages/LeadsPage/components/DealServicesTable/columns.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import {MRT_ColumnDef} from "mantine-react-table";
|
||||||
|
import {useMemo} from "react";
|
||||||
|
import {DealServiceSchema} from "../../../../client";
|
||||||
|
import PlusMinusInput from "../../../../components/PlusMinusInput/PlusMinusInput.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onChange: (service: DealServiceSchema, quantity: number) => void;
|
||||||
|
data: DealServiceSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDealServicesTableColumns = (props: Props) => {
|
||||||
|
const {onChange, data} = props;
|
||||||
|
const totalPrice = useMemo(() =>
|
||||||
|
data.reduce((acc, row) => acc + row.quantity * row.service.price, 0)
|
||||||
|
,
|
||||||
|
[data]);
|
||||||
|
|
||||||
|
return useMemo<MRT_ColumnDef<DealServiceSchema>[]>(() => [
|
||||||
|
{
|
||||||
|
accessorKey: "service.category",
|
||||||
|
header: "Категория",
|
||||||
|
accessorFn: (row) => row.service.category.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enableGrouping: false,
|
||||||
|
accessorKey: "service.name",
|
||||||
|
header: "Услуга",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enableGrouping: false,
|
||||||
|
accessorKey: "service.price",
|
||||||
|
header: "Цена",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enableGrouping: false,
|
||||||
|
accessorKey: "quantity",
|
||||||
|
header: "Количество",
|
||||||
|
Cell: ({row}) => {
|
||||||
|
return (
|
||||||
|
<PlusMinusInput
|
||||||
|
value={row.original.quantity}
|
||||||
|
onChange={(value) => onChange(row.original, value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enableGrouping: false,
|
||||||
|
header: "Сумма",
|
||||||
|
Cell: ({row}) => {
|
||||||
|
return row.original.quantity * row.original.service.price;
|
||||||
|
},
|
||||||
|
aggregationFn: "sum",
|
||||||
|
AggregatedCell: ({cell}) => {
|
||||||
|
return <>Итоговая сумма по категории: {" "}
|
||||||
|
{
|
||||||
|
cell.row.subRows?.reduce((acc, row) =>
|
||||||
|
acc + row.original.quantity * row.original.service.price, 0)
|
||||||
|
}
|
||||||
|
</>;
|
||||||
|
},
|
||||||
|
Footer: <>Итоговая сумма по услугам: {totalPrice}</>
|
||||||
|
|
||||||
|
}
|
||||||
|
], [onChange]);
|
||||||
|
}
|
||||||
34
src/pages/LeadsPage/contexts/DealPageContext.tsx
Normal file
34
src/pages/LeadsPage/contexts/DealPageContext.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import {createContext, FC, useContext, useState} from "react";
|
||||||
|
import {DealSchema} from "../../../client";
|
||||||
|
|
||||||
|
type DealPageContextState = {
|
||||||
|
selectedDeal?: DealSchema;
|
||||||
|
setSelectedDeal: (deal: DealSchema | undefined) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DealPageContext = createContext<DealPageContextState | undefined>(undefined);
|
||||||
|
const useDealPageContextState = () => {
|
||||||
|
const [selectedDeal, setSelectedDeal] = useState<DealSchema | undefined>(undefined);
|
||||||
|
return {selectedDeal, setSelectedDeal};
|
||||||
|
}
|
||||||
|
|
||||||
|
type DealPageContextProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DealPageContextProvider: FC<DealPageContextProviderProps> = ({children}) => {
|
||||||
|
const state = useDealPageContextState();
|
||||||
|
return (
|
||||||
|
<DealPageContext.Provider value={state}>
|
||||||
|
{children}
|
||||||
|
</DealPageContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDealPageContext = () => {
|
||||||
|
const context = useContext(DealPageContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useDealPageContext must be used within a DealPageContextProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
175
src/pages/LeadsPage/drawers/DealEditDrawer/DealEditDrawer.tsx
Normal file
175
src/pages/LeadsPage/drawers/DealEditDrawer/DealEditDrawer.tsx
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
import {Drawer, Text} from "@mantine/core";
|
||||||
|
import {FC, useRef} from "react";
|
||||||
|
import DealServicesTable from "../../components/DealServicesTable/DealServicesTable.tsx";
|
||||||
|
import {useDealPageContext} from "../../contexts/DealPageContext.tsx";
|
||||||
|
import {DealService, DealServiceSchema} from "../../../../client";
|
||||||
|
import {notifications} from "../../../../shared/lib/notifications.ts";
|
||||||
|
import {modals} from "@mantine/modals";
|
||||||
|
import {BaseTableRef} from "../../../../components/BaseTable/BaseTable.tsx";
|
||||||
|
|
||||||
|
const useDealServicesTableState = () => {
|
||||||
|
const {selectedDeal, setSelectedDeal} = useDealPageContext();
|
||||||
|
const tableRef = useRef<BaseTableRef<DealServiceSchema>>(null);
|
||||||
|
|
||||||
|
const onServiceUpdate = (service: DealServiceSchema) => {
|
||||||
|
if (!selectedDeal) return;
|
||||||
|
DealService.updateDealServiceQuantity({
|
||||||
|
requestBody: {
|
||||||
|
dealId: selectedDeal.id,
|
||||||
|
serviceId: service.service.id,
|
||||||
|
quantity: service.quantity
|
||||||
|
}
|
||||||
|
}).then(async ({ok, message}) => {
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
notifications.guess(ok, {message});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await DealService.getDealById({dealId: selectedDeal.id})
|
||||||
|
.then(setSelectedDeal)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onServiceDelete = (service: DealServiceSchema) => {
|
||||||
|
if (!selectedDeal) return;
|
||||||
|
modals.openConfirmModal({
|
||||||
|
title: "Удаление услуги",
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Text>
|
||||||
|
Вы уверены, что хотите удалить услугу:
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
{service.service.name}?
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
</>
|
||||||
|
|
||||||
|
),
|
||||||
|
onConfirm: () => {
|
||||||
|
DealService.deleteDealService({
|
||||||
|
requestBody: {
|
||||||
|
dealId: selectedDeal.id,
|
||||||
|
serviceId: service.service.id
|
||||||
|
}
|
||||||
|
}).then(async ({ok, message}) => {
|
||||||
|
if (!ok) {
|
||||||
|
notifications.guess(ok, {message});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await DealService.getDealById({dealId: selectedDeal.id})
|
||||||
|
.then(setSelectedDeal)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
cancel: "Отмена",
|
||||||
|
confirm: "Удалить"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onServiceCreate = (service: DealServiceSchema) => {
|
||||||
|
console.log('-------Drawer')
|
||||||
|
console.log(service);
|
||||||
|
if (!selectedDeal) return;
|
||||||
|
DealService.addDealService({
|
||||||
|
requestBody: {
|
||||||
|
dealId: selectedDeal.id,
|
||||||
|
serviceId: service.service.id,
|
||||||
|
quantity: service.quantity
|
||||||
|
}
|
||||||
|
}).then(async ({ok, message}) => {
|
||||||
|
if (!ok) {
|
||||||
|
notifications.guess(ok, {message});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await DealService.getDealById({dealId: selectedDeal.id})
|
||||||
|
.then(setSelectedDeal)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onsServiceMultipleDelete = (items: DealServiceSchema[]) => {
|
||||||
|
if (!selectedDeal) return;
|
||||||
|
modals.openConfirmModal({
|
||||||
|
title: "Удаление услуг",
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Text>
|
||||||
|
Вы уверены, что хотите удалить выбранные услуги?
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onConfirm: () => {
|
||||||
|
DealService.deleteMultipleDealServices({
|
||||||
|
requestBody: {
|
||||||
|
dealId: selectedDeal.id,
|
||||||
|
serviceIds: items.map(item => item.service.id)
|
||||||
|
}
|
||||||
|
}).then(async ({ok, message}) => {
|
||||||
|
if (!ok) {
|
||||||
|
notifications.guess(ok, {message});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await DealService.getDealById({dealId: selectedDeal.id})
|
||||||
|
.then(setSelectedDeal)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
cancel: "Отмена",
|
||||||
|
confirm: "Удалить"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
onServiceUpdate,
|
||||||
|
onServiceDelete,
|
||||||
|
onServiceCreate,
|
||||||
|
onsServiceMultipleDelete,
|
||||||
|
tableRef,
|
||||||
|
services: selectedDeal?.services || []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DealEditDrawerServicesTable = () => {
|
||||||
|
const {
|
||||||
|
services,
|
||||||
|
tableRef,
|
||||||
|
onServiceCreate,
|
||||||
|
onServiceUpdate,
|
||||||
|
onServiceDelete,
|
||||||
|
onsServiceMultipleDelete
|
||||||
|
} = useDealServicesTableState();
|
||||||
|
|
||||||
|
return (<DealServicesTable
|
||||||
|
tableRef={tableRef}
|
||||||
|
items={services}
|
||||||
|
onChange={onServiceUpdate}
|
||||||
|
onDelete={onServiceDelete}
|
||||||
|
onCreate={onServiceCreate}
|
||||||
|
onMultipleDelete={onsServiceMultipleDelete}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
const useDealEditDrawerState = () => {
|
||||||
|
const {selectedDeal, setSelectedDeal} = useDealPageContext();
|
||||||
|
return {
|
||||||
|
isVisible: selectedDeal !== undefined,
|
||||||
|
onClose: () => setSelectedDeal(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DealEditDrawer: FC = () => {
|
||||||
|
const {isVisible, onClose} = useDealEditDrawerState();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
size={"95%"}
|
||||||
|
position={"right"}
|
||||||
|
onClose={onClose}
|
||||||
|
opened={isVisible}>
|
||||||
|
<DealEditDrawerServicesTable/>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DealEditDrawer;
|
||||||
55
src/pages/LeadsPage/modals/AddDealServiceModal.tsx
Normal file
55
src/pages/LeadsPage/modals/AddDealServiceModal.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import {ContextModalProps} from "@mantine/modals";
|
||||||
|
import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||||
|
import {DealServiceSchema} from "../../../client";
|
||||||
|
import {useForm} from "@mantine/form";
|
||||||
|
import {NumberInput} from "@mantine/core";
|
||||||
|
import ServiceSelect from "../../../components/ServiceSelect/ServiceSelect.tsx";
|
||||||
|
|
||||||
|
type Props = CreateEditFormProps<Partial<DealServiceSchema>>;
|
||||||
|
const AddDealServiceModal = ({
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
innerProps
|
||||||
|
}: ContextModalProps<Props>) => {
|
||||||
|
const form = useForm<Partial<DealServiceSchema>>({
|
||||||
|
initialValues: {
|
||||||
|
service: undefined,
|
||||||
|
quantity: 0,
|
||||||
|
},
|
||||||
|
validate: {
|
||||||
|
service: (service?: DealServiceSchema['service']) => service !== undefined ? null : "Необходимо выбрать услугу",
|
||||||
|
quantity: (quantity?: number) => (quantity && quantity > 0) ? null : "Количество должно быть больше 0"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const onClose = () => {
|
||||||
|
context.closeContextModal(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseFormModal
|
||||||
|
{...innerProps}
|
||||||
|
form={form}
|
||||||
|
closeOnSubmit
|
||||||
|
onClose={onClose}>
|
||||||
|
<BaseFormModal.Body>
|
||||||
|
<>
|
||||||
|
<ServiceSelect
|
||||||
|
placeholder={"Выберите услугу"}
|
||||||
|
label={"Услуга"}
|
||||||
|
{...form.getInputProps('service')}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
placeholder={"Введите количество"}
|
||||||
|
label={"Количество"}
|
||||||
|
min={1}
|
||||||
|
{...form.getInputProps('quantity')}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
</BaseFormModal.Body>
|
||||||
|
</BaseFormModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddDealServiceModal;
|
||||||
@@ -5,6 +5,8 @@ import {DragDropContext} from "@hello-pangea/dnd";
|
|||||||
import {useDealSummaries} from "../hooks/useDealSummaries.tsx";
|
import {useDealSummaries} from "../hooks/useDealSummaries.tsx";
|
||||||
import {DealStatus} from "../../../shared/enums/DealStatus.ts";
|
import {DealStatus} 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 {DealPageContextProvider} from "../contexts/DealPageContext.tsx";
|
||||||
|
|
||||||
|
|
||||||
export const LeadsPage: FC = () => {
|
export const LeadsPage: FC = () => {
|
||||||
@@ -25,45 +27,50 @@ export const LeadsPage: FC = () => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageBlock>
|
<DealPageContextProvider>
|
||||||
<div className={styles['container']}>
|
|
||||||
<div className={styles['boards']}>
|
<PageBlock>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<div className={styles['container']}>
|
||||||
<Board
|
<div className={styles['boards']}>
|
||||||
withCreateButton
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
summaries={summaries
|
<Board
|
||||||
.filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
|
withCreateButton
|
||||||
title={"Ожидает приемки"}
|
summaries={summaries
|
||||||
droppableId={"AWAITING_ACCEPTANCE"}
|
.filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
|
||||||
/>
|
title={"Ожидает приемки"}
|
||||||
<Board
|
droppableId={"AWAITING_ACCEPTANCE"}
|
||||||
summaries={summaries
|
/>
|
||||||
.filter(summary => summary.status == DealStatus.PACKAGING)}
|
<Board
|
||||||
title={"Упаковка"}
|
summaries={summaries
|
||||||
droppableId={"PACKAGING"}
|
.filter(summary => summary.status == DealStatus.PACKAGING)}
|
||||||
/>
|
title={"Упаковка"}
|
||||||
<Board
|
droppableId={"PACKAGING"}
|
||||||
summaries={summaries
|
/>
|
||||||
.filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
|
<Board
|
||||||
title={"Ожидает отгрузки"}
|
summaries={summaries
|
||||||
droppableId={"AWAITING_SHIPMENT"}
|
.filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
|
||||||
/>
|
title={"Ожидает отгрузки"}
|
||||||
<Board
|
droppableId={"AWAITING_SHIPMENT"}
|
||||||
summaries={summaries
|
/>
|
||||||
.filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
|
<Board
|
||||||
title={"Ожидает оплаты"}
|
summaries={summaries
|
||||||
droppableId={"AWAITING_PAYMENT"}
|
.filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
|
||||||
/>
|
title={"Ожидает оплаты"}
|
||||||
<Board
|
droppableId={"AWAITING_PAYMENT"}
|
||||||
summaries={summaries
|
/>
|
||||||
.filter(summary => summary.status == DealStatus.COMPLETED)}
|
<Board
|
||||||
title={"Завершена"}
|
summaries={summaries
|
||||||
droppableId={"COMPLETED"}
|
.filter(summary => summary.status == DealStatus.COMPLETED)}
|
||||||
/>
|
title={"Завершена"}
|
||||||
</DragDropContext>
|
droppableId={"COMPLETED"}
|
||||||
|
/>
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PageBlock>
|
||||||
</PageBlock>
|
<DealEditDrawer
|
||||||
|
/>
|
||||||
|
</DealPageContextProvider>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ type Props = {
|
|||||||
page: number,
|
page: number,
|
||||||
itemsPerPage: number,
|
itemsPerPage: number,
|
||||||
}
|
}
|
||||||
const useServicesList = (props: Props) => {
|
const useProductsList = (props: Props) => {
|
||||||
const {clientId, page, itemsPerPage} = props;
|
const {clientId, page, itemsPerPage} = props;
|
||||||
const {data, refetch} = useQuery({
|
const {data, refetch} = useQuery({
|
||||||
queryKey: ['getAllServices', clientId, page, itemsPerPage],
|
queryKey: ['getAllServices', clientId, page, itemsPerPage],
|
||||||
@@ -16,4 +16,4 @@ const useServicesList = (props: Props) => {
|
|||||||
const paginationInfo = data?.paginationInfo;
|
const paginationInfo = data?.paginationInfo;
|
||||||
return {products, paginationInfo, refetch}
|
return {products, paginationInfo, refetch}
|
||||||
}
|
}
|
||||||
export default useServicesList;
|
export default useProductsList;
|
||||||
@@ -4,11 +4,13 @@ import styles from './ProductsPage.module.css';
|
|||||||
import {Button, Text, Pagination} from "@mantine/core";
|
import {Button, Text, Pagination} from "@mantine/core";
|
||||||
import ClientSelect from "../../../components/Selects/ClientSelect/ClientSelect.tsx";
|
import ClientSelect from "../../../components/Selects/ClientSelect/ClientSelect.tsx";
|
||||||
import ProductsTable from "../components/ProductsTable/ProductsTable.tsx";
|
import ProductsTable from "../components/ProductsTable/ProductsTable.tsx";
|
||||||
import useProductsList from "../hooks/useProductsList.tsx";
|
|
||||||
import {modals} from "@mantine/modals";
|
import {modals} from "@mantine/modals";
|
||||||
import {notifications} from "../../../shared/lib/notifications.ts";
|
import {notifications} from "../../../shared/lib/notifications.ts";
|
||||||
import {CreateProductRequest} from "../types.ts";
|
import {CreateProductRequest} from "../types.ts";
|
||||||
import {ProductSchema, ProductService} from "../../../client";
|
import {ProductSchema, ProductService} from "../../../client";
|
||||||
|
import ServiceSelect from "../../../components/ServiceSelect/ServiceSelect.tsx";
|
||||||
|
import useProductsList from "../hooks/useProductsList.tsx";
|
||||||
|
import useServicesList from "../../ServicesPage/hooks/useServicesList.tsx";
|
||||||
|
|
||||||
export const ProductsPage: FC = () => {
|
export const ProductsPage: FC = () => {
|
||||||
const [clientId, setClientId] = useState(-1);
|
const [clientId, setClientId] = useState(-1);
|
||||||
@@ -79,8 +81,6 @@ export const ProductsPage: FC = () => {
|
|||||||
if (!paginationInfo) return;
|
if (!paginationInfo) return;
|
||||||
setTotalPages(paginationInfo.totalPages);
|
setTotalPages(paginationInfo.totalPages);
|
||||||
}, [paginationInfo]);
|
}, [paginationInfo]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles['container']}>
|
<div className={styles['container']}>
|
||||||
@@ -91,6 +91,9 @@ export const ProductsPage: FC = () => {
|
|||||||
onClick={() => onCreateProductClick()}
|
onClick={() => onCreateProductClick()}
|
||||||
variant={"default"}
|
variant={"default"}
|
||||||
>Создать</Button>
|
>Создать</Button>
|
||||||
|
{/*<ServiceSelect*/}
|
||||||
|
{/* value={selectedService}*/}
|
||||||
|
{/* onChange={setSelectedService}/>*/}
|
||||||
</div>
|
</div>
|
||||||
</PageBlock>
|
</PageBlock>
|
||||||
<PageBlock>
|
<PageBlock>
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ export interface CRUDTableProps<T extends MRT_RowData> {
|
|||||||
onCreate?: (item: T) => void;
|
onCreate?: (item: T) => void;
|
||||||
onDelete?: (item: T) => void;
|
onDelete?: (item: T) => void;
|
||||||
onChange?: (item: T) => void;
|
onChange?: (item: T) => void;
|
||||||
|
onSelectionChange?: (selectedItems: T[]) => void;
|
||||||
tableRef?: RefObject<BaseTableRef<T>>
|
tableRef?: RefObject<BaseTableRef<T>>
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user