diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index c165a3a..0000000 --- a/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore artifacts: -build -node_modules -*.html diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 7d97f6e..0000000 --- a/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "semi": true, - "trailingComma": "all", - "singleQuote": true, - "printWidth": 120, - "tabWidth": 2, - "useTabs": false -} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..dde884c --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +## Демо + +--- + +## Запуск проекта + +Прежде чем запускать проект, необходимо создать файл `.env` в корневой директории проекта. В этом файле укажите следующую конфигурацию для подключения к Firebase: + +```.env +FIREBASE_API_KEY = "apiKey" +FIREBASE_API_AUTH_DOMAIN = "authDomain" +FIREBASE_API_PROJECT_ID = "projectId" +FIREBASE_API_STORAGE_BUCKET = "storageBucket" +FIREBASE_API_MESSAGING_SENDER_ID = "messagingSenderId" +FIREBASE_API_APP_ID = "appId" +``` + +Замените "apiKey", "authDomain", "projectId", "storageBucket", "messagingSenderId", и "appId" соответственно вашими значениями, полученными от Firebase. + +Эти параметры необходимы для правильной идентификации вашего проекта Firebase и его подключения к приложению. + +Ссылка на документацию - [Add Firebase to your JavaScript project](https://firebase.google.com/docs/web/setup) + +``` +npm install - устанавливаем зависимости +npm start - запуск UI + +``` + +--- + +## Скрипты + +- `npm run start` - Запуск frontend проекта на webpack dev server +- `npm run build:prod` - Сборка в prod режиме +- `npm run build:dev` - Сборка в dev режиме (не минимизирован) + +--- + +## Архитектура проекта + +Проект написан в соответствии с методологией Feature Sliced Design + +Ссылка на документацию - [Feature Sliced Design](https://feature-sliced.design/docs/get-started/tutorial) + +--- + +## Конфигурация проекта + +Для разработки проект содержит Webpack конфиг + +Вся конфигурация хранится в ./config + +--- + +### Работа с данными + +Взаимодействие с данными осуществляется с помощью Redux Toolkit + +В качестве авторизации используется сервис Firebase Authentication +Ссылка на документацию - [Firebase Authentication](https://firebase.google.com/docs/auth) + +В качестве базы данных предприятий / установок используется сервис Firebase Realtime Database +Ссылка на документацию - [Firebase Realtime Database](https://firebase.google.com/docs/database) + +В качестве базы данных статичных файлов используется сервис Cloud Storage for Firebase +Ссылка на документацию - [Cloud Storage for Firebase](https://firebase.google.com/docs/storage) + +--- diff --git a/package-lock.json b/package-lock.json index d9cab47..2ffc0ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,14 @@ "@ant-design/icons": "^5.2.6", "@reduxjs/toolkit": "^1.8.0", "antd": "^5.10.0", + "axios": "^1.6.8", "firebase": "^10.6.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-redux": "^7.2.6", - "react-router-dom": "^6.2.1" + "react-router-dom": "^6.2.1", + "redux-persist": "^6.0.0", + "ts-md5": "^1.3.1" }, "devDependencies": { "@svgr/webpack": "^6.2.1", @@ -3797,6 +3800,21 @@ "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", @@ -4180,6 +4198,17 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -4546,6 +4575,14 @@ "node": ">=8" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5156,10 +5193,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "dev": true, + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -5175,6 +5211,19 @@ } } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6154,7 +6203,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -6163,7 +6211,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -6852,6 +6899,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -7647,6 +7699,14 @@ "@babel/runtime": "^7.9.2" } }, + "node_modules/redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "peerDependencies": { + "redux": ">4.0.0" + } + }, "node_modules/redux-thunk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", @@ -8674,6 +8734,14 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/ts-md5": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-1.3.1.tgz", + "integrity": "sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg==", + "engines": { + "node": ">=12" + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", diff --git a/package.json b/package.json index 1aabd0b..55182f8 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,7 @@ "scripts": { "start": "webpack serve --env port=3000", "build:prod": "webpack --env mode=production", - "build:dev": "webpack --env mode=development", - "prettier": "npx prettier --write **/*.{ts,tsx}" + "build:dev": "webpack --env mode=development" }, "keywords": [], "author": "", @@ -41,6 +40,7 @@ "@ant-design/icons": "^5.2.6", "@reduxjs/toolkit": "^1.8.0", "antd": "^5.10.0", + "axios": "^1.6.8", "firebase": "^10.6.0", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/src/app/App.tsx b/src/app/App.tsx index 4e9c3af..65e86d5 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,4 +1,3 @@ -import { Message } from 'shared/ui/Message'; import { routeConfig } from './providers/router/routeConfig/appRouteConfig'; import './styles/index.scss'; import { AppRouter } from 'app/providers/router'; @@ -7,8 +6,6 @@ const App = () => { return ( <> {AppRouter(routeConfig)} - {/* тут будет Notification */} - ); }; diff --git a/src/app/providers/StoreProvider/config/store.ts b/src/app/providers/StoreProvider/config/store.ts index df0bf12..486a3fc 100644 --- a/src/app/providers/StoreProvider/config/store.ts +++ b/src/app/providers/StoreProvider/config/store.ts @@ -1,8 +1,16 @@ -import { configureStore } from '@reduxjs/toolkit'; +import { configureStore, combineReducers } from '@reduxjs/toolkit'; import { userReducer } from 'entities/Auth/index'; +import {factoryReducer} from "entities/Factory/index" +import { facilityReducer } from 'entities/Facility'; +import { parameterReducer } from 'entities/TechnologicalParameters'; +import {valveReducer} from 'entities/Valve'; export const store = configureStore({ reducer: { user: userReducer, + factory: factoryReducer, + facility: facilityReducer, + parameter: parameterReducer, + valve: valveReducer, }, -}); +}); \ No newline at end of file diff --git a/src/app/providers/StoreProvider/config/types.ts b/src/app/providers/StoreProvider/config/types.ts index 65b3163..1a7b261 100644 --- a/src/app/providers/StoreProvider/config/types.ts +++ b/src/app/providers/StoreProvider/config/types.ts @@ -1,9 +1,9 @@ -import { UserSchema } from 'entities/Auth'; +import { UserState } from 'entities/Auth'; import { store } from './store'; export type AppDispatch = typeof store.dispatch; export type RootState = ReturnType; -export interface StateSchema { - user: UserSchema; +export interface IUserState { + user: UserState; } diff --git a/src/app/providers/router/routeConfig/appRouteConfig.tsx b/src/app/providers/router/routeConfig/appRouteConfig.tsx index 1de6114..6cf21ee 100644 --- a/src/app/providers/router/routeConfig/appRouteConfig.tsx +++ b/src/app/providers/router/routeConfig/appRouteConfig.tsx @@ -6,10 +6,12 @@ import { Page } from 'widgets/Page/ui/Page'; import { AppRoutesProps } from './types'; import { getLogin, getNotFound, getRegister } from './routes'; import { getMain } from 'app/providers/router/routeConfig/routes'; +import { AccountPage } from 'pages/AccountPage'; export enum AppRoutes { LOGIN = 'login', REGISTER = 'register', + ACCOUNT = 'account', MAIN = 'main', SCADA = 'scada', NOT_FOUND = 'not_found', @@ -18,9 +20,10 @@ export enum AppRoutes { export const RoutePath: Record = { [AppRoutes.LOGIN]: getLogin(), [AppRoutes.REGISTER]: getRegister(), - [AppRoutes.SCADA]: 'scada/*', + [AppRoutes.SCADA]: '/*', [AppRoutes.MAIN]: getMain(), [AppRoutes.NOT_FOUND]: getNotFound(), + [AppRoutes.ACCOUNT]: 'account/user/:userId', }; export const routeConfig: Record = { @@ -46,4 +49,8 @@ export const routeConfig: Record = { path: RoutePath.not_found, element: , }, + [AppRoutes.ACCOUNT]: { + path: RoutePath.account, + element: , + }, }; diff --git a/src/app/providers/router/routeConfig/pageRouteConfig.tsx b/src/app/providers/router/routeConfig/pageRouteConfig.tsx index 3a416e0..809db25 100644 --- a/src/app/providers/router/routeConfig/pageRouteConfig.tsx +++ b/src/app/providers/router/routeConfig/pageRouteConfig.tsx @@ -1,25 +1,26 @@ import { AccountPage } from 'pages/AccountPage'; import { InfographicsPage } from 'pages/InfographicsPage'; import { NotFoundPage } from 'pages/NotFoundPage'; -import { ObjectPage } from 'pages/ObjectPage'; +import { FacilityPage } from 'pages/FacilityPage'; import { TestPage } from 'pages/TestPage'; import { AppRoutesProps } from './types'; import { getMain, getNotFound } from 'app/providers/router/routeConfig/routes'; + export enum AppRoutes { INFOGRAPHICS = 'infographics', ACCOUNT = 'account', - OBJECT = 'object', - TEST = 'test', + FACILITY = 'facility', + TEST = 'description', MAIN = 'main', NOT_FOUND = 'not_found', } export const RoutePath: Record = { - [AppRoutes.INFOGRAPHICS]: 'infographics', - [AppRoutes.ACCOUNT]: 'account', - [AppRoutes.OBJECT]: 'object/:id', - [AppRoutes.TEST]: 'test', + [AppRoutes.INFOGRAPHICS]: '/:factoryKey/facility/:facilityId/infographics', + [AppRoutes.ACCOUNT]: 'account/user/:userId', + [AppRoutes.FACILITY]: '/:factoryKey/facility/:facilityId', + [AppRoutes.TEST]: '/:factoryKey/facility/:facilityId/description', [AppRoutes.MAIN]: getMain(), [AppRoutes.NOT_FOUND]: getNotFound(), }; @@ -33,12 +34,12 @@ export const routeConfig: Record = { path: RoutePath.infographics, element: , }, - [AppRoutes.OBJECT]: { - path: RoutePath.object, - element: , + [AppRoutes.FACILITY]: { + path: RoutePath.facility, + element: , }, [AppRoutes.TEST]: { - path: RoutePath.test, + path: RoutePath.description, element: , }, [AppRoutes.MAIN]: { diff --git a/src/app/providers/router/routeConfig/routes.ts b/src/app/providers/router/routeConfig/routes.ts index e1da28a..c4e5499 100644 --- a/src/app/providers/router/routeConfig/routes.ts +++ b/src/app/providers/router/routeConfig/routes.ts @@ -1,11 +1,12 @@ +import { IGetPath } from "./types"; + export const getLogin = () => '/login'; export const getRegister = () => '/register'; export const getMain = () => '/'; export const getNotFound = () => '*'; -export const getScada = () => '/scada'; +export const getFacility: IGetPath = (factoryKey, facilityId) => `/${factoryKey}/facility/${facilityId}`; -export const getAccount = () => `${getScada()}/account`; -export const getInfographics = () => `${getScada()}/infographics`; -export const getTest = () => `${getScada()}/test`; -export const getObject = (id?: number) => (id ? `${getScada()}/object/${id}` : `${getScada()}/object`); +export const getAccount = (userId: string) => `account/user/${userId}`; +export const getInfographics: IGetPath = (factoryKey, facilityId) => `${getFacility(factoryKey, facilityId)}/infographics`; +export const getTest: IGetPath = (factoryKey, facilityId) => `${getFacility(factoryKey, facilityId)}/description`; diff --git a/src/app/providers/router/routeConfig/types.ts b/src/app/providers/router/routeConfig/types.ts index 7961257..7bf8ff3 100644 --- a/src/app/providers/router/routeConfig/types.ts +++ b/src/app/providers/router/routeConfig/types.ts @@ -5,3 +5,7 @@ export type AppRoutesProps = RouteProps & { element: JSX.Element; authOnly?: boolean; }; + +export interface IGetPath { + (factoryKey: string, facilityId: string): string; +} diff --git a/src/app/styles/variables/global.scss b/src/app/styles/variables/global.scss index f820ab4..b6bbce0 100644 --- a/src/app/styles/variables/global.scss +++ b/src/app/styles/variables/global.scss @@ -13,4 +13,7 @@ --font-line-m: 24px; --font-line-l: 28px; --font-line-xl: 32px; + + --schemeSidebar-width: 350px; + --schemeSidebar-width-collapsed: 80px; } \ No newline at end of file diff --git a/src/entities/Auth/hooks/useLogin.ts b/src/entities/Auth/hooks/useLogin.ts index 891ada4..3a31952 100644 --- a/src/entities/Auth/hooks/useLogin.ts +++ b/src/entities/Auth/hooks/useLogin.ts @@ -9,7 +9,6 @@ import { getMain } from 'app/providers/router/routeConfig/routes'; export const useLogin = (): IUseLoginReturn => { const dispatch = useAppDispatch(); const navigate = useNavigate(); - const showMessage = () => {}; const handleLogin = (email: string, password: string) => { const auth = getAuth(); @@ -25,6 +24,5 @@ export const useLogin = (): IUseLoginReturn => { }) .catch(() => message.error('Введены некорректные данные!')); }; - return { login: handleLogin }; }; diff --git a/src/entities/Auth/index.ts b/src/entities/Auth/index.ts index 89a8639..ee28d31 100644 --- a/src/entities/Auth/index.ts +++ b/src/entities/Auth/index.ts @@ -1,2 +1,4 @@ export { default as userReducer } from './model/slice/userSlice'; -export type { User, UserSchema } from './model/types/user'; + +export type { User, UserState } from './model/types/user'; +export type { LoginState } from './model/types/login'; diff --git a/src/entities/Auth/model/selectors/userSelector.ts b/src/entities/Auth/model/selectors/userSelector.ts index 29deabd..4b9f2a7 100644 --- a/src/entities/Auth/model/selectors/userSelector.ts +++ b/src/entities/Auth/model/selectors/userSelector.ts @@ -1,3 +1,3 @@ -import { StateSchema } from 'app/providers/StoreProvider'; +import { IUserState } from 'app/providers/StoreProvider'; -export const getUserSelector = (state: StateSchema) => state.user; +export const getUserSelector = (state: IUserState) => state.user; diff --git a/src/entities/Auth/model/slice/userSlice.ts b/src/entities/Auth/model/slice/userSlice.ts index dfaa430..7150282 100644 --- a/src/entities/Auth/model/slice/userSlice.ts +++ b/src/entities/Auth/model/slice/userSlice.ts @@ -1,7 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { User, UserSchema } from '../types/user'; +import { User, UserState } from '../types/user'; -const initialState: UserSchema = { +const initialState: UserState = { isAuthorized: false, user: null, }; @@ -10,7 +10,7 @@ const userSlice = createSlice({ name: 'user', initialState, reducers: { - setUser(state: UserSchema, { payload }: PayloadAction) { + setUser(state: UserState, { payload }: PayloadAction) { state.user = payload; state.isAuthorized = true; }, @@ -18,7 +18,7 @@ const userSlice = createSlice({ state.user = null; state.isAuthorized = false; }, - }, + } }); export const { setUser, deleteUser } = userSlice.actions; diff --git a/src/entities/Auth/model/types/login.ts b/src/entities/Auth/model/types/login.ts new file mode 100644 index 0000000..29df949 --- /dev/null +++ b/src/entities/Auth/model/types/login.ts @@ -0,0 +1,3 @@ +export interface LoginState { + token: string +} diff --git a/src/entities/Auth/model/types/user.ts b/src/entities/Auth/model/types/user.ts index 6bddba4..b85aae8 100644 --- a/src/entities/Auth/model/types/user.ts +++ b/src/entities/Auth/model/types/user.ts @@ -8,7 +8,7 @@ export interface User { avatar?: string; } -export interface UserSchema { +export interface UserState { user: User | null; isAuthorized: boolean; } diff --git a/src/entities/Facility/api/fetchFacilities.ts b/src/entities/Facility/api/fetchFacilities.ts new file mode 100644 index 0000000..bf672ea --- /dev/null +++ b/src/entities/Facility/api/fetchFacilities.ts @@ -0,0 +1,26 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import { IFacility, IFacilityMainInfo } from "../types/types" +import { collection, getDocs } from "firebase/firestore"; +import { db } from "shared/services/firebase/firebase"; + +export const fetchFacilities = createAsyncThunk( + "fetchFacilities", + async (_, { rejectWithValue }) => { + try { + const querySnapshot = await getDocs(collection(db, "facilities")); + const facilitiesData = querySnapshot.docs.map(doc => { + const {id, factoryId, title, enabled, visible} = doc.data(); + + return {id, factoryId, title, enabled, visible} as IFacilityMainInfo; + }); + + if (querySnapshot.empty) { + throw new Error("Server Error! Can not GET facilities") + } + + return facilitiesData + } catch (error) { + return rejectWithValue(error.message) + } + } +) diff --git a/src/entities/Facility/api/fetchFacilitiesByFactoryId.ts b/src/entities/Facility/api/fetchFacilitiesByFactoryId.ts new file mode 100644 index 0000000..7aa0d8b --- /dev/null +++ b/src/entities/Facility/api/fetchFacilitiesByFactoryId.ts @@ -0,0 +1,30 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import { IFacilityMainInfo } from "../types/types" +import { collection, getDocs, query, where } from "firebase/firestore"; +import { db } from "shared/services/firebase/firebase"; + +export const fetchFacilitiesByFactoryId = createAsyncThunk( + "fetchFacilitiesByFactoryId", + async (factoryId, { rejectWithValue }) => { + try { + const facilitiesCollectionRef = collection(db, 'facilities'); + const facilityQuery = query(facilitiesCollectionRef, where('factoryId', '==', factoryId)); + const querySnapshot = await getDocs(facilityQuery); + + if (!querySnapshot.empty) { + const facilitiesData = querySnapshot.docs.map(doc => { + const { id, title, visible, enabled } = doc.data(); + + return { id, title, visible, enabled } as IFacilityMainInfo; + }); + + return facilitiesData; + } else { + throw new Error("Server Error! Can not GET facilities by factory ID") + } + + } catch (error) { + return rejectWithValue(error.message) + } + } +) \ No newline at end of file diff --git a/src/entities/Facility/api/fetchFacilitiesById.ts b/src/entities/Facility/api/fetchFacilitiesById.ts new file mode 100644 index 0000000..bcb4878 --- /dev/null +++ b/src/entities/Facility/api/fetchFacilitiesById.ts @@ -0,0 +1,25 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import { IFacility } from "../types/types" +import { collection, getDocs, query, where } from "firebase/firestore"; +import { db } from "shared/services/firebase/firebase"; + +export const fetchFacilitiesById = createAsyncThunk( + "fetchFacilitiesById", + async (facilityId, { rejectWithValue }) => { + try { + const facilitiesCollectionRef = collection(db, 'facilities'); + const facilityQuery = query(facilitiesCollectionRef, where('id', '==', facilityId)); + const querySnapshot = await getDocs(facilityQuery); + + if (!querySnapshot.empty) { + const facilityData = querySnapshot.docs[0].data() as IFacility; + return facilityData; + } else { + throw new Error("Server Error! Can not GET facility") + } + + } catch (error) { + return rejectWithValue(error.message) + } + } +) \ No newline at end of file diff --git a/src/entities/Facility/index.ts b/src/entities/Facility/index.ts new file mode 100644 index 0000000..1aeffc9 --- /dev/null +++ b/src/entities/Facility/index.ts @@ -0,0 +1,5 @@ +export {default as facilityReducer} from "./model/slice/facilitySlice" +export * from "./types/types" +export {fetchFacilities} from "./api/fetchFacilities" +export {fetchFacilitiesById} from "./api/fetchFacilitiesById" +export {fetchFacilitiesByFactoryId} from "./api/fetchFacilitiesByFactoryId" \ No newline at end of file diff --git a/src/entities/Facility/model/slice/facilitySlice.ts b/src/entities/Facility/model/slice/facilitySlice.ts new file mode 100644 index 0000000..e1d386e --- /dev/null +++ b/src/entities/Facility/model/slice/facilitySlice.ts @@ -0,0 +1,64 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { fetchFacilities } from "entities/Facility/api/fetchFacilities"; +import { fetchFacilitiesById } from "entities/Facility/api/fetchFacilitiesById"; +import {fetchFacilitiesByFactoryId} from "entities/Facility/api/fetchFacilitiesByFactoryId"; +import {IFacilityState} from "../../types/types" + +const initialState:IFacilityState = { + list: [], + error: null, + loading: false, + currentFacility: null, +} + +export const facilitySlice = createSlice({ + name: "facility", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchFacilities.fulfilled, (state, action) => { + state.list = action.payload + state.loading = false + state.error = null + }) + .addCase(fetchFacilities.pending, (state) => { + state.loading = true + state.error = null + }) + .addCase(fetchFacilities.rejected, (state, action) => { + state.loading = false + state.error = action.payload + }) + + .addCase(fetchFacilitiesById.fulfilled, (state, action) => { + state.currentFacility = action.payload + state.loading = false + state.error = null + }) + .addCase(fetchFacilitiesById.pending, (state) => { + state.loading = true + state.error = null + }) + .addCase(fetchFacilitiesById.rejected, (state, action) => { + state.loading = false + state.error = action.payload + }) + + .addCase(fetchFacilitiesByFactoryId.fulfilled, (state, action) => { + state.list = action.payload + state.loading = false + state.error = null + }) + .addCase(fetchFacilitiesByFactoryId.pending, (state) => { + state.loading = true + state.error = null + }) + .addCase(fetchFacilitiesByFactoryId.rejected, (state, action) => { + state.loading = false + state.error = action.payload + }) + } +}) + +export default facilitySlice.reducer \ No newline at end of file diff --git a/src/entities/Facility/types/types.ts b/src/entities/Facility/types/types.ts new file mode 100644 index 0000000..0e47359 --- /dev/null +++ b/src/entities/Facility/types/types.ts @@ -0,0 +1,25 @@ +export interface IFacility { + id: number, + title: string, + description: string, + enabled?: boolean, + visible?: boolean, + factoryId: string, + schemeDarkURL: string, + schemeLightURL: string, +} + +export interface IFacilityMainInfo { + id: string, + title: string, + factoryId: number, + enabled?: boolean, + visible?: boolean, +} + +export interface IFacilityState { + list: IFacilityMainInfo[], + error: string | null, + loading: boolean, + currentFacility?: IFacility, +} \ No newline at end of file diff --git a/src/entities/Factory/api/fetchFactories.ts b/src/entities/Factory/api/fetchFactories.ts new file mode 100644 index 0000000..e3d4fa4 --- /dev/null +++ b/src/entities/Factory/api/fetchFactories.ts @@ -0,0 +1,22 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import { IFactory } from "../types/types" +import { collection, getDocs } from "firebase/firestore"; +import { db } from "shared/services/firebase/firebase"; + +export const fetchFactories = createAsyncThunk( + "fetchFactories", + async (_, { rejectWithValue }) => { + try { + const querySnapshot = await getDocs(collection(db, "factories")); + const factoriesData = querySnapshot.docs.map(doc => doc.data() as IFactory); + + if (querySnapshot.empty) { + throw new Error("Server Error! Can not GET factories") + } + + return factoriesData + } catch (error) { + return rejectWithValue(error.message) + } + } +) \ No newline at end of file diff --git a/src/entities/Factory/api/fetchFactoriesById.ts b/src/entities/Factory/api/fetchFactoriesById.ts new file mode 100644 index 0000000..be68f40 --- /dev/null +++ b/src/entities/Factory/api/fetchFactoriesById.ts @@ -0,0 +1,24 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import { IFactory } from "../types/types" +import { collection, getDocs, query, where } from "firebase/firestore"; +import { db } from "shared/services/firebase/firebase"; + +export const fetchFactoriesById = createAsyncThunk( + "fetchFactoriesById", + async (factoryId, { rejectWithValue }) => { + try { + const factoriesCollectionRef = collection(db, 'factories'); + const factoryQuery = query(factoriesCollectionRef, where('key', '==', factoryId)); + const querySnapshot = await getDocs(factoryQuery); + + if (!querySnapshot.empty) { + const factoryData = querySnapshot.docs[0].data() as IFactory; + return factoryData; + } else { + throw new Error("Server Error! Can not GET factories by ID") + } + } catch (error) { + return rejectWithValue(error.message) + } + } +) \ No newline at end of file diff --git a/src/entities/Factory/index.ts b/src/entities/Factory/index.ts new file mode 100644 index 0000000..1194b05 --- /dev/null +++ b/src/entities/Factory/index.ts @@ -0,0 +1,4 @@ +export {default as factoryReducer} from "./model/slice/factorySlice" +export * from "./model/slice/factorySlice" +export {fetchFactories} from "./api/fetchFactories" +export {fetchFactoriesById} from "./api/fetchFactoriesById" \ No newline at end of file diff --git a/src/entities/Factory/model/slice/factorySlice.ts b/src/entities/Factory/model/slice/factorySlice.ts new file mode 100644 index 0000000..fe0779d --- /dev/null +++ b/src/entities/Factory/model/slice/factorySlice.ts @@ -0,0 +1,49 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { fetchFactories } from "entities/Factory/api/fetchFactories"; +import { fetchFactoriesById } from "entities/Factory/api/fetchFactoriesById"; +import {IFactoryState} from "../../types/types" + +const initialState:IFactoryState = { + list: [], + error: null, + loading: false, + currentFactory: null +} + +export const factorySlice = createSlice({ + name: "factory", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchFactories.fulfilled, (state, action) => { + state.list = action.payload + state.loading = false + state.error = null + }) + .addCase(fetchFactories.pending, (state) => { + state.loading = true + state.error = null + }) + .addCase(fetchFactories.rejected, (state, action) => { + state.loading = false + state.error = action.payload + }) + + .addCase(fetchFactoriesById.fulfilled, (state, action) => { + state.currentFactory = state.list?.find(item => item.id === action.payload.id) + state.loading = false + state.error = null + }) + .addCase(fetchFactoriesById.pending, (state) => { + state.loading = true + state.error = null + }) + .addCase(fetchFactoriesById.rejected, (state, action) => { + state.loading = false + state.error = action.payload + }) + } +}) + +export default factorySlice.reducer \ No newline at end of file diff --git a/src/entities/Factory/types/types.ts b/src/entities/Factory/types/types.ts new file mode 100644 index 0000000..ff36460 --- /dev/null +++ b/src/entities/Factory/types/types.ts @@ -0,0 +1,14 @@ +export interface IFactory { + id: number, + key: string, + title: string, + enabled?: boolean, + visible?: boolean +} + +export interface IFactoryState { + list: IFactory[], + error: string | null, + loading: boolean, + currentFactory?: IFactory, +} diff --git a/src/entities/TechnologicalParameters/api/fetchParametersByFacilityId.ts b/src/entities/TechnologicalParameters/api/fetchParametersByFacilityId.ts new file mode 100644 index 0000000..dfa7c2d --- /dev/null +++ b/src/entities/TechnologicalParameters/api/fetchParametersByFacilityId.ts @@ -0,0 +1,28 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import { collection, getDocs } from "firebase/firestore"; +import { db } from "shared/services/firebase/firebase"; +import { IParameters } from "../types/types"; + +export const fetchParametersByFacilityId = createAsyncThunk( + "fetchParametersByFacilityId", + async (facilityId, { rejectWithValue }) => { + try {const querySnapshot = await getDocs(collection(db, "valves")); + + if (!querySnapshot.empty) { + const parametersData = querySnapshot.docs + .filter(doc => doc.data().valve.facilityId === facilityId) + .map(doc => { + const {pressure, flow, pressureDrop, temperature, valveOpening, collapsedPositionX, collapsedPositionY, expandedPositionX, expandedPositionY} = doc.data().parameters as IParameters + return {pressure, flow, pressureDrop, temperature, valveOpening, collapsedPositionX, collapsedPositionY, expandedPositionX, expandedPositionY} + }); + + return parametersData; + } else { + throw new Error("Server Error! Can not GET parameters"); + } + + } catch (error) { + return rejectWithValue(error.message) + } + } +) \ No newline at end of file diff --git a/src/entities/TechnologicalParameters/index.ts b/src/entities/TechnologicalParameters/index.ts new file mode 100644 index 0000000..40b8a4c --- /dev/null +++ b/src/entities/TechnologicalParameters/index.ts @@ -0,0 +1,3 @@ +export { fetchParametersByFacilityId } from "./api/fetchParametersByFacilityId" +export {default as parameterReducer} from "./model/slice/parameterSlice" +export * from "./model/slice/parameterSlice" diff --git a/src/entities/TechnologicalParameters/model/slice/parameterSlice.ts b/src/entities/TechnologicalParameters/model/slice/parameterSlice.ts new file mode 100644 index 0000000..5e46330 --- /dev/null +++ b/src/entities/TechnologicalParameters/model/slice/parameterSlice.ts @@ -0,0 +1,33 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { IParameterState } from "entities/TechnologicalParameters/types/types"; +import { fetchParametersByFacilityId } from "entities/TechnologicalParameters/api/fetchParametersByFacilityId"; + +const initialState:IParameterState = { + list: [], + error: null, + loading: false, +} + +export const parameterSlice = createSlice({ + name: "parameter", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchParametersByFacilityId.fulfilled, (state, action) => { + state.list = action.payload + state.loading = false + state.error = null + }) + .addCase(fetchParametersByFacilityId.pending, (state) => { + state.loading = true + state.error = null + }) + .addCase(fetchParametersByFacilityId.rejected, (state, action) => { + state.loading = false + state.error = action.payload + }) + } +}) + +export default parameterSlice.reducer \ No newline at end of file diff --git a/src/entities/TechnologicalParameters/types/types.ts b/src/entities/TechnologicalParameters/types/types.ts new file mode 100644 index 0000000..ac881c4 --- /dev/null +++ b/src/entities/TechnologicalParameters/types/types.ts @@ -0,0 +1,25 @@ +export interface IParameterInfo { + title?: string, + id?: string, + unit?: string, + value?: number, + name?: string +} + +export interface IParameters { + pressure?: IParameterInfo; + flow?: IParameterInfo; + pressureDrop?: IParameterInfo; + temperature?: IParameterInfo; + valveOpening: IParameterInfo; + collapsedPositionX?: number, + collapsedPositionY?: number, + expandedPositionX?: number, + expandedPositionY?: number, +} + +export interface IParameterState { + list: IParameters[], + error: string | null, + loading: boolean, +} \ No newline at end of file diff --git a/src/entities/Valve/api/fetchValves.ts b/src/entities/Valve/api/fetchValves.ts new file mode 100644 index 0000000..0576013 --- /dev/null +++ b/src/entities/Valve/api/fetchValves.ts @@ -0,0 +1,26 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import { collection, getDocs } from "firebase/firestore"; +import { db } from "shared/services/firebase/firebase"; +import { IValveMainInfo} from "../types/types"; + +export const fetchValves = createAsyncThunk( + "fetchValves", + async (_, { rejectWithValue }) => { + try { + const querySnapshot = await getDocs(collection(db, "valves")); + + if (!querySnapshot.empty) { + const valvesData = querySnapshot.docs.map(doc => { + const { facilityId, id, title, collapsedPositionX, collapsedPositionY, expandedPositionX, expandedPositionY } = doc.data().valve as IValveMainInfo; + return { facilityId, id, title, collapsedPositionX, collapsedPositionY, expandedPositionX, expandedPositionY} + }); + + return valvesData; + } else { + throw new Error("Server Error! Can not GET valves"); + } + } catch (error) { + return rejectWithValue(error.message); + } + } +); \ No newline at end of file diff --git a/src/entities/Valve/api/fetchValvesByFacilityId.ts b/src/entities/Valve/api/fetchValvesByFacilityId.ts new file mode 100644 index 0000000..9cdaf58 --- /dev/null +++ b/src/entities/Valve/api/fetchValvesByFacilityId.ts @@ -0,0 +1,29 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import { collection, getDocs, query, where } from "firebase/firestore"; +import { db } from "shared/services/firebase/firebase"; +import { IValveMainInfo } from "../types/types"; + +export const fetchValvesByFacilityId = createAsyncThunk( + "fetchValvesByFacilityId", + async (facilityId, { rejectWithValue }) => { + try { + const querySnapshot = await getDocs(collection(db, "valves")); + + if (!querySnapshot.empty) { + const valvesData = querySnapshot.docs + .filter(doc => doc.data().valve.facilityId === facilityId) + .map(doc => { + const {title, facilityId, id, collapsedPositionX, collapsedPositionY, expandedPositionX, expandedPositionY} = doc.data().valve as IValveMainInfo + return {title, facilityId, id, collapsedPositionX, collapsedPositionY, expandedPositionX, expandedPositionY} + }); + + return valvesData; + } else { + throw new Error("Server Error! Can not GET valve"); + } + + } catch (error) { + return rejectWithValue(error.message) + } + } +) \ No newline at end of file diff --git a/src/entities/Valve/api/fetchValvesById.ts b/src/entities/Valve/api/fetchValvesById.ts new file mode 100644 index 0000000..580d12a --- /dev/null +++ b/src/entities/Valve/api/fetchValvesById.ts @@ -0,0 +1,35 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import { collection, getDocs, query, where } from "firebase/firestore"; +import { db } from "shared/services/firebase/firebase"; +import { IValve } from "../types/types"; + +export const fetchValvesById = createAsyncThunk( + "fetchValvesById", + async (valveId, { rejectWithValue }) => { + try { + const querySnapshot = await getDocs(collection(db, "valves")); + + if (!querySnapshot.empty) { + const valvesData = querySnapshot.docs + .filter(doc => doc.data().valve.id === valveId) + .map(doc => { + const {valve, parameters} = doc.data() as IValve + return {valve, parameters} + }); + + return valvesData; + } else { + throw new Error("Server Error! Can not GET valve"); + } + } catch (error) { + return rejectWithValue(error.message) + } + } +) + +// const { facilityId, id, title } = doc.data().valve; + // return { facilityId, id, title }; + // const valvesData = querySnapshot.docs.map(doc => { + // console.log(doc.data()) + // const { facilityId, id, title } = doc.data().valve; + // return { facilityId, id, title }; \ No newline at end of file diff --git a/src/entities/Valve/index.ts b/src/entities/Valve/index.ts new file mode 100644 index 0000000..e97c735 --- /dev/null +++ b/src/entities/Valve/index.ts @@ -0,0 +1,4 @@ +export {default as valveReducer} from "./model/slice/valveSlice" +export {fetchValves} from "./api/fetchValves" +export {fetchValvesById} from "./api/fetchValvesById" +// export {fetchParametersByValveId} from "./api/fetchParametersByValveId" \ No newline at end of file diff --git a/src/entities/Valve/model/slice/valveSlice.ts b/src/entities/Valve/model/slice/valveSlice.ts new file mode 100644 index 0000000..4fbaf68 --- /dev/null +++ b/src/entities/Valve/model/slice/valveSlice.ts @@ -0,0 +1,64 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { fetchValves } from "entities/Valve/api/fetchValves"; +import { fetchValvesByFacilityId } from "entities/Valve/api/fetchValvesByFacilityId"; +import { fetchValvesById } from "entities/Valve/api/fetchValvesById"; +import { IValveState } from "entities/Valve/types/types"; + +const initialState:IValveState = { + list: [], + error: null, + loading: false, + currentValve: null +} + +export const valveSlice = createSlice({ + name: "valve", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchValves.fulfilled, (state, action) => { + state.list = action.payload + state.loading = false + state.error = null + }) + .addCase(fetchValves.pending, (state) => { + state.loading = true + state.error = null + }) + .addCase(fetchValves.rejected, (state, action) => { + state.loading = false + state.error = action.payload + }) + + .addCase(fetchValvesById.fulfilled, (state, action) => { + state.currentValve = action.payload + state.loading = false + state.error = null + }) + .addCase(fetchValvesById.pending, (state) => { + state.loading = true + state.error = null + }) + .addCase(fetchValvesById.rejected, (state, action) => { + state.loading = false + state.error = action.payload + }) + + .addCase(fetchValvesByFacilityId.fulfilled, (state, action) => { + state.list = action.payload + state.loading = false + state.error = null + }) + .addCase(fetchValvesByFacilityId.pending, (state) => { + state.loading = true + state.error = null + }) + .addCase(fetchValvesByFacilityId.rejected, (state, action) => { + state.loading = false + state.error = action.payload + }) + } +}) + +export default valveSlice.reducer \ No newline at end of file diff --git a/src/entities/Valve/types/types.ts b/src/entities/Valve/types/types.ts new file mode 100644 index 0000000..fb0b1c9 --- /dev/null +++ b/src/entities/Valve/types/types.ts @@ -0,0 +1,44 @@ +export interface IValve { + valve: { + title: string + facilityId: string, + id: string, + name: string, + collapsedPositionX: number, + collapsedPositionY: number, + expandedPositionX: number, + expandedPositionY: number, + } + parameters: { + pressure?: IParameter; + flow?: IParameter; + pressureDrop?: IParameter; + temperature?: IParameter; + valveOpening: IParameter; + } +} + +export interface IValveMainInfo { + title: string + facilityId: string, + id: string, + collapsedPositionX?: number, + collapsedPositionY?: number, + expandedPositionX?: number, + expandedPositionY?: number, +} + +interface IParameter { + id: string; + title: string; + unit: string; + value: number; + name: string +} + +export interface IValveState { + list: IValveMainInfo[], + error: string | null, + loading: boolean, + currentValve: IValve[] +} diff --git a/src/index.tsx b/src/index.tsx index bc6a7ec..10ea511 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,11 +8,11 @@ import 'shared/services/firebase/firebase'; render( - - - - - + + + + + , document.getElementById('root'), ); diff --git a/src/pages/AccountPage/ui/AccountPage.module.scss b/src/pages/AccountPage/ui/AccountPage.module.scss new file mode 100644 index 0000000..ac57ea0 --- /dev/null +++ b/src/pages/AccountPage/ui/AccountPage.module.scss @@ -0,0 +1,78 @@ +.AccountPage { + display: flex; + justify-content: center; + padding-top: 50px; +} + +.account { + padding: 8px; + height: 150px; + width: 150px; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + background: var(--color-primary-900); + border-radius: 100%; + cursor: pointer; +} + +.infoText { + font-size: 18px; + font-weight: 500; +} + +.mainText { + font-weight: 700; + font-size: 18px; +} + +.card { + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; +} + +.info { + padding: 40px 60px; + background: var(--color-primary-500); + border-radius: 14px; + display: flex; + justify-content: center; + width: 400px; +} + +.infoBlock { + display: flex; + gap: 10px; +} + +.scadaBlock { + display: flex; + gap: 1px; +} + +.scadaBlockText { + padding-top: 5px; + color: var(--color-accent-800); + font-weight: 600; +} + +.scadaBlockText:hover { + transition: background-color 0.2s; + color: var(--color-accent-600); + cursor: pointer; +} + + +.textBack { + cursor: pointer; + font-weight: 600; +} + +.textBack:hover { + color: var(--color-accent-600); + transition: color 0.2s; + +} \ No newline at end of file diff --git a/src/pages/AccountPage/ui/AccountPage.tsx b/src/pages/AccountPage/ui/AccountPage.tsx index 2f316e3..8f2ae81 100644 --- a/src/pages/AccountPage/ui/AccountPage.tsx +++ b/src/pages/AccountPage/ui/AccountPage.tsx @@ -2,21 +2,52 @@ import { Link, Navigate } from 'react-router-dom'; import { useAuth } from 'entities/Auth/hooks/useAuth'; import { Button } from 'shared/ui/Button/Button'; import { useLogout } from 'entities/Auth/hooks/useLogout'; -import { getLogin, getMain } from 'app/providers/router/routeConfig/routes'; +import { getAccount, getLogin, getMain } from 'app/providers/router/routeConfig/routes'; +import cl from "./AccountPage.module.scss" +import { classNames } from 'shared/lib/classNames/classNames'; +import { useTheme } from 'app/providers/ThemeProvider'; +import { ThemeSwitcher } from 'shared/ui/ThemeSwitcher'; +import LogoutIcon from 'shared/assets/icons/LogoutIcon'; +import { Navigation } from 'widgets/Header/Navigation'; +import DefaultUserIcon from 'shared/assets/icons/DefaultUserIcon'; +import TurnLeftArrow from 'shared/assets/icons/TurnLeftArrow'; const AccountPage = () => { const { isAuth, user } = useAuth(); const { logout } = useLogout(); + const { theme } = useTheme(); - return isAuth ? ( -
-

