feat: profit chart in statistics
This commit is contained in:
		@@ -16,8 +16,9 @@
 | 
				
			|||||||
        "@fortawesome/free-solid-svg-icons": "^6.6.0",
 | 
					        "@fortawesome/free-solid-svg-icons": "^6.6.0",
 | 
				
			||||||
        "@fortawesome/react-fontawesome": "^0.2.2",
 | 
					        "@fortawesome/react-fontawesome": "^0.2.2",
 | 
				
			||||||
        "@hello-pangea/dnd": "^17.0.0",
 | 
					        "@hello-pangea/dnd": "^17.0.0",
 | 
				
			||||||
 | 
					        "@mantine/charts": "^7.13.5",
 | 
				
			||||||
        "@mantine/core": "^7.11.2",
 | 
					        "@mantine/core": "^7.11.2",
 | 
				
			||||||
        "@mantine/dates": "^7.11.2",
 | 
					        "@mantine/dates": "^7.13.5",
 | 
				
			||||||
        "@mantine/dropzone": "^7.11.2",
 | 
					        "@mantine/dropzone": "^7.11.2",
 | 
				
			||||||
        "@mantine/form": "^7.11.2",
 | 
					        "@mantine/form": "^7.11.2",
 | 
				
			||||||
        "@mantine/hooks": "^7.11.2",
 | 
					        "@mantine/hooks": "^7.11.2",
 | 
				
			||||||
