add statistics

main
Anton Zhuravlev 1 year ago
parent 9100449a61
commit dc55a46cf9

@ -1,16 +1,21 @@
import { StrictMode } from 'react';
import {StrictMode} from 'react';
import * as ReactDOM from 'react-dom/client';
import App from './app/app';
import {BrowserRouter} from "react-router-dom";
import {Provider} from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
document.getElementById('root') as HTMLElement
);
root.render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
<StrictMode>
<BrowserRouter>
<Provider store={store}>
<App/>
</Provider>
</BrowserRouter>
</StrictMode>
);

@ -0,0 +1,32 @@
import {createSlice} from "@reduxjs/toolkit";
import {timeFormatEnum} from "@perm-hack/ui";
export interface IStatFilters {
data: {
start_date: string
} | {
start_data: string,
end_date: string
} | {}
type: "date" | "period"
}
const initialState: IStatFilters = {
data: {},
type: "date"
}
const statisticsFilterSlice = createSlice({
name: "statFilter",
initialState,
reducers: {
setData: (state, action) => {
state.data = action.payload;
},
setType: (state, action) => {
state.type = state.type === "date" ? "period" : "date"
}
}
})
export const {setData, setType} = statisticsFilterSlice.actions;
export default statisticsFilterSlice.reducer

