Artem-Darius Weber 1 year ago
commit 469b618c41

@ -1,16 +1,21 @@
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
); );
root.render( root.render(
<StrictMode> <StrictMode>
<BrowserRouter> <BrowserRouter>
<App /> <Provider store={store}>
</BrowserRouter> <App/>
</StrictMode> </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

@ -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,15 +1,19 @@
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/>
</div> <StatisticsCharts/>
); </div>
);
} }
export default Statistics; export default Statistics;

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

@ -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,42 +1,60 @@
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) {
return ( const [randomNumber, setRandomNumber] = useState(null);
<div className={cls.container}>
<div className={cls.select}> useEffect(() => {
<div> // Создаем WebSocket-соединение
<p className={cls.date}> const ws = new WebSocket('ws://localhost:8765/');
2020-23-21
</p> // Обработка сообщений от сервера
<p className={cls.time}> ws.onmessage = (event) => {
14:32 const newRandomNumber = event.data;
</p> setRandomNumber(newRandomNumber);
</div> };
<span className={cls.line}></span>
<div> // Закрытие соединения при размонтировании компонента
<p className={cls.name}> return () => {
Cum ws.close();
</p> };
<p className={cls.number}> }, []);
69 return (
</p> <div className={cls.container}>
<svg width="24" height="22" viewBox="0 0 24 22" fill="none" xmlns="http://www.w3.org/2000/svg"> <div className={cls.select}>
<path d="M21 9.16699H3" stroke="black" strokeLinecap="round" strokeLinejoin="round"/> <div>
<path d="M21 5.5H3" stroke="black" strokeLinecap="round" strokeLinejoin="round"/> <p className={cls.date}>
<path d="M21 12.833H3" stroke="black" strokeLinecap="round" strokeLinejoin="round"/> 2020-23-21
<path d="M21 16.5H3" stroke="black" strokeLinecap="round" strokeLinejoin="round"/> </p>
</svg> <p className={cls.time}>
14:32
</p>
</div>
<span className={cls.line}></span>
<div>
<p className={cls.name}>
Cum
</p>
<p className={cls.number}>
69
</p>
<svg width="24" height="22" viewBox="0 0 24 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 9.16699H3" stroke="black" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M21 5.5H3" stroke="black" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M21 12.833H3" stroke="black" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M21 16.5H3" stroke="black" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
</div>
<div>
{randomNumber}
</div>
</div> </div>
</div> );
<div>
here camera
</div>
</div>
);
} }
export default Camera; export default Camera;

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

@ -1,59 +1,64 @@
.item { .item {
background: var(--white); background: var(--white);
border-radius: var(--border-medium); border-radius: var(--border-medium);
padding: 10px 20px; padding: 10px 20px;
display: flex; display: flex;
gap: 0 8px; gap: 0 8px;
justify-content: space-between; justify-content: space-between;
} }
.line { .line {
width: 1px; width: 1px;
background-color: var(--black); background-color: var(--black);
} }
.heading { .heading {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0 10px; gap: 0 10px;
justify-content: center; justify-content: center;
.text { .text {
text-align: end; display: flex;
color: var(--gray); align-items: center;
font-size: 20px; color: var(--gray);
font-style: normal; font-size: 20px;
font-weight: 300; font-style: normal;
line-height: normal; font-weight: 300;
} line-height: normal;
& svg {
margin-right: 5px;
}
}
.percent { .percent {
font-size: 54px; font-size: 54px;
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
line-height: normal; line-height: normal;
} }
} }
.info { .info {
padding: 10px; padding: 10px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; gap: 5px;
& p {
padding: 0;
margin: 0;
font-size: 36px;
font-style: normal;
font-weight: 300;
line-height: normal;
color: var(--black
);
}
& span { & p {
font-size: 20px; padding: 0;
font-style: normal; margin: 0;
font-weight: 300; font-size: 36px;
line-height: normal; 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 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 {
name: string name: string
percent: number | string percent: number | string
weight: number | string weight: number | string
href: string href: string
} }
export function SidebarItem() { export function SidebarItem() {
const obj: SidebarItemProps[] = [ const obj: SidebarItemProps[] = [
{ {
name: "Дерево", name: "Дерево",
href: "some", href: "some",
percent: "10%", percent: "10%",
weight: 200 weight: 200
}, },
{ {
name: "Металл", name: "Металл",
href: "some", href: "some",
percent: "10%", percent: "10%",
weight: 200 weight: 200
}, },
{ {
name: "Пластик", name: "Пластик",
href: "some", href: "some",
percent: "10%", percent: "10%",
weight: 200 weight: 200
}, },
{ {
name: "Стекло", name: "Стекло",
href: "some", href: "some",
percent: "10%", percent: "10%",
weight: 200 weight: 200
} }
] ]
return ( return (
<> <>
{obj.map((item, i) => ( {obj.map((item, i) => (
<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}>
{item.name} <ImLeaf color="#94BF5E"/>
</p> {item.name}
<p className={styles.percent}> </p>
{item.percent} <p className={styles.percent}>
</p> {item.percent}
</div> </p>
<span className={styles.line}></span> </div>
<div className={styles.info}> <span className={styles.line}></span>
<p>{item.weight}</p> <div className={styles.info}>
<span>Тонн</span> <p>{item.weight}</p>
<Button link={"some"} className={styles.link}>Аналитика</Button> <span>Тонн</span>
</div> <Button link={"some"} className={styles.link}>Аналитика</Button>
</div> </div>
))} </div>
</> ))}
); </>
);
} }
export default SidebarItem; export default SidebarItem;

@ -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