Artem-Darius Weber 1 year ago
commit 469b618c41

@ -1,8 +1,11 @@
import { StrictMode } from 'react'; import {StrictMode} from 'react';
import * as ReactDOM from 'react-dom/client'; import * as ReactDOM from 'react-dom/client';
import App from './app/app'; import App from './app/app';
import {BrowserRouter} from "react-router-dom"; import {BrowserRouter} from "react-router-dom";
import {Provider} from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement document.getElementById('root') as HTMLElement
@ -10,7 +13,9 @@ const root = ReactDOM.createRoot(
root.render( root.render(
<StrictMode> <StrictMode>
<BrowserRouter> <BrowserRouter>
<App /> <Provider store={store}>
<App/>
</Provider>
</BrowserRouter> </BrowserRouter>
</StrictMode> </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

@ -0,0 +1,18 @@
import asyncio
from websockets.server import serve
async def handler(websocket):
async for message in websocket:
print('recv msg', message)
async def main():
async with serve(handler, '0.0.0.0', 8765):
await asyncio.Future() # run forever
asyncio.run(main())

@ -63,6 +63,7 @@ class ResponseAnalyticsFactory:
@classmethod @classmethod
def get_from_model_timeline_chart(cls, models: list[DBConveer]) -> list[TimelineCharts]: def get_from_model_timeline_chart(cls, models: list[DBConveer]) -> list[TimelineCharts]:
response = [] response = []
print(len(models))
for i in models: for i in models:
response.append(cls.get_from_model_timeline_charts(name='wood', value=i.wood, time=i.created_at)) response.append(cls.get_from_model_timeline_charts(name='wood', value=i.wood, time=i.created_at))
response.append(cls.get_from_model_timeline_charts(name='glass', value=i.glass, time=i.created_at)) response.append(cls.get_from_model_timeline_charts(name='glass', value=i.glass, time=i.created_at))

@ -7,6 +7,7 @@ Create Date: 2023-10-27 18:19:47.402501
""" """
import random import random
import time import time
from datetime import datetime, timedelta
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
@ -17,7 +18,8 @@ down_revision = None
branch_labels = None branch_labels = None
depends_on = None depends_on = None
def generate_normal_random(mean, std_dev):
return int(random.gauss(mean, std_dev))
def upgrade(): def upgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('camera', op.create_table('camera',
@ -48,10 +50,34 @@ def upgrade():
sa.UniqueConstraint('id', name=op.f('uq_conveer_id')) sa.UniqueConstraint('id', name=op.f('uq_conveer_id'))
) )
op.execute('''INSERT INTO camera(id,order_numb, camera_type) VALUES (1,1, 'По умолчанию')''') op.execute('''INSERT INTO camera(id,order_numb, camera_type) VALUES (1,1, 'По умолчанию')''')
for i in range(0, 12): import random
op.execute('''INSERT INTO conveer (metal, glass, plastic, wood, camera_id) VALUES (floor(random() * 20 + 1), from datetime import datetime, timedelta
floor(random() * 10 + 1), floor(random() * 15 + 1), floor(random() * 30 + 1), 1);''')
time.sleep(random.randint(0, 3)) # Устанавливаем параметры нормального распределения
mean_metal = 10 # Среднее значение для металла
std_dev_metal = 3 # Стандартное отклонение для металла
mean_glass = 5 # Среднее значение для стекла
std_dev_glass = 2 # Стандартное отклонение для стекла
mean_plastic = 7.5 # Среднее значение для пластика
std_dev_plastic = 2.5 # Стандартное отклонение для пластика
mean_wood = 15 # Среднее значение для дерева
std_dev_wood = 5 # Стандартное отклонение для дерева
for _ in range(0, 10):
random_date = datetime.now() - timedelta(days=random.randint(1, 365))
# Генерируем случайные значения, следующие нормальному распределению
metal = generate_normal_random(mean_metal, std_dev_metal)
glass = generate_normal_random(mean_glass, std_dev_glass)
plastic = generate_normal_random(mean_plastic, std_dev_plastic)
wood = generate_normal_random(mean_wood, std_dev_wood)
# Далее вы можете выполнить ваш SQL-запрос с этими случайными значениями
op.execute(f'''INSERT INTO conveer (metal, glass, plastic, wood, camera_id, created_at)
VALUES ({metal}, {glass}, {plastic}, {wood}, 1, '{random_date}');''')
def downgrade(): def downgrade():

1253
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -8,22 +8,27 @@
"@nestjs/common": "^10.0.2", "@nestjs/common": "^10.0.2",
"@nestjs/core": "^10.0.2", "@nestjs/core": "^10.0.2",
"@nestjs/platform-express": "^10.0.2", "@nestjs/platform-express": "^10.0.2",
"@reduxjs/toolkit": "^1.9.7",
"@swc/helpers": "~0.5.2", "@swc/helpers": "~0.5.2",
"@tanstack/react-query": "^5.0.5",
"@types/classnames": "^2.3.1", "@types/classnames": "^2.3.1",
"@types/react-icons": "^3.0.0",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"antd": "^5.10.2", "antd": "^5.10.2",
"axios": "^1.0.0", "axios": "^1.0.0",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "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-router-dom": "^6.17.0",
"react-switch-selector": "^2.2.1", "react-switch-selector": "^2.2.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0", "rxjs": "^7.8.0",
"tslib": "^2.3.0" "tslib": "^2.3.0",
"websocket": "^1.0.34"
}, },
"devDependencies": { "devDependencies": {
"@ant-design/plots": "^1.2.5",
"@babel/core": "^7.14.5", "@babel/core": "^7.14.5",
"@babel/preset-react": "^7.14.5", "@babel/preset-react": "^7.14.5",
"@nestjs/schematics": "^10.0.1", "@nestjs/schematics": "^10.0.1",

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

@ -1,13 +1,17 @@
import cls from './statistics.module.scss'; 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 */ /* eslint-disable-next-line */
export interface StatisticsProps {} export interface StatisticsProps {
}
export function Statistics(props: StatisticsProps) { export function Statistics(props: StatisticsProps) {
return ( return (
<div className={cls.container}> <div className={cls.container}>
<Filters/> <Filters/>
<StatisticsCharts/>
</div> </div>
); );
} }

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

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

@ -1,10 +1,28 @@
import cls from './camera.module.scss'; import cls from './camera.module.scss';
import {useEffect, useState} from "react";
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface CameraProps { export interface CameraProps {
} }
export function Camera(props: CameraProps) { export function Camera(props: CameraProps) {
const [randomNumber, setRandomNumber] = useState(null);
useEffect(() => {
// Создаем WebSocket-соединение
const ws = new WebSocket('ws://localhost:8765/');
// Обработка сообщений от сервера
ws.onmessage = (event) => {
const newRandomNumber = event.data;
setRandomNumber(newRandomNumber);
};
// Закрытие соединения при размонтировании компонента
return () => {
ws.close();
};
}, []);
return ( return (
<div className={cls.container}> <div className={cls.container}>
<div className={cls.select}> <div className={cls.select}>
@ -33,7 +51,7 @@ export function Camera(props: CameraProps) {
</div> </div>
</div> </div>
<div> <div>
here camera {randomNumber}
</div> </div>
</div> </div>
); );

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

@ -1,39 +1,73 @@
import cls from './filters.module.scss'; import React, {useEffect, useState} from "react";
import {ConfigProvider, DatePicker} from "antd"; import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
import utc from "dayjs/plugin/utc";
import SwitchSelector from "react-switch-selector"; import SwitchSelector from "react-switch-selector";
import dayjs from 'dayjs'; import {ConfigProvider, DatePicker} from "antd";
import customParseFormat from 'dayjs/plugin/customParseFormat'; import cls from "./filters.module.scss";
import {useState} from "react"; import {useGetDateMutation} from "../../../../apps/crud/src/store/services/statisticsFilterApi";
import {useAppDispatch} from "../../../../apps/crud/src/store";
/* eslint-disable-next-line */ import {setData, setType} from "../../../../apps/crud/src/store/features/statisticsFilterSlice";
enum timeFormatEnum { export enum timeFormatEnum {
DATE = "date", DATE = "date",
PERIOD = "period" PERIOD = "period",
} }
export function Filters() { export function Filters() {
const [timeFormat, setTimeFormat] = useState<timeFormatEnum>(timeFormatEnum.DATE) const [timeFormat, setTimeFormat] = useState<timeFormatEnum>(timeFormatEnum.DATE);
const [timeData, setTimeData] = useState<string>();
const [mutation, {data}] = useGetDateMutation();
const dispatch = useAppDispatch();
const options = [ const options = [
{ {
label: "Дата", label: "Дата",
value: "date", value: "date",
selectedBackgroundColor: "#94BF5E" selectedBackgroundColor: "#94BF5E",
}, },
{ {
label: "Период", label: "Период",
value: "period", value: "period",
selectedBackgroundColor: "#94BF5E" selectedBackgroundColor: "#94BF5E",
}, },
] ];
const handleChange = (time: never, timeString: string) => { dayjs.extend(customParseFormat);
console.log(timeString) dayjs.extend(utc);
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;
} }
const handleChange = (time: never, timeString: string) => {
setTimeData(timeString);
};
const onChange = (newValue: timeFormatEnum) => { const onChange = (newValue: timeFormatEnum) => {
setTimeFormat(newValue) 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]),
});
}
}
useEffect(() => {
if (data) {
dispatch(setData(data));
}
}, [data]);
return ( return (
<div className={cls.container}> <div className={cls.container}>
<div className="your-required-wrapper" style={{width: 200, height: 30}}> <div className="your-required-wrapper" style={{width: 200, height: 30}}>
@ -46,17 +80,30 @@ export function Filters() {
border={"1px solid #94BF5E"} border={"1px solid #94BF5E"}
/> />
</div> </div>
<ConfigProvider theme={{ <ConfigProvider
theme={{
token: { token: {
colorPrimary: "#94BF5E" colorPrimary: "#94BF5E",
} },
}}> }}
{timeFormat === timeFormatEnum.PERIOD ? >
<DatePicker.RangePicker placeholder={["Время начала", "Время конца"]} onChange={handleChange} showTime/> : {timeFormat === timeFormatEnum.PERIOD ? (
<DatePicker onChange={handleChange} showTime placeholder="Дата и время"/> <DatePicker.RangePicker
} placeholder={["Время начала", "Время конца"]}
onChange={handleChange}
showTime
/>
) : (
<DatePicker
onChange={handleChange}
showTime
placeholder="Дата и время"
/>
)}
</ConfigProvider> </ConfigProvider>
<button className={cls.button}>Показать</button> <button disabled={!timeData} onClick={handleData} className={cls.button}>
Показать
</button>
</div> </div>
); );
} }

@ -18,12 +18,16 @@
gap: 0 10px; gap: 0 10px;
justify-content: center; justify-content: center;
.text { .text {
text-align: end; display: flex;
align-items: center;
color: var(--gray); color: var(--gray);
font-size: 20px; font-size: 20px;
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
line-height: normal; line-height: normal;
& svg {
margin-right: 5px;
}
} }
.percent { .percent {
@ -39,6 +43,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; gap: 5px;
& p { & p {
padding: 0; padding: 0;
margin: 0; margin: 0;

@ -1,5 +1,6 @@
import styles from './sidebar-item.module.scss'; import styles from './sidebar-item.module.scss';
import Button from "../button/button"; import Button from "../button/button";
import {ImLeaf} from "react-icons/im"
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface SidebarItemProps { export interface SidebarItemProps {
@ -42,6 +43,7 @@ export function SidebarItem() {
<div className={styles.item} key={i * 2}> <div className={styles.item} key={i * 2}>
<div className={styles.heading}> <div className={styles.heading}>
<p className={styles.text}> <p className={styles.text}>
<ImLeaf color="#94BF5E"/>
{item.name} {item.name}
</p> </p>
<p className={styles.percent}> <p className={styles.percent}>

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

Loading…
Cancel
Save