@ -0,0 +1,20 @@
import {configureStore} from "@reduxjs/toolkit"
import {TypedUseSelectorHook, useDispatch, useSelector} from "react-redux";
import statisticsFilterSlice from "./features/statisticsFilterSlice";
import {statisticsFilterApi} from "./services/statisticsFilterApi";
const store = configureStore({
reducer: {
statFilter: statisticsFilterSlice,
[statisticsFilterApi.reducerPath]: statisticsFilterApi.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(statisticsFilterApi.middleware),
})
export default store;
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch: (data: any) => AppDispatch = useDispatch // Export a hook that can be reused to resolve types
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

@ -0,0 +1,16 @@
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/query/react";
export const statisticsFilterApi = createApi({
reducerPath: "statFilterApi",
baseQuery: fetchBaseQuery({baseUrl: "https://ayin.k-lab.su/api/analytics"}),
endpoints: (build) => ({
getDate: build.mutation({
query: (data) => ({
url: '/all',
method: 'POST',
body: data,
}),
}),
}),
})
export const {useGetDateMutation} = statisticsFilterApi

1083
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -8,15 +8,18 @@
"@nestjs/common": "^10.0.2",
"@nestjs/core": "^10.0.2",
"@nestjs/platform-express": "^10.0.2",
"@reduxjs/toolkit": "^1.9.7",
"@swc/helpers": "~0.5.2",
"@tanstack/react-query": "^5.0.5",
"@types/classnames": "^2.3.1",
"@types/react-icons": "^3.0.0",
"@types/react-router-dom": "^5.3.3",
"antd": "^5.10.2",
"axios": "^1.0.0",
"classnames": "^2.3.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.11.0",
"react-redux": "^8.1.3",
"react-router-dom": "^6.17.0",
"react-switch-selector": "^2.2.1",
"reflect-metadata": "^0.1.13",
@ -24,6 +27,7 @@
"tslib": "^2.3.0"
},
"devDependencies": {
"@ant-design/plots": "^1.2.5",
"@babel/core": "^7.14.5",
"@babel/preset-react": "^7.14.5",
"@nestjs/schematics": "^10.0.1",

@ -1,5 +1,3 @@
.container {
padding: 20px;
display: flex;
flex-direction: column;
}

@ -1,15 +1,19 @@
import cls from './statistics.module.scss';
import {Filters} from "@perm-hack/ui";
// eslint-disable-next-line @nx/enforce-module-boundaries
import {Filters, Sidebar, StatisticsCharts} from "@perm-hack/ui";
import {useState} from "react";
/* eslint-disable-next-line */
export interface StatisticsProps {}
export interface StatisticsProps {
}
export function Statistics(props: StatisticsProps) {
return (
<div className={cls.container}>
<Filters/>
</div>
);
return (
<div className={cls.container}>
<Filters/>
<StatisticsCharts/>
</div>
);
}
export default Statistics;

@ -1,23 +1,23 @@
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@perm-hack/page": ["page/src/index.ts"],
"@perm-hack/ui": ["ui/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@perm-hack/page": ["page/src/index.ts"],
"@perm-hack/ui": ["ui/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]
}

@ -1,3 +1,4 @@
export * from './lib/statistics-charts/statistics-charts';
export * from './lib/filters/filters';
export * from './lib/button/button';
export * from './lib/sidebar-item/sidebar-item';

@ -1,7 +1,7 @@
.container {
display: flex;
flex-direction: column;
align-items: end;
align-items: flex-end;
flex: 1 1 auto;
padding: 20px;
background-color: var(--white);

@ -1,12 +1,18 @@
.container {
grid-area: 1 / 1 / 2 / 6;
display: flex;
flex-direction: row;
gap: 30px;
}
.button {
font-size: 14px;
border-radius: 20px;
padding: 5px 10px;
color: var(--white);
background-color: #94BF5E;
&:disabled {
background-color: #eee;
}
}

@ -1,64 +1,111 @@
import cls from './filters.module.scss';
import {ConfigProvider, DatePicker} from "antd";
import React, {useEffect, useState} from "react";
import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
import utc from "dayjs/plugin/utc";
import SwitchSelector from "react-switch-selector";
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import {useState} from "react";
/* eslint-disable-next-line */
import {ConfigProvider, DatePicker} from "antd";
import cls from "./filters.module.scss";
import {useGetDateMutation} from "../../../../apps/crud/src/store/services/statisticsFilterApi";
import {useAppDispatch} from "../../../../apps/crud/src/store";
import {setData, setType} from "../../../../apps/crud/src/store/features/statisticsFilterSlice";
enum timeFormatEnum {
DATE = "date",
PERIOD = "period"
export enum timeFormatEnum {
DATE = "date",
PERIOD = "period",
}
export function Filters() {
const [timeFormat, setTimeFormat] = useState<timeFormatEnum>(timeFormatEnum.DATE)
const options = [
{
label: "Дата",
value: "date",
selectedBackgroundColor: "#94BF5E"
},
{
label: "Период",
value: "period",
selectedBackgroundColor: "#94BF5E"
},
]
const [timeFormat, setTimeFormat] = useState<timeFormatEnum>(timeFormatEnum.DATE);
const [timeData, setTimeData] = useState<string>();
const [mutation, {data}] = useGetDateMutation();
const dispatch = useAppDispatch();
const options = [
{
label: "Дата",
value: "date",
selectedBackgroundColor: "#94BF5E",
},
{
label: "Период",
value: "period",
selectedBackgroundColor: "#94BF5E",
},
];
dayjs.extend(customParseFormat);
dayjs.extend(utc);
const handleChange = (time: never, timeString: string) => {
console.log(timeString)
}
const onChange = (newValue: timeFormatEnum) => {
setTimeFormat(newValue)
};
function convertToISO8601(timeString: string): string {
const parsedTime = dayjs(timeString, {format: "YYYY-MM-DD HH:mm:ss"});
const iso8601Time = parsedTime.utc().format("YYYY-MM-DDTHH:mm:ss.SSS[Z]");
return iso8601Time;
}
return (
<div className={cls.container}>
<div className="your-required-wrapper" style={{width: 200, height: 30}}>
<SwitchSelector
onChange={onChange}
options={options}
fontSize={16}
backgroundColor={"#fff"}
fontColor={"#94BF5E"}
border={"1px solid #94BF5E"}
/>
</div>
<ConfigProvider theme={{
token: {
colorPrimary: "#94BF5E"
const handleChange = (time: never, timeString: string) => {
setTimeData(timeString);
};
const onChange = (newValue: timeFormatEnum) => {
setTimeFormat(newValue);
dispatch(setType())
};
async function handleData() {
if (timeFormat === timeFormatEnum.DATE) {
mutation({start_date: convertToISO8601(timeData)});
} else {
mutation({
start_date: convertToISO8601(timeData[0]),
end_date: convertToISO8601(timeData[1]),
});
}
}}>
{timeFormat === timeFormatEnum.PERIOD ?
<DatePicker.RangePicker placeholder={["Время начала", "Время конца"]} onChange={handleChange} showTime/> :
<DatePicker onChange={handleChange} showTime placeholder="Дата и время"/>
}
useEffect(() => {
if (data) {
dispatch(setData(data));
}
</ConfigProvider>
<button className={cls.button}>Показать</button>
</div>
);
}, [data]);
return (
<div className={cls.container}>
<div className="your-required-wrapper" style={{width: 200, height: 30}}>
<SwitchSelector
onChange={onChange}
options={options}
fontSize={16}
backgroundColor={"#fff"}
fontColor={"#94BF5E"}
border={"1px solid #94BF5E"}
/>
</div>
<ConfigProvider
theme={{
token: {
colorPrimary: "#94BF5E",
},
}}
>
{timeFormat === timeFormatEnum.PERIOD ? (
<DatePicker.RangePicker
placeholder={["Время начала", "Время конца"]}
onChange={handleChange}
showTime
/>
) : (
<DatePicker
onChange={handleChange}
showTime
placeholder="Дата и время"
/>
)}
</ConfigProvider>
<button disabled={!timeData} onClick={handleData} className={cls.button}>
Показать
</button>
</div>
);
}
export default Filters;

@ -1,59 +1,64 @@
.item {
background: var(--white);
border-radius: var(--border-medium);
padding: 10px 20px;
display: flex;
gap: 0 8px;
justify-content: space-between;
background: var(--white);
border-radius: var(--border-medium);
padding: 10px 20px;
display: flex;
gap: 0 8px;
justify-content: space-between;
}
.line {
width: 1px;
background-color: var(--black);
width: 1px;
background-color: var(--black);
}
.heading {
display: flex;
flex-direction: column;
gap: 0 10px;
justify-content: center;
.text {
text-align: end;
color: var(--gray);
font-size: 20px;
font-style: normal;
font-weight: 300;
line-height: normal;
}
display: flex;
flex-direction: column;
gap: 0 10px;
justify-content: center;
.text {
display: flex;
align-items: center;
color: var(--gray);
font-size: 20px;
font-style: normal;
font-weight: 300;
line-height: normal;
& svg {
margin-right: 5px;
}
}
.percent {
font-size: 54px;
font-style: normal;
font-weight: 300;
line-height: normal;
}
.percent {
font-size: 54px;
font-style: normal;
font-weight: 300;
line-height: normal;
}
}
.info {
padding: 10px;
display: flex;
flex-direction: column;
gap: 5px;
& p {
padding: 0;
margin: 0;
font-size: 36px;
font-style: normal;
font-weight: 300;
line-height: normal;
color: var(--black
);
}
padding: 10px;
display: flex;
flex-direction: column;
gap: 5px;
& span {
font-size: 20px;
font-style: normal;
font-weight: 300;
line-height: normal;
}
& p {
padding: 0;
margin: 0;
font-size: 36px;
font-style: normal;
font-weight: 300;
line-height: normal;
color: var(--black
);
}
& span {
font-size: 20px;
font-style: normal;
font-weight: 300;
line-height: normal;
}
}

@ -1,63 +1,65 @@
import styles from './sidebar-item.module.scss';
import Button from "../button/button";
import {ImLeaf} from "react-icons/im"
/* eslint-disable-next-line */
export interface SidebarItemProps {
name: string
percent: number | string
weight: number | string
href: string
name: string
percent: number | string
weight: number | string
href: string
}
export function SidebarItem() {
const obj: SidebarItemProps[] = [
{
name: "Дерево",
href: "some",
percent: "10%",
weight: 200
},
{
name: "Металл",
href: "some",
percent: "10%",
weight: 200
},
{
name: "Пластик",
href: "some",
percent: "10%",
weight: 200
},
{
name: "Стекло",
href: "some",
percent: "10%",
weight: 200
}
]
return (
<>
{obj.map((item, i) => (
<div className={styles.item} key={i * 2}>
<div className={styles.heading}>
<p className={styles.text}>
{item.name}
</p>
<p className={styles.percent}>
{item.percent}
</p>
</div>
<span className={styles.line}></span>
<div className={styles.info}>
<p>{item.weight}</p>
<span>Тонн</span>
<Button link={"some"} className={styles.link}>Аналитика</Button>
</div>
</div>
))}
</>
);
const obj: SidebarItemProps[] = [
{
name: "Дерево",
href: "some",
percent: "10%",
weight: 200
},
{
name: "Металл",
href: "some",
percent: "10%",
weight: 200
},
{
name: "Пластик",
href: "some",
percent: "10%",
weight: 200
},
{
name: "Стекло",
href: "some",
percent: "10%",
weight: 200
}
]
return (
<>
{obj.map((item, i) => (
<div className={styles.item} key={i * 2}>
<div className={styles.heading}>
<p className={styles.text}>
<ImLeaf color="#94BF5E"/>
{item.name}
</p>
<p className={styles.percent}>
{item.percent}
</p>
</div>
<span className={styles.line}></span>
<div className={styles.info}>
<p>{item.weight}</p>
<span>Тонн</span>
<Button link={"some"} className={styles.link}>Аналитика</Button>
</div>
</div>
))}
</>
);
}
export default SidebarItem;

@ -1,5 +1,5 @@
.container {
flex: 0 0 20%;
flex: 0 0 25%;
display: flex;
flex-direction: column;
gap: 30px;

Loading…
Cancel
Save