This commit is contained in:
2024-04-12 07:35:41 +03:00
parent 9815ebbc3b
commit 2d1fdf1ad9
41 changed files with 1199 additions and 85 deletions

View File

@@ -30,16 +30,20 @@
"clsx": "^2.1.0",
"dayjs": "^1.11.10",
"dot-object": "^2.1.4",
"lodash": "^4.17.21",
"mantine-form-zod-resolver": "^1.1.0",
"mantine-react-table": "^2.0.0-beta.0",
"react": "^18.2.0",
"react-barcode": "^1.5.1",
"react-dom": "^18.2.0",
"react-redux": "^9.1.0",
"react-to-print": "^2.15.1",
"reactflow": "^11.10.4",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/dot-object": "^2",
"@types/lodash": "^4",
"@types/react": "^18.2.56",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^7.0.2",

View File

@@ -0,0 +1,11 @@
import {ProductService} from "../../client";
import {useQuery} from "@tanstack/react-query";
export const useGetProductById = (id: number) => {
const {data, error, isLoading, refetch} = useQuery({
queryKey: ['getProductById', id],
queryFn: () => ProductService.getProductById({productId: id}),
select: (data) => data
});
return {product: data, error, isLoading, refetch}
}

View File

@@ -19,6 +19,8 @@ export type { ClientSchema } from './models/ClientSchema';
export type { ClientUpdateDetailsRequest } from './models/ClientUpdateDetailsRequest';
export type { ClientUpdateRequest } from './models/ClientUpdateRequest';
export type { ClientUpdateResponse } from './models/ClientUpdateResponse';
export type { DealAddProductRequest } from './models/DealAddProductRequest';
export type { DealAddProductResponse } from './models/DealAddProductResponse';
export type { DealAddServiceRequest } from './models/DealAddServiceRequest';
export type { DealAddServiceResponse } from './models/DealAddServiceResponse';
export type { DealAddServicesRequest } from './models/DealAddServicesRequest';
@@ -26,26 +28,41 @@ export type { DealAddServicesResponse } from './models/DealAddServicesResponse';
export type { DealChangeStatusRequest } from './models/DealChangeStatusRequest';
export type { DealChangeStatusResponse } from './models/DealChangeStatusResponse';
export type { DealCreateRequest } from './models/DealCreateRequest';
export type { DealDeleteProductRequest } from './models/DealDeleteProductRequest';
export type { DealDeleteProductResponse } from './models/DealDeleteProductResponse';
export type { DealDeleteProductsRequest } from './models/DealDeleteProductsRequest';
export type { DealDeleteProductsResponse } from './models/DealDeleteProductsResponse';
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 { DealGeneralInfoSchema } from './models/DealGeneralInfoSchema';
export type { DealGetAllResponse } from './models/DealGetAllResponse';
export type { DealProductSchema } from './models/DealProductSchema';
export type { DealQuickCreateRequest } from './models/DealQuickCreateRequest';
export type { DealQuickCreateResponse } from './models/DealQuickCreateResponse';
export type { DealSchema } from './models/DealSchema';
export type { DealServiceSchema } from './models/DealServiceSchema';
export type { DealStatusHistorySchema } from './models/DealStatusHistorySchema';
export type { DealSummary } from './models/DealSummary';
export type { DealSummaryResponse } from './models/DealSummaryResponse';
export type { DealUpdateGeneralInfoRequest } from './models/DealUpdateGeneralInfoRequest';
export type { DealUpdateGeneralInfoResponse } from './models/DealUpdateGeneralInfoResponse';
export type { DealUpdateProductQuantityRequest } from './models/DealUpdateProductQuantityRequest';
export type { DealUpdateProductQuantityResponse } from './models/DealUpdateProductQuantityResponse';
export type { DealUpdateServiceQuantityRequest } from './models/DealUpdateServiceQuantityRequest';
export type { DealUpdateServiceQuantityResponse } from './models/DealUpdateServiceQuantityResponse';
export type { HTTPValidationError } from './models/HTTPValidationError';
export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
export type { ProductAddBarcodeRequest } from './models/ProductAddBarcodeRequest';
export type { ProductAddBarcodeResponse } from './models/ProductAddBarcodeResponse';
export type { ProductCreateRequest } from './models/ProductCreateRequest';
export type { ProductCreateResponse } from './models/ProductCreateResponse';
export type { ProductDeleteRequest } from './models/ProductDeleteRequest';
export type { ProductDeleteResponse } from './models/ProductDeleteResponse';
export type { ProductExistsBarcodeResponse } from './models/ProductExistsBarcodeResponse';
export type { ProductGenerateBarcodeRequest } from './models/ProductGenerateBarcodeRequest';
export type { ProductGenerateBarcodeResponse } from './models/ProductGenerateBarcodeResponse';
export type { ProductGetResponse } from './models/ProductGetResponse';
export type { ProductSchema } from './models/ProductSchema';
export type { ProductUpdateRequest } from './models/ProductUpdateRequest';
@@ -58,6 +75,7 @@ export type { ServiceCreateResponse } from './models/ServiceCreateResponse';
export type { ServiceGetAllCategoriesResponse } from './models/ServiceGetAllCategoriesResponse';
export type { ServiceGetAllResponse } from './models/ServiceGetAllResponse';
export type { ServiceSchema } from './models/ServiceSchema';
export type { UserSchema } from './models/UserSchema';
export type { ValidationError } from './models/ValidationError';
export { AuthService } from './services/AuthService';

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DealAddProductRequest = {
dealId: number;
productId: number;
quantity: number;
};

