feat: filling deals from excel file
This commit is contained in:
@@ -30,12 +30,14 @@ export type { BaseShippingWarehouseSchema } from './models/BaseShippingWarehouse
|
|||||||
export type { BaseTransactionTagSchema } from './models/BaseTransactionTagSchema';
|
export type { BaseTransactionTagSchema } from './models/BaseTransactionTagSchema';
|
||||||
export type { BillPaymentStatus } from './models/BillPaymentStatus';
|
export type { BillPaymentStatus } from './models/BillPaymentStatus';
|
||||||
export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest';
|
export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest';
|
||||||
|
export type { Body_parse_deals_excel } from './models/Body_parse_deals_excel';
|
||||||
export type { Body_upload_passport_image } from './models/Body_upload_passport_image';
|
export type { Body_upload_passport_image } from './models/Body_upload_passport_image';
|
||||||
export type { Body_upload_product_barcode_image } from './models/Body_upload_product_barcode_image';
|
export type { Body_upload_product_barcode_image } from './models/Body_upload_product_barcode_image';
|
||||||
export type { Body_upload_product_image } from './models/Body_upload_product_image';
|
export type { Body_upload_product_image } from './models/Body_upload_product_image';
|
||||||
export type { BoxSchema } from './models/BoxSchema';
|
export type { BoxSchema } from './models/BoxSchema';
|
||||||
export type { CancelDealBillRequest } from './models/CancelDealBillRequest';
|
export type { CancelDealBillRequest } from './models/CancelDealBillRequest';
|
||||||
export type { CancelDealBillResponse } from './models/CancelDealBillResponse';
|
export type { CancelDealBillResponse } from './models/CancelDealBillResponse';
|
||||||
|
export type { CityBreakdownFromExcelSchema } from './models/CityBreakdownFromExcelSchema';
|
||||||
export type { ClientCreateRequest } from './models/ClientCreateRequest';
|
export type { ClientCreateRequest } from './models/ClientCreateRequest';
|
||||||
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
||||||
export type { ClientDeleteRequest } from './models/ClientDeleteRequest';
|
export type { ClientDeleteRequest } from './models/ClientDeleteRequest';
|
||||||
@@ -52,6 +54,8 @@ export type { CreateBoxInDealSchema } from './models/CreateBoxInDealSchema';
|
|||||||
export type { CreateBoxInPalletSchema } from './models/CreateBoxInPalletSchema';
|
export type { CreateBoxInPalletSchema } from './models/CreateBoxInPalletSchema';
|
||||||
export type { CreateDealBillRequest } from './models/CreateDealBillRequest';
|
export type { CreateDealBillRequest } from './models/CreateDealBillRequest';
|
||||||
export type { CreateDealBillResponse } from './models/CreateDealBillResponse';
|
export type { CreateDealBillResponse } from './models/CreateDealBillResponse';
|
||||||
|
export type { CreateDealsFromExcelRequest } from './models/CreateDealsFromExcelRequest';
|
||||||
|
export type { CreateDealsFromExcelResponse } from './models/CreateDealsFromExcelResponse';
|
||||||
export type { CreateDepartmentRequest } from './models/CreateDepartmentRequest';
|
export type { CreateDepartmentRequest } from './models/CreateDepartmentRequest';
|
||||||
export type { CreateDepartmentResponse } from './models/CreateDepartmentResponse';
|
export type { CreateDepartmentResponse } from './models/CreateDepartmentResponse';
|
||||||
export type { CreateDepartmentSectionRequest } from './models/CreateDepartmentSectionRequest';
|
export type { CreateDepartmentSectionRequest } from './models/CreateDepartmentSectionRequest';
|
||||||
@@ -222,8 +226,12 @@ export type { ManageEmployeeResponse } from './models/ManageEmployeeResponse';
|
|||||||
export type { MarketplaceCreateSchema } from './models/MarketplaceCreateSchema';
|
export type { MarketplaceCreateSchema } from './models/MarketplaceCreateSchema';
|
||||||
export type { MarketplaceSchema } from './models/MarketplaceSchema';
|
export type { MarketplaceSchema } from './models/MarketplaceSchema';
|
||||||
export type { NotificationChannel } from './models/NotificationChannel';
|
export type { NotificationChannel } from './models/NotificationChannel';
|
||||||
|
export type { OptionalShippingWarehouseSchema } from './models/OptionalShippingWarehouseSchema';
|
||||||
export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
|
export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
|
||||||
export type { PalletSchema } from './models/PalletSchema';
|
export type { PalletSchema } from './models/PalletSchema';
|
||||||
|
export type { ParsedCityBreakdownSchema } from './models/ParsedCityBreakdownSchema';
|
||||||
|
export type { ParseDealsExcelResponse } from './models/ParseDealsExcelResponse';
|
||||||
|
export type { ParsedProductRowSchema } from './models/ParsedProductRowSchema';
|
||||||
export type { PassportImageSchema } from './models/PassportImageSchema';
|
export type { PassportImageSchema } from './models/PassportImageSchema';
|
||||||
export type { PaymentRecordCreateSchema } from './models/PaymentRecordCreateSchema';
|
export type { PaymentRecordCreateSchema } from './models/PaymentRecordCreateSchema';
|
||||||
export type { PaymentRecordGetSchema } from './models/PaymentRecordGetSchema';
|
export type { PaymentRecordGetSchema } from './models/PaymentRecordGetSchema';
|
||||||
@@ -240,6 +248,7 @@ export type { ProductDeleteBarcodeImageResponse } from './models/ProductDeleteBa
|
|||||||
export type { ProductDeleteRequest } from './models/ProductDeleteRequest';
|
export type { ProductDeleteRequest } from './models/ProductDeleteRequest';
|
||||||
export type { ProductDeleteResponse } from './models/ProductDeleteResponse';
|
export type { ProductDeleteResponse } from './models/ProductDeleteResponse';
|
||||||
export type { ProductExistsBarcodeResponse } from './models/ProductExistsBarcodeResponse';
|
export type { ProductExistsBarcodeResponse } from './models/ProductExistsBarcodeResponse';
|
||||||
|
export type { ProductFromExcelSchema } from './models/ProductFromExcelSchema';
|
||||||
export type { ProductGenerateBarcodeRequest } from './models/ProductGenerateBarcodeRequest';
|
export type { ProductGenerateBarcodeRequest } from './models/ProductGenerateBarcodeRequest';
|
||||||
export type { ProductGenerateBarcodeResponse } from './models/ProductGenerateBarcodeResponse';
|
export type { ProductGenerateBarcodeResponse } from './models/ProductGenerateBarcodeResponse';
|
||||||
export type { ProductGetBarcodeImageResponse } from './models/ProductGetBarcodeImageResponse';
|
export type { ProductGetBarcodeImageResponse } from './models/ProductGetBarcodeImageResponse';
|
||||||
|
|||||||
8
src/client/models/Body_parse_deals_excel.ts
Normal file
8
src/client/models/Body_parse_deals_excel.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type Body_parse_deals_excel = {
|
||||||
|
upload_file: Blob;
|
||||||
|
};
|
||||||
|
|
||||||
12
src/client/models/CityBreakdownFromExcelSchema.ts
Normal file
12
src/client/models/CityBreakdownFromExcelSchema.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { BaseMarketplaceSchema } from './BaseMarketplaceSchema';
|
||||||
|
import type { OptionalShippingWarehouseSchema } from './OptionalShippingWarehouseSchema';
|
||||||
|
export type CityBreakdownFromExcelSchema = {
|
||||||
|
baseMarketplace: BaseMarketplaceSchema;
|
||||||
|
shippingWarehouse: OptionalShippingWarehouseSchema;
|
||||||
|
quantity: number;
|
||||||
|
};
|
||||||
|
|
||||||
10
src/client/models/CreateDealsFromExcelRequest.ts
Normal file
10
src/client/models/CreateDealsFromExcelRequest.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ProductFromExcelSchema } from './ProductFromExcelSchema';
|
||||||
|
export type CreateDealsFromExcelRequest = {
|
||||||
|
clientId: number;
|
||||||
|
products: Array<ProductFromExcelSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreateDealsFromExcelResponse.ts
Normal file
9
src/client/models/CreateDealsFromExcelResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreateDealsFromExcelResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/OptionalShippingWarehouseSchema.ts
Normal file
9
src/client/models/OptionalShippingWarehouseSchema.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type OptionalShippingWarehouseSchema = {
|
||||||
|
name: string;
|
||||||
|
id?: (number | null);
|
||||||
|
};
|
||||||
|
|
||||||
10
src/client/models/ParseDealsExcelResponse.ts
Normal file
10
src/client/models/ParseDealsExcelResponse.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ParsedProductRowSchema } from './ParsedProductRowSchema';
|
||||||
|
export type ParseDealsExcelResponse = {
|
||||||
|
rows: Array<ParsedProductRowSchema>;
|
||||||
|
errors: Array<string>;
|
||||||
|
};
|
||||||
|
|
||||||
12
src/client/models/ParsedCityBreakdownSchema.ts
Normal file
12
src/client/models/ParsedCityBreakdownSchema.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { BaseMarketplaceSchema } from './BaseMarketplaceSchema';
|
||||||
|
import type { OptionalShippingWarehouseSchema } from './OptionalShippingWarehouseSchema';
|
||||||
|
export type ParsedCityBreakdownSchema = {
|
||||||
|
baseMarketplace: BaseMarketplaceSchema;
|
||||||
|
shippingWarehouse: OptionalShippingWarehouseSchema;
|
||||||
|
quantity: number;
|
||||||
|
};
|
||||||
|
|
||||||
12
src/client/models/ParsedProductRowSchema.ts
Normal file
12
src/client/models/ParsedProductRowSchema.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ParsedCityBreakdownSchema } from './ParsedCityBreakdownSchema';
|
||||||
|
import type { ProductSchema } from './ProductSchema';
|
||||||
|
export type ParsedProductRowSchema = {
|
||||||
|
barcode: string;
|
||||||
|
products: Array<ProductSchema>;
|
||||||
|
breakdowns: Array<ParsedCityBreakdownSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
10
src/client/models/ProductFromExcelSchema.ts
Normal file
10
src/client/models/ProductFromExcelSchema.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { CityBreakdownFromExcelSchema } from './CityBreakdownFromExcelSchema';
|
||||||
|
export type ProductFromExcelSchema = {
|
||||||
|
productId: number;
|
||||||
|
citiesBreakdown: Array<CityBreakdownFromExcelSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
import type { Body_parse_deals_excel } from '../models/Body_parse_deals_excel';
|
||||||
|
import type { CreateDealsFromExcelRequest } from '../models/CreateDealsFromExcelRequest';
|
||||||
|
import type { CreateDealsFromExcelResponse } from '../models/CreateDealsFromExcelResponse';
|
||||||
import type { DealAddKitRequest } from '../models/DealAddKitRequest';
|
import type { DealAddKitRequest } from '../models/DealAddKitRequest';
|
||||||
import type { DealAddKitResponse } from '../models/DealAddKitResponse';
|
import type { DealAddKitResponse } from '../models/DealAddKitResponse';
|
||||||
import type { DealAddProductRequest } from '../models/DealAddProductRequest';
|
import type { DealAddProductRequest } from '../models/DealAddProductRequest';
|
||||||
@@ -66,6 +69,7 @@ import type { GetDealProductsBarcodesPdfRequest } from '../models/GetDealProduct
|
|||||||
import type { GetDealProductsBarcodesPdfResponse } from '../models/GetDealProductsBarcodesPdfResponse';
|
import type { GetDealProductsBarcodesPdfResponse } from '../models/GetDealProductsBarcodesPdfResponse';
|
||||||
import type { ManageEmployeeRequest } from '../models/ManageEmployeeRequest';
|
import type { ManageEmployeeRequest } from '../models/ManageEmployeeRequest';
|
||||||
import type { ManageEmployeeResponse } from '../models/ManageEmployeeResponse';
|
import type { ManageEmployeeResponse } from '../models/ManageEmployeeResponse';
|
||||||
|
import type { ParseDealsExcelResponse } from '../models/ParseDealsExcelResponse';
|
||||||
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';
|
||||||
@@ -426,6 +430,46 @@ export class DealService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Parse Deals Excel
|
||||||
|
* @returns ParseDealsExcelResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static parseDealsExcel({
|
||||||
|
formData,
|
||||||
|
}: {
|
||||||
|
formData: Body_parse_deals_excel,
|
||||||
|
}): CancelablePromise<ParseDealsExcelResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/deal/prefill/excel/parse',
|
||||||
|
formData: formData,
|
||||||
|
mediaType: 'multipart/form-data',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create Deals From Excel
|
||||||
|
* @returns CreateDealsFromExcelResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static createDealsExcel({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: CreateDealsFromExcelRequest,
|
||||||
|
}): CancelablePromise<CreateDealsFromExcelResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/deal/prefill/excel/create',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Services Add
|
* Services Add
|
||||||
* @returns DealAddServicesResponse Successful Response
|
* @returns DealAddServicesResponse Successful Response
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import classNames from "classnames";
|
|||||||
import { getPluralForm } from "../../../shared/lib/utils.ts";
|
import { getPluralForm } from "../../../shared/lib/utils.ts";
|
||||||
import { groupBy, has, sum, uniq } from "lodash";
|
import { groupBy, has, sum, uniq } from "lodash";
|
||||||
import { DealGroupView } from "../DealGroupView/DealGroupView.tsx";
|
import { DealGroupView } from "../DealGroupView/DealGroupView.tsx";
|
||||||
|
import CreateDealsFromFileButton from "../CreateDealsFromFileButton/CreateDealsFromFileButton.tsx";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
droppableId: string;
|
droppableId: string;
|
||||||
@@ -181,8 +182,10 @@ export const Board: FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
{...provided.droppableProps}>
|
{...provided.droppableProps}>
|
||||||
{withCreateButton && (
|
{withCreateButton && (
|
||||||
<CreateDealButton onClick={() => {
|
<>
|
||||||
}} />
|
<CreateDealButton />
|
||||||
|
<CreateDealsFromFileButton />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{getDealsAndGroups().map(obj => {
|
{getDealsAndGroups().map(obj => {
|
||||||
if (isGroup(obj)) {
|
if (isGroup(obj)) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import styles from "./CreateDealButton.module.css";
|
import styles from "./CreateDealButton.module.css";
|
||||||
import { Text, Transition } from "@mantine/core";
|
import { Text, Transition } from "@mantine/core";
|
||||||
@@ -8,10 +8,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||||||
import { dateWithoutTimezone } from "../../../shared/lib/date.ts";
|
import { dateWithoutTimezone } from "../../../shared/lib/date.ts";
|
||||||
import { usePrefillDealContext } from "../../../pages/LeadsPage/contexts/PrefillDealContext.tsx";
|
import { usePrefillDealContext } from "../../../pages/LeadsPage/contexts/PrefillDealContext.tsx";
|
||||||
|
|
||||||
type Props = {
|
const CreateDealButton = () => {
|
||||||
onClick: () => void;
|
|
||||||
};
|
|
||||||
const CreateDealButton: FC<Props> = () => {
|
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
const [isTransitionEnded, setIsTransitionEnded] = useState(true);
|
const [isTransitionEnded, setIsTransitionEnded] = useState(true);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
.container {
|
||||||
|
/*background-color: red;*/
|
||||||
|
min-height: 5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: stretch;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
border: dashed var(--item-border-size) var(--mantine-color-default-border);
|
||||||
|
border-radius: var(--item-border-radius);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container:hover {
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-color-default-hover),
|
||||||
|
var(--mantine-color-gray-filled-hover)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import styles from "./CreateDealsFromFileButton.module.css";
|
||||||
|
import { Text } from "@mantine/core";
|
||||||
|
import { usePrefillDealsWithExcelContext } from "../../../pages/LeadsPage/contexts/PrefillDealsWithExcelContext.tsx";
|
||||||
|
|
||||||
|
const CreateDealsFromFileButton = () => {
|
||||||
|
const { prefillWithExcelOnOpen } = usePrefillDealsWithExcelContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles["container"]}
|
||||||
|
onClick={prefillWithExcelOnOpen}>
|
||||||
|
<Text>Добавление из файла</Text>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateDealsFromFileButton;
|
||||||
86
src/components/ExcelDropzone/ExcelDropzone.tsx
Normal file
86
src/components/ExcelDropzone/ExcelDropzone.tsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { Dropzone, DropzoneProps, FileWithPath, MIME_TYPES } from "@mantine/dropzone";
|
||||||
|
import { FC } from "react";
|
||||||
|
import { Fieldset, Flex, Group, Loader, rem, Text } from "@mantine/core";
|
||||||
|
import { IconFileExcel, IconUpload, IconX } from "@tabler/icons-react";
|
||||||
|
import UseExcelDropzone from "../../types/UseExcelDropzone.tsx";
|
||||||
|
|
||||||
|
interface RestProps {
|
||||||
|
dropzone: UseExcelDropzone;
|
||||||
|
onDrop: (files: FileWithPath[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = RestProps & Omit<DropzoneProps, "onDrop">;
|
||||||
|
|
||||||
|
const ExcelDropzone: FC<Props> = (props: Props) => {
|
||||||
|
const { isLoading } = props.dropzone;
|
||||||
|
|
||||||
|
const getBody = () => {
|
||||||
|
return (
|
||||||
|
<Dropzone
|
||||||
|
{...props}
|
||||||
|
accept={[
|
||||||
|
MIME_TYPES.xlsx,
|
||||||
|
]}
|
||||||
|
multiple={false}
|
||||||
|
onDrop={props.onDrop}>
|
||||||
|
<Group
|
||||||
|
justify="center"
|
||||||
|
gap="xl"
|
||||||
|
style={{ pointerEvents: "none" }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload
|
||||||
|
style={{
|
||||||
|
width: rem(52),
|
||||||
|
height: rem(52),
|
||||||
|
color: "var(--mantine-color-blue-6)",
|
||||||
|
}}
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX
|
||||||
|
style={{
|
||||||
|
width: rem(52),
|
||||||
|
height: rem(52),
|
||||||
|
color: "var(--mantine-color-red-6)",
|
||||||
|
}}
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconFileExcel
|
||||||
|
style={{
|
||||||
|
width: rem(52),
|
||||||
|
height: rem(52),
|
||||||
|
color: "var(--mantine-color-dimmed)",
|
||||||
|
}}
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<Text
|
||||||
|
size="xl"
|
||||||
|
inline>
|
||||||
|
Перенесите или нажмите чтоб выбрать файл формата xlsx
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
gap={rem(10)}
|
||||||
|
direction={"column"}>
|
||||||
|
<Fieldset legend={"Файл"}>
|
||||||
|
<Flex justify={"center"}>
|
||||||
|
{isLoading ? <Loader /> : getBody()}
|
||||||
|
</Flex>
|
||||||
|
</Fieldset>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExcelDropzone;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import { FC, ReactNode } from "react";
|
||||||
import { Select } from "@mantine/core";
|
import { Select } from "@mantine/core";
|
||||||
import { ClientSchema } from "../../../client";
|
import { ClientSchema } from "../../../client";
|
||||||
import useClientsList from "../../../pages/ClientsPage/hooks/useClientsList.tsx";
|
import useClientsList from "../../../pages/ClientsPage/hooks/useClientsList.tsx";
|
||||||
@@ -7,8 +7,10 @@ type Props = {
|
|||||||
value?: ClientSchema;
|
value?: ClientSchema;
|
||||||
onChange: (client: ClientSchema) => void;
|
onChange: (client: ClientSchema) => void;
|
||||||
withLabel?: boolean;
|
withLabel?: boolean;
|
||||||
|
error?: string;
|
||||||
|
inputContainer?: (children: ReactNode) => ReactNode;
|
||||||
};
|
};
|
||||||
const ClientSelect: FC<Props> = ({ value, onChange, withLabel = false }) => {
|
const ClientSelect: FC<Props> = ({ value, onChange, error, inputContainer, withLabel = false }) => {
|
||||||
const { clients } = useClientsList();
|
const { clients } = useClientsList();
|
||||||
const options = clients.map(client => ({
|
const options = clients.map(client => ({
|
||||||
label: client.name,
|
label: client.name,
|
||||||
@@ -33,6 +35,8 @@ const ClientSelect: FC<Props> = ({ value, onChange, withLabel = false }) => {
|
|||||||
}}
|
}}
|
||||||
data={options}
|
data={options}
|
||||||
label={withLabel && "Клиент"}
|
label={withLabel && "Клиент"}
|
||||||
|
error={error}
|
||||||
|
inputContainer={inputContainer}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
150
src/pages/LeadsPage/contexts/PrefillDealsWithExcelContext.tsx
Normal file
150
src/pages/LeadsPage/contexts/PrefillDealsWithExcelContext.tsx
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import { createContext, FC, useContext, useState } from "react";
|
||||||
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
|
import { DealsWithExcelForm, ProductExcelData } from "../drawers/PrefillDealWithExcelDrawer/types.tsx";
|
||||||
|
import { FileWithPath } from "@mantine/dropzone";
|
||||||
|
import { notifications } from "../../../shared/lib/notifications.ts";
|
||||||
|
import { DealService, type ProductFromExcelSchema, ProductSchema } from "../../../client";
|
||||||
|
import UseExcelDropzone from "../../../types/UseExcelDropzone.tsx";
|
||||||
|
import { useForm, UseFormReturnType } from "@mantine/form";
|
||||||
|
import { useDealPageContext } from "./DealPageContext.tsx";
|
||||||
|
|
||||||
|
type PrefillDealsWithExcelContextState = {
|
||||||
|
prefillWithExcelOpened: boolean;
|
||||||
|
prefillWithExcelOnClose: () => void;
|
||||||
|
prefillWithExcelOnOpen: () => void;
|
||||||
|
barcodeProductsMap: Map<string, ProductExcelData>,
|
||||||
|
onProductSelectChange: (barcode: string, selectedProduct: ProductSchema) => void,
|
||||||
|
onDrop: (files: FileWithPath[]) => void;
|
||||||
|
excelDropzone: UseExcelDropzone;
|
||||||
|
createDeals: (values: DealsWithExcelForm) => void;
|
||||||
|
form: UseFormReturnType<DealsWithExcelForm>;
|
||||||
|
errors: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const PrefillDealsWithExcelContext = createContext<PrefillDealsWithExcelContextState | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
const usePrefillDealsWithExcelContextState = () => {
|
||||||
|
const [prefillWithExcelOpened, { open, close }] = useDisclosure(false);
|
||||||
|
const { refetchDeals } = useDealPageContext();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [errors, setErrors] = useState<string[]>([]);
|
||||||
|
const excelDropzone: UseExcelDropzone = {
|
||||||
|
isLoading,
|
||||||
|
setIsLoading,
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = useForm<DealsWithExcelForm>({
|
||||||
|
validate: {
|
||||||
|
client: client => !client && "Выберите клиента",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [barcodeProductsMap, setBarcodeProductsMap] = useState<Map<string, ProductExcelData>>(new Map());
|
||||||
|
|
||||||
|
const onDrop = (files: FileWithPath[]) => {
|
||||||
|
if (files.length > 1) {
|
||||||
|
notifications.error({ message: "Прикрепите одно изображение" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const file = files[0];
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
DealService.parseDealsExcel({
|
||||||
|
formData: {
|
||||||
|
upload_file: file,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
const barcodeProductsMap = new Map();
|
||||||
|
res.rows.forEach((row) => {
|
||||||
|
const productsData: ProductExcelData = row;
|
||||||
|
productsData.selectedProduct = productsData.products[0];
|
||||||
|
barcodeProductsMap.set(row.barcode, row);
|
||||||
|
});
|
||||||
|
setBarcodeProductsMap(barcodeProductsMap);
|
||||||
|
setErrors(res.errors);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notifications.error({ message: error.toString() });
|
||||||
|
})
|
||||||
|
.finally(() => setIsLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onProductSelectChange = (barcode: string, selectedProduct: ProductSchema) => {
|
||||||
|
const newBarcodeProductsMap = new Map(barcodeProductsMap);
|
||||||
|
const productsData = newBarcodeProductsMap.get(barcode);
|
||||||
|
if (!productsData) return;
|
||||||
|
productsData.selectedProduct = selectedProduct;
|
||||||
|
|
||||||
|
newBarcodeProductsMap.set(
|
||||||
|
barcode,
|
||||||
|
productsData,
|
||||||
|
);
|
||||||
|
setBarcodeProductsMap(newBarcodeProductsMap);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prefillWithExcelOnClose = () => {
|
||||||
|
close();
|
||||||
|
setBarcodeProductsMap(new Map());
|
||||||
|
form.reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
const createDeals = (values: DealsWithExcelForm) => {
|
||||||
|
const products: ProductFromExcelSchema[] = barcodeProductsMap.entries().map(([, productData]) => {
|
||||||
|
return {
|
||||||
|
productId: productData.selectedProduct!.id,
|
||||||
|
citiesBreakdown: productData.breakdowns,
|
||||||
|
};
|
||||||
|
}).toArray();
|
||||||
|
|
||||||
|
DealService.createDealsExcel({
|
||||||
|
requestBody: {
|
||||||
|
products,
|
||||||
|
clientId: values.client?.id ?? -1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
notifications.guess(ok, { message });
|
||||||
|
if (ok) prefillWithExcelOnClose();
|
||||||
|
refetchDeals();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
prefillWithExcelOpened,
|
||||||
|
prefillWithExcelOnClose,
|
||||||
|
prefillWithExcelOnOpen: open,
|
||||||
|
barcodeProductsMap,
|
||||||
|
onProductSelectChange,
|
||||||
|
onDrop,
|
||||||
|
excelDropzone,
|
||||||
|
createDeals,
|
||||||
|
form,
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type PrefillDealsWithExcelContextProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PrefillDealsWithExcelContextProvider: FC<PrefillDealsWithExcelContextProviderProps> = ({ children }) => {
|
||||||
|
const state = usePrefillDealsWithExcelContextState();
|
||||||
|
return (
|
||||||
|
<PrefillDealsWithExcelContext.Provider value={state}>
|
||||||
|
{children}
|
||||||
|
</PrefillDealsWithExcelContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePrefillDealsWithExcelContext = () => {
|
||||||
|
const context = useContext(PrefillDealsWithExcelContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"usePrefillDealsWithExcelContext must be used within a PrefillDealsWithExcelContextProvider",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: rem(10);
|
||||||
|
max-height: 95vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: rem(10);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-container-wrapper {
|
||||||
|
border: dashed var(--item-border-size) var(--mantine-color-default-border);
|
||||||
|
border-radius: var(--item-border-radius);
|
||||||
|
padding: rem(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-container-buttons {
|
||||||
|
gap: rem(10);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-panel {
|
||||||
|
padding-bottom: rem(9);
|
||||||
|
gap: rem(10);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Drawer, rem } from "@mantine/core";
|
||||||
|
import ExcelDropzone from "../../../../components/ExcelDropzone/ExcelDropzone.tsx";
|
||||||
|
import styles from "../PrefillDealWithExcelDrawer/PrefillDealsWithExcelDrawer.module.css";
|
||||||
|
import { usePrefillDealsWithExcelContext } from "../../contexts/PrefillDealsWithExcelContext.tsx";
|
||||||
|
import ProductsPreview from "./components/ProductsPreview.tsx";
|
||||||
|
|
||||||
|
const PrefillDealsWithExcelDrawer = () => {
|
||||||
|
const {
|
||||||
|
prefillWithExcelOpened,
|
||||||
|
prefillWithExcelOnClose,
|
||||||
|
barcodeProductsMap,
|
||||||
|
onDrop,
|
||||||
|
excelDropzone,
|
||||||
|
} = usePrefillDealsWithExcelContext();
|
||||||
|
|
||||||
|
const getBody = () => {
|
||||||
|
if (barcodeProductsMap?.size === 0) {
|
||||||
|
return <ExcelDropzone dropzone={excelDropzone} onDrop={onDrop} />;
|
||||||
|
}
|
||||||
|
return <ProductsPreview />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
size={"calc(77vw)"}
|
||||||
|
position={"right"}
|
||||||
|
onClose={prefillWithExcelOnClose}
|
||||||
|
removeScrollProps={{ allowPinchZoom: true }}
|
||||||
|
withCloseButton={false}
|
||||||
|
opened={prefillWithExcelOpened}
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: rem(20),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={styles["deal-container"]}>
|
||||||
|
{getBody()}
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PrefillDealsWithExcelDrawer;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
|
||||||
|
import { ParsedCityBreakdownSchema } from "../../../../../client";
|
||||||
|
import { useBreakdownByCityTableColumns } from "../hooks/useBreakdownByCityTableColumns.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
breakdowns: ParsedCityBreakdownSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const BreakdownByCityTable = ({ breakdowns }: Props) => {
|
||||||
|
const columns = useBreakdownByCityTableColumns();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseTable
|
||||||
|
data={breakdowns}
|
||||||
|
columns={columns}
|
||||||
|
w={"30%"}
|
||||||
|
|
||||||
|
restProps={{
|
||||||
|
enableSorting: false,
|
||||||
|
enableRowActions: false,
|
||||||
|
enableTopToolbar: false,
|
||||||
|
enableColumnActions: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BreakdownByCityTable;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { usePrefillDealsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
|
||||||
|
import { Text, Tooltip } from "@mantine/core";
|
||||||
|
import { IconAlertCircle, IconCircleCheck } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
const ParsingResultsTooltip = () => {
|
||||||
|
const { errors } = usePrefillDealsWithExcelContext();
|
||||||
|
const isError = errors.length !== 0;
|
||||||
|
|
||||||
|
const errorLines = errors.map((error, i) => <Text key={i}>{error}</Text>);
|
||||||
|
const tooltipData = isError ? errorLines : "Ошибок при обработке нет";
|
||||||
|
const color = isError ? "red" : "grey";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
label={tooltipData}
|
||||||
|
multiline
|
||||||
|
w={350}
|
||||||
|
withArrow
|
||||||
|
color={color}
|
||||||
|
>
|
||||||
|
{isError ? (
|
||||||
|
<IconAlertCircle color={"red"}/>
|
||||||
|
) : (
|
||||||
|
<IconCircleCheck color={"green"}/>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ParsingResultsTooltip;
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import styles from "../PrefillDealsWithExcelDrawer.module.css";
|
||||||
|
import ProductsTable from "./ProductsTable.tsx";
|
||||||
|
import { usePrefillDealsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
|
||||||
|
import { Box, Button, Flex, Group, Stack, Title } from "@mantine/core";
|
||||||
|
import { ProductExcelData } from "../types.tsx";
|
||||||
|
import BreakdownByCityTable from "./BreakdownByCityTable.tsx";
|
||||||
|
import ClientSelect from "../../../../../components/Selects/ClientSelect/ClientSelect.tsx";
|
||||||
|
import ParsingResultsTooltip from "./ParsingResultsTooltip.tsx";
|
||||||
|
|
||||||
|
const ProductsPreview = () => {
|
||||||
|
const { barcodeProductsMap, createDeals, form } = usePrefillDealsWithExcelContext();
|
||||||
|
|
||||||
|
const getTitle = (barcode: string, productsData: ProductExcelData) => {
|
||||||
|
if (productsData.products.length === 1) {
|
||||||
|
return `Товар со штрихкодом ${barcode}`;
|
||||||
|
}
|
||||||
|
return `Товары со штрихкодом ${barcode}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProductsData = () => {
|
||||||
|
return barcodeProductsMap.entries().map(([barcode, productsData]) => (
|
||||||
|
<div key={barcode} className={styles["deal-container-wrapper"]}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={5}>
|
||||||
|
{getTitle(barcode, productsData)}
|
||||||
|
</Title>
|
||||||
|
<Flex direction={"row"} gap={"md"} flex={10}>
|
||||||
|
<Box flex={7}>
|
||||||
|
<ProductsTable barcode={barcode} productsData={productsData} />
|
||||||
|
</Box>
|
||||||
|
<Box flex={3}>
|
||||||
|
<BreakdownByCityTable breakdowns={productsData.breakdowns} />
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
).toArray();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap={"md"}>
|
||||||
|
<Title order={3}>Предпросмотр</Title>
|
||||||
|
<form onSubmit={form.onSubmit((values) => createDeals(values))}>
|
||||||
|
<ClientSelect
|
||||||
|
{...form.getInputProps("client")}
|
||||||
|
inputContainer={(children) => (
|
||||||
|
<Group align={"flex-start"}>
|
||||||
|
{children}
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Создать сделки
|
||||||
|
</Button>
|
||||||
|
<ParsingResultsTooltip />
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
{getProductsData()}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductsPreview;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
|
||||||
|
import { useProductsTableColumns } from "../hooks/useProductsTableColumns.tsx";
|
||||||
|
import { ProductExcelData } from "../types.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
barcode: string;
|
||||||
|
productsData: ProductExcelData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductsTable = ({ barcode, productsData }: Props) => {
|
||||||
|
const columns = useProductsTableColumns({ barcode });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseTable
|
||||||
|
data={productsData.products}
|
||||||
|
columns={columns}
|
||||||
|
w={"100%"}
|
||||||
|
|
||||||
|
restProps={{
|
||||||
|
enableSorting: false,
|
||||||
|
enableRowActions: false,
|
||||||
|
enableTopToolbar: false,
|
||||||
|
enableColumnActions: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductsTable;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { MRT_ColumnDef } from "mantine-react-table";
|
||||||
|
import { ParsedCityBreakdownSchema } from "../../../../../client";
|
||||||
|
import { ActionIcon, Image } from "@mantine/core";
|
||||||
|
|
||||||
|
export const useBreakdownByCityTableColumns = () => {
|
||||||
|
return useMemo<MRT_ColumnDef<ParsedCityBreakdownSchema>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "baseMarketplace.iconUrl",
|
||||||
|
header: "Маркетплейс",
|
||||||
|
Cell: ({ cell }) => (
|
||||||
|
<ActionIcon
|
||||||
|
radius={"md"}
|
||||||
|
variant={"transparent"}>
|
||||||
|
<Image src={cell.getValue()} />
|
||||||
|
</ActionIcon>
|
||||||
|
),
|
||||||
|
size: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "shippingWarehouse.name",
|
||||||
|
header: "Склад отгрузки",
|
||||||
|
size: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "quantity",
|
||||||
|
header: "Количество",
|
||||||
|
size: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { MRT_ColumnDef } from "mantine-react-table";
|
||||||
|
import { ProductSchema } from "../../../../../client";
|
||||||
|
import { Radio } from "@mantine/core";
|
||||||
|
import { usePrefillDealsWithExcelContext } from "../../../contexts/PrefillDealsWithExcelContext.tsx";
|
||||||
|
import { ProductExcelData } from "../types.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
barcode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProductsTableColumns = ({ barcode }: Props) => {
|
||||||
|
const { onProductSelectChange, barcodeProductsMap } = usePrefillDealsWithExcelContext();
|
||||||
|
const [productData, setProductData] = useState<ProductExcelData>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setProductData(barcodeProductsMap.get(barcode));
|
||||||
|
}, [barcodeProductsMap]);
|
||||||
|
|
||||||
|
return useMemo<MRT_ColumnDef<ProductSchema>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
header: "Выбор",
|
||||||
|
size: 10,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<Radio
|
||||||
|
checked={productData?.selectedProduct?.id === row.original.id}
|
||||||
|
onChange={() => onProductSelectChange(barcode, row.original)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "article",
|
||||||
|
header: "Артикул",
|
||||||
|
size: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "name",
|
||||||
|
header: "Название",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "brand",
|
||||||
|
header: "Бренд",
|
||||||
|
size: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "color",
|
||||||
|
header: "Цвет",
|
||||||
|
size: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "size",
|
||||||
|
header: "Размер",
|
||||||
|
size: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[productData],
|
||||||
|
).filter(columnDef => (
|
||||||
|
!(columnDef.header === "Выбор" && (productData?.products.length ?? 0) === 1)
|
||||||
|
));
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { ClientSchema, type ParsedCityBreakdownSchema, ProductSchema } from "../../../../client";
|
||||||
|
|
||||||
|
export type ProductExcelData = {
|
||||||
|
products: ProductSchema[];
|
||||||
|
breakdowns: ParsedCityBreakdownSchema[];
|
||||||
|
selectedProduct?: ProductSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DealsWithExcelForm = {
|
||||||
|
client?: ClientSchema;
|
||||||
|
}
|
||||||
@@ -23,6 +23,8 @@ import { dateWithoutTimezone } from "../../../shared/lib/date.ts";
|
|||||||
import DealPrefillDrawer from "../drawers/DealPrefillDrawer/DealPrefillDrawer.tsx";
|
import DealPrefillDrawer from "../drawers/DealPrefillDrawer/DealPrefillDrawer.tsx";
|
||||||
import { PrefillDealContextProvider } from "../contexts/PrefillDealContext.tsx";
|
import { PrefillDealContextProvider } from "../contexts/PrefillDealContext.tsx";
|
||||||
import { useParams } from "@tanstack/react-router";
|
import { useParams } from "@tanstack/react-router";
|
||||||
|
import { PrefillDealsWithExcelContextProvider } from "../contexts/PrefillDealsWithExcelContext.tsx";
|
||||||
|
import PrefillDealsWithExcelDrawer from "../drawers/PrefillDealWithExcelDrawer/PrefillDealsWithExcelDrawer.tsx";
|
||||||
|
|
||||||
enum DisplayMode {
|
enum DisplayMode {
|
||||||
BOARD,
|
BOARD,
|
||||||
@@ -401,101 +403,104 @@ export const LeadsPage: FC = () => {
|
|||||||
await refetch();
|
await refetch();
|
||||||
}}>
|
}}>
|
||||||
<PrefillDealContextProvider>
|
<PrefillDealContextProvider>
|
||||||
<PageBlock style={{ flex: 0 }}>
|
<PrefillDealsWithExcelContextProvider>
|
||||||
<Flex
|
<PageBlock style={{ flex: 0 }}>
|
||||||
align={"center"}
|
|
||||||
justify={"space-between"}>
|
|
||||||
<Flex
|
<Flex
|
||||||
gap={rem(10)}
|
align={"center"}
|
||||||
direction={"column"}
|
justify={"space-between"}>
|
||||||
align={"center"}>
|
<Flex
|
||||||
<Text size={"xs"}>Вид</Text>
|
gap={rem(10)}
|
||||||
<Flex gap={rem(10)}>
|
direction={"column"}
|
||||||
<ActionIcon
|
align={"center"}>
|
||||||
onClick={() =>
|
<Text size={"xs"}>Вид</Text>
|
||||||
setDisplayMode(DisplayMode.BOARD)
|
<Flex gap={rem(10)}>
|
||||||
}
|
<ActionIcon
|
||||||
variant={
|
onClick={() =>
|
||||||
displayMode === DisplayMode.BOARD
|
setDisplayMode(DisplayMode.BOARD)
|
||||||
? "filled"
|
}
|
||||||
: "default"
|
variant={
|
||||||
}>
|
displayMode === DisplayMode.BOARD
|
||||||
<IconMenuDeep
|
? "filled"
|
||||||
style={{ rotate: "-90deg" }}
|
: "default"
|
||||||
/>
|
}>
|
||||||
</ActionIcon>
|
<IconMenuDeep
|
||||||
<ActionIcon
|
style={{ rotate: "-90deg" }}
|
||||||
onClick={() =>
|
/>
|
||||||
setDisplayMode(DisplayMode.TABLE)
|
</ActionIcon>
|
||||||
}
|
<ActionIcon
|
||||||
variant={
|
onClick={() =>
|
||||||
displayMode === DisplayMode.TABLE
|
setDisplayMode(DisplayMode.TABLE)
|
||||||
? "filled"
|
}
|
||||||
: "default"
|
variant={
|
||||||
}>
|
displayMode === DisplayMode.TABLE
|
||||||
<IconMenu2 />
|
? "filled"
|
||||||
</ActionIcon>
|
: "default"
|
||||||
|
}>
|
||||||
|
<IconMenu2 />
|
||||||
|
</ActionIcon>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<motion.div
|
||||||
|
key={displayMode}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.2 }}>
|
||||||
|
<div
|
||||||
|
className={styles["top-panel"]}
|
||||||
|
style={{
|
||||||
|
display:
|
||||||
|
displayMode === DisplayMode.TABLE
|
||||||
|
? "flex"
|
||||||
|
: "none",
|
||||||
|
}}>
|
||||||
|
<NumberInput
|
||||||
|
min={1}
|
||||||
|
placeholder={"Введите номер"}
|
||||||
|
{...form.getInputProps("id")}
|
||||||
|
hideControls
|
||||||
|
/>
|
||||||
|
<DealStatusSelect
|
||||||
|
onClear={() =>
|
||||||
|
form.setFieldValue("dealStatus", null)
|
||||||
|
}
|
||||||
|
clearable
|
||||||
|
placeholder={"Выберите статус "}
|
||||||
|
{...form.getInputProps("dealStatus")}
|
||||||
|
/>
|
||||||
|
<BaseMarketplaceSelect
|
||||||
|
onClear={() =>
|
||||||
|
form.setFieldValue("marketplace", null)
|
||||||
|
}
|
||||||
|
clearable
|
||||||
|
placeholder={"Выберите маркетплейс"}
|
||||||
|
{...form.getInputProps("marketplace")}
|
||||||
|
/>
|
||||||
|
<ClientSelectNew
|
||||||
|
onClear={() =>
|
||||||
|
form.setFieldValue("client", null)
|
||||||
|
}
|
||||||
|
clearable
|
||||||
|
searchable
|
||||||
|
placeholder={"Выберите клиента"}
|
||||||
|
{...form.getInputProps("client")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
</Flex>
|
</Flex>
|
||||||
<motion.div
|
</PageBlock>
|
||||||
key={displayMode}
|
<PageBlock
|
||||||
initial={{ opacity: 0 }}
|
style={{
|
||||||
animate={{ opacity: 1 }}
|
display: "flex",
|
||||||
transition={{ duration: 0.2 }}>
|
flexDirection: "column",
|
||||||
<div
|
flex: 1,
|
||||||
className={styles["top-panel"]}
|
height: "100%",
|
||||||
style={{
|
}}>
|
||||||
display:
|
{getBody()}
|
||||||
displayMode === DisplayMode.TABLE
|
</PageBlock>
|
||||||
? "flex"
|
<DealEditDrawer />
|
||||||
: "none",
|
<DealPrefillDrawer />
|
||||||
}}>
|
<PrefillDealsWithExcelDrawer />
|
||||||
<NumberInput
|
</PrefillDealsWithExcelContextProvider>
|
||||||
min={1}
|
|
||||||
placeholder={"Введите номер"}
|
|
||||||
{...form.getInputProps("id")}
|
|
||||||
hideControls
|
|
||||||
/>
|
|
||||||
<DealStatusSelect
|
|
||||||
onClear={() =>
|
|
||||||
form.setFieldValue("dealStatus", null)
|
|
||||||
}
|
|
||||||
clearable
|
|
||||||
placeholder={"Выберите статус "}
|
|
||||||
{...form.getInputProps("dealStatus")}
|
|
||||||
/>
|
|
||||||
<BaseMarketplaceSelect
|
|
||||||
onClear={() =>
|
|
||||||
form.setFieldValue("marketplace", null)
|
|
||||||
}
|
|
||||||
clearable
|
|
||||||
placeholder={"Выберите маркетплейс"}
|
|
||||||
{...form.getInputProps("marketplace")}
|
|
||||||
/>
|
|
||||||
<ClientSelectNew
|
|
||||||
onClear={() =>
|
|
||||||
form.setFieldValue("client", null)
|
|
||||||
}
|
|
||||||
clearable
|
|
||||||
searchable
|
|
||||||
placeholder={"Выберите клиента"}
|
|
||||||
{...form.getInputProps("client")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</Flex>
|
|
||||||
</PageBlock>
|
|
||||||
<PageBlock
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
flex: 1,
|
|
||||||
height: "100%",
|
|
||||||
}}>
|
|
||||||
{getBody()}
|
|
||||||
</PageBlock>
|
|
||||||
<DealEditDrawer />
|
|
||||||
<DealPrefillDrawer />
|
|
||||||
</PrefillDealContextProvider>
|
</PrefillDealContextProvider>
|
||||||
</DealPageContextProvider>
|
</DealPageContextProvider>
|
||||||
</PageBlock>
|
</PageBlock>
|
||||||
|
|||||||
8
src/types/UseExcelDropzone.tsx
Normal file
8
src/types/UseExcelDropzone.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
|
type UseExcelDropzone = {
|
||||||
|
isLoading: boolean;
|
||||||
|
setIsLoading: Dispatch<SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UseExcelDropzone;
|
||||||
Reference in New Issue
Block a user