@@ -51,6 +52,7 @@
 | 
				
			|||||||
        "react-redux": "^9.1.2",
 | 
					        "react-redux": "^9.1.2",
 | 
				
			||||||
        "react-to-print": "^2.15.1",
 | 
					        "react-to-print": "^2.15.1",
 | 
				
			||||||
        "reactflow": "^11.11.4",
 | 
					        "reactflow": "^11.11.4",
 | 
				
			||||||
 | 
					        "recharts": "^2.13.3",
 | 
				
			||||||
        "zod": "^3.23.8"
 | 
					        "zod": "^3.23.8"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "devDependencies": {
 | 
					    "devDependencies": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -166,6 +166,8 @@ export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfR
 | 
				
			|||||||
export type { GetProductBarcodePdfResponse } from './models/GetProductBarcodePdfResponse';
 | 
					export type { GetProductBarcodePdfResponse } from './models/GetProductBarcodePdfResponse';
 | 
				
			||||||
export type { GetProductBarcodeRequest } from './models/GetProductBarcodeRequest';
 | 
					export type { GetProductBarcodeRequest } from './models/GetProductBarcodeRequest';
 | 
				
			||||||
export type { GetProductBarcodeResponse } from './models/GetProductBarcodeResponse';
 | 
					export type { GetProductBarcodeResponse } from './models/GetProductBarcodeResponse';
 | 
				
			||||||
 | 
					export type { GetProfitDataRequest } from './models/GetProfitDataRequest';
 | 
				
			||||||
 | 
					export type { GetProfitDataResponse } from './models/GetProfitDataResponse';
 | 
				
			||||||
export type { GetServiceKitSchema } from './models/GetServiceKitSchema';
 | 
					export type { GetServiceKitSchema } from './models/GetServiceKitSchema';
 | 
				
			||||||
export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
 | 
					export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
 | 
				
			||||||
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
 | 
					export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
 | 
				
			||||||
@@ -199,6 +201,7 @@ export type { ProductUpdateRequest } from './models/ProductUpdateRequest';
 | 
				
			|||||||
export type { ProductUpdateResponse } from './models/ProductUpdateResponse';
 | 
					export type { ProductUpdateResponse } from './models/ProductUpdateResponse';
 | 
				
			||||||
export type { ProductUploadBarcodeImageResponse } from './models/ProductUploadBarcodeImageResponse';
 | 
					export type { ProductUploadBarcodeImageResponse } from './models/ProductUploadBarcodeImageResponse';
 | 
				
			||||||
export type { ProductUploadImageResponse } from './models/ProductUploadImageResponse';
 | 
					export type { ProductUploadImageResponse } from './models/ProductUploadImageResponse';
 | 
				
			||||||
 | 
					export type { ProfitDataItem } from './models/ProfitDataItem';
 | 
				
			||||||
export type { RoleSchema } from './models/RoleSchema';
 | 
					export type { RoleSchema } from './models/RoleSchema';
 | 
				
			||||||
export type { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema';
 | 
					export type { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema';
 | 
				
			||||||
export type { ServiceCategoryReorderRequest } from './models/ServiceCategoryReorderRequest';
 | 
					export type { ServiceCategoryReorderRequest } from './models/ServiceCategoryReorderRequest';
 | 
				
			||||||
@@ -260,6 +263,7 @@ export { ProductService } from './services/ProductService';
 | 
				
			|||||||
export { RoleService } from './services/RoleService';
 | 
					export { RoleService } from './services/RoleService';
 | 
				
			||||||
export { ServiceService } from './services/ServiceService';
 | 
					export { ServiceService } from './services/ServiceService';
 | 
				
			||||||
export { ShippingWarehouseService } from './services/ShippingWarehouseService';
 | 
					export { ShippingWarehouseService } from './services/ShippingWarehouseService';
 | 
				
			||||||
 | 
					export { StatisticsService } from './services/StatisticsService';
 | 
				
			||||||
export { TaskService } from './services/TaskService';
 | 
					export { TaskService } from './services/TaskService';
 | 
				
			||||||
export { TimeTrackingService } from './services/TimeTrackingService';
 | 
					export { TimeTrackingService } from './services/TimeTrackingService';
 | 
				
			||||||
export { UserService } from './services/UserService';
 | 
					export { UserService } from './services/UserService';
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								src/client/models/GetProfitDataRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/client/models/GetProfitDataRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					export type GetProfitDataRequest = {
 | 
				
			||||||
 | 
					    dateRange: any[];
 | 
				
			||||||
 | 
					    clientId: number;
 | 
				
			||||||
 | 
					    baseMarketplaceKey: string;
 | 
				
			||||||
 | 
					    dealStatusId: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/client/models/GetProfitDataResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/GetProfitDataResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					import type { ProfitDataItem } from './ProfitDataItem';
 | 
				
			||||||
 | 
					export type GetProfitDataResponse = {
 | 
				
			||||||
 | 
					    data: Array<ProfitDataItem>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								src/client/models/ProfitDataItem.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/client/models/ProfitDataItem.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					export type ProfitDataItem = {
 | 
				
			||||||
 | 
					    date: string;
 | 
				
			||||||
 | 
					    revenue: number;
 | 
				
			||||||
 | 
					    profit: number;
 | 
				
			||||||
 | 
					    dealsCount: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/client/services/StatisticsService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/client/services/StatisticsService.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					import type { GetProfitDataRequest } from '../models/GetProfitDataRequest';
 | 
				
			||||||
 | 
					import type { GetProfitDataResponse } from '../models/GetProfitDataResponse';
 | 
				
			||||||
 | 
					import type { CancelablePromise } from '../core/CancelablePromise';
 | 
				
			||||||
 | 
					import { OpenAPI } from '../core/OpenAPI';
 | 
				
			||||||
 | 
					import { request as __request } from '../core/request';
 | 
				
			||||||
 | 
					export class StatisticsService {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get Profit Data
 | 
				
			||||||
 | 
					     * @returns GetProfitDataResponse Successful Response
 | 
				
			||||||
 | 
					     * @throws ApiError
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static getProfitData({
 | 
				
			||||||
 | 
					        requestBody,
 | 
				
			||||||
 | 
					    }: {
 | 
				
			||||||
 | 
					        requestBody: GetProfitDataRequest,
 | 
				
			||||||
 | 
					    }): CancelablePromise<GetProfitDataResponse> {
 | 
				
			||||||
 | 
					        return __request(OpenAPI, {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            url: '/statistics/get-profit-data',
 | 
				
			||||||
 | 
					            body: requestBody,
 | 
				
			||||||
 | 
					            mediaType: 'application/json',
 | 
				
			||||||
 | 
					            errors: {
 | 
				
			||||||
 | 
					                422: `Validation Error`,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,7 +4,7 @@ import {
 | 
				
			|||||||
    IconBarcode,
 | 
					    IconBarcode,
 | 
				
			||||||
    IconBox,
 | 
					    IconBox,
 | 
				
			||||||
    IconBuildingWarehouse,
 | 
					    IconBuildingWarehouse,
 | 
				
			||||||
    IconCash,
 | 
					    IconCash, IconChartDots,
 | 
				
			||||||
    IconDashboard,
 | 
					    IconDashboard,
 | 
				
			||||||
    IconFileBarcode,
 | 
					    IconFileBarcode,
 | 
				
			||||||
    IconHome2,
 | 
					    IconHome2,
 | 
				
			||||||
@@ -93,6 +93,11 @@ const mockdata = [
 | 
				
			|||||||
        label: "Маркетплейсы",
 | 
					        label: "Маркетплейсы",
 | 
				
			||||||
        href: "/marketplaces",
 | 
					        href: "/marketplaces",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        icon: IconChartDots,
 | 
				
			||||||
 | 
					        label: "Статистика",
 | 
				
			||||||
 | 
					        href: "/statistics",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Navbar() {
 | 
					export function Navbar() {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										90
									
								
								src/pages/StatisticsPage/components/Filters/Filters.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/pages/StatisticsPage/components/Filters/Filters.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					import PageBlock from "../../../../components/PageBlock/PageBlock.tsx";
 | 
				
			||||||
 | 
					import { DatePickerInput, DatePickerInputProps } from "@mantine/dates";
 | 
				
			||||||
 | 
					import { Group, Text } from "@mantine/core";
 | 
				
			||||||
 | 
					import ClientSelectNew from "../../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
 | 
				
			||||||
 | 
					import { BaseMarketplaceSchema, ClientSchema } from "../../../../client";
 | 
				
			||||||
 | 
					import { ObjectSelectProps } from "../../../../components/ObjectSelect/ObjectSelect.tsx";
 | 
				
			||||||
 | 
					import BaseMarketplaceSelect from "../../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
 | 
				
			||||||
 | 
					import DealStatusSelect from "../../../DealsPage/components/DealStatusSelect/DealStatusSelect.tsx";
 | 
				
			||||||
 | 
					import { DealStatusType } from "../../../../shared/enums/DealStatus.ts";
 | 
				
			||||||
 | 
					import { StatisticsTableSegmentControl } from "../StatisticsTableSegmentControl/StatisticsTableSegmentControl.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FiltersProps = {
 | 
				
			||||||
 | 
					    datePickerProps?: DatePickerInputProps<"range">;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clientSelectProps?: Omit<ObjectSelectProps<ClientSchema>, "data">;
 | 
				
			||||||
 | 
					    onClientClear?: () => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    baseMarketplaceSelectProps?: Omit<
 | 
				
			||||||
 | 
					        ObjectSelectProps<BaseMarketplaceSchema>,
 | 
				
			||||||
 | 
					        "data" | "getValueFn" | "getLabelFn"
 | 
				
			||||||
 | 
					    >;
 | 
				
			||||||
 | 
					    onBaseMarketplaceClear?: () => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dealStatusSelectProps?: Omit<ObjectSelectProps<DealStatusType>, "data">;
 | 
				
			||||||
 | 
					    onDealStatusClear?: () => void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Filters = (props: FiltersProps) => {
 | 
				
			||||||
 | 
					    const {
 | 
				
			||||||
 | 
					        datePickerProps,
 | 
				
			||||||
 | 
					        clientSelectProps,
 | 
				
			||||||
 | 
					        onClientClear,
 | 
				
			||||||
 | 
					        baseMarketplaceSelectProps,
 | 
				
			||||||
 | 
					        onBaseMarketplaceClear,
 | 
				
			||||||
 | 
					        dealStatusSelectProps,
 | 
				
			||||||
 | 
					        onDealStatusClear,
 | 
				
			||||||
 | 
					    } = props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <PageBlock>
 | 
				
			||||||
 | 
					            <Group>
 | 
				
			||||||
 | 
					                {datePickerProps &&
 | 
				
			||||||
 | 
					                    <DatePickerInput
 | 
				
			||||||
 | 
					                        {...datePickerProps}
 | 
				
			||||||
 | 
					                        type="range"
 | 
				
			||||||
 | 
					                        placeholder="Выберите даты"
 | 
				
			||||||
 | 
					                        maxDate={new Date()}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                {dealStatusSelectProps &&
 | 
				
			||||||
 | 
					                    <DealStatusSelect
 | 
				
			||||||
 | 
					                        {...dealStatusSelectProps}
 | 
				
			||||||
 | 
					                        onClear={onDealStatusClear}
 | 
				
			||||||
 | 
					                        clearable
 | 
				
			||||||
 | 
					                        placeholder={"Выберите статус"}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                {clientSelectProps &&
 | 
				
			||||||
 | 
					                    <ClientSelectNew
 | 
				
			||||||
 | 
					                        {...clientSelectProps}
 | 
				
			||||||
 | 
					                        onClear={onClientClear}
 | 
				
			||||||
 | 
					                        clearable
 | 
				
			||||||
 | 
					                        searchable
 | 
				
			||||||
 | 
					                        placeholder={"Выберите клиента"}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                {baseMarketplaceSelectProps &&
 | 
				
			||||||
 | 
					                    <BaseMarketplaceSelect
 | 
				
			||||||
 | 
					                        {...baseMarketplaceSelectProps}
 | 
				
			||||||
 | 
					                        onClear={onBaseMarketplaceClear}
 | 
				
			||||||
 | 
					                        clearable
 | 
				
			||||||
 | 
					                        placeholder={"Выберите маркетплейс"}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    <>
 | 
				
			||||||
 | 
					                        <Text>
 | 
				
			||||||
 | 
					                            Группировать:
 | 
				
			||||||
 | 
					                        </Text>
 | 
				
			||||||
 | 
					                        <StatisticsTableSegmentControl
 | 
				
			||||||
 | 
					                            value={groupTableBy.toString()}
 | 
				
			||||||
 | 
					                            onChange={event => setGroupTableBy(parseInt(event))}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    </>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            </Group>
 | 
				
			||||||
 | 
					        </PageBlock>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					import { useProfitTableColumns } from "./columns.tsx";
 | 
				
			||||||
 | 
					import { ProfitDataItem } from "../../../../client";
 | 
				
			||||||
 | 
					import PageBlock from "../../../../components/PageBlock/PageBlock.tsx";
 | 
				
			||||||
 | 
					import { GroupStatisticsTable } from "../StatisticsTableSegmentControl/StatisticsTableSegmentControl.tsx";
 | 
				
			||||||
 | 
					import styles from "../../ui/StatisticsPage.module.css";
 | 
				
			||||||
 | 
					import { MantineReactTable, useMantineReactTable } from "mantine-react-table";
 | 
				
			||||||
 | 
					import { MRT_Localization_RU } from "mantine-react-table/locales/ru/index.cjs";
 | 
				
			||||||
 | 
					import { Filters } from "../Filters/Filters.tsx";
 | 
				
			||||||
 | 
					import { useForm } from "@mantine/form";
 | 
				
			||||||
 | 
					import { getDefaultDates } from "../../utils/dates.ts";
 | 
				
			||||||
 | 
					import { TableFormFilters } from "../../types/TableFormFilters.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = {
 | 
				
			||||||
 | 
					    data: ProfitDataItem[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProfitTable = ({ data }: Props) => {
 | 
				
			||||||
 | 
					    const form = useForm<TableFormFilters>({
 | 
				
			||||||
 | 
					        mode: "controlled",
 | 
				
			||||||
 | 
					        initialValues: {
 | 
				
			||||||
 | 
					            dateRange: getDefaultDates(),
 | 
				
			||||||
 | 
					            groupTableBy: GroupStatisticsTable.BY_DATES,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const columns = useProfitTableColumns();
 | 
				
			||||||
 | 
					    const defaultSorting = [{ id: "date", desc: true }];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const table = useMantineReactTable({
 | 
				
			||||||
 | 
					        localization: MRT_Localization_RU,
 | 
				
			||||||
 | 
					        enablePagination: true,
 | 
				
			||||||
 | 
					        data,
 | 
				
			||||||
 | 
					        columns,
 | 
				
			||||||
 | 
					        enableTopToolbar: false,
 | 
				
			||||||
 | 
					        enableBottomToolbar: true,
 | 
				
			||||||
 | 
					        enableSorting: true,
 | 
				
			||||||
 | 
					        initialState: {
 | 
				
			||||||
 | 
					            sorting: defaultSorting,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className={styles["container"]}>
 | 
				
			||||||
 | 
					            <PageBlock>
 | 
				
			||||||
 | 
					                <Filters />
 | 
				
			||||||
 | 
					            </PageBlock>
 | 
				
			||||||
 | 
					            <PageBlock>
 | 
				
			||||||
 | 
					                <MantineReactTable table={table} />
 | 
				
			||||||
 | 
					            </PageBlock>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/pages/StatisticsPage/components/ProfitTable/columns.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/pages/StatisticsPage/components/ProfitTable/columns.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					import { useMemo } from "react";
 | 
				
			||||||
 | 
					import { MRT_ColumnDef } from "mantine-react-table";
 | 
				
			||||||
 | 
					import { ProfitTableRow } from "../../types/ProfitTableRow.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useProfitTableColumns = () => {
 | 
				
			||||||
 | 
					    return useMemo<MRT_ColumnDef<ProfitTableRow>[]>(
 | 
				
			||||||
 | 
					        () => [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                accessorKey: "date",
 | 
				
			||||||
 | 
					                header: "Дата",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                accessorKey: "dealsCount",
 | 
				
			||||||
 | 
					                header: "Кол-во сделок"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                accessorKey: "profit",
 | 
				
			||||||
 | 
					                header: "Прибыль",
 | 
				
			||||||
 | 
					                Cell: ({ row }) =>
 | 
				
			||||||
 | 
					                    row.original.profit.toLocaleString("ru-RU") + "₽",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                accessorKey: "revenue",
 | 
				
			||||||
 | 
					                header: "Выручка",
 | 
				
			||||||
 | 
					                Cell: ({ row }) =>
 | 
				
			||||||
 | 
					                    row.original.revenue.toLocaleString("ru-RU") + "₽",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        []
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
 | 
				
			||||||
 | 
					import { FC } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum StatisticsTab {
 | 
				
			||||||
 | 
					    PROFIT,
 | 
				
			||||||
 | 
					    SALARIES,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = Omit<SegmentedControlProps, "data">;
 | 
				
			||||||
 | 
					const data = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        label: "Выручка по сделкам",
 | 
				
			||||||
 | 
					        value: StatisticsTab.PROFIT.toString(),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        label: "Зарплаты",
 | 
				
			||||||
 | 
					        value: StatisticsTab.SALARIES.toString(),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const StatisticsTabSegmentControl: FC<Props> = props => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <SegmentedControl
 | 
				
			||||||
 | 
					            data={data}
 | 
				
			||||||
 | 
					            {...props}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
 | 
				
			||||||
 | 
					import { FC } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum GroupStatisticsTable {
 | 
				
			||||||
 | 
					    BY_DATES,
 | 
				
			||||||
 | 
					    BY_CLIENTS,
 | 
				
			||||||
 | 
					    BY_STATUSES,
 | 
				
			||||||
 | 
					    BY_WAREHOUSES,
 | 
				
			||||||
 | 
					    BY_MARKETPLACES,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = Omit<SegmentedControlProps, "data">;
 | 
				
			||||||
 | 
					const data = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        label: "По датам",
 | 
				
			||||||
 | 
					        value: GroupStatisticsTable.BY_DATES.toString(),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        label: "По клиентам",
 | 
				
			||||||
 | 
					        value: GroupStatisticsTable.BY_CLIENTS.toString(),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        label: "По статусам",
 | 
				
			||||||
 | 
					        value: GroupStatisticsTable.BY_STATUSES.toString(),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        label: "По складам отгрузки",
 | 
				
			||||||
 | 
					        value: GroupStatisticsTable.BY_WAREHOUSES.toString(),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        label: "По маркетплейсам",
 | 
				
			||||||
 | 
					        value: GroupStatisticsTable.BY_MARKETPLACES.toString(),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const StatisticsTableSegmentControl: FC<Props> = props => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <SegmentedControl
 | 
				
			||||||
 | 
					            data={data}
 | 
				
			||||||
 | 
					            {...props}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/pages/StatisticsPage/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/pages/StatisticsPage/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export { StatisticsPage } from "./ui/StatisticsPage.tsx";
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/pages/StatisticsPage/tabs/ProfitTab/Chart.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/pages/StatisticsPage/tabs/ProfitTab/Chart.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					import { AreaChart } from "@mantine/charts";
 | 
				
			||||||
 | 
					import "@mantine/charts/styles.css";
 | 
				
			||||||
 | 
					import PageBlock from "../../../../components/PageBlock/PageBlock.tsx";
 | 
				
			||||||
 | 
					import { ProfitDataItem } from "../../../../client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = {
 | 
				
			||||||
 | 
					    data: ProfitDataItem[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Chart = ({ data }: Props) => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <PageBlock>
 | 
				
			||||||
 | 
					            <AreaChart
 | 
				
			||||||
 | 
					                mx={"lg"}
 | 
				
			||||||
 | 
					                my={"sm"}
 | 
				
			||||||
 | 
					                w={"98%"}
 | 
				
			||||||
 | 
					                h={"50vh"}
 | 
				
			||||||
 | 
					                data={data}
 | 
				
			||||||
 | 
					                dataKey="date"
 | 
				
			||||||
 | 
					                unit="₽"
 | 
				
			||||||
 | 
					                tooltipAnimationDuration={200}
 | 
				
			||||||
 | 
					                valueFormatter={(value) => new Intl.NumberFormat("ru-RU").format(value)}
 | 
				
			||||||
 | 
					                series={[
 | 
				
			||||||
 | 
					                    { name: "profit", label: "Прибыль", color: "indigo.6" },
 | 
				
			||||||
 | 
					                    { name: "revenue", label: "Выручка", color: "teal.6" },
 | 
				
			||||||
 | 
					                ]}
 | 
				
			||||||
 | 
					                fillOpacity={0.5}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </PageBlock>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										72
									
								
								src/pages/StatisticsPage/tabs/ProfitTab/ProfitTab.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/pages/StatisticsPage/tabs/ProfitTab/ProfitTab.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					import { useForm } from "@mantine/form";
 | 
				
			||||||
 | 
					import { Chart } from "./Chart.tsx";
 | 
				
			||||||
 | 
					import { Filters } from "../../components/Filters/Filters.tsx";
 | 
				
			||||||
 | 
					import styles from "../../ui/StatisticsPage.module.css";
 | 
				
			||||||
 | 
					import { ChartFormFilters } from "../../types/ChartFormFilters.ts";
 | 
				
			||||||
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import { ProfitDataItem, StatisticsService } from "../../../../client";
 | 
				
			||||||
 | 
					import { dateToString, getDefaultDates } from "../../utils/dates.ts";
 | 
				
			||||||
 | 
					import { ProfitTable } from "../../components/ProfitTable/ProfitTable.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProfitTab = () => {
 | 
				
			||||||
 | 
					    const form = useForm<ChartFormFilters>({
 | 
				
			||||||
 | 
					        mode: "controlled",
 | 
				
			||||||
 | 
					        initialValues: {
 | 
				
			||||||
 | 
					            dateRange: getDefaultDates(),
 | 
				
			||||||
 | 
					            client: null,
 | 
				
			||||||
 | 
					            marketplace: null,
 | 
				
			||||||
 | 
					            dealStatus: null,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const [profitData, setProfitData] = useState<ProfitDataItem[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const getFilters = () => {
 | 
				
			||||||
 | 
					        const dateRange = form.values.dateRange;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            dateRange: [
 | 
				
			||||||
 | 
					                dateToString(dateRange[0]),
 | 
				
			||||||
 | 
					                dateToString(dateRange[1]),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            clientId: form.values.client?.id ?? -1,
 | 
				
			||||||
 | 
					            baseMarketplaceKey: form.values.marketplace?.key ?? "all",
 | 
				
			||||||
 | 
					            dealStatusId: form.values.dealStatus?.id ?? -1,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const fetchProfitData = () => {
 | 
				
			||||||
 | 
					        StatisticsService.getProfitData({
 | 
				
			||||||
 | 
					            requestBody: getFilters(),
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					            .then(res => {
 | 
				
			||||||
 | 
					                setProfitData(res.data);
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(err => console.log(err));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (form.values.dateRange.length < 2 || form.values.dateRange[1] === null) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log(form.values);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fetchProfitData();
 | 
				
			||||||
 | 
					    }, [form.values]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className={styles["container"]}>
 | 
				
			||||||
 | 
					            <Filters
 | 
				
			||||||
 | 
					                datePickerProps={form.getInputProps("dateRange")}
 | 
				
			||||||
 | 
					                clientSelectProps={form.getInputProps("client")}
 | 
				
			||||||
 | 
					                onClientClear={() => form.setFieldValue("client", null)}
 | 
				
			||||||
 | 
					                baseMarketplaceSelectProps={form.getInputProps("marketplace")}
 | 
				
			||||||
 | 
					                onBaseMarketplaceClear={() => form.setFieldValue("marketplace", null)}
 | 
				
			||||||
 | 
					                dealStatusSelectProps={form.getInputProps("dealStatus")}
 | 
				
			||||||
 | 
					                onDealStatusClear={() => form.setFieldValue("dealStatus", null)}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <Chart data={profitData} />
 | 
				
			||||||
 | 
					            <ProfitTable data={profitData} />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/pages/StatisticsPage/types/ChartFormFilters.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/pages/StatisticsPage/types/ChartFormFilters.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { BaseMarketplaceSchema, ClientSchema } from "../../../client";
 | 
				
			||||||
 | 
					import { DealStatusType } from "../../../shared/enums/DealStatus.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ChartFormFilters {
 | 
				
			||||||
 | 
					    dateRange: [Date | null, Date | null];
 | 
				
			||||||
 | 
					    client: ClientSchema | null;
 | 
				
			||||||
 | 
					    marketplace: BaseMarketplaceSchema | null;
 | 
				
			||||||
 | 
					    dealStatus: DealStatusType | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/pages/StatisticsPage/types/ProfitTableRow.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/pages/StatisticsPage/types/ProfitTableRow.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					export type ProfitTableRow = {
 | 
				
			||||||
 | 
					    date: string;
 | 
				
			||||||
 | 
					    dealsCount: number;
 | 
				
			||||||
 | 
					    profit: number;
 | 
				
			||||||
 | 
					    revenue: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/pages/StatisticsPage/types/TableFormFilters.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/pages/StatisticsPage/types/TableFormFilters.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { GroupStatisticsTable } from "../components/StatisticsTableSegmentControl/StatisticsTableSegmentControl.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface TableFormFilters {
 | 
				
			||||||
 | 
					    dateRange: [Date | null, Date | null];
 | 
				
			||||||
 | 
					    groupTableBy: GroupStatisticsTable;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								src/pages/StatisticsPage/ui/StatisticsPage.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/pages/StatisticsPage/ui/StatisticsPage.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					.container {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					    gap: rem(10);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.top-panel {
 | 
				
			||||||
 | 
					    padding: rem(5);
 | 
				
			||||||
 | 
					    gap: rem(10);
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										38
									
								
								src/pages/StatisticsPage/ui/StatisticsPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/pages/StatisticsPage/ui/StatisticsPage.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					import { useState } from "react";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    StatisticsTab,
 | 
				
			||||||
 | 
					    StatisticsTabSegmentControl,
 | 
				
			||||||
 | 
					} from "../components/StatisticsTabSegmentControl/StatisticsTabSegmentControl.tsx";
 | 
				
			||||||
 | 
					import styles from "./StatisticsPage.module.css";
 | 
				
			||||||
 | 
					import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
 | 
				
			||||||
 | 
					import { ProfitTab } from "../tabs/ProfitTab/ProfitTab.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const StatisticsPage = () => {
 | 
				
			||||||
 | 
					    const [serviceType, setServiceType] = useState(StatisticsTab.PROFIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const getBody = () => {
 | 
				
			||||||
 | 
					        switch (serviceType) {
 | 
				
			||||||
 | 
					            case StatisticsTab.PROFIT:
 | 
				
			||||||
 | 
					                return (
 | 
				
			||||||
 | 
					                    <ProfitTab />
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            case StatisticsTab.SALARIES:
 | 
				
			||||||
 | 
					                return (
 | 
				
			||||||
 | 
					                    <>Статистика по ЗП</>
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className={styles["container"]}>
 | 
				
			||||||
 | 
					            <PageBlock>
 | 
				
			||||||
 | 
					                <StatisticsTabSegmentControl
 | 
				
			||||||
 | 
					                    size={"md"}
 | 
				
			||||||
 | 
					                    value={serviceType.toString()}
 | 
				
			||||||
 | 
					                    onChange={event => setServiceType(parseInt(event))}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </PageBlock>
 | 
				
			||||||
 | 
					            {getBody()}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/pages/StatisticsPage/utils/dates.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/pages/StatisticsPage/utils/dates.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					export const getDefaultDates = (): [Date, Date] => {
 | 
				
			||||||
 | 
					    const dateTo = new Date();
 | 
				
			||||||
 | 
					    const dateFrom = new Date();
 | 
				
			||||||
 | 
					    dateFrom.setDate(dateTo.getDate() - 28);
 | 
				
			||||||
 | 
					    return [dateFrom, dateTo];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const dateToString = (date: Date | null) => {
 | 
				
			||||||
 | 
					    if (date === null) return null;
 | 
				
			||||||
 | 
					    const month = date.getMonth() + 1;
 | 
				
			||||||
 | 
					    const day = date.getDate();
 | 
				
			||||||
 | 
					    const monthStr = month < 10 ? `0${month}` : month;
 | 
				
			||||||
 | 
					    const dayStr = day < 10 ? `0${day}` : day;
 | 
				
			||||||
 | 
					    return `${date.getFullYear()}-${monthStr}-${dayStr}`;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/routes/statistics.lazy.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/routes/statistics.lazy.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { createLazyFileRoute } from "@tanstack/react-router";
 | 
				
			||||||
 | 
					import { StatisticsPage } from "../pages/StatisticsPage";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Route = createLazyFileRoute("/statistics")({
 | 
				
			||||||
 | 
					    component: StatisticsPage,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Reference in New Issue
	
	Block a user