Личный кабинет диспетчера

- - На главную + return isAuth ? ( +
+ + +

+ + Главная +

+ +
+ {/*

Личный кабинет диспетчера

*/} +
+
+
+ +
+
+ Грошев + Алексей + Игоревич +
+ ID: {user?.id} + Время в системе: с 15:12 - сейчас +
+
+
+
) : ( - - ); + + ) }; export default AccountPage; diff --git a/src/pages/FacilityPage/index.ts b/src/pages/FacilityPage/index.ts new file mode 100644 index 0000000..987ab50 --- /dev/null +++ b/src/pages/FacilityPage/index.ts @@ -0,0 +1 @@ +export { FacilityPageAsync as FacilityPage } from './ui/FacilityPage.async'; diff --git a/src/pages/FacilityPage/ui/FacilityMenu/index.ts b/src/pages/FacilityPage/ui/FacilityMenu/index.ts new file mode 100644 index 0000000..57a5b8d --- /dev/null +++ b/src/pages/FacilityPage/ui/FacilityMenu/index.ts @@ -0,0 +1 @@ +export { FacilityMenu } from './ui/FacilityMenu'; diff --git a/src/widgets/Sidebar/ui/ObjectMenu/ui/ObjectMenu.module.scss b/src/pages/FacilityPage/ui/FacilityMenu/ui/FacilityMenu.module.scss similarity index 58% rename from src/widgets/Sidebar/ui/ObjectMenu/ui/ObjectMenu.module.scss rename to src/pages/FacilityPage/ui/FacilityMenu/ui/FacilityMenu.module.scss index a9d8bd1..249f542 100644 --- a/src/widgets/Sidebar/ui/ObjectMenu/ui/ObjectMenu.module.scss +++ b/src/pages/FacilityPage/ui/FacilityMenu/ui/FacilityMenu.module.scss @@ -16,6 +16,10 @@ font-size: 14px; font-weight: 300; cursor: pointer; + height: 380px; + /* -- scroll -- */ + overflow: auto; + scroll-snap-type: y mandatory; } .objectLink { @@ -51,3 +55,20 @@ color: var(--color-primary-400); } +div[title]::after { + content: attr(data-title); + /* Выводим текст из атрибута data-title */ + position: absolute; + /* Абсолютное позиционирование */ + left: 0; + top: 1em; + /* Положение подсказки */ + transition: 0.5s; + opacity: 0; + /* Скрываем подсказку, делая её прозрачной */ +} + +div[title]:hover::after { + opacity: 1; + /* Показываем подсказку */ +} \ No newline at end of file diff --git a/src/pages/FacilityPage/ui/FacilityMenu/ui/FacilityMenu.tsx b/src/pages/FacilityPage/ui/FacilityMenu/ui/FacilityMenu.tsx new file mode 100644 index 0000000..14444ad --- /dev/null +++ b/src/pages/FacilityPage/ui/FacilityMenu/ui/FacilityMenu.tsx @@ -0,0 +1,57 @@ +import { getFacility } from 'app/providers/router/routeConfig/routes'; +import cl from './FacilityMenu.module.scss'; +import { FC } from 'react'; +import { NavLink as Link, useParams } from 'react-router-dom'; +import LabelIcon from 'shared/assets/icons/LabelIcon'; +import { useAppDispatch } from 'shared/lib/hooks/useAppDispatch/useAppDispatch'; +import { fetchFacilitiesById } from 'entities/Facility/api/fetchFacilitiesById'; + +import React from 'react'; +import { IFacilityMainInfo } from 'entities/Facility/types/types'; + +interface FacilityMenuProps { + className?: string; + children?: React.ReactNode; + activeClassName?: string; + filteredFacilities?: IFacilityMainInfo[] +} + +export const FacilityMenu: FC = ({filteredFacilities}) => { + const URL = useParams() + const value = URL["*"]; + const parts = value.split('/'); + const factoryKey = parts[0]; + + const dispatch = useAppDispatch() + + const FacilitiesHandle = (id:string) => { + dispatch(fetchFacilitiesById(id)) + } + + return ( +
+
+ Установки + {filteredFacilities?.length} +
+
+ {filteredFacilities.length != 0 ? + filteredFacilities.map((item) => ( + `${cl.objectLink} ${isActive ? cl.objectLinkActive : ''}`} + onClick={() => FacilitiesHandle(item?.id)} + > +
+ + {item?.title} +
+ + )) + :

Установок не найдено

+ } +
+
+ ); +}; diff --git a/src/pages/FacilityPage/ui/FacilityPage.async.tsx b/src/pages/FacilityPage/ui/FacilityPage.async.tsx new file mode 100644 index 0000000..69416d8 --- /dev/null +++ b/src/pages/FacilityPage/ui/FacilityPage.async.tsx @@ -0,0 +1,6 @@ +import { lazy } from 'react'; + +export const FacilityPageAsync = lazy(() => new Promise(resolve => { + // @ts-ignore + setTimeout(() => resolve(import('./FacilityPage')), 1500) +})); \ No newline at end of file diff --git a/src/pages/FacilityPage/ui/FacilityPage.module.scss b/src/pages/FacilityPage/ui/FacilityPage.module.scss new file mode 100644 index 0000000..0c8e90f --- /dev/null +++ b/src/pages/FacilityPage/ui/FacilityPage.module.scss @@ -0,0 +1,53 @@ +.FacilityPage { + margin-top: 15px; + height: 100%; + display: flex; + flex-direction: column; + gap: 20px; +} + +.schemePage { + box-sizing: border-box, ; + height: 100%; + display: flex; + gap: 50px; + position: relative; +} + +.scheme { + position: relative; + height: 100%; + width: 100%; + background-repeat: no-repeat; +} + +.clapan { + position: absolute; + cursor: pointer; + width: 55px; + height: 40px; + // background: red; + opacity: 0.5; +} + + +.mainText { + font-weight: 700; +} + +// .clapan:hover { +// opacity: 0.5; +// } + +[data-title]::after { + content: attr(data-title); + position: absolute; + left: 0; + top: 1em; + transition: 0.5s; + opacity: 0; +} + +[data-title]:hover::after { + opacity: 1; +} \ No newline at end of file diff --git a/src/pages/FacilityPage/ui/FacilityPage.tsx b/src/pages/FacilityPage/ui/FacilityPage.tsx new file mode 100644 index 0000000..2d8aebf --- /dev/null +++ b/src/pages/FacilityPage/ui/FacilityPage.tsx @@ -0,0 +1,80 @@ +import { useAppSelector } from "shared/lib/hooks/useAppSelector/useAppSelector"; +import { SchemeSidebar } from 'widgets/SchemeSidebar/ui/SchemeSidebar'; +import { useTheme } from 'app/providers/ThemeProvider'; +import {Theme} from "app/providers/ThemeProvider/lib/ThemeContext" +import cl from "./FacilityPage.module.scss" +import { useEffect, useState } from "react"; +import { useAppDispatch } from "shared/lib/hooks/useAppDispatch/useAppDispatch"; +import { fetchFacilitiesById } from "entities/Facility/api/fetchFacilitiesById"; +import { useParams } from "react-router-dom"; +import { Button } from "shared/ui/Button"; +import { fetchValves } from "entities/Valve"; +import { fetchValvesById } from "entities/Valve/api/fetchValvesById"; +import { fetchValvesByFacilityId } from "entities/Valve/api/fetchValvesByFacilityId"; +import { fetchParametersByFacilityId } from "entities/TechnologicalParameters"; +import { FacilityParameters } from "widgets/FacilityParameters"; + +const FacilityPage = () => { + const {theme} = useTheme() + const dispatch = useAppDispatch() + + const currentFacility = useAppSelector(state => state.facility.currentFacility) + const schemeURL: string = theme === Theme.LIGHT ? currentFacility?.schemeLightURL : currentFacility?.schemeDarkURL; + + const {factoryKey, facilityId} = useParams() + + const valveList = useAppSelector(state => state.valve.list) + + useEffect(() => { + !currentFacility && dispatch(fetchFacilitiesById(facilityId)) + + dispatch(fetchValvesByFacilityId(facilityId)) + dispatch(fetchParametersByFacilityId(facilityId)) + }, [facilityId]) + + + const [collapsed, setCollapsed] = useState(true); + + const positionX = collapsed ? valveList?.map(item => item?.collapsedPositionX) : valveList?.map(item => item?.expandedPositionX) + const positionY = collapsed ? valveList?.map(item => item?.collapsedPositionY) : valveList?.map(item => item?.expandedPositionY) + + const buttonHandler = (id:string) => { + dispatch(fetchValvesById(id)) + collapsed && setCollapsed(!collapsed) + } + + const facilityParameters = useAppSelector(state => state?.parameter?.list) + + + if (!currentFacility || factoryKey != currentFacility?.factoryId) + return ( +

Ой! Такой установки не существует

+ ); + + return ( +
+

+ {currentFacility?.title} +

+
+ +
+ + {valveList?.map((valve, index) => { + return ( +
+ buttonHandler(valve?.id)}> +
+ ) + })} +
+
+
+ ); +}; + +export default FacilityPage; diff --git a/src/pages/InfographicsPage/ui/InfographicsPage.tsx b/src/pages/InfographicsPage/ui/InfographicsPage.tsx index ae5be14..cf85c56 100644 --- a/src/pages/InfographicsPage/ui/InfographicsPage.tsx +++ b/src/pages/InfographicsPage/ui/InfographicsPage.tsx @@ -1,7 +1,7 @@ const InfographicsPage = () => { return ( -
+

Инфографика процесса

); diff --git a/src/pages/MainPage/ui/MainPage.tsx b/src/pages/MainPage/ui/MainPage.tsx index 147792b..97a48cb 100644 --- a/src/pages/MainPage/ui/MainPage.tsx +++ b/src/pages/MainPage/ui/MainPage.tsx @@ -1,51 +1,86 @@ import { Button } from 'shared/ui/Button/Button'; import { useLogout } from 'entities/Auth/hooks/useLogout'; import AppLink from 'shared/ui/AppLink/AppLink'; -import { getScada } from 'app/providers/router/routeConfig/routes'; +import { getAccount, getFacility } from 'app/providers/router/routeConfig/routes'; import { classNames } from 'shared/lib/classNames/classNames'; import { useTheme } from 'app/providers/ThemeProvider'; import { Select } from 'shared/ui/Select/index'; import cl from './MainPage.module.scss' import LogoutIcon from 'shared/assets/icons/LogoutIcon'; import { ThemeSwitcher } from 'shared/ui/ThemeSwitcher'; +import { useAppSelector } from 'shared/lib/hooks/useAppSelector/useAppSelector'; +import { useAppDispatch } from 'shared/lib/hooks/useAppDispatch/useAppDispatch'; +import {fetchFactories, fetchFactoriesById} from "entities/Factory/index" +import { fetchFacilities, fetchFacilitiesById, fetchFacilitiesByFactoryId } from 'entities/Facility'; +import { useEffect, useMemo, useState } from 'react'; +import { ISelectProps } from 'shared/ui/Select/IProps'; +import { Message } from 'shared/ui/Message'; +import { Link } from 'react-router-dom'; +import { Navigation } from 'widgets/Header/Navigation'; const MainPage = () => { const { logout, user } = useLogout(); const { theme } = useTheme(); + const [buttonValue, setButtonValue] = useState({factory: false, facility: false}) - interface FactoriesProps { - value: string, - label: string, - disabled?: boolean + const {list} = useAppSelector(state => state.factory) + const facility = useAppSelector(state => state.facility) + const dispatch = useAppDispatch() + + const listFactories: ISelectProps['options'] = useMemo( + () => + list + ?.filter((elem) => elem.visible) + ?.map((elem) => ({ + value: elem.key, + label: elem.title, + disabled: !elem.enabled, + })), + [list], + ); + + const listFacilitiesByFactoryId: ISelectProps['options'] = useMemo( + () => + facility.list + ?.filter((elem) => elem.visible) + ?.map((elem) => ({ + value: elem.id, + label: elem.title, + disabled: !elem.enabled, + })), + [facility.list], + ); + + useEffect(() => { + dispatch(fetchFactories()) + }, []) + + const [id, setId] = useState({factory: null, facility: null}) + + const FactoriesHandle = (factoryId: string) => { + dispatch(fetchFacilitiesByFactoryId(factoryId)) + setId({...id, factory: factoryId}) + setButtonValue({...buttonValue, factory: true}) + } + + const FacilitiesHandle = (facilityId: string) => { + dispatch(fetchFacilitiesById(facilityId)) + setId({...id, facility: facilityId}) + setButtonValue({...buttonValue, facility: true}) } - const Factories: FactoriesProps[] = [ - { value: 'portovaya', label: 'Завод по сжижению газа (СПГ Портовая)'}, - { value: 'lucy', label: 'Завод по переработке нефти "Киришинефтеоргсинтез"' }, - { value: 'Yiminghe', label: 'yiminghe' }, - { value: 'disabled', label: 'Disabled', disabled: true }, - ] return ( - + +
); }; diff --git a/src/pages/ObjectPage/index.ts b/src/pages/ObjectPage/index.ts deleted file mode 100644 index 00644e3..0000000 --- a/src/pages/ObjectPage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ObjectPageAsync as ObjectPage } from './ui/ObjectPage.async'; diff --git a/src/pages/ObjectPage/ui/ObjectPage.async.tsx b/src/pages/ObjectPage/ui/ObjectPage.async.tsx deleted file mode 100644 index 1f1e679..0000000 --- a/src/pages/ObjectPage/ui/ObjectPage.async.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { lazy } from 'react'; - -export const ObjectPageAsync = lazy(() => new Promise(resolve => { - // @ts-ignore - setTimeout(() => resolve(import('./ObjectPage')), 1500) -})); \ No newline at end of file diff --git a/src/pages/ObjectPage/ui/ObjectPage.tsx b/src/pages/ObjectPage/ui/ObjectPage.tsx deleted file mode 100644 index 39f8635..0000000 --- a/src/pages/ObjectPage/ui/ObjectPage.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useParams } from "react-router-dom"; - -const ObjectPage = () => { - const { id } = useParams(); - console.log("id", id); - return ( -
-

- Технологическая схема объекта c id {id} -

-
- ); -}; - -export default ObjectPage; diff --git a/src/pages/TestPage/ui/TestPage.tsx b/src/pages/TestPage/ui/TestPage.tsx index dba0a37..b193ce7 100644 --- a/src/pages/TestPage/ui/TestPage.tsx +++ b/src/pages/TestPage/ui/TestPage.tsx @@ -1,8 +1,17 @@ +import { useAppSelector } from "shared/lib/hooks/useAppSelector/useAppSelector"; const TestPage = () => { + const currentFacility = useAppSelector(state => state.facility.currentFacility) + return ( -
-

Тестирование

+
+

+ {currentFacility?.title} +

+

Описание ТП

+

+ {currentFacility?.description} +

); }; diff --git a/src/shared/api/api.ts b/src/shared/api/api.ts new file mode 100644 index 0000000..302b0ed --- /dev/null +++ b/src/shared/api/api.ts @@ -0,0 +1,6 @@ +import axios from 'axios'; +const __API__ = 'http://localhost:3001'; + +export const $api = axios.create({ + baseURL: __API__, +}); \ No newline at end of file diff --git a/src/shared/assets/icons/SidebarToggleIconLeft.tsx b/src/shared/assets/icons/SidebarToggleIconLeft.tsx new file mode 100644 index 0000000..2400f06 --- /dev/null +++ b/src/shared/assets/icons/SidebarToggleIconLeft.tsx @@ -0,0 +1,18 @@ +const SidebarToggleIconLeft = ({ + color = "var(--color-accent-800)", + width = 30, + height = 30, +}: { + color?: string; + width?: number; + height?: number +}) => ( + + + +); + +export default SidebarToggleIconLeft; \ No newline at end of file diff --git a/src/shared/assets/icons/SidebarToggleIconRight.tsx b/src/shared/assets/icons/SidebarToggleIconRight.tsx new file mode 100644 index 0000000..9f66165 --- /dev/null +++ b/src/shared/assets/icons/SidebarToggleIconRight.tsx @@ -0,0 +1,18 @@ +const SidebarToggleIconRight = ({ + color = "var(--color-accent-800)", + width = 30, + height = 30, +}: { + color?: string; + width?: number; + height?: number +}) => ( + + + +); + +export default SidebarToggleIconRight; \ No newline at end of file diff --git a/src/shared/assets/icons/TurnLeftArrow.tsx b/src/shared/assets/icons/TurnLeftArrow.tsx new file mode 100644 index 0000000..a02182c --- /dev/null +++ b/src/shared/assets/icons/TurnLeftArrow.tsx @@ -0,0 +1,18 @@ +const TurnLeftArrow = ({ + color = "var(--color-accent-800)", + width = 30, + height = 30, +}: { + color?: string; + width?: number; + height?: number; +}) => ( + + + +); + +export default TurnLeftArrow; diff --git a/src/shared/lib/hooks/useGetFileURL/useGetFileURL.ts b/src/shared/lib/hooks/useGetFileURL/useGetFileURL.ts new file mode 100644 index 0000000..58a9332 --- /dev/null +++ b/src/shared/lib/hooks/useGetFileURL/useGetFileURL.ts @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import { storage } from "shared/services/firebase/firebase"; +import { ref, getDownloadURL, uploadBytesResumable } from "firebase/storage"; +import { message } from "antd"; + +export interface IUseGetFileURLReturn { + url: string, + isLoading: boolean +} + +export const useGetFileURL = (folder: string, fileName: string ): IUseGetFileURLReturn => { + const [url, setUrl] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + const fileRef = ref(storage, `${folder}/${fileName}`) + + useEffect(() => { + setIsLoading(true); + getDownloadURL(fileRef).then(function(url) { + setUrl(url); + setIsLoading(false); + }).catch(function(error) { + setIsLoading(false); + message.error(`${error}`); + }); + }, [fileName]) + + return {url, isLoading} +} \ No newline at end of file diff --git a/src/shared/services/firebase/firebase.ts b/src/shared/services/firebase/firebase.ts index 05475da..35495ce 100644 --- a/src/shared/services/firebase/firebase.ts +++ b/src/shared/services/firebase/firebase.ts @@ -1,4 +1,6 @@ import { initializeApp } from 'firebase/app'; +import { getFirestore } from "firebase/firestore"; +import { getStorage } from "firebase/storage"; const firebaseConfig = { apiKey: process.env.FIREBASE_API_KEY, @@ -9,6 +11,6 @@ const firebaseConfig = { appId: process.env.FIREBASE_API_APP_ID, }; -console.log(firebaseConfig); - const app = initializeApp(firebaseConfig); +export const db = getFirestore(app); +export const storage = getStorage(app) \ No newline at end of file diff --git a/src/shared/ui/Button/Button.tsx b/src/shared/ui/Button/Button.tsx index 7846a30..20bf17e 100644 --- a/src/shared/ui/Button/Button.tsx +++ b/src/shared/ui/Button/Button.tsx @@ -18,7 +18,9 @@ export const Button:FC = memo((props) => { colorPrimary: 'var(--color-accent-800)', colorPrimaryHover: 'var(--color-accent-600)', colorPrimaryActive: 'var(--color-accent-800)', - colorTextLightSolid: color[theme]['--color-primary-200'], + colorTextLightSolid: color[theme]['--bg-color'], + colorTextDisabled: color[theme]['--color-primary-200'], + borderColorDisabled: color[theme]['--color-primary-600'], } } }} diff --git a/src/shared/ui/Message/Message.tsx b/src/shared/ui/Message/Message.tsx index a99df1b..51b4f15 100644 --- a/src/shared/ui/Message/Message.tsx +++ b/src/shared/ui/Message/Message.tsx @@ -1,21 +1,27 @@ import { ConfigProvider, message as ANTMessage } from "antd"; -import { FC, memo } from "react"; +import { FC, memo, useEffect, useState } from "react"; import { IMessageProps } from "./IProps"; import { Button } from "../Button"; import { useTheme } from "app/providers/ThemeProvider"; import { color } from "app/styles/themes/theme" import { Theme } from "app/providers/ThemeProvider"; +import React from "react"; export const Message:FC = memo((props) => { const {theme} = useTheme() + const [errorState, setErrorState] = useState(false) const [messageApi, contextHolder] = ANTMessage.useMessage(); - const btnClickHdlr = (): void => { - messageApi.success("Регистрация прошла успешно!"); - messageApi.error("Введены некорректные данные!"); + const messageShow = (): void => { + setErrorState(true) + messageApi.error(props.content); }; + useEffect(() => { + messageShow() + }, [errorState]) + return ( = memo((props) => { }} > {contextHolder} - ); }); diff --git a/src/shared/ui/Select/IProps.ts b/src/shared/ui/Select/IProps.ts index 576ba67..a326b94 100644 --- a/src/shared/ui/Select/IProps.ts +++ b/src/shared/ui/Select/IProps.ts @@ -1,3 +1,4 @@ import { SelectProps } from "antd"; -export interface ISelectProps extends SelectProps {} \ No newline at end of file +export interface ISelectProps extends SelectProps { +} \ No newline at end of file diff --git a/src/shared/ui/Select/Select.tsx b/src/shared/ui/Select/Select.tsx index 8a76a68..e089b0f 100644 --- a/src/shared/ui/Select/Select.tsx +++ b/src/shared/ui/Select/Select.tsx @@ -4,13 +4,9 @@ import { ConfigProvider, Select as ANTSelect } from "antd"; import { useTheme } from "app/providers/ThemeProvider"; import { color } from "app/styles/themes/theme" -export const Select:FC = memo(({options, defaultValue}, props) => { +export const Select:FC = memo(({options, defaultValue, onChange, disabled} , props) => { const {theme} = useTheme() - const handleChange = (value: string) => { - console.log(`selected ${value}`); -}; -console.log(options) return ( ) }) diff --git a/src/shared/ui/Skeleton/Skeleton.module.scss b/src/shared/ui/Skeleton/Skeleton.module.scss new file mode 100644 index 0000000..7277e59 --- /dev/null +++ b/src/shared/ui/Skeleton/Skeleton.module.scss @@ -0,0 +1,26 @@ +.template { + display: flex; + flex-direction: column; +} + +.Skeleton:empty { + display: flex; + border-radius: 5px; + padding: 17px 20px; + margin-bottom: 5px; + + cursor: progress; + background: + linear-gradient(0.25turn, transparent, var(--color-primary-600), transparent), + linear-gradient(var(--color-primary-900), var(--color-primary-900)), + linear-gradient(var(--color-primary-900), var(--color-primary-900)); + background-repeat: no-repeat; + background-position: -315px 0, 0 0, 0px 190px, 50px 195px; + animation: loading 1.5s infinite; +} + +@keyframes loading { + to { + background-position: 315px 0, 0 0, 0 190px, 50px 195px; + } +} \ No newline at end of file diff --git a/src/shared/ui/Skeleton/Skeleton.tsx b/src/shared/ui/Skeleton/Skeleton.tsx new file mode 100644 index 0000000..606a373 --- /dev/null +++ b/src/shared/ui/Skeleton/Skeleton.tsx @@ -0,0 +1,19 @@ +import { FC } from 'react'; +import cl from './Skeleton.module.scss' + +interface SkeletonProps { + count: number; +} + +export const Skeleton:FC = ({count}) => { + const skelentons = Array.from({length: count}, (_, index) => { + return ( +
+
+
+ ); + }); + + + return <>{skelentons}; +}; \ No newline at end of file diff --git a/src/shared/ui/Skeleton/index.ts b/src/shared/ui/Skeleton/index.ts new file mode 100644 index 0000000..6b1cf97 --- /dev/null +++ b/src/shared/ui/Skeleton/index.ts @@ -0,0 +1 @@ +export { Skeleton } from './Skeleton'; \ No newline at end of file diff --git a/src/widgets/FacilityParameters/index.ts b/src/widgets/FacilityParameters/index.ts new file mode 100644 index 0000000..08f46c7 --- /dev/null +++ b/src/widgets/FacilityParameters/index.ts @@ -0,0 +1 @@ +export {FacilityParameters} from "./ui/FacilityParameters" \ No newline at end of file diff --git a/src/widgets/FacilityParameters/ui/FacilityParameters.module.scss b/src/widgets/FacilityParameters/ui/FacilityParameters.module.scss new file mode 100644 index 0000000..eabad20 --- /dev/null +++ b/src/widgets/FacilityParameters/ui/FacilityParameters.module.scss @@ -0,0 +1,20 @@ + +.FacilityParameters { + display: flex; + // flex-direction: column; +} + +.parameters { + padding: 3px 5px; + background: var(--color-primary-600); + border-radius: 5px; +} + +.parameterInfo { + font-size: 12px; +} + +.parameterText { + font-weight: 700; + color: var(--color-accent-800) +} diff --git a/src/widgets/FacilityParameters/ui/FacilityParameters.tsx b/src/widgets/FacilityParameters/ui/FacilityParameters.tsx new file mode 100644 index 0000000..197cbd8 --- /dev/null +++ b/src/widgets/FacilityParameters/ui/FacilityParameters.tsx @@ -0,0 +1,50 @@ +import { IParameters } from 'entities/TechnologicalParameters/types/types'; +import { FC, useState } from 'react'; +import cl from "./FacilityParameters.module.scss" +import React from 'react'; +import { useAppSelector } from 'shared/lib/hooks/useAppSelector/useAppSelector'; + +interface IProps { + parameters: IParameters[]; + collapsed?: boolean +} + + +export const FacilityParameters:FC = ({parameters, collapsed}) => { + + const positionX = collapsed ? parameters?.map(item => item?.collapsedPositionX) : parameters?.map(item => item?.expandedPositionX) + const positionY = collapsed ? parameters?.map(item => item?.collapsedPositionY) : parameters?.map(item => item?.expandedPositionY) + return ( +
+ {parameters?.map((item, index) => ( +
+ {item?.pressure && ( +
+ {item.pressure.title} = + {item.pressure.value} + {item.pressure.unit} +
+ )} + {item?.flow && ( +
+ {item.flow.title} = + {item.flow.value} + {item.flow.unit} +
+ )} + {item?.temperature && ( +
+ {item.temperature.title} = + {item.temperature.value} + {item.temperature.unit} +
+ )} +
+ ))} +
+ ); +}; diff --git a/src/widgets/Header/AccountMenu/ui/AccountMenu.tsx b/src/widgets/Header/AccountMenu/ui/AccountMenu.tsx index dc91f26..a4e1a84 100644 --- a/src/widgets/Header/AccountMenu/ui/AccountMenu.tsx +++ b/src/widgets/Header/AccountMenu/ui/AccountMenu.tsx @@ -4,6 +4,7 @@ import { ThemeSwitcher } from 'shared/ui/ThemeSwitcher'; import { NavLink as Link } from 'react-router-dom'; import DefaultUserIcon from 'shared/assets/icons/DefaultUserIcon'; import { getAccount } from 'app/providers/router/routeConfig/routes'; +import { useAppSelector } from 'shared/lib/hooks/useAppSelector/useAppSelector'; interface AccountMenuProps { className?: string; @@ -11,11 +12,13 @@ interface AccountMenuProps { } export const AccountMenu: FC = () => { + + const userId = useAppSelector(state => state.user.user.id) return (
- +
diff --git a/src/widgets/Header/Navigation/index.ts b/src/widgets/Header/Navigation/index.ts new file mode 100644 index 0000000..32b2f7c --- /dev/null +++ b/src/widgets/Header/Navigation/index.ts @@ -0,0 +1 @@ +export {Navigation} from "./ui/Navigation" \ No newline at end of file diff --git a/src/widgets/Header/Navigation/ui/Navigation.module.scss b/src/widgets/Header/Navigation/ui/Navigation.module.scss new file mode 100644 index 0000000..b006c0c --- /dev/null +++ b/src/widgets/Header/Navigation/ui/Navigation.module.scss @@ -0,0 +1,45 @@ +.header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.logout { + display: flex; + align-items: center; + gap: 5px; + cursor: pointer; + color: var(--color-accent-800); +} + +.logout:hover { + transition: background-color 0.2s; + color: var(--color-accent-600); +} + +.logoutText { + font-weight: 600; +} + +.account { + display: flex; + align-items: center; + gap: 15px +} + +.accountText { + padding: 8px 20px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + background: var(--color-primary-900); + border-radius: 40px; + cursor: pointer; +} + +.accountText:hover { + background: var(--color-primary-600); + transition: background-color 0.2s; +} \ No newline at end of file diff --git a/src/widgets/Header/Navigation/ui/Navigation.tsx b/src/widgets/Header/Navigation/ui/Navigation.tsx new file mode 100644 index 0000000..d1464fd --- /dev/null +++ b/src/widgets/Header/Navigation/ui/Navigation.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import cl from "./Navigation.module.scss" +import LogoutIcon from 'shared/assets/icons/LogoutIcon'; +import { ThemeSwitcher } from 'shared/ui/ThemeSwitcher'; +import { Link } from 'react-router-dom'; +import { getAccount } from 'app/providers/router/routeConfig/routes'; +import { useAuth } from 'entities/Auth/hooks/useAuth'; +import { useLogout } from 'entities/Auth/hooks/useLogout'; + +export const Navigation = () => { + const { isAuth, user } = useAuth(); + const { logout } = useLogout(); + return ( + + ); +}; diff --git a/src/widgets/Header/NavigationMenu/ui/NavigationMenu.tsx b/src/widgets/Header/NavigationMenu/ui/NavigationMenu.tsx index b10e52b..ad3350b 100644 --- a/src/widgets/Header/NavigationMenu/ui/NavigationMenu.tsx +++ b/src/widgets/Header/NavigationMenu/ui/NavigationMenu.tsx @@ -1,19 +1,28 @@ -import { getInfographics, getObject, getTest } from 'app/providers/router/routeConfig/routes'; +import { getInfographics, getTest, getFacility } from 'app/providers/router/routeConfig/routes'; import cl from './NavigationMenu.module.scss'; import AppLink, { AppLinkTheme } from 'shared/ui/AppLink/AppLink'; +import { useAppSelector } from 'shared/lib/hooks/useAppSelector/useAppSelector'; +import { useParams } from 'react-router-dom'; interface LinksProps { to: string; name: string; } -const Links: LinksProps[] = [ - { to: getObject(), name: 'Объект' }, - { to: getInfographics(), name: 'Инфографика' }, - { to: getTest(), name: 'Тестирование' }, -]; - export const NavigationMenu = () => { + const {currentFacility} = useAppSelector(state => state.facility) + const URL = useParams() + const value = URL["*"]; + const parts = value.split('/'); + const factoryKey = parts[0]; + const facilityId= parts[2]; + + const Links: LinksProps[] = [ + { to: getFacility(factoryKey, facilityId), name: 'Установка' }, + { to: getInfographics(factoryKey, facilityId), name: 'Инфографика ТП' }, + { to: getTest(factoryKey, facilityId), name: 'Описание ТП' }, + ]; + return (