feat: profit chart in statistics
This commit is contained in:
		@@ -16,8 +16,9 @@
 | 
			
		||||
        "@fortawesome/free-solid-svg-icons": "^6.6.0",
 | 
			
		||||
        "@fortawesome/react-fontawesome": "^0.2.2",
 | 
			
		||||
        "@hello-pangea/dnd": "^17.0.0",
 | 
			
		||||
        "@mantine/charts": "^7.13.5",
 | 
			
		||||
        "@mantine/core": "^7.11.2",
 | 
			
		||||
        "@mantine/dates": "^7.11.2",
 | 
			
		||||
        "@mantine/dates": "^7.13.5",
 | 
			
		||||
        "@mantine/dropzone": "^7.11.2",
 | 
			
		||||
        "@mantine/form": "^7.11.2",
 | 
			
		||||
        "@mantine/hooks": "^7.11.2",
 | 
			
		||||
@@ -51,6 +52,7 @@
 | 
			
		||||
        "react-redux": "^9.1.2",
 | 
			
		||||
        "react-to-print": "^2.15.1",
 | 
			
		||||
        "reactflow": "^11.11.4",
 | 
			
		||||
        "recharts": "^2.13.3",
 | 
			
		||||
        "zod": "^3.23.8"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
 
 | 
			
		||||
@@ -166,6 +166,8 @@ export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfR
 | 
			
		||||
export type { GetProductBarcodePdfResponse } from './models/GetProductBarcodePdfResponse';
 | 
			
		||||
export type { GetProductBarcodeRequest } from './models/GetProductBarcodeRequest';
 | 
			
		||||
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 { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
 | 
			
		||||
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
 | 
			
		||||
@@ -199,6 +201,7 @@ export type { ProductUpdateRequest } from './models/ProductUpdateRequest';
 | 
			
		||||
export type { ProductUpdateResponse } from './models/ProductUpdateResponse';
 | 
			
		||||
export type { ProductUploadBarcodeImageResponse } from './models/ProductUploadBarcodeImageResponse';
 | 
			
		||||
export type { ProductUploadImageResponse } from './models/ProductUploadImageResponse';
 | 
			
		||||
export type { ProfitDataItem } from './models/ProfitDataItem';
 | 
			
		||||
export type { RoleSchema } from './models/RoleSchema';
 | 
			
		||||
export type { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema';
 | 
			
		||||
export type { ServiceCategoryReorderRequest } from './models/ServiceCategoryReorderRequest';
 | 
			
		||||
@@ -260,6 +263,7 @@ export { ProductService } from './services/ProductService';
 | 
			
		||||
export { RoleService } from './services/RoleService';
 | 
			
		||||
export { ServiceService } from './services/ServiceService';
 | 
			
		||||
export { ShippingWarehouseService } from './services/ShippingWarehouseService';
 | 
			
		||||
export { StatisticsService } from './services/StatisticsService';
 | 
			
		||||
export { TaskService } from './services/TaskService';
 | 
			
		||||
export { TimeTrackingService } from './services/TimeTrackingService';
 | 
			
		||||
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,
 | 
			
		||||
    IconBox,
 | 
			
		||||
    IconBuildingWarehouse,
 | 
			
		||||
    IconCash,
 | 
			
		||||
    IconCash, IconChartDots,
 | 
			
		||||
    IconDashboard,
 | 
			
		||||
    IconFileBarcode,
 | 
			
		||||
    IconHome2,
 | 
			
		||||
@@ -93,6 +93,11 @@ const mockdata = [
 | 
			
		||||
        label: "Маркетплейсы",
 | 
			
		||||
        href: "/marketplaces",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        icon: IconChartDots,
 | 
			
		||||
        label: "Статистика",
 | 
			
		||||
        href: "/statistics",
 | 
			
		||||
    }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
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