From 3b8c75d3d356725656c75e61d02c6a57f566623d Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Sun, 17 Nov 2024 01:23:04 +0400 Subject: [PATCH] feat: profit table and division of charts in statistics --- src/client/index.ts | 10 +- ...equest.ts => GetProfitChartDataRequest.ts} | 2 +- .../models/GetProfitChartDataResponse.ts | 9 ++ .../models/GetProfitTableDataRequest.ts | 10 ++ .../models/GetProfitTableDataResponse.ts | 9 ++ ...ofitDataItem.ts => ProfitChartDataItem.ts} | 2 +- src/client/models/ProfitTableDataItem.ts | 11 +++ ...tDataResponse.ts => ProfitTableGroupBy.ts} | 6 +- src/client/services/StatisticsService.ts | 38 ++++++-- .../components/Filters/Filters.tsx | 90 ------------------ .../components/ProfitTable/ProfitTable.tsx | 52 ---------- .../components/ProfitTable/columns.tsx | 31 ------ .../StatisticsTabSegmentedControl.tsx} | 2 +- .../StatisticsPage/tabs/ProfitTab/Chart.tsx | 31 ------ .../tabs/ProfitTab/ProfitTab.tsx | 70 +------------- .../ProfitTab/components/Filters/Filters.tsx | 94 +++++++++++++++++++ .../components/ProfitChart/ProfitChart.tsx | 55 +++++++++++ .../ProfitChart/hooks/useProfitChart.tsx | 59 ++++++++++++ .../components/ProfitTable/ProfitTable.tsx | 19 ++++ .../components/ProfitTable/hooks/columns.tsx | 53 +++++++++++ .../hooks/useProfitMantineTable.tsx | 36 +++++++ .../ProfitTable/hooks/useProfitTable.tsx | 63 +++++++++++++ .../ProfitTableSegmentedControl.tsx} | 2 +- .../modals/ProfitChartFiltersModal.tsx | 45 +++++++++ .../modals/ProfitTableFiltersModal.tsx | 43 +++++++++ .../StatisticsPage/types/ProfitTableRow.ts | 6 -- .../StatisticsPage/types/TableFormFilters.ts | 2 +- .../ui/StatisticsPage.module.css | 8 ++ .../StatisticsPage/ui/StatisticsPage.tsx | 18 ++-- 29 files changed, 571 insertions(+), 305 deletions(-) rename src/client/models/{GetProfitDataRequest.ts => GetProfitChartDataRequest.ts} (85%) create mode 100644 src/client/models/GetProfitChartDataResponse.ts create mode 100644 src/client/models/GetProfitTableDataRequest.ts create mode 100644 src/client/models/GetProfitTableDataResponse.ts rename src/client/models/{ProfitDataItem.ts => ProfitChartDataItem.ts} (85%) create mode 100644 src/client/models/ProfitTableDataItem.ts rename src/client/models/{GetProfitDataResponse.ts => ProfitTableGroupBy.ts} (50%) delete mode 100644 src/pages/StatisticsPage/components/Filters/Filters.tsx delete mode 100644 src/pages/StatisticsPage/components/ProfitTable/ProfitTable.tsx delete mode 100644 src/pages/StatisticsPage/components/ProfitTable/columns.tsx rename src/pages/StatisticsPage/components/{StatisticsTabSegmentControl/StatisticsTabSegmentControl.tsx => StatisticsTabSegmentedControl/StatisticsTabSegmentedControl.tsx} (89%) delete mode 100644 src/pages/StatisticsPage/tabs/ProfitTab/Chart.tsx create mode 100644 src/pages/StatisticsPage/tabs/ProfitTab/components/Filters/Filters.tsx create mode 100644 src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/ProfitChart.tsx create mode 100644 src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/hooks/useProfitChart.tsx create mode 100644 src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/ProfitTable.tsx create mode 100644 src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/columns.tsx create mode 100644 src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitMantineTable.tsx create mode 100644 src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitTable.tsx rename src/pages/StatisticsPage/{components/StatisticsTableSegmentControl/StatisticsTableSegmentControl.tsx => tabs/ProfitTab/components/ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx} (93%) create mode 100644 src/pages/StatisticsPage/tabs/ProfitTab/modals/ProfitChartFiltersModal.tsx create mode 100644 src/pages/StatisticsPage/tabs/ProfitTab/modals/ProfitTableFiltersModal.tsx delete mode 100644 src/pages/StatisticsPage/types/ProfitTableRow.ts diff --git a/src/client/index.ts b/src/client/index.ts index 24b729c..d97ce91 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -166,8 +166,10 @@ 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 { GetProfitChartDataRequest } from './models/GetProfitChartDataRequest'; +export type { GetProfitChartDataResponse } from './models/GetProfitChartDataResponse'; +export type { GetProfitTableDataRequest } from './models/GetProfitTableDataRequest'; +export type { GetProfitTableDataResponse } from './models/GetProfitTableDataResponse'; export type { GetServiceKitSchema } from './models/GetServiceKitSchema'; export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest'; export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse'; @@ -201,7 +203,9 @@ 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 { ProfitChartDataItem } from './models/ProfitChartDataItem'; +export type { ProfitTableDataItem } from './models/ProfitTableDataItem'; +export type { ProfitTableGroupBy } from './models/ProfitTableGroupBy'; export type { RoleSchema } from './models/RoleSchema'; export type { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema'; export type { ServiceCategoryReorderRequest } from './models/ServiceCategoryReorderRequest'; diff --git a/src/client/models/GetProfitDataRequest.ts b/src/client/models/GetProfitChartDataRequest.ts similarity index 85% rename from src/client/models/GetProfitDataRequest.ts rename to src/client/models/GetProfitChartDataRequest.ts index 47fb791..0d586c4 100644 --- a/src/client/models/GetProfitDataRequest.ts +++ b/src/client/models/GetProfitChartDataRequest.ts @@ -2,7 +2,7 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export type GetProfitDataRequest = { +export type GetProfitChartDataRequest = { dateRange: any[]; clientId: number; baseMarketplaceKey: string; diff --git a/src/client/models/GetProfitChartDataResponse.ts b/src/client/models/GetProfitChartDataResponse.ts new file mode 100644 index 0000000..99ff762 --- /dev/null +++ b/src/client/models/GetProfitChartDataResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProfitChartDataItem } from './ProfitChartDataItem'; +export type GetProfitChartDataResponse = { + data: Array; +}; + diff --git a/src/client/models/GetProfitTableDataRequest.ts b/src/client/models/GetProfitTableDataRequest.ts new file mode 100644 index 0000000..898bde3 --- /dev/null +++ b/src/client/models/GetProfitTableDataRequest.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProfitTableGroupBy } from './ProfitTableGroupBy'; +export type GetProfitTableDataRequest = { + dateRange: any[]; + groupTableBy: ProfitTableGroupBy; +}; + diff --git a/src/client/models/GetProfitTableDataResponse.ts b/src/client/models/GetProfitTableDataResponse.ts new file mode 100644 index 0000000..e03f713 --- /dev/null +++ b/src/client/models/GetProfitTableDataResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProfitTableDataItem } from './ProfitTableDataItem'; +export type GetProfitTableDataResponse = { + data: Array; +}; + diff --git a/src/client/models/ProfitDataItem.ts b/src/client/models/ProfitChartDataItem.ts similarity index 85% rename from src/client/models/ProfitDataItem.ts rename to src/client/models/ProfitChartDataItem.ts index d2fdee8..bb63817 100644 --- a/src/client/models/ProfitDataItem.ts +++ b/src/client/models/ProfitChartDataItem.ts @@ -2,7 +2,7 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export type ProfitDataItem = { +export type ProfitChartDataItem = { date: string; revenue: number; profit: number; diff --git a/src/client/models/ProfitTableDataItem.ts b/src/client/models/ProfitTableDataItem.ts new file mode 100644 index 0000000..b755b96 --- /dev/null +++ b/src/client/models/ProfitTableDataItem.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ProfitTableDataItem = { + groupedValue: (string | number); + revenue: number; + profit: number; + dealsCount: number; +}; + diff --git a/src/client/models/GetProfitDataResponse.ts b/src/client/models/ProfitTableGroupBy.ts similarity index 50% rename from src/client/models/GetProfitDataResponse.ts rename to src/client/models/ProfitTableGroupBy.ts index 2f7eb91..c9a9c1d 100644 --- a/src/client/models/GetProfitDataResponse.ts +++ b/src/client/models/ProfitTableGroupBy.ts @@ -2,8 +2,4 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ProfitDataItem } from './ProfitDataItem'; -export type GetProfitDataResponse = { - data: Array; -}; - +export type ProfitTableGroupBy = 0 | 1 | 2 | 3 | 4; diff --git a/src/client/services/StatisticsService.ts b/src/client/services/StatisticsService.ts index b729913..6a9c77e 100644 --- a/src/client/services/StatisticsService.ts +++ b/src/client/services/StatisticsService.ts @@ -2,25 +2,47 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { GetProfitDataRequest } from '../models/GetProfitDataRequest'; -import type { GetProfitDataResponse } from '../models/GetProfitDataResponse'; +import type { GetProfitChartDataRequest } from '../models/GetProfitChartDataRequest'; +import type { GetProfitChartDataResponse } from '../models/GetProfitChartDataResponse'; +import type { GetProfitTableDataRequest } from '../models/GetProfitTableDataRequest'; +import type { GetProfitTableDataResponse } from '../models/GetProfitTableDataResponse'; 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 + * Get Profit Chart Data + * @returns GetProfitChartDataResponse Successful Response * @throws ApiError */ - public static getProfitData({ + public static getProfitChartData({ requestBody, }: { - requestBody: GetProfitDataRequest, - }): CancelablePromise { + requestBody: GetProfitChartDataRequest, + }): CancelablePromise { return __request(OpenAPI, { method: 'POST', - url: '/statistics/get-profit-data', + url: '/statistics/get-profit-chart-data', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Get Profit Table Data + * @returns GetProfitTableDataResponse Successful Response + * @throws ApiError + */ + public static getProfitTableData({ + requestBody, + }: { + requestBody: GetProfitTableDataRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/statistics/get-profit-table-data', body: requestBody, mediaType: 'application/json', errors: { diff --git a/src/pages/StatisticsPage/components/Filters/Filters.tsx b/src/pages/StatisticsPage/components/Filters/Filters.tsx deleted file mode 100644 index 9db79f9..0000000 --- a/src/pages/StatisticsPage/components/Filters/Filters.tsx +++ /dev/null @@ -1,90 +0,0 @@ -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, "data">; - onClientClear?: () => void; - - baseMarketplaceSelectProps?: Omit< - ObjectSelectProps, - "data" | "getValueFn" | "getLabelFn" - >; - onBaseMarketplaceClear?: () => void; - - dealStatusSelectProps?: Omit, "data">; - onDealStatusClear?: () => void; -} - -export const Filters = (props: FiltersProps) => { - const { - datePickerProps, - clientSelectProps, - onClientClear, - baseMarketplaceSelectProps, - onBaseMarketplaceClear, - dealStatusSelectProps, - onDealStatusClear, - } = props; - - return ( - - - {datePickerProps && - - } - {dealStatusSelectProps && - - } - {clientSelectProps && - - } - {baseMarketplaceSelectProps && - - } - { - <> - - Группировать: - - setGroupTableBy(parseInt(event))} - /> - - } - - - ); -}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/components/ProfitTable/ProfitTable.tsx b/src/pages/StatisticsPage/components/ProfitTable/ProfitTable.tsx deleted file mode 100644 index 4be9973..0000000 --- a/src/pages/StatisticsPage/components/ProfitTable/ProfitTable.tsx +++ /dev/null @@ -1,52 +0,0 @@ -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({ - 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 ( -
- - - - - - -
- ); -}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/components/ProfitTable/columns.tsx b/src/pages/StatisticsPage/components/ProfitTable/columns.tsx deleted file mode 100644 index 4708d87..0000000 --- a/src/pages/StatisticsPage/components/ProfitTable/columns.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useMemo } from "react"; -import { MRT_ColumnDef } from "mantine-react-table"; -import { ProfitTableRow } from "../../types/ProfitTableRow.ts"; - -export const useProfitTableColumns = () => { - return useMemo[]>( - () => [ - { - 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") + "₽", - }, - ], - [] - ); -}; diff --git a/src/pages/StatisticsPage/components/StatisticsTabSegmentControl/StatisticsTabSegmentControl.tsx b/src/pages/StatisticsPage/components/StatisticsTabSegmentedControl/StatisticsTabSegmentedControl.tsx similarity index 89% rename from src/pages/StatisticsPage/components/StatisticsTabSegmentControl/StatisticsTabSegmentControl.tsx rename to src/pages/StatisticsPage/components/StatisticsTabSegmentedControl/StatisticsTabSegmentedControl.tsx index 14100a7..10e25e7 100644 --- a/src/pages/StatisticsPage/components/StatisticsTabSegmentControl/StatisticsTabSegmentControl.tsx +++ b/src/pages/StatisticsPage/components/StatisticsTabSegmentedControl/StatisticsTabSegmentedControl.tsx @@ -18,7 +18,7 @@ const data = [ }, ]; -export const StatisticsTabSegmentControl: FC = props => { +export const StatisticsTabSegmentedControl: FC = props => { return ( { - return ( - - new Intl.NumberFormat("ru-RU").format(value)} - series={[ - { name: "profit", label: "Прибыль", color: "indigo.6" }, - { name: "revenue", label: "Выручка", color: "teal.6" }, - ]} - fillOpacity={0.5} - /> - - ); -}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/ProfitTab.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/ProfitTab.tsx index a9e7309..286a583 100644 --- a/src/pages/StatisticsPage/tabs/ProfitTab/ProfitTab.tsx +++ b/src/pages/StatisticsPage/tabs/ProfitTab/ProfitTab.tsx @@ -1,72 +1,12 @@ -import { useForm } from "@mantine/form"; -import { Chart } from "./Chart.tsx"; -import { Filters } from "../../components/Filters/Filters.tsx"; +import { ProfitChart } from "./components/ProfitChart/ProfitChart.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"; +import { ProfitTable } from "./components/ProfitTable/ProfitTable.tsx"; export const ProfitTab = () => { - const form = useForm({ - mode: "controlled", - initialValues: { - dateRange: getDefaultDates(), - client: null, - marketplace: null, - dealStatus: null, - }, - }); - const [profitData, setProfitData] = useState([]); - - 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 ( -
- form.setFieldValue("client", null)} - baseMarketplaceSelectProps={form.getInputProps("marketplace")} - onBaseMarketplaceClear={() => form.setFieldValue("marketplace", null)} - dealStatusSelectProps={form.getInputProps("dealStatus")} - onDealStatusClear={() => form.setFieldValue("dealStatus", null)} - /> - - +
+ +
); }; \ No newline at end of file diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/components/Filters/Filters.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/components/Filters/Filters.tsx new file mode 100644 index 0000000..c7b4e35 --- /dev/null +++ b/src/pages/StatisticsPage/tabs/ProfitTab/components/Filters/Filters.tsx @@ -0,0 +1,94 @@ +import { DatePickerInput, DatePickerInputProps } from "@mantine/dates"; +import { Stack, 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 { ProfitTableSegmentedControl } from "../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx"; + + +type FiltersProps = { + datePickerProps?: DatePickerInputProps<"range">; + + clientSelectProps?: Omit, "data">; + onClientClear?: () => void; + + baseMarketplaceSelectProps?: Omit< + ObjectSelectProps, + "data" | "getValueFn" | "getLabelFn" + >; + onBaseMarketplaceClear?: () => void; + + dealStatusSelectProps?: Omit, "data">; + onDealStatusClear?: () => void; + + groupTableByProps?: { + value: string, + onChange: (value: string) => void, + }; +} + +export const Filters = (props: FiltersProps) => { + const { + datePickerProps, + clientSelectProps, + onClientClear, + baseMarketplaceSelectProps, + onBaseMarketplaceClear, + dealStatusSelectProps, + onDealStatusClear, + groupTableByProps, + } = props; + + return ( + + {datePickerProps && + + } + {dealStatusSelectProps && + + } + {clientSelectProps && + + } + {baseMarketplaceSelectProps && + + } + {groupTableByProps && + <> + Группировать: + + + } + + ); +}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/ProfitChart.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/ProfitChart.tsx new file mode 100644 index 0000000..86b5a88 --- /dev/null +++ b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/ProfitChart.tsx @@ -0,0 +1,55 @@ +import { AreaChart } from "@mantine/charts"; +import "@mantine/charts/styles.css"; +import PageBlock from "../../../../../../components/PageBlock/PageBlock.tsx"; +import { useProfitChart } from "./hooks/useProfitChart.tsx"; +import { Skeleton, Stack } from "@mantine/core"; +import { ProfitChartFiltersModal } from "../../modals/ProfitChartFiltersModal.tsx"; + + +export const ProfitChart = () => { + const { + profitData, + form, + isLoading, + } = useProfitChart(); + + const getChartsSeries = [ + [ + { name: "profit", label: "Прибыль", color: "indigo.6" }, + { name: "revenue", label: "Выручка", color: "teal.6" }, + ], + [ + { name: "dealsCount", label: "Количество сделок", color: "indigo.6" }, + ], + ]; + + const units = ["₽", "шт"]; + + return ( + + + + + {getChartsSeries.map((series, idx) => { + return ( + new Intl.NumberFormat("ru-RU").format(value)} + series={series} + fillOpacity={0.5} + /> + ); + })} + + + + ); +}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/hooks/useProfitChart.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/hooks/useProfitChart.tsx new file mode 100644 index 0000000..00a41cf --- /dev/null +++ b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/hooks/useProfitChart.tsx @@ -0,0 +1,59 @@ +import { ChartFormFilters } from "../../../../../types/ChartFormFilters.ts"; +import { useForm } from "@mantine/form"; +import { dateToString, getDefaultDates } from "../../../../../utils/dates.ts"; +import { useEffect, useState } from "react"; +import { ProfitChartDataItem, StatisticsService } from "../../../../../../../client"; + + +export const useProfitChart = () => { + const form = useForm({ + mode: "controlled", + initialValues: { + dateRange: getDefaultDates(), + client: null, + marketplace: null, + dealStatus: null, + }, + }); + const [profitData, setProfitData] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + 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 = () => { + setIsLoading(true); + StatisticsService.getProfitChartData({ + requestBody: getFilters(), + }) + .then(res => { + setProfitData(res.data); + }) + .catch(err => console.log(err)) + .finally(() => setIsLoading(false)); + }; + + useEffect(() => { + if (form.values.dateRange.length < 2 || form.values.dateRange[1] === null) { + return; + } + fetchProfitData(); + }, [form.values]); + + return { + profitData, + form, + isLoading, + }; +}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/ProfitTable.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/ProfitTable.tsx new file mode 100644 index 0000000..6d70697 --- /dev/null +++ b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/ProfitTable.tsx @@ -0,0 +1,19 @@ +import PageBlock from "../../../../../../components/PageBlock/PageBlock.tsx"; +import { MantineReactTable } from "mantine-react-table"; +import { useProfitTable } from "./hooks/useProfitTable.tsx"; +import { Skeleton } from "@mantine/core"; +import { ProfitTableFiltersModal } from "../../modals/ProfitTableFiltersModal.tsx"; + + +export const ProfitTable = () => { + const { table, form, isLoading } = useProfitTable(); + + return ( + + + + + + + ); +}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/columns.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/columns.tsx new file mode 100644 index 0000000..e5e742b --- /dev/null +++ b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/columns.tsx @@ -0,0 +1,53 @@ +import { useMemo } from "react"; +import { MRT_ColumnDef } from "mantine-react-table"; +import { ProfitTableDataItem } from "../../../../../../../client"; +import { GroupStatisticsTable } from "../../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx"; +import { DealStatus, DealStatusDictionary } from "../../../../../../../shared/enums/DealStatus.ts"; + +type Props = { + groupTableBy: GroupStatisticsTable; +} + +export const useProfitTableColumns = ({ groupTableBy }: Props) => { + const groupedValueHeader = { + [GroupStatisticsTable.BY_DATES]: "Дата", + [GroupStatisticsTable.BY_CLIENTS]: "Клиент", + [GroupStatisticsTable.BY_STATUSES]: "Статус", + [GroupStatisticsTable.BY_MARKETPLACES]: "Маркетплейс", + [GroupStatisticsTable.BY_WAREHOUSES]: "Склад отгрузки", + }; + + return useMemo[]>( + () => [ + { + accessorKey: "groupedValue", + header: groupedValueHeader[groupTableBy], + enableSorting: groupTableBy === GroupStatisticsTable.BY_DATES, + Cell: ({ row }) => { + if (groupTableBy === GroupStatisticsTable.BY_STATUSES) { + const statusIndex = row.original.groupedValue as DealStatus; + return DealStatusDictionary[statusIndex]; + } + return row.original.groupedValue; + }, + }, + { + 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") + "₽", + }, + ], + [groupTableBy], + ); +}; diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitMantineTable.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitMantineTable.tsx new file mode 100644 index 0000000..2abed81 --- /dev/null +++ b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitMantineTable.tsx @@ -0,0 +1,36 @@ +import { GroupStatisticsTable } from "../../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx"; +import { useProfitTableColumns } from "./columns.tsx"; +import { useMantineReactTable } from "mantine-react-table"; +import { ProfitTableDataItem } from "../../../../../../../client"; +import { MRT_Localization_RU } from "mantine-react-table/locales/ru/index.cjs"; + + +type Props = { + groupTableBy: GroupStatisticsTable; + profitData: ProfitTableDataItem[]; +} + +export const useProfitMantineTable = ({ groupTableBy, profitData }: Props) => { + const columns = useProfitTableColumns({ + groupTableBy, + }); + + const defaultSorting = [{ id: "groupedValue", desc: true }]; + + const table = useMantineReactTable({ + enablePagination: false, + data: profitData, + columns, + enableTopToolbar: false, + enableBottomToolbar: false, + enableSorting: true, + initialState: { + sorting: defaultSorting, + }, + localization: MRT_Localization_RU, + enableRowVirtualization: true, + mantineTableContainerProps: { style: { maxHeight: "86vh" } }, + }); + + return { table }; +}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitTable.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitTable.tsx new file mode 100644 index 0000000..3717aeb --- /dev/null +++ b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitTable.tsx @@ -0,0 +1,63 @@ +import { useForm } from "@mantine/form"; +import { TableFormFilters } from "../../../../../types/TableFormFilters.ts"; +import { dateToString, getDefaultDates } from "../../../../../utils/dates.ts"; +import { GroupStatisticsTable } from "../../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx"; +import { useEffect, useState } from "react"; +import { ProfitTableDataItem, StatisticsService } from "../../../../../../../client"; +import { useProfitMantineTable } from "./useProfitMantineTable.tsx"; + + +export const useProfitTable = () => { + const form = useForm({ + mode: "controlled", + initialValues: { + dateRange: getDefaultDates(), + groupTableBy: GroupStatisticsTable.BY_DATES, + }, + }); + const [isLoading, setIsLoading] = useState(false); + + const [profitData, setProfitData] = useState([]); + + const { table } = useProfitMantineTable({ + groupTableBy: form.values.groupTableBy, + profitData, + }); + + const getFilters = () => { + const dateRange = form.values.dateRange; + + return { + dateRange: [ + dateToString(dateRange[0]), + dateToString(dateRange[1]), + ], + groupTableBy: form.values.groupTableBy, + }; + }; + + const fetchProfitData = () => { + setIsLoading(true); + StatisticsService.getProfitTableData({ + requestBody: getFilters(), + }) + .then(res => { + setProfitData(res.data); + }) + .catch(err => console.log(err)) + .finally(() => setIsLoading(false)); + }; + + useEffect(() => { + if (form.values.dateRange.length < 2 || form.values.dateRange[1] === null) { + return; + } + fetchProfitData(); + }, [form.values]); + + return { + table, + form, + isLoading, + }; +}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/components/StatisticsTableSegmentControl/StatisticsTableSegmentControl.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx similarity index 93% rename from src/pages/StatisticsPage/components/StatisticsTableSegmentControl/StatisticsTableSegmentControl.tsx rename to src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx index b901735..5f1aee1 100644 --- a/src/pages/StatisticsPage/components/StatisticsTableSegmentControl/StatisticsTableSegmentControl.tsx +++ b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx @@ -33,7 +33,7 @@ const data = [ }, ]; -export const StatisticsTableSegmentControl: FC = props => { +export const ProfitTableSegmentedControl: FC = props => { return ( ; +} + +export const ProfitChartFiltersModal = ({ form }: Props) => { + const [opened, { open, close }] = useDisclosure(); + + return ( + <> + + + form.setFieldValue("client", null)} + baseMarketplaceSelectProps={form.getInputProps("marketplace")} + onBaseMarketplaceClear={() => form.setFieldValue("marketplace", null)} + dealStatusSelectProps={form.getInputProps("dealStatus")} + onDealStatusClear={() => form.setFieldValue("dealStatus", null)} + /> + + + ); +}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/modals/ProfitTableFiltersModal.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/modals/ProfitTableFiltersModal.tsx new file mode 100644 index 0000000..b95d3ee --- /dev/null +++ b/src/pages/StatisticsPage/tabs/ProfitTab/modals/ProfitTableFiltersModal.tsx @@ -0,0 +1,43 @@ +import { Button, Group, Modal } from "@mantine/core"; +import { IconFilter } from "@tabler/icons-react"; +import { Filters } from "../components/Filters/Filters.tsx"; +import { TableFormFilters } from "../../../types/TableFormFilters.ts"; +import { UseFormReturnType } from "@mantine/form"; +import { useDisclosure } from "@mantine/hooks"; + + +type Props = { + form: UseFormReturnType; +} + +export const ProfitTableFiltersModal = ({ form }: Props) => { + const [opened, { open, close }] = useDisclosure(); + + return ( + <> + + + form.setFieldValue("groupTableBy", parseInt(value)), + }} + /> + + + ); +}; \ No newline at end of file diff --git a/src/pages/StatisticsPage/types/ProfitTableRow.ts b/src/pages/StatisticsPage/types/ProfitTableRow.ts deleted file mode 100644 index f775a01..0000000 --- a/src/pages/StatisticsPage/types/ProfitTableRow.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type ProfitTableRow = { - date: string; - dealsCount: number; - profit: number; - revenue: number; -} \ No newline at end of file diff --git a/src/pages/StatisticsPage/types/TableFormFilters.ts b/src/pages/StatisticsPage/types/TableFormFilters.ts index eb9b802..689493e 100644 --- a/src/pages/StatisticsPage/types/TableFormFilters.ts +++ b/src/pages/StatisticsPage/types/TableFormFilters.ts @@ -1,4 +1,4 @@ -import { GroupStatisticsTable } from "../components/StatisticsTableSegmentControl/StatisticsTableSegmentControl.tsx"; +import { GroupStatisticsTable } from "../tabs/ProfitTab/components/ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx"; export interface TableFormFilters { dateRange: [Date | null, Date | null]; diff --git a/src/pages/StatisticsPage/ui/StatisticsPage.module.css b/src/pages/StatisticsPage/ui/StatisticsPage.module.css index 61ac571..2407bc3 100644 --- a/src/pages/StatisticsPage/ui/StatisticsPage.module.css +++ b/src/pages/StatisticsPage/ui/StatisticsPage.module.css @@ -5,6 +5,14 @@ gap: rem(10); } +.page-container { + display: flex; + flex-direction: row; + gap: rem(10); + height: 96vh; + flex-wrap: wrap; +} + .top-panel { padding: rem(5); gap: rem(10); diff --git a/src/pages/StatisticsPage/ui/StatisticsPage.tsx b/src/pages/StatisticsPage/ui/StatisticsPage.tsx index 3436bf2..491d599 100644 --- a/src/pages/StatisticsPage/ui/StatisticsPage.tsx +++ b/src/pages/StatisticsPage/ui/StatisticsPage.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import { StatisticsTab, - StatisticsTabSegmentControl, -} from "../components/StatisticsTabSegmentControl/StatisticsTabSegmentControl.tsx"; + StatisticsTabSegmentedControl, +} from "../components/StatisticsTabSegmentedControl/StatisticsTabSegmentedControl.tsx"; import styles from "./StatisticsPage.module.css"; import PageBlock from "../../../components/PageBlock/PageBlock.tsx"; import { ProfitTab } from "../tabs/ProfitTab/ProfitTab.tsx"; @@ -25,13 +25,13 @@ export const StatisticsPage = () => { return (
- - setServiceType(parseInt(event))} - /> - + {/**/} + {/* setServiceType(parseInt(event))}*/} + {/* />*/} + {/**/} {getBody()}
);