Merge branch 'main' of https://github.com/perm-hack/perm-hack
	
		
	
				
					
				
			
						commit
						469b618c41
					
				@ -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
 | 
				
			||||
@ -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())
 | 
				
			||||
@ -0,0 +1 @@
 | 
				
			||||
websockets
 | 
				
			||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -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,42 +1,60 @@
 | 
				
			||||
import cls from './camera.module.scss';
 | 
				
			||||
import {useEffect, useState} from "react";
 | 
				
			||||
 | 
				
			||||
/* eslint-disable-next-line */
 | 
				
			||||
export interface CameraProps {
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
export function Camera(props: CameraProps) {
 | 
				
			||||
  return (
 | 
				
			||||
    <div className={cls.container}>
 | 
				
			||||
      <div className={cls.select}>
 | 
				
			||||
        <div>
 | 
				
			||||
          <p className={cls.date}>
 | 
				
			||||
            2020-23-21
 | 
				
			||||
          </p>
 | 
				
			||||
          <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>
 | 
				
			||||
    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 (
 | 
				
			||||
        <div className={cls.container}>
 | 
				
			||||
            <div className={cls.select}>
 | 
				
			||||
                <div>
 | 
				
			||||
                    <p className={cls.date}>
 | 
				
			||||
                        2020-23-21
 | 
				
			||||
                    </p>
 | 
				
			||||
                    <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>
 | 
				
			||||
        here camera
 | 
				
			||||
      </div>
 | 
				
			||||
    </div>
 | 
				
			||||
  );
 | 
				
			||||
    );
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
export default Camera;
 | 
				
			||||
 | 
				
			||||
@ -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,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;
 | 
				
			||||
    }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue