Merge remote-tracking branch 'origin/statistics'
# Conflicts: # src/client/models/DealSchema.ts
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,11 +166,16 @@ export type { GetClientMarketplacesResponse } from './models/GetClientMarketplac
|
|||||||
export type { GetDealBillById } from './models/GetDealBillById';
|
export type { GetDealBillById } from './models/GetDealBillById';
|
||||||
export type { GetDealProductsBarcodesPdfRequest } from './models/GetDealProductsBarcodesPdfRequest';
|
export type { GetDealProductsBarcodesPdfRequest } from './models/GetDealProductsBarcodesPdfRequest';
|
||||||
export type { GetDealProductsBarcodesPdfResponse } from './models/GetDealProductsBarcodesPdfResponse';
|
export type { GetDealProductsBarcodesPdfResponse } from './models/GetDealProductsBarcodesPdfResponse';
|
||||||
|
export type { GetManagersResponse } from './models/GetManagersResponse';
|
||||||
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
||||||
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
|
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
|
||||||
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 { 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 { 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';
|
||||||
@@ -205,6 +210,9 @@ 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 { ProfitChartDataItem } from './models/ProfitChartDataItem';
|
||||||
|
export type { ProfitTableDataItem } from './models/ProfitTableDataItem';
|
||||||
|
export type { ProfitTableGroupBy } from './models/ProfitTableGroupBy';
|
||||||
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';
|
||||||
@@ -267,6 +275,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';
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
import type { UserSchema } from './UserSchema';
|
||||||
export type DealGeneralInfoSchema = {
|
export type DealGeneralInfoSchema = {
|
||||||
name: string;
|
name: string;
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
@@ -10,5 +11,6 @@ export type DealGeneralInfoSchema = {
|
|||||||
shippingWarehouse?: (string | null);
|
shippingWarehouse?: (string | null);
|
||||||
deliveryDate?: (string | null);
|
deliveryDate?: (string | null);
|
||||||
receivingSlotDate?: (string | null);
|
receivingSlotDate?: (string | null);
|
||||||
|
manager?: (UserSchema | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import type { ClientSchema } from './ClientSchema';
|
import type { ClientSchema } from './ClientSchema';
|
||||||
import type { DealBillRequestSchema } from './DealBillRequestSchema';
|
import type { DealBillRequestSchema } from './DealBillRequestSchema';
|
||||||
import type { DealGroupSchema } from './DealGroupSchema';
|
|
||||||
import type { DealProductSchema } from './DealProductSchema';
|
import type { DealProductSchema } from './DealProductSchema';
|
||||||
import type { DealServiceSchema } from './DealServiceSchema';
|
import type { DealServiceSchema } from './DealServiceSchema';
|
||||||
import type { DealStatusHistorySchema } from './DealStatusHistorySchema';
|
import type { DealStatusHistorySchema } from './DealStatusHistorySchema';
|
||||||
import type { ServicePriceCategorySchema } from './ServicePriceCategorySchema';
|
import type { ServicePriceCategorySchema } from './ServicePriceCategorySchema';
|
||||||
import type { ShippingWarehouseSchema } from './ShippingWarehouseSchema';
|
import type { ShippingWarehouseSchema } from './ShippingWarehouseSchema';
|
||||||
|
import type { UserSchema } from './UserSchema';
|
||||||
|
import type { DealGroupSchema } from './DealGroupSchema';
|
||||||
|
|
||||||
export type DealSchema = {
|
export type DealSchema = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -28,6 +30,8 @@ export type DealSchema = {
|
|||||||
billRequest?: (DealBillRequestSchema | null);
|
billRequest?: (DealBillRequestSchema | null);
|
||||||
category?: (ServicePriceCategorySchema | null);
|
category?: (ServicePriceCategorySchema | null);
|
||||||
group?: (DealGroupSchema | null);
|
group?: (DealGroupSchema | null);
|
||||||
|
manager?: (UserSchema | null);
|
||||||
|
|
||||||
deliveryDate?: (string | null);
|
deliveryDate?: (string | null);
|
||||||
receivingSlotDate?: (string | null);
|
receivingSlotDate?: (string | null);
|
||||||
};
|
};
|
||||||
|
|||||||
11
src/client/models/GetProfitChartDataRequest.ts
Normal file
11
src/client/models/GetProfitChartDataRequest.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type GetProfitChartDataRequest = {
|
||||||
|
dateRange: any[];
|
||||||
|
clientId: number;
|
||||||
|
baseMarketplaceKey: string;
|
||||||
|
dealStatusId: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/GetProfitChartDataResponse.ts
Normal file
9
src/client/models/GetProfitChartDataResponse.ts
Normal file
@@ -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<ProfitChartDataItem>;
|
||||||
|
};
|
||||||
|
|
||||||
10
src/client/models/GetProfitTableDataRequest.ts
Normal file
10
src/client/models/GetProfitTableDataRequest.ts
Normal file
@@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/GetProfitTableDataResponse.ts
Normal file
9
src/client/models/GetProfitTableDataResponse.ts
Normal file
@@ -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<ProfitTableDataItem>;
|
||||||
|
};
|
||||||
|
|
||||||
11
src/client/models/ProfitChartDataItem.ts
Normal file
11
src/client/models/ProfitChartDataItem.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type ProfitChartDataItem = {
|
||||||
|
date: string;
|
||||||
|
revenue: number;
|
||||||
|
profit: number;
|
||||||
|
dealsCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
11
src/client/models/ProfitTableDataItem.ts
Normal file
11
src/client/models/ProfitTableDataItem.ts
Normal file
@@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
5
src/client/models/ProfitTableGroupBy.ts
Normal file
5
src/client/models/ProfitTableGroupBy.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type ProfitTableGroupBy = 0 | 1 | 2 | 3 | 4;
|
||||||
53
src/client/services/StatisticsService.ts
Normal file
53
src/client/services/StatisticsService.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
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 Chart Data
|
||||||
|
* @returns GetProfitChartDataResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getProfitChartData({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: GetProfitChartDataRequest,
|
||||||
|
}): CancelablePromise<GetProfitChartDataResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
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<GetProfitTableDataResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/statistics/get-profit-table-data',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
import type { CreateUserRequest } from '../models/CreateUserRequest';
|
import type { CreateUserRequest } from '../models/CreateUserRequest';
|
||||||
import type { CreateUserResponse } from '../models/CreateUserResponse';
|
import type { CreateUserResponse } from '../models/CreateUserResponse';
|
||||||
import type { GetAllUsersResponse } from '../models/GetAllUsersResponse';
|
import type { GetAllUsersResponse } from '../models/GetAllUsersResponse';
|
||||||
|
import type { GetManagersResponse } from '../models/GetManagersResponse';
|
||||||
import type { UpdateUserRequest } from '../models/UpdateUserRequest';
|
import type { UpdateUserRequest } from '../models/UpdateUserRequest';
|
||||||
import type { UpdateUserResponse } from '../models/UpdateUserResponse';
|
import type { UpdateUserResponse } from '../models/UpdateUserResponse';
|
||||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||||
@@ -62,4 +63,15 @@ export class UserService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get Managers
|
||||||
|
* @returns GetManagersResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getManagers(): CancelablePromise<GetManagersResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/user/get-managers',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/components/ManagerSelect/ManagerSelect.tsx
Normal file
23
src/components/ManagerSelect/ManagerSelect.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import ObjectSelect, { ObjectSelectProps } from "../ObjectSelect/ObjectSelect.tsx";
|
||||||
|
import { UserSchema } from "../../client";
|
||||||
|
import useManagersList from "../../pages/LeadsPage/hooks/useManagersList.tsx";
|
||||||
|
|
||||||
|
type Props = Omit<
|
||||||
|
ObjectSelectProps<UserSchema | null>,
|
||||||
|
"data" | "getValueFn" | "getLabelFn"
|
||||||
|
>;
|
||||||
|
const UserSelect: FC<Props> = props => {
|
||||||
|
const { objects: managers } = useManagersList();
|
||||||
|
return (
|
||||||
|
<ObjectSelect
|
||||||
|
data={managers}
|
||||||
|
getLabelFn={(manager: UserSchema) => `${manager.firstName} ${manager.secondName}`}
|
||||||
|
getValueFn={(manager: UserSchema) => manager.id.toString()}
|
||||||
|
clearable
|
||||||
|
{...props}
|
||||||
|
onClear={() => props.onChange(null)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default UserSelect;
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { IconBarcode, IconPrinter } from "@tabler/icons-react";
|
|||||||
import styles from "../../../ui/LeadsPage.module.css";
|
import styles from "../../../ui/LeadsPage.module.css";
|
||||||
import { base64ToBlob } from "../../../../../shared/lib/utils.ts";
|
import { base64ToBlob } from "../../../../../shared/lib/utils.ts";
|
||||||
import { DatePickerInput } from "@mantine/dates";
|
import { DatePickerInput } from "@mantine/dates";
|
||||||
|
import ManagerSelect from "../../../../../components/ManagerSelect/ManagerSelect.tsx";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
deal: DealSchema;
|
deal: DealSchema;
|
||||||
@@ -211,6 +212,11 @@ const Content: FC<Props> = ({ deal }) => {
|
|||||||
{...form.getInputProps("receivingSlotDate")}
|
{...form.getInputProps("receivingSlotDate")}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
<ManagerSelect
|
||||||
|
placeholder={"Укажите менеджера"}
|
||||||
|
label={"Менеджер"}
|
||||||
|
{...form.getInputProps("manager")}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<Flex
|
<Flex
|
||||||
|
|||||||
11
src/pages/LeadsPage/hooks/useManagersList.tsx
Normal file
11
src/pages/LeadsPage/hooks/useManagersList.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import ObjectList from "../../../hooks/objectList.tsx";
|
||||||
|
import { UserService } from "../../../client";
|
||||||
|
|
||||||
|
const useManagersList = () =>
|
||||||
|
ObjectList({
|
||||||
|
queryFn: UserService.getManagers,
|
||||||
|
getObjectsFn: response => response.managers,
|
||||||
|
queryKey: "getManagers",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useManagersList;
|
||||||
@@ -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 StatisticsTabSegmentedControl: 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";
|
||||||
12
src/pages/StatisticsPage/tabs/ProfitTab/ProfitTab.tsx
Normal file
12
src/pages/StatisticsPage/tabs/ProfitTab/ProfitTab.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ProfitChart } from "./components/ProfitChart/ProfitChart.tsx";
|
||||||
|
import styles from "../../ui/StatisticsPage.module.css";
|
||||||
|
import { ProfitTable } from "./components/ProfitTable/ProfitTable.tsx";
|
||||||
|
|
||||||
|
export const ProfitTab = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles["page-container"]}>
|
||||||
|
<ProfitChart />
|
||||||
|
<ProfitTable />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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<ObjectSelectProps<ClientSchema>, "data">;
|
||||||
|
onClientClear?: () => void;
|
||||||
|
|
||||||
|
baseMarketplaceSelectProps?: Omit<
|
||||||
|
ObjectSelectProps<BaseMarketplaceSchema>,
|
||||||
|
"data" | "getValueFn" | "getLabelFn"
|
||||||
|
>;
|
||||||
|
onBaseMarketplaceClear?: () => void;
|
||||||
|
|
||||||
|
dealStatusSelectProps?: Omit<ObjectSelectProps<DealStatusType>, "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 (
|
||||||
|
<Stack mb={"lg"}>
|
||||||
|
{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={"Выберите маркетплейс"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{groupTableByProps &&
|
||||||
|
<>
|
||||||
|
<Text>Группировать:</Text>
|
||||||
|
<ProfitTableSegmentedControl
|
||||||
|
{...groupTableByProps}
|
||||||
|
orientation={"vertical"}
|
||||||
|
size={"md"}
|
||||||
|
w={"100%"}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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 (
|
||||||
|
<PageBlock style={{ flex: 3, minWidth: "600px", padding: "25px" }}>
|
||||||
|
<ProfitChartFiltersModal
|
||||||
|
form={form}
|
||||||
|
/>
|
||||||
|
<Skeleton visible={isLoading}>
|
||||||
|
<Stack gap={"xl"}>
|
||||||
|
{getChartsSeries.map((series, idx) => {
|
||||||
|
return (
|
||||||
|
<AreaChart
|
||||||
|
my={"sm"}
|
||||||
|
w={"98%"}
|
||||||
|
h={"39vh"}
|
||||||
|
data={profitData}
|
||||||
|
dataKey="date"
|
||||||
|
unit={units[idx]}
|
||||||
|
tooltipAnimationDuration={200}
|
||||||
|
valueFormatter={(value) => new Intl.NumberFormat("ru-RU").format(value)}
|
||||||
|
series={series}
|
||||||
|
fillOpacity={0.5}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
</Skeleton>
|
||||||
|
</PageBlock>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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<ChartFormFilters>({
|
||||||
|
mode: "controlled",
|
||||||
|
initialValues: {
|
||||||
|
dateRange: getDefaultDates(),
|
||||||
|
client: null,
|
||||||
|
marketplace: null,
|
||||||
|
dealStatus: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [profitData, setProfitData] = useState<ProfitChartDataItem[]>([]);
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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 (
|
||||||
|
<PageBlock style={{ flex: 2, minWidth: "600px", padding: "25px" }}>
|
||||||
|
<ProfitTableFiltersModal form={form} />
|
||||||
|
<Skeleton visible={isLoading}>
|
||||||
|
<MantineReactTable table={table} />
|
||||||
|
</Skeleton>
|
||||||
|
</PageBlock>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
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<MRT_ColumnDef<ProfitTableDataItem>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
size: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dealsCount",
|
||||||
|
header: "Кол-во",
|
||||||
|
size: 40,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "profit",
|
||||||
|
header: "Прибыль",
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.profit.toLocaleString("ru-RU") + "₽",
|
||||||
|
size: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "revenue",
|
||||||
|
header: "Выручка",
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.revenue.toLocaleString("ru-RU") + "₽",
|
||||||
|
size: 50,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[groupTableBy],
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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 };
|
||||||
|
};
|
||||||
@@ -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<TableFormFilters>({
|
||||||
|
mode: "controlled",
|
||||||
|
initialValues: {
|
||||||
|
dateRange: getDefaultDates(),
|
||||||
|
groupTableBy: GroupStatisticsTable.BY_DATES,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const [profitData, setProfitData] = useState<ProfitTableDataItem[]>([]);
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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 ProfitTableSegmentedControl: FC<Props> = props => {
|
||||||
|
return (
|
||||||
|
<SegmentedControl
|
||||||
|
data={data}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Button, Group, Modal } from "@mantine/core";
|
||||||
|
import { IconFilter } from "@tabler/icons-react";
|
||||||
|
import { Filters } from "../components/Filters/Filters.tsx";
|
||||||
|
import { UseFormReturnType } from "@mantine/form";
|
||||||
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
|
import { ChartFormFilters } from "../../../types/ChartFormFilters.ts";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
form: UseFormReturnType<ChartFormFilters>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProfitChartFiltersModal = ({ form }: Props) => {
|
||||||
|
const [opened, { open, close }] = useDisclosure();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={open}
|
||||||
|
mb={"lg"}
|
||||||
|
>
|
||||||
|
<Group gap={"xs"}>
|
||||||
|
<IconFilter />
|
||||||
|
Фильтры графиков
|
||||||
|
</Group>
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={close}
|
||||||
|
title={"Фильтры графиков"}
|
||||||
|
>
|
||||||
|
<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)}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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<TableFormFilters>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProfitTableFiltersModal = ({ form }: Props) => {
|
||||||
|
const [opened, { open, close }] = useDisclosure();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={open}
|
||||||
|
mb={"lg"}
|
||||||
|
>
|
||||||
|
<Group gap={"xs"}>
|
||||||
|
<IconFilter />
|
||||||
|
Фильтры таблицы
|
||||||
|
</Group>
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={close}
|
||||||
|
title={"Фильтры таблицы"}
|
||||||
|
>
|
||||||
|
<Filters
|
||||||
|
datePickerProps={form.getInputProps("dateRange")}
|
||||||
|
groupTableByProps={{
|
||||||
|
value: form.values.groupTableBy.toString(),
|
||||||
|
onChange: (value: string) => form.setFieldValue("groupTableBy", parseInt(value)),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
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/TableFormFilters.ts
Normal file
6
src/pages/StatisticsPage/types/TableFormFilters.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { GroupStatisticsTable } from "../tabs/ProfitTab/components/ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx";
|
||||||
|
|
||||||
|
export interface TableFormFilters {
|
||||||
|
dateRange: [Date | null, Date | null];
|
||||||
|
groupTableBy: GroupStatisticsTable;
|
||||||
|
}
|
||||||
20
src/pages/StatisticsPage/ui/StatisticsPage.module.css
Normal file
20
src/pages/StatisticsPage/ui/StatisticsPage.module.css
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
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);
|
||||||
|
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,
|
||||||
|
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";
|
||||||
|
|
||||||
|
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>*/}
|
||||||
|
{/* <StatisticsTabSegmentedControl*/}
|
||||||
|
{/* 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