feat: deal prefilling
This commit is contained in:
@@ -25,6 +25,7 @@ export type { BaseEnumListSchema } from './models/BaseEnumListSchema';
|
||||
export type { BaseEnumSchema } from './models/BaseEnumSchema';
|
||||
export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
|
||||
export type { BaseShippingWarehouseSchema } from './models/BaseShippingWarehouseSchema';
|
||||
export type { BillPaymentInfo } from './models/BillPaymentInfo';
|
||||
export type { BillPaymentStatus } from './models/BillPaymentStatus';
|
||||
export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest';
|
||||
export type { Body_upload_product_image } from './models/Body_upload_product_image';
|
||||
@@ -90,6 +91,8 @@ export type { DealDeleteServicesRequest } from './models/DealDeleteServicesReque
|
||||
export type { DealDeleteServicesResponse } from './models/DealDeleteServicesResponse';
|
||||
export type { DealGeneralInfoSchema } from './models/DealGeneralInfoSchema';
|
||||
export type { DealGetAllResponse } from './models/DealGetAllResponse';
|
||||
export type { DealPrefillRequest } from './models/DealPrefillRequest';
|
||||
export type { DealPrefillResponse } from './models/DealPrefillResponse';
|
||||
export type { DealProductAddKitRequest } from './models/DealProductAddKitRequest';
|
||||
export type { DealProductAddKitResponse } from './models/DealProductAddKitResponse';
|
||||
export type { DealProductSchema } from './models/DealProductSchema';
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { BillPaymentInfo } from './BillPaymentInfo';
|
||||
import type { BillPaymentStatus } from './BillPaymentStatus';
|
||||
import type { NotificationChannel } from './NotificationChannel';
|
||||
export type BillStatusUpdateRequest = {
|
||||
listenerTransactionId: number;
|
||||
channel: NotificationChannel;
|
||||
info: BillPaymentStatus;
|
||||
info: (BillPaymentInfo | BillPaymentStatus);
|
||||
};
|
||||
|
||||
|
||||
9
src/client/models/DealPrefillRequest.ts
Normal file
9
src/client/models/DealPrefillRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type DealPrefillRequest = {
|
||||
oldDealId: number;
|
||||
newDealId: number;
|
||||
};
|
||||
|
||||
@@ -28,6 +28,8 @@ import type { DealDeleteServiceResponse } from '../models/DealDeleteServiceRespo
|
||||
import type { DealDeleteServicesRequest } from '../models/DealDeleteServicesRequest';
|
||||
import type { DealDeleteServicesResponse } from '../models/DealDeleteServicesResponse';
|
||||
import type { DealGetAllResponse } from '../models/DealGetAllResponse';
|
||||
import type { DealPrefillRequest } from '../models/DealPrefillRequest';
|
||||
import type { DealPrefillResponse } from '../models/DealPrefillResponse';
|
||||
import type { DealProductAddKitRequest } from '../models/DealProductAddKitRequest';
|
||||
import type { DealProductAddKitResponse } from '../models/DealProductAddKitResponse';
|
||||
import type { DealQuickCreateRequest } from '../models/DealQuickCreateRequest';
|
||||
@@ -328,6 +330,26 @@ export class DealService {
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Post Prefill Deal
|
||||
* @returns DealPrefillResponse Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static prefillDeal({
|
||||
requestBody,
|
||||
}: {
|
||||
requestBody: DealPrefillRequest,
|
||||
}): CancelablePromise<DealPrefillResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/deal/prefill',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Services Add
|
||||
* @returns DealAddServicesResponse Successful Response
|
||||
|
||||
@@ -6,6 +6,7 @@ import CreateDealFrom from "../CreateDealForm/CreateDealFrom.tsx";
|
||||
import { DealService } from "../../../client";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { dateWithoutTimezone } from "../../../shared/lib/date.ts";
|
||||
import { useDealPageContext } from "../../../pages/LeadsPage/contexts/DealPageContext.tsx";
|
||||
|
||||
type Props = {
|
||||
onClick: () => void;
|
||||
@@ -14,6 +15,7 @@ const CreateDealButton: FC<Props> = () => {
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [isTransitionEnded, setIsTransitionEnded] = useState(true);
|
||||
const queryClient = useQueryClient();
|
||||
const { prefillDeal, setPrefillDeal } = useDealPageContext();
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -34,6 +36,7 @@ const CreateDealButton: FC<Props> = () => {
|
||||
<div style={styles}>
|
||||
<CreateDealFrom
|
||||
onCancel={() => {
|
||||
setPrefillDeal(undefined);
|
||||
setIsCreating(false);
|
||||
}}
|
||||
onSubmit={quickDeal => {
|
||||
@@ -41,10 +44,18 @@ const CreateDealButton: FC<Props> = () => {
|
||||
requestBody: {
|
||||
...quickDeal,
|
||||
acceptanceDate: dateWithoutTimezone(
|
||||
quickDeal.acceptanceDate
|
||||
quickDeal.acceptanceDate,
|
||||
),
|
||||
},
|
||||
}).then(async () => {
|
||||
}).then(async (result) => {
|
||||
if (prefillDeal) {
|
||||
DealService.prefillDeal({
|
||||
requestBody: {
|
||||
oldDealId: prefillDeal.id,
|
||||
newDealId: result.dealId,
|
||||
},
|
||||
});
|
||||
}
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ["getDealSummaries"],
|
||||
});
|
||||
|
||||
@@ -12,3 +12,8 @@
|
||||
display: flex;
|
||||
gap: rem(10);
|
||||
}
|
||||
|
||||
.button-prefill {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -8,12 +8,14 @@ import { DateTimePicker } from "@mantine/dates";
|
||||
import ShippingWarehouseAutocomplete from "../../Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
|
||||
import BaseMarketplaceSelect from "../../Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
|
||||
import ServicePriceCategorySelect from "../../Selects/ServicePriceCategorySelect/ServicePriceCategorySelect.tsx";
|
||||
import { useDealPageContext } from "../../../pages/LeadsPage/contexts/DealPageContext.tsx";
|
||||
|
||||
type Props = {
|
||||
onSubmit: (quickDeal: QuickDeal) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
const CreateDealFrom: FC<Props> = ({ onSubmit, onCancel }) => {
|
||||
const { prefillOnOpen, prefillDeal } = useDealPageContext();
|
||||
const form = useForm<QuickDeal>({
|
||||
initialValues: {
|
||||
name: "",
|
||||
@@ -29,6 +31,9 @@ const CreateDealFrom: FC<Props> = ({ onSubmit, onCancel }) => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const prefillButtonLabel = prefillDeal ? `Предзаполнено [ID: ${prefillDeal.id}]` : "Предзаполнить";
|
||||
|
||||
return (
|
||||
<form
|
||||
style={{ width: "100%" }}
|
||||
@@ -86,7 +91,14 @@ const CreateDealFrom: FC<Props> = ({ onSubmit, onCancel }) => {
|
||||
{...form.getInputProps("acceptanceDate")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles["button-prefill"]}>
|
||||
<Button
|
||||
style={{ whiteSpace: "wrap" }}
|
||||
variant={"outline"}
|
||||
onClick={() => prefillOnOpen()}>
|
||||
{prefillButtonLabel}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles["buttons"]}>
|
||||
<Button type={"submit"}>Добавить</Button>
|
||||
<Button
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import { createContext, FC, useContext, useState } from "react";
|
||||
import { DealSchema } from "../../../client";
|
||||
import { createContext, Dispatch, FC, SetStateAction, useContext, useState } from "react";
|
||||
import { DealSchema, DealService } from "../../../client";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
|
||||
type DealPageContextState = {
|
||||
selectedDeal?: DealSchema;
|
||||
setSelectedDeal: (deal: DealSchema | undefined) => void;
|
||||
|
||||
prefillOpened: boolean;
|
||||
prefillOnClose: () => void;
|
||||
prefillOnOpen: () => void;
|
||||
|
||||
selectedPrefillDeal?: DealSchema;
|
||||
selectPrefillDeal: (dealId: number) => void;
|
||||
prefillDeal?: DealSchema;
|
||||
setPrefillDeal: Dispatch<SetStateAction<DealSchema | undefined>>;
|
||||
};
|
||||
|
||||
const DealPageContext = createContext<DealPageContextState | undefined>(
|
||||
@@ -13,7 +23,35 @@ const useDealPageContextState = () => {
|
||||
const [selectedDeal, setSelectedDeal] = useState<DealSchema | undefined>(
|
||||
undefined
|
||||
);
|
||||
return { selectedDeal, setSelectedDeal };
|
||||
const [selectedPrefillDeal, setSelectedPrefillDeal] = useState<DealSchema | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [prefillDeal, setPrefillDeal] = useState<DealSchema | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [prefillOpened, { open, close }] = useDisclosure(false);
|
||||
const prefillOnClose = close;
|
||||
const prefillOnOpen = open;
|
||||
|
||||
const selectPrefillDeal = (dealId: number) => {
|
||||
DealService.getDealById({ dealId }).then(deal => {
|
||||
setSelectedPrefillDeal(deal);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
selectedDeal,
|
||||
setSelectedDeal,
|
||||
|
||||
prefillOpened,
|
||||
prefillOnClose,
|
||||
prefillOnOpen,
|
||||
|
||||
selectedPrefillDeal,
|
||||
selectPrefillDeal,
|
||||
prefillDeal,
|
||||
setPrefillDeal,
|
||||
};
|
||||
};
|
||||
|
||||
type DealPageContextProviderProps = {
|
||||
|
||||
@@ -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,82 @@
|
||||
import { FC, useEffect } from "react";
|
||||
import { Button, Drawer, Flex, rem, TextInput } from "@mantine/core";
|
||||
import { useDealPageContext } from "../../contexts/DealPageContext.tsx";
|
||||
import DealsTable from "./components/tables/DealsTable/DealsTable.tsx";
|
||||
import Preview from "./components/Preview/Preview.tsx";
|
||||
import styles from "./DealPrefillDrawer.module.css";
|
||||
import BaseMarketplaceSelect from "../../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
|
||||
import usePrefillDeal from "./hooks/usePrefillDeal.tsx";
|
||||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||
|
||||
const DealPrefillDrawer: FC = () => {
|
||||
const { prefillOpened, prefillOnClose, selectedPrefillDeal, setPrefillDeal, prefillDeal } = useDealPageContext();
|
||||
const { data, form } = usePrefillDeal();
|
||||
|
||||
useEffect(() => {
|
||||
if (prefillOpened) return;
|
||||
}, [prefillOpened]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
size={"calc(77vw)"}
|
||||
position={"right"}
|
||||
onClose={prefillOnClose}
|
||||
removeScrollProps={{ allowPinchZoom: true }}
|
||||
withCloseButton={false}
|
||||
opened={prefillOpened}
|
||||
styles={{
|
||||
body: {
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: rem(20),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className={styles["deal-container"]}>
|
||||
<div className={styles["deal-container-wrapper"]}>
|
||||
<div className={styles["top-panel"]}>
|
||||
<TextInput
|
||||
placeholder={"Введите название / id"}
|
||||
{...form.getInputProps("idOrName")}
|
||||
/>
|
||||
<BaseMarketplaceSelect
|
||||
onClear={() =>
|
||||
form.setFieldValue("marketplace", null)
|
||||
}
|
||||
clearable
|
||||
placeholder={"Выберите маркетплейс"}
|
||||
{...form.getInputProps("marketplace")}
|
||||
/>
|
||||
</div>
|
||||
<DealsTable items={data} />
|
||||
<Flex direction={"row"} gap="sm">
|
||||
<Button mt={10} w={"100%"} onClick={() => {
|
||||
if (!selectedPrefillDeal) {
|
||||
notifications.error({ message: "Сделка не выбрана." });
|
||||
return;
|
||||
}
|
||||
setPrefillDeal(selectedPrefillDeal);
|
||||
prefillOnClose();
|
||||
}}>
|
||||
Предзаполнить
|
||||
</Button>
|
||||
{
|
||||
prefillDeal &&
|
||||
<Button mt={10} w={"100%"} variant={"outline"} onClick={() => {
|
||||
setPrefillDeal(undefined);
|
||||
notifications.success({ message: "Предзаполнение отменено." });
|
||||
prefillOnClose();
|
||||
}}>
|
||||
Отменить предзаполнение
|
||||
</Button>
|
||||
}
|
||||
</Flex>
|
||||
</div>
|
||||
</div>
|
||||
<Preview />
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealPrefillDrawer;
|
||||
@@ -0,0 +1,34 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: rem(10);
|
||||
max-height: 95vh;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.products-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: rem(10);
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { FC } from "react";
|
||||
import styles from "./Preview.module.css";
|
||||
import { ScrollArea, Skeleton, Title } from "@mantine/core";
|
||||
import { useDealPageContext } from "../../../../contexts/DealPageContext.tsx";
|
||||
import DealServicesTable from "../tables/DealServicesTable/DealServicesTable.tsx";
|
||||
import ProductPreview from "../ProductPreview/ProductPreview.tsx";
|
||||
|
||||
const Preview: FC = () => {
|
||||
const { selectedPrefillDeal } = useDealPageContext();
|
||||
|
||||
const getTotalPrice = () => {
|
||||
if (!selectedPrefillDeal) return 0;
|
||||
const productServicesPrice = selectedPrefillDeal.products.reduce(
|
||||
(acc, row) =>
|
||||
acc +
|
||||
row.services.reduce(
|
||||
(acc2, row2) => acc2 + row2.price * row.quantity,
|
||||
0,
|
||||
),
|
||||
0,
|
||||
);
|
||||
const dealServicesPrice = selectedPrefillDeal.services.reduce(
|
||||
(acc, row) => acc + row.price * row.quantity,
|
||||
0,
|
||||
);
|
||||
return dealServicesPrice + productServicesPrice;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles["container"]}>
|
||||
<ScrollArea offsetScrollbars={"y"} w={"100%"}>
|
||||
<Skeleton visible={!selectedPrefillDeal}>
|
||||
<div className={styles["deal-container-wrapper"]}>
|
||||
<Title order={4} mb={18}>
|
||||
Общая стоимость всех услуг:{" "}
|
||||
{getTotalPrice().toLocaleString("ru")}₽
|
||||
</Title>
|
||||
|
||||
<DealServicesTable items={selectedPrefillDeal?.services} />
|
||||
|
||||
<div className={styles["products-list"]}>
|
||||
{selectedPrefillDeal?.products.map(product => (
|
||||
<ProductPreview
|
||||
key={product.product.id}
|
||||
product={product}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Preview;
|
||||
@@ -0,0 +1,33 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: rem(20);
|
||||
margin-bottom: rem(10);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
max-height: rem(250);
|
||||
max-width: rem(250);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.services-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: rem(10);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.data-container {
|
||||
max-width: rem(250);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: rem(10);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.attributes-container {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { FC } from "react";
|
||||
import { DealProductSchema, ProductSchema } from "../../../../../../client";
|
||||
import { Image, rem, Text, Title } from "@mantine/core";
|
||||
import { isNil } from "lodash";
|
||||
import { ProductFieldNames } from "../../../../tabs/ProductAndServiceTab/components/ProductView/ProductView.tsx";
|
||||
import ProductServicesTable from "../tables/ProductServicesTable/ProductServicesTable.tsx";
|
||||
import styles from "./ProductPreview.module.css";
|
||||
|
||||
type Props = {
|
||||
product: DealProductSchema;
|
||||
};
|
||||
|
||||
const ProductPreview: FC<Props> = ({ product }) => {
|
||||
return (
|
||||
<div className={styles["container"]}>
|
||||
<div className={styles["data-container"]}>
|
||||
<div className={styles["image-container"]}>
|
||||
<Image
|
||||
flex={1}
|
||||
radius={rem(10)}
|
||||
fit={"cover"}
|
||||
src={product.product.imageUrl}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["attributes-container"]}>
|
||||
<Title order={3}>{product.product.name}</Title>
|
||||
|
||||
{Object.entries(product.product).map(([key, value]) => {
|
||||
const fieldName =
|
||||
ProductFieldNames[key as keyof ProductSchema];
|
||||
if (!fieldName || isNil(value) || value === "") return;
|
||||
return (
|
||||
<Text key={fieldName}>
|
||||
{fieldName}: {value.toString()}{" "}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
<Text>
|
||||
Штрихкоды: {product.product.barcodes.join(", ")}
|
||||
</Text>
|
||||
<Text>Количество товара: {product.quantity}</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles["services-container"]}>
|
||||
<ProductServicesTable
|
||||
items={product.services}
|
||||
quantity={product.quantity}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductPreview;
|
||||
@@ -0,0 +1,59 @@
|
||||
import { FC } from "react";
|
||||
import { Flex, rem, Title } from "@mantine/core";
|
||||
import { DealServiceSchema, DealSummary } from "../../../../../../../client";
|
||||
import useDealServicesTableColumns from "./columns.tsx";
|
||||
import { BaseTable } from "../../../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
|
||||
type Props = {
|
||||
items?: DealServiceSchema[];
|
||||
};
|
||||
|
||||
const DealServicesTable: FC<Props> = ({ items }) => {
|
||||
const columns = useDealServicesTableColumns();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
h={"100%"}
|
||||
mb={10}>
|
||||
<Flex
|
||||
h={"100%"}
|
||||
direction={"column"}>
|
||||
{
|
||||
items && items.length > 0 &&
|
||||
<>
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
|
||||
restProps={
|
||||
{
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
enablePagination: false,
|
||||
enableBottomToolbar: false,
|
||||
} as MRT_TableOptions<DealSummary>
|
||||
}
|
||||
/>
|
||||
|
||||
<Title
|
||||
style={{ textAlign: "end" }}
|
||||
mt={rem(10)}
|
||||
mb={rem(6)}
|
||||
order={4}>
|
||||
Итог:{" "}
|
||||
{items.reduce(
|
||||
(acc, item) => acc + item.price * item.quantity,
|
||||
0,
|
||||
)}
|
||||
₽
|
||||
</Title>
|
||||
</>
|
||||
}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
export default DealServicesTable;
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { DealServiceSchema } from "../../../../../../../client";
|
||||
|
||||
const useDealServicesTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<DealServiceSchema>[]>(
|
||||
() => [
|
||||
{
|
||||
header: "Название",
|
||||
accessorKey: "service.name",
|
||||
size: 450,
|
||||
},
|
||||
{
|
||||
header: "Количество",
|
||||
accessorKey: "quantity",
|
||||
size: 50,
|
||||
Cell: ({ cell }) => cell.getValue() + " шт.",
|
||||
},
|
||||
{
|
||||
accessorKey: "price",
|
||||
header: "Цена",
|
||||
size: 50,
|
||||
Cell: ({ cell }) => cell.getValue() + " ₽",
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
export default useDealServicesTableColumns;
|
||||
@@ -0,0 +1,45 @@
|
||||
import { FC, useEffect } from "react";
|
||||
import useDealsTableColumns from "./columns.tsx";
|
||||
import { DealSummary } from "../../../../../../../client";
|
||||
import { useDealPageContext } from "../../../../../contexts/DealPageContext.tsx";
|
||||
import { BaseTable } from "../../../../../../../components/BaseTable/BaseTable.tsx";
|
||||
|
||||
type Props = {
|
||||
items: DealSummary[];
|
||||
};
|
||||
|
||||
const DealsTable: FC<Props> = ({ items }) => {
|
||||
const { selectPrefillDeal } = useDealPageContext();
|
||||
const columns = useDealsTableColumns();
|
||||
const defaultSorting = [{ id: "createdAt", desc: false }];
|
||||
|
||||
useEffect(() => {
|
||||
if (items.length < 1) return;
|
||||
selectPrefillDeal(items[0].id);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
|
||||
restProps={
|
||||
{
|
||||
enableSorting: true,
|
||||
enableColumnActions: false,
|
||||
enablePagination: true,
|
||||
enableBottomToolbar: true,
|
||||
paginationDisplayMode: "pages",
|
||||
initialState: {
|
||||
sorting: defaultSorting,
|
||||
},
|
||||
mantinePaginationProps: {
|
||||
showRowsPerPage: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealsTable;
|
||||
@@ -0,0 +1,72 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { ActionIcon, Image, Radio } from "@mantine/core";
|
||||
import { DealSummary } from "../../../../../../../client";
|
||||
import { useDealPageContext } from "../../../../../contexts/DealPageContext.tsx";
|
||||
|
||||
const useDealsTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<DealSummary>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "select",
|
||||
header: "",
|
||||
size: 5,
|
||||
enableSorting: false,
|
||||
Cell: ({ row }) => {
|
||||
const { selectPrefillDeal, selectedPrefillDeal } = useDealPageContext();
|
||||
const checked = row.original.id === selectedPrefillDeal?.id;
|
||||
return (
|
||||
<Radio
|
||||
checked={checked}
|
||||
onChange={() => {
|
||||
selectPrefillDeal(row.original.id);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "ID",
|
||||
size: 20,
|
||||
},
|
||||
{
|
||||
accessorKey: "clientName",
|
||||
header: "Клиент",
|
||||
size: 60,
|
||||
enableSorting: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Название",
|
||||
enableSorting: false,
|
||||
size: 60,
|
||||
},
|
||||
{
|
||||
header: "Дата создания",
|
||||
accessorKey: "createdAt",
|
||||
size: 10,
|
||||
Cell: ({ row }) =>
|
||||
new Date(row.original.createdAt).toLocaleString("ru-RU").substring(0, 17),
|
||||
enableSorting: true,
|
||||
sortingFn: (rowA, rowB) =>
|
||||
new Date(rowB.original.createdAt).getTime() -
|
||||
new Date(rowA.original.createdAt).getTime(),
|
||||
},
|
||||
{
|
||||
header: "МП",
|
||||
size: 5,
|
||||
Cell: ({ row }) => (
|
||||
<ActionIcon variant={"transparent"}>
|
||||
<Image
|
||||
src={row.original.baseMarketplace?.iconUrl || ""}
|
||||
/>
|
||||
</ActionIcon>
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export default useDealsTableColumns;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { FC } from "react";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import { DealProductServiceSchema } from "../../../../../../../client";
|
||||
import { BaseTable } from "../../../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import useProductServicesTableColumns from "./columns.tsx";
|
||||
|
||||
|
||||
type Props = {
|
||||
items: DealProductServiceSchema[];
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
const ProductServicesTable: FC<Props> = ({ items, quantity }) => {
|
||||
const columns = useProductServicesTableColumns({ data: items, quantity });
|
||||
|
||||
return (
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
restProps={
|
||||
{
|
||||
enableColumnActions: false,
|
||||
enableSorting: false,
|
||||
enableRowActions: false,
|
||||
enableBottomToolbar: false,
|
||||
} as MRT_TableOptions<DealProductServiceSchema>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default ProductServicesTable;
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { DealProductServiceSchema } from "../../../../../../../client";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../../../../../../redux/store.ts";
|
||||
|
||||
type Props = {
|
||||
data: DealProductServiceSchema[];
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
const useProductServicesTableColumns = (props: Props) => {
|
||||
const { data, quantity } = props;
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
const totalPrice = useMemo(
|
||||
() => data.reduce((acc, row) => acc + row.price * quantity, 0),
|
||||
[data, quantity]
|
||||
);
|
||||
const hideGuestColumns = ["service.cost"];
|
||||
return useMemo<MRT_ColumnDef<DealProductServiceSchema>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "service.name",
|
||||
header: "Услуга",
|
||||
},
|
||||
{
|
||||
accessorKey: "price",
|
||||
header: "Цена",
|
||||
size: 5,
|
||||
Cell: ({ cell }) => cell.getValue() + " ₽",
|
||||
Footer: () => <>Итог: {totalPrice.toLocaleString("ru")}₽</>,
|
||||
},
|
||||
],
|
||||
[totalPrice]
|
||||
).filter(
|
||||
columnDef =>
|
||||
!(
|
||||
hideGuestColumns.includes(columnDef.accessorKey || "") &&
|
||||
authState.isGuest
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default useProductServicesTableColumns;
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useEffect, useState } from "react";
|
||||
import { BaseMarketplaceSchema } from "../../../../../client";
|
||||
import { useDealSummariesFull } from "../../../hooks/useDealSummaries.tsx";
|
||||
|
||||
type State = {
|
||||
idOrName: string | null;
|
||||
marketplace: BaseMarketplaceSchema | null;
|
||||
};
|
||||
|
||||
const usePrefillDeal = () => {
|
||||
const { objects } = useDealSummariesFull();
|
||||
const form = useForm<State>({
|
||||
initialValues: {
|
||||
idOrName: null,
|
||||
marketplace: null,
|
||||
},
|
||||
});
|
||||
const [data, setData] = useState(objects);
|
||||
|
||||
const applyFilters = () => {
|
||||
let result = objects;
|
||||
if (form.values.idOrName) {
|
||||
if (isNaN(parseInt(form.values.idOrName))) {
|
||||
const name: string = form.values.idOrName.toLowerCase();
|
||||
result = result.filter(
|
||||
obj => obj.name.toLowerCase().search(name) !== -1,
|
||||
);
|
||||
}
|
||||
else {
|
||||
const id = parseInt(form.values.idOrName);
|
||||
result = result.filter(
|
||||
obj => obj.id === id,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (form.values.marketplace) {
|
||||
result = result.filter(
|
||||
obj => obj.baseMarketplace?.key === form.values.marketplace?.key,
|
||||
);
|
||||
}
|
||||
setData(result);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
applyFilters();
|
||||
}, [form.values, objects]);
|
||||
|
||||
return { data, form };
|
||||
};
|
||||
|
||||
export default usePrefillDeal;
|
||||
@@ -20,6 +20,7 @@ import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientS
|
||||
import DealsTable from "../../DealsPage/components/DealsTable/DealsTable.tsx";
|
||||
import { motion } from "framer-motion";
|
||||
import { dateWithoutTimezone } from "../../../shared/lib/date.ts";
|
||||
import DealPrefillDrawer from "../drawers/DealPrefillDrawer/DealPrefillDrawer.tsx";
|
||||
|
||||
enum DisplayMode {
|
||||
BOARD,
|
||||
@@ -410,6 +411,7 @@ export const LeadsPage: FC = () => {
|
||||
{getBody()}
|
||||
</PageBlock>
|
||||
<DealEditDrawer />
|
||||
<DealPrefillDrawer />
|
||||
</DealPageContextProvider>
|
||||
</PageBlock>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user