View File

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

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DealDeleteProductRequest = {
dealId: number;
productId: number;
};

View File

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

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DealDeleteProductsRequest = {
dealId: number;
productIds: Array<number>;
};

View File

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

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DealGeneralInfoSchema = {
name: string;
isDeleted: boolean;
isCompleted: boolean;
};

View File

@@ -2,8 +2,10 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ClientSchema } from './ClientSchema';
import type { DealProductSchema } from './DealProductSchema';
import type { DealServiceSchema } from './DealServiceSchema';
import type { DealStatusHistorySchema } from './DealStatusHistorySchema';
export type DealSchema = {
id: number;
name: string;
@@ -12,5 +14,9 @@ export type DealSchema = {
currentStatus: number;
services: Array<DealServiceSchema>;
products: Array<DealProductSchema>;
statusHistory: Array<DealStatusHistorySchema>;
isDeleted: boolean;
isCompleted: boolean;
client: ClientSchema;
};

View File

@@ -0,0 +1,13 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { UserSchema } from './UserSchema';
export type DealStatusHistorySchema = {
user: UserSchema;
changedAt: string;
fromStatus: number;
toStatus: number;
nextStatusDeadline: string;
};

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { DealGeneralInfoSchema } from './DealGeneralInfoSchema';
export type DealUpdateGeneralInfoRequest = {
dealId: number;
data: DealGeneralInfoSchema;
};

View File

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

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DealUpdateProductQuantityRequest = {
dealId: number;
productId: number;
quantity: number;
};

View File

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

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ProductAddBarcodeRequest = {
productId: number;
barcode: string;
};

View File

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

View File

@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ProductExistsBarcodeResponse = {
exists: boolean;
};

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UserSchema = {
id: number;
telegramId: number;
phoneNumber?: (string | null);
isAdmin: boolean;
};

View File

@@ -2,6 +2,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { DealAddProductRequest } from '../models/DealAddProductRequest';
import type { DealAddProductResponse } from '../models/DealAddProductResponse';
import type { DealAddServiceRequest } from '../models/DealAddServiceRequest';
import type { DealAddServiceResponse } from '../models/DealAddServiceResponse';
import type { DealAddServicesRequest } from '../models/DealAddServicesRequest';
@@ -9,6 +11,10 @@ import type { DealAddServicesResponse } from '../models/DealAddServicesResponse'
import type { DealChangeStatusRequest } from '../models/DealChangeStatusRequest';
import type { DealChangeStatusResponse } from '../models/DealChangeStatusResponse';
import type { DealCreateRequest } from '../models/DealCreateRequest';
import type { DealDeleteProductRequest } from '../models/DealDeleteProductRequest';
import type { DealDeleteProductResponse } from '../models/DealDeleteProductResponse';
import type { DealDeleteProductsRequest } from '../models/DealDeleteProductsRequest';
import type { DealDeleteProductsResponse } from '../models/DealDeleteProductsResponse';
import type { DealDeleteServiceRequest } from '../models/DealDeleteServiceRequest';
import type { DealDeleteServiceResponse } from '../models/DealDeleteServiceResponse';
import type { DealDeleteServicesRequest } from '../models/DealDeleteServicesRequest';
@@ -18,6 +24,10 @@ import type { DealQuickCreateRequest } from '../models/DealQuickCreateRequest';
import type { DealQuickCreateResponse } from '../models/DealQuickCreateResponse';
import type { DealSchema } from '../models/DealSchema';
import type { DealSummaryResponse } from '../models/DealSummaryResponse';
import type { DealUpdateGeneralInfoRequest } from '../models/DealUpdateGeneralInfoRequest';
import type { DealUpdateGeneralInfoResponse } from '../models/DealUpdateGeneralInfoResponse';
import type { DealUpdateProductQuantityRequest } from '../models/DealUpdateProductQuantityRequest';
import type { DealUpdateProductQuantityResponse } from '../models/DealUpdateProductQuantityResponse';
import type { DealUpdateServiceQuantityRequest } from '../models/DealUpdateServiceQuantityRequest';
import type { DealUpdateServiceQuantityResponse } from '../models/DealUpdateServiceQuantityResponse';
import type { CancelablePromise } from '../core/CancelablePromise';
@@ -95,6 +105,58 @@ export class DealService {
url: '/deal/summaries',
});
}
/**
* 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`,
},
});
}
/**
* Update General Info
* @returns DealUpdateGeneralInfoResponse Successful Response
* @throws ApiError
*/
public static updateDealGeneralInfo({
requestBody,
}: {
requestBody: DealUpdateGeneralInfoRequest,
}): CancelablePromise<DealUpdateGeneralInfoResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/deal/update-general-info',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Services Add
* @returns DealAddServicesResponse Successful Response
@@ -196,32 +258,80 @@ export class DealService {
});
}
/**
* Get All
* @returns DealGetAllResponse Successful Response
* Products Update
* @returns DealUpdateProductQuantityResponse Successful Response
* @throws ApiError
*/
public static getAllDeals(): CancelablePromise<DealGetAllResponse> {
public static updateDealProductQuantity({
requestBody,
}: {
requestBody: DealUpdateProductQuantityRequest,
}): CancelablePromise<DealUpdateProductQuantityResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/deal/get-all',
method: 'POST',
url: '/deal/products/update-quantity',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Get Deal By Id
* @returns DealSchema Successful Response
* Products Add
* @returns DealAddProductResponse Successful Response
* @throws ApiError
*/
public static getDealById({
dealId,
public static addDealProduct({
requestBody,
}: {
dealId: number,
}): CancelablePromise<DealSchema> {
requestBody: DealAddProductRequest,
}): CancelablePromise<DealAddProductResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/deal/get/{deal_id}',
path: {
'deal_id': dealId,
method: 'POST',
url: '/deal/products/add',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Products Delete
* @returns DealDeleteProductResponse Successful Response
* @throws ApiError
*/
public static deleteDealProduct({
requestBody,
}: {
requestBody: DealDeleteProductRequest,
}): CancelablePromise<DealDeleteProductResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/deal/products/delete',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Products Delete
* @returns DealDeleteProductsResponse Successful Response
* @throws ApiError
*/
public static deleteMultipleDealProducts({
requestBody,
}: {
requestBody: DealDeleteProductsRequest,
}): CancelablePromise<DealDeleteProductsResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/deal/products/delete/multiple',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},

View File

@@ -2,11 +2,17 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ProductAddBarcodeRequest } from '../models/ProductAddBarcodeRequest';
import type { ProductAddBarcodeResponse } from '../models/ProductAddBarcodeResponse';
import type { ProductCreateRequest } from '../models/ProductCreateRequest';
import type { ProductCreateResponse } from '../models/ProductCreateResponse';
import type { ProductDeleteRequest } from '../models/ProductDeleteRequest';
import type { ProductDeleteResponse } from '../models/ProductDeleteResponse';
import type { ProductExistsBarcodeResponse } from '../models/ProductExistsBarcodeResponse';
import type { ProductGenerateBarcodeRequest } from '../models/ProductGenerateBarcodeRequest';
import type { ProductGenerateBarcodeResponse } from '../models/ProductGenerateBarcodeResponse';
import type { ProductGetResponse } from '../models/ProductGetResponse';
import type { ProductSchema } from '../models/ProductSchema';
import type { ProductUpdateRequest } from '../models/ProductUpdateRequest';
import type { ProductUpdateResponse } from '../models/ProductUpdateResponse';
import type { CancelablePromise } from '../core/CancelablePromise';
@@ -100,4 +106,89 @@ export class ProductService {
},
});
}
/**
* Get Product By Id
* @returns ProductSchema Successful Response
* @throws ApiError
*/
public static getProductById({
productId,
}: {
productId: number,
}): CancelablePromise<ProductSchema> {
return __request(OpenAPI, {
method: 'GET',
url: '/product/get-by-id',
query: {
'product_id': productId,
},
errors: {
422: `Validation Error`,
},
});
}
/**
* Add Product Barcode
* @returns ProductAddBarcodeResponse Successful Response
* @throws ApiError
*/
public static addProductBarcode({
requestBody,
}: {
requestBody: ProductAddBarcodeRequest,
}): CancelablePromise<ProductAddBarcodeResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/product/barcode/add',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Exists Product Barcode
* @returns ProductExistsBarcodeResponse Successful Response
* @throws ApiError
*/
public static existsProductBarcode({
productId,
barcode,
}: {
productId: number,
barcode: string,
}): CancelablePromise<ProductExistsBarcodeResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/product/barcode/exists',
query: {
'product_id': productId,
'barcode': barcode,
},
errors: {
422: `Validation Error`,
},
});
}
/**
* Generate Product Barcode
* @returns ProductGenerateBarcodeResponse Successful Response
* @throws ApiError
*/
public static generateProductBarcode({
requestBody,
}: {
requestBody: ProductGenerateBarcodeRequest,
}): CancelablePromise<ProductGenerateBarcodeResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/product/barcode/generate',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
}

View File

@@ -0,0 +1,63 @@
import {ContextModalProps} from "@mantine/modals";
import {Button, Flex, rem, TextInput} from "@mantine/core";
import {useEffect, useState} from "react";
import {ProductService} from "../../client";
type Props = {
productId: number;
onSubmit: (barcode: string) => void;
}
const PrintBarcodeModal = ({
id,
context,
innerProps
}: ContextModalProps<Props>) => {
const {productId, onSubmit} = innerProps;
const [barcode, setBarcode] = useState<string | undefined>();
const [isBarcodeExist, setIsBarcodeExist] = useState<boolean>(true);
const getErrorMessage = () => {
if (!barcode) return "Штрихкод не может быть пустым";
if (isBarcodeExist) return "Штрихкод уже существует";
return undefined;
}
useEffect(() => {
if (!barcode) return;
ProductService.existsProductBarcode({
productId: innerProps.productId,
barcode: barcode.trim()
}).then((response) => {
setIsBarcodeExist(response.exists);
})
}, [productId, barcode]);
const onSubmitClick = () => {
if (!barcode || isBarcodeExist) return;
onSubmit(barcode.trim());
context.closeModal(id);
}
return (
<Flex
gap={rem(10)}
direction={"column"}
>
<TextInput
required
error={getErrorMessage()}
label={"Штрихкод"}
placeholder={"Введите или отсканируйте штрихкод"}
value={barcode}
onChange={(event) => setBarcode(event.currentTarget.value)}
/>
<Button
onClick={onSubmitClick}
disabled={!barcode || isBarcodeExist}
>
Добавить
</Button>
</Flex>
)
}
export default PrintBarcodeModal;

View File

@@ -0,0 +1,9 @@
.print-only {
display: none;
}
@media print {
.print-only {
display: block;
}
}

View File

@@ -0,0 +1,132 @@
import {ContextModalProps, modals} from "@mantine/modals";
import {Button, Divider, Flex, NumberInput, rem, Select, Spoiler} from "@mantine/core";
import Barcode from "react-barcode";
import {useEffect, useRef, useState} from "react";
import {useReactToPrint} from "react-to-print";
import {ProductService} from "../../client";
import styles from "./PrintBarcodeModal.module.css";
import {useGetProductById} from "../../api/product/useGetProductById.tsx";
import {notifications} from "../../shared/lib/notifications.ts";
type Props = {
productId: number;
defaultQuantity?: number;
}
const PrintBarcodeModal = ({
innerProps
}: ContextModalProps<Props>) => {
const {productId, defaultQuantity = 1} = innerProps;
const [quantity, setQuantity] = useState(defaultQuantity);
const [barcode, setBarcode] = useState<string | undefined>()
const {product, refetch} = useGetProductById(productId);
const barcodeRef = useRef(null);
const handlePrint = useReactToPrint({
content: () => barcodeRef.current
});
const onAdd = (newBarcode: string) => {
ProductService.addProductBarcode({requestBody: {productId, barcode: newBarcode}})
.then(async ({ok, message}) => {
notifications.guess(ok, {message});
})
}
const onAddClick = () => {
if (!product) return;
modals.openContextModal({
modal: "addBarcode",
title: 'Добавление штрихкода',
withCloseButton: true,
innerProps: {
productId: product.id,
onSubmit: onAdd
}
})
}
const onGenerateClick = () => {
if (!product) return;
ProductService.generateProductBarcode({requestBody: {productId}})
.then(async ({ok, message, barcode}) => {
notifications.guess(ok, {message});
if (!ok) return;
await refetch();
setBarcode(barcode);
})
}
useEffect(() => {
if (!product) return;
if (product.barcodes.length === 1)
setBarcode(product.barcodes[0]);
}, [product]);
return (
<>
<Flex
gap={rem(10)}
direction={"column"}
>
<Select
value={barcode}
onChange={(value) => setBarcode(value || undefined)}
data={product?.barcodes}
label={"Штрихкод"}
placeholder={"Выберите штрихкод"}
/>
<NumberInput
label={"Количество копий"}
placeholder={"Введите количество копий"}
value={quantity}
onChange={(value) => typeof value === "number" && setQuantity(value)}
min={1}
/>
<Spoiler
w={"100%"}
style={{
textAlign: "center",
}}
styles={{control: {width: "100%"}}}
showLabel={"Показать предпросмотр"}
hideLabel={"Скрыть предпросмотр"}
maxHeight={0}>
<div>
{barcode &&
<Barcode value={barcode}/>
}
</div>
</Spoiler>
<Divider
my={rem(10)}
/>
<Flex direction={"column"} gap={rem(10)}>
<Flex gap={rem(10)}>
<Button
onClick={() => onAddClick()}
variant={"default"}
fullWidth>
Добавить вручную
</Button>
<Button
onClick={() => onGenerateClick()}
variant={"default"}
fullWidth>
Сгенерировать
</Button>
</Flex>
<Button
size={"lg"}
disabled={!barcode}
onClick={() => handlePrint()}
>Печать</Button>
</Flex>
</Flex>
<div className={styles['print-only']} ref={barcodeRef}>
{barcode && Array.from({length: quantity}).map((_, index) => (
<Barcode key={index} value={barcode}/>
))}
</div>
</>
)
}
export default PrintBarcodeModal;

View File

@@ -5,6 +5,8 @@ import createProductModal from "../pages/ProductsPage/modals/CreateProductModal/
import ProductFormModal from "../pages/ClientsPage/modals/ClientFormModal/ClientFormModal.tsx";
import AddDealServiceModal from "../pages/LeadsPage/modals/AddDealServiceModal.tsx";
import AddDealProductModal from "../pages/LeadsPage/modals/AddDealProductModal.tsx";
import PrintBarcodeModal from "./PrintBarcodeModal/PrintBarcodeModal.tsx";
import AddBarcodeModal from "./AddBarcodeModal/AddBarcodeModal.tsx";
export const modals = {
enterDeadline: EnterDeadlineModal,
@@ -13,5 +15,7 @@ export const modals = {
createProduct: createProductModal,
productFormModal: ProductFormModal,
addDealService: AddDealServiceModal,
addDealProduct: AddDealProductModal
addDealProduct: AddDealProductModal,
printBarcode: PrintBarcodeModal,
addBarcode: AddBarcodeModal
}

View File

@@ -1,4 +1,4 @@
import {FC, useState} from "react";
import {FC} from "react";
import ClientsTable from "./components/ClientsTable/ClientsTable.tsx";
import useClientsList from "./hooks/useClientsList.tsx";
import PageBlock from "../../components/PageBlock/PageBlock.tsx";
@@ -7,7 +7,6 @@ import {Button} from "@mantine/core";
import {modals} from "@mantine/modals";
import {ClientSchema, ClientService} from "../../client";
import {notifications} from "../../shared/lib/notifications.ts";
import PlusMinusInput from "../../components/PlusMinusInput/PlusMinusInput.tsx";
const ClientsPage: FC = () => {
const {clients, refetch} = useClientsList();
@@ -60,7 +59,6 @@ const ClientsPage: FC = () => {
})
}
const [a, setA] = useState(5);
return (
<div className={styles['container']}>
<PageBlock>
@@ -71,12 +69,6 @@ const ClientsPage: FC = () => {
>
Создать клиента
</Button>
<PlusMinusInput onChange={event=>{
console.log(event);
setA(event)
}}
value={a}
/>
</div>
</PageBlock>

View File

@@ -25,8 +25,6 @@ const BaseFormModal = <T, >(props: Props<T>) => {
const isEditing = 'onChange' in props;
const onSubmit = (values: T) => {
console.log('----values');
console.log(values)
if (isEditing) {
props.onChange(values);
} else {

View File

@@ -3,26 +3,53 @@ import useDealProductsTableColumns from "./columns.tsx";
import {FC} from "react";
import {CRUDTableProps} from "../../../../types/CRUDTable.tsx";
import {DealProductSchema} from "../../../../client";
import {Button, Flex, rem} from "@mantine/core";
import {ActionIcon, Button, Flex, rem, Tooltip} from "@mantine/core";
import {MRT_TableOptions} from "mantine-react-table";
import {modals} from "@mantine/modals";
import {IconBarcode, IconTrash} from "@tabler/icons-react";
type RestProps = {
clientId: number;
onMultipleDelete?: (items: DealProductSchema[]) => void;
}
type Props = CRUDTableProps<DealProductSchema> & RestProps;
const DealProductsTable: FC<Props> = ({items, clientId}) => {
const columns = useDealProductsTableColumns();
const DealProductsTable: FC<Props> = (props: Props) => {
const {items, clientId, onChange, onCreate, onDelete, onMultipleDelete} = props;
const columns = useDealProductsTableColumns({
onChange: (product, quantity) => {
if (!onChange) return;
if (quantity <= 0 && onDelete) {
onDelete(product);
return;
}
onChange({...product, quantity})
},
data: items
});
const onCreateClick = () => {
if (!onCreate) return;
modals.openContextModal({
modal: "addDealProduct",
title: "Добавление товара",
innerProps: {
onCreate: event => console.log(event),
onCreate: (product) => onCreate(product as DealProductSchema),
clientId
}
})
}
const onPrintBarcodeClick = (product: DealProductSchema) => {
modals.openContextModal({
modal: "printBarcode",
title: 'Печать штрихкода',
withCloseButton: true,
innerProps: {
productId: product.product.id,
defaultQuantity: product.quantity
}
})
}
return (
@@ -30,23 +57,39 @@ const DealProductsTable: FC<Props> = ({items, clientId}) => {
data={items}
columns={columns}
restProps={{
renderBottomToolbar: ({}) => (
enableBottomToolbar: true,
enableRowActions: true,
enableRowSelection: true,
renderRowActions: ({row}) => (
<Flex gap="md">
<Tooltip label="Удалить">
<ActionIcon onClick={() => onDelete && onDelete(row.original)} variant={"default"}>
<IconTrash/>
</ActionIcon>
</Tooltip>
<Tooltip label="Печать штрихкода">
<ActionIcon onClick={() => onPrintBarcodeClick(row.original)} variant={"default"}>
<IconBarcode/>
</ActionIcon>
</Tooltip>
</Flex>
),
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>*/}
{/*)}*/}
{(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>
),
} as MRT_TableOptions<DealProductSchema>}

View File

@@ -1,22 +1,65 @@
import {useMemo} from "react";
import {MRT_ColumnDef} from "mantine-react-table";
import {DealProductSchema} from "../../../../client";
import PlusMinusInput from "../../../../components/PlusMinusInput/PlusMinusInput.tsx";
import {List} from "@mantine/core";
type Props = {
onChange: (product: DealProductSchema, quantity: number) => void;
data: DealProductSchema[];
}
const useDealProductsTableColumns = (props: Props) => {
const {onChange, data} = props;
const totalQuantity = useMemo(() => data.reduce((acc, row) => acc + row.quantity, 0), [data]);
const useDealProductsTableColumns = () => {
return useMemo<MRT_ColumnDef<DealProductSchema>[]>(() => [
{
accessorKey: "products.name",
header: "Название"
accessorKey: "product.article",
header: "Артикул",
enableSorting: false,
enableColumnActions: false,
},
{
accessorKey: "products.article",
header: "Артикул"
accessorKey: "product.name",
header: "Название",
enableSorting: false,
enableColumnActions: false,
},
{
accessorKey: "product.barcodes",
header: "Штрихкоды",
Cell: ({cell}) => {
return (
<List size={"sm"}>
{cell.getValue<string[]>()?.map(barcode => (
<List.Item key={barcode}>
{barcode}
</List.Item>
))}
</List>
)
},
enableSorting: false,
enableColumnActions: false,
},
{
accessorKey: "quantity",
header: "Количество"
header: "Количество",
enableSorting: false,
enableColumnActions: false,
Footer: <>Всего товаров: {totalQuantity} </>,
Cell: ({row}) => {
return (
<PlusMinusInput
value={row.original.quantity}
onChange={(value) => onChange(row.original, value)}
/>
)
}
}
], [])
], [onChange, data])
}
export default useDealProductsTableColumns;

View File

@@ -0,0 +1,28 @@
import {DealStatusHistorySchema} from "../../../../client";
import {useDealStatusChangeTableColumns} from "./columns.tsx";
import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
import {FC} from "react";
type Props = {
items: DealStatusHistorySchema[];
}
const DealStatusChangeTable: FC<Props> = (props: Props) => {
const {items} = props;
return (
<BaseTable
data={items}
columns={useDealStatusChangeTableColumns()}
restProps={{
enableRowActions: false,
enableColumnActions: false,
enableSorting: false,
enableBottomToolbar: false,
enableColumnFilters: false,
enableColumnVisibilityToggle: false,
}}
/>
);
}
export default DealStatusChangeTable;

View File

@@ -0,0 +1,29 @@
import {useMemo} from "react";
import {MRT_ColumnDef} from "mantine-react-table";
import {DealStatusHistorySchema} from "../../../../client";
import {DealStatus, DealStatusDictionary} from "../../../../shared/enums/DealStatus.ts";
export const useDealStatusChangeTableColumns = () => {
return useMemo<MRT_ColumnDef<DealStatusHistorySchema>[]>(() => [
{
accessorKey: "changedAt",
header: "Дата",
accessorFn: (row) => new Date(row.changedAt).toLocaleString('ru-RU'),
},
{
accessorKey: "user.id",
header: "ID пользователя",
},
{
accessorKey: "fromStatus",
header: "Из статуса",
accessorFn: (row) => DealStatusDictionary[row.fromStatus as DealStatus],
},
{
accessorKey: "toStatus",
header: "В статус",
accessorFn: (row) => DealStatusDictionary[row.toStatus as DealStatus],
}
], []);
}

View File

@@ -1,12 +1,15 @@
import {Drawer, Text} from "@mantine/core";
import {Box, Drawer, rem, Tabs, 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 {DealProductSchema, DealService, DealServiceSchema} from "../../../../client";
import {notifications} from "../../../../shared/lib/notifications.ts";
import {modals} from "@mantine/modals";
import {BaseTableRef} from "../../../../components/BaseTable/BaseTable.tsx";
import DealProductsTable from "../../components/DealProductsTable/DealProductsTable.tsx";
import {IconBarcode, IconBox, IconCalendarUser, IconSettings} from "@tabler/icons-react";
import DealStatusChangeTable from "../../components/DealStatusChangeTable/DealStatusChangeTable.tsx";
import DealEditDrawerGeneralTab from "./tabs/DealEditDrawerGeneralTab.tsx";
const useDealServicesTableState = () => {
const {selectedDeal, setSelectedDeal} = useDealPageContext();
@@ -30,7 +33,6 @@ const useDealServicesTableState = () => {
.then(setSelectedDeal)
})
}
const onServiceDelete = (service: DealServiceSchema) => {
if (!selectedDeal) return;
modals.openConfirmModal({
@@ -68,7 +70,6 @@ const useDealServicesTableState = () => {
}
})
}
const onServiceCreate = (service: DealServiceSchema) => {
console.log('-------Drawer')
console.log(service);
@@ -88,7 +89,6 @@ const useDealServicesTableState = () => {
.then(setSelectedDeal)
})
}
const onsServiceMultipleDelete = (items: DealServiceSchema[]) => {
if (!selectedDeal) return;
modals.openConfirmModal({
@@ -152,27 +152,158 @@ const DealEditDrawerServicesTable = () => {
}
const useDealProductTableState = () => {
const {selectedDeal} = useDealPageContext();
const {selectedDeal, setSelectedDeal} = useDealPageContext();
const onProductUpdate = (product: DealProductSchema) => {
if (!selectedDeal) return;
DealService.updateDealProductQuantity({
requestBody: {
dealId: selectedDeal.id,
productId: product.product.id,
quantity: product.quantity
}
}).then(async ({ok, message}) => {
if (!ok) {
notifications.guess(ok, {message});
return;
}
await DealService.getDealById({dealId: selectedDeal.id})
.then(setSelectedDeal)
})
}
const onProductDelete = (product: DealProductSchema) => {
if (!selectedDeal) return;
modals.openConfirmModal({
title: "Удаление товара",
children: (
<>
<Text>
Вы уверены, что хотите удалить товар:
</Text>
<Text>
{product.product.name}?
</Text>
</>
),
onConfirm: () => {
DealService.deleteDealProduct({
requestBody: {
dealId: selectedDeal.id,
productId: product.product.id
}
}).then(async ({ok, message}) => {
if (!ok) {
notifications.guess(ok, {message});
return;
}
await DealService.getDealById({dealId: selectedDeal.id})
.then(setSelectedDeal)
})
},
labels: {
cancel: "Отмена",
confirm: "Удалить"
}
})
}
const onProductCreate = (product: DealProductSchema) => {
if (!selectedDeal) return;
DealService.addDealProduct({
requestBody: {
dealId: selectedDeal.id,
productId: product.product.id,
quantity: product.quantity
}
}).then(async ({ok, message}) => {
if (!ok) {
notifications.guess(ok, {message});
return;
}
await DealService.getDealById({dealId: selectedDeal.id})
.then(setSelectedDeal)
})
}
const onProductMultipleDelete = (items: DealProductSchema[]) => {
if (!selectedDeal) return;
modals.openConfirmModal({
title: "Удаление товаров",
children: (
<>
<Text>
Вы уверены, что хотите удалить выбранные товары?
</Text>
</>
),
onConfirm: () => {
DealService.deleteMultipleDealProducts({
requestBody: {
dealId: selectedDeal.id,
productIds: items.map(item => item.product.id)
}
}).then(async ({ok, message}) => {
if (!ok) {
notifications.guess(ok, {message});
return;
}
await DealService.getDealById({dealId: selectedDeal.id})
.then(setSelectedDeal)
})
},
labels: {
cancel: "Отмена",
confirm: "Удалить"
}
})
}
return {
clientId: selectedDeal?.clientId || -1,
products: selectedDeal?.products || []
products: selectedDeal?.products || [],
onProductUpdate,
onProductDelete,
onProductCreate,
onProductMultipleDelete
}
}
const DealEditDrawerProductsTable = () => {
const {
products,
clientId
clientId,
onProductUpdate,
onProductDelete,
onProductCreate,
onProductMultipleDelete,
} = useDealProductTableState();
return (
<DealProductsTable
clientId={clientId}
items={products}
onChange={onProductUpdate}
onMultipleDelete={onProductMultipleDelete}
onDelete={onProductDelete}
onCreate={onProductCreate}
/>
)
}
const useDealStatusChangeState = () => {
const {selectedDeal} = useDealPageContext();
return {
statusHistory: selectedDeal?.statusHistory || []
}
}
const DealEditDrawerStatusChangeTable = () => {
const {statusHistory} = useDealStatusChangeState();
return (
<DealStatusChangeTable
items={statusHistory}
/>)
}
const useDealEditDrawerState = () => {
const {selectedDeal, setSelectedDeal} = useDealPageContext();
return {
@@ -189,9 +320,55 @@ const DealEditDrawer: FC = () => {
size={"95%"}
position={"right"}
onClose={onClose}
opened={isVisible}>
<DealEditDrawerServicesTable/>
<DealEditDrawerProductsTable/>
removeScrollProps={{allowPinchZoom: true}}
withCloseButton={false}
opened={isVisible}
styles={{body: {height: '100%'}}}
>
<Tabs
defaultValue={"general"}
h={'100%'}
variant={"outline"}
orientation={"vertical"}>
<Tabs.List
>
<Tabs.Tab value={"general"} leftSection={<IconSettings/>}>
Общее
</Tabs.Tab>
<Tabs.Tab value={"history"} leftSection={<IconCalendarUser/>}>
История
</Tabs.Tab>
<Tabs.Tab value={"services"} leftSection={<IconBox/>}>
Услуги
</Tabs.Tab>
<Tabs.Tab value={"products"} leftSection={<IconBarcode/>}>
Товары
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value={"general"}>
<Box h={"100%"} w={"100%"} p={rem(10)}>
<DealEditDrawerGeneralTab/>
</Box>
</Tabs.Panel>
<Tabs.Panel value={"history"}>
<Box p={rem(10)}>
<DealEditDrawerStatusChangeTable/>
</Box>
</Tabs.Panel>
<Tabs.Panel value={"services"}>
<Box p={rem(10)}>
<DealEditDrawerServicesTable/>
</Box>
</Tabs.Panel>
<Tabs.Panel value={"products"}>
<Box p={rem(10)}>
<DealEditDrawerProductsTable/>
</Box>
</Tabs.Panel>
</Tabs>
</Drawer>
);
}

View File

@@ -0,0 +1,148 @@
import {FC} from "react";
import {useDealPageContext} from "../../../contexts/DealPageContext.tsx";
import {Button, Checkbox, Fieldset, Flex, Group, rem, TextInput} from "@mantine/core";
import {useForm} from "@mantine/form";
import {ClientService, DealSchema, DealService} from "../../../../../client";
import {DealStatus, DealStatusDictionary} from "../../../../../shared/enums/DealStatus.ts";
import {isEqual} from "lodash";
import {notifications} from "../../../../../shared/lib/notifications.ts";
import {useQueryClient} from "@tanstack/react-query";
type Props = {
deal: DealSchema
}
type FormType = Omit<DealSchema, 'statusHistory' | 'services' | 'products'>
const Content: FC<Props> = ({deal}) => {
const {setSelectedDeal} = useDealPageContext();
const queryClient = useQueryClient();
const initialValues: FormType = deal;
const form = useForm<FormType>(
{
initialValues: initialValues,
validate: {
name: (value: string) => value.length > 0 ? null : 'Название сделки не может быть пустым'
}
}
)
const updateDealInfo = async (values: FormType) => {
return DealService.updateDealGeneralInfo({
requestBody: {
dealId: deal.id,
data: values
}
}).then(({ok, message}) => {
notifications.guess(ok, {message});
if (!ok) return;
DealService.getDealById({dealId: deal.id})
.then((data) => {
setSelectedDeal(data);
form.setInitialValues(data);
queryClient.invalidateQueries({
queryKey: ['getDealSummaries']
})
})
});
}
const updateClientInfo = async (values: FormType) => {
return ClientService.updateClient({
requestBody: {
data: values.client
}
}).then(({ok, message}) =>
notifications.guess(ok, {message}));
}
const handleSubmit = async (values: FormType) => {
// Updating client info if there changes
if (!isEqual(values.client, deal.client)) {
await updateClientInfo(values);
}
// updating deal info
await updateDealInfo(values);
}
return (
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
<Fieldset legend={"Общие параметры"}>
<Flex direction={"column"} gap={rem(10)}>
<TextInput
placeholder={"Название сделки"}
label={"Название сделки"}
{...form.getInputProps('name')}
/>
<TextInput
disabled
placeholder={"Дата создания"}
label={"Дата создания"}
value={new Date(deal.createdAt).toLocaleString('ru-RU')}
/>
<TextInput
disabled
placeholder={"Текущий статус"}
label={"Текущий статус"}
value={DealStatusDictionary[deal.currentStatus as DealStatus]}/>
<Checkbox
label={"Сделка завершена"}
{...form.getInputProps('isCompleted')}
/>
<Checkbox
label={"Сделка удалена"}
{...form.getInputProps('isDeleted')}
/>
</Flex>
</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.address')}
/>
<TextInput
placeholder={"Введите ИНН"}
label={"ИНН"}
{...form.getInputProps('client.details.inn')}
/>
</Fieldset>
<Group justify={"flex-end"} mt={"md"}>
<Button
color={"red"}
type={"reset"}
disabled={isEqual(initialValues, form.values)}
onClick={() => form.reset()}
>Отменить изменения</Button>
<Button
variant={"default"}
type={"submit"}
disabled={isEqual(initialValues, form.values)}
>Сохранить изменения</Button>
</Group>
</form>
)
}
const DealEditDrawerGeneralTab: FC = () => {
const {selectedDeal} = useDealPageContext();
if (!selectedDeal) return <>No deal selected</>;
return (
<Content deal={selectedDeal}/>
);
}
export default DealEditDrawerGeneralTab;

View File

@@ -29,7 +29,6 @@ const AddDealProductModal = ({
context.closeContextModal(id);
}
return (
<BaseFormModal
{...innerProps}
@@ -39,10 +38,10 @@ const AddDealProductModal = ({
<BaseFormModal.Body>
<>
<ProductSelect
placeholder={"Выберите услугу"}
label={"Услуга"}
placeholder={"Выберите товар"}
label={"Товар"}
clientId={innerProps.clientId}
{...form.getInputProps('service')}
{...form.getInputProps('product')}
/>
<NumberInput
placeholder={"Введите количество"}

View File

@@ -4,7 +4,7 @@ import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
import {MRT_TableOptions} from "mantine-react-table";
import {useProductsTableColumns} from "./columns.tsx";
import {ActionIcon, Flex, Tooltip} from "@mantine/core";
import {IconEdit, IconTrash} from "@tabler/icons-react";
import {IconBarcode, IconEdit, IconTrash} from "@tabler/icons-react";
import {CRUDTableProps} from "../../../../types/CRUDTable.tsx";
import {modals} from "@mantine/modals";
@@ -23,6 +23,16 @@ const ProductsTable: FC<CRUDTableProps<ProductSchema>> = ({items, onDelete, onCh
}
})
}
const onPrintBarcodeClick = (product: ProductSchema) => {
modals.openContextModal({
modal: "printBarcode",
title: 'Печать штрихкода',
withCloseButton: true,
innerProps: {
productId: product.id
}
})
}
return (
<BaseTable
ref={tableRef}
@@ -33,6 +43,13 @@ const ProductsTable: FC<CRUDTableProps<ProductSchema>> = ({items, onDelete, onCh
enableRowActions: true,
renderRowActions: ({row}) => (
<Flex gap="md">
<Tooltip label="Печать штрихкода">
<ActionIcon
onClick={() => onPrintBarcodeClick(row.original)}
variant={"default"}>
<IconBarcode/>
</ActionIcon>
</Tooltip>
<Tooltip label="Редактировать">
<ActionIcon
onClick={() => onEditClick(row.original)}

View File

@@ -9,7 +9,6 @@ import {notifications} from "../../../shared/lib/notifications.ts";
import {CreateProductRequest} from "../types.ts";
import {ProductSchema, ProductService} from "../../../client";
import useProductsList from "../hooks/useProductsList.tsx";
import ProductSelect from "../../../components/ProductSelect/ProductSelect.tsx";
export const ProductsPage: FC = () => {
const [clientId, setClientId] = useState(-1);
@@ -90,11 +89,7 @@ export const ProductsPage: FC = () => {
onClick={() => onCreateProductClick()}
variant={"default"}
>Создать</Button>
<ProductSelect
onChange={event => console.log(event)}
clientId={8}
limit={10}
/>
</div>
</PageBlock>
<PageBlock>

View File

@@ -1,6 +1,6 @@
import {createLazyFileRoute} from "@tanstack/react-router";
import {Button} from "@mantine/core";
import {notifications} from "@mantine/notifications";
import {Button, Text} from "@mantine/core";
import {modals} from "@mantine/modals";
export const Route = createLazyFileRoute('/test')({
component: TestPage
@@ -8,11 +8,28 @@ export const Route = createLazyFileRoute('/test')({
function TestPage() {
return (
<>
<Button onClick={() => {
notifications.show({title: "test", message: "хуй"})
}}>TEST</Button>
</>
)
<Button
onClick={() =>
modals.openConfirmModal({
title: 'Please confirm your action',
closeOnConfirm: false,
labels: {confirm: 'Next modal', cancel: 'Close modal'},
onConfirm: () =>
modals.openConfirmModal({
title: 'This is modal at second layer',
labels: {confirm: 'Close modal', cancel: 'Back'},
closeOnConfirm: false,
children: (
<Text size="sm">
When this modal is closed modals state will revert to first modal
</Text>
),
onConfirm: modals.closeAll,
}),
})
}
>
Open multiple steps modal
</Button>
);
}

View File

@@ -10,4 +10,13 @@ export enum DealStatus {
export const getDealStatusByName = (name: string): DealStatus => {
return DealStatus[name as keyof typeof DealStatus];
}
export const DealStatusDictionary = {
[DealStatus.CREATED]: "Создан",
[DealStatus.AWAITING_ACCEPTANCE]: "Ожидает приемки",
[DealStatus.PACKAGING]: "Упаковка",
[DealStatus.AWAITING_SHIPMENT]: "Ожидает отгрузки",
[DealStatus.AWAITING_PAYMENT]: "Ожидает оплаты",
[DealStatus.COMPLETED]: "Завершена",
[DealStatus.CANCELLED]: "Отменена",
}