diff --git a/.vscode/settings.json b/.vscode/settings.json
index a8ab85f..01228b7 100755
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,17 @@
{
"cSpell.words": [
- "день"
+ "день",
+ "спиннера",
+ "Saub",
+ "КНБК",
+ "САУБ",
+ "antd",
+ "Poprompt",
+ "saub",
+ "setpoint",
+ "Setpoints",
+ "usehooks"
],
- "liveServer.settings.port": 5501
+ "liveServer.settings.port": 5501,
+ "cSpell.language": "en,ru"
}
\ No newline at end of file
diff --git a/CODE_STANDART.md b/CODE_STANDART.md
new file mode 100644
index 0000000..92dce3a
--- /dev/null
+++ b/CODE_STANDART.md
@@ -0,0 +1,170 @@
+## 1. Общие положения
+1. Все несамостоятельные компоненты должны быть написаны на TypeScript. Для самостоятельных компонентов (использующихся как страницы) (далее страницы) допускается использование JavaScript для ускорения написания;
+
+### 1.1. Файловая структура проекта
+1. Компоненты должны распределяться по директориям в соответствии со своим назначением:
+ * `src/context` - Для контекстов приложения;
+ * `src/components` - Для несамостоятельных компонентов, применяющихся многократно;
+ * `src/pages` - Для страниц и компонентов, использующихся исключительно в единственном экземпляре;
+ * `src/images` - Для компонентов-изображений.
+2. Если страница описывается 1 файлом она должна именоваться в соответствии с содержимым, в ином случае должна быть создана директория с соответствующим названием, внутри которой будут находиться файлы страницы. Основной файл в таком случае должен быть переименован в `index.jsx`;
+3. Файлы именуются в соответствии с таблицей:
+ | Тип содержимого файла | Расширение | Стиль именования |
+ |--------------------------------------|------------|--------------------------|
+ | Компонент или страница | jsx/tsx | **PascalCase** |
+ | Файл стилей | css/less | **snake_case** |
+ | Вспомогательные методы или константы | js/ts | **snake_case** |
+ | Описательные документы | md | **SCREAMING_SNAKE_CASE** |
+
+### 1.2. Стилизация кода
+1. Все строки должны по возможности описываться одинарными кавычками или при необходимости обратными:
+ ```js
+ const name = 'world'
+ const msg = 'Hello, \'' + name + '\'!'
+ const toPrint = `Message: ${msg}`
+ ```
+2. Все переменные по возможности должны инициализироваться как `const`, применение `var` не допускается;
+3. Переменные именуются в соответствии с таблицей:
+ | Тип переменной | Стиль именования |
+ |-------------------|--------------------------|
+ | Метод, переменная | **camelCase** |
+ | Константы | **SCREAMING_SNAKE_CASE** |
+ | Компонент | **PascalCase** |
+
+### 1.3. Импортирование / Экспортирование
+1. Импортированные файлы (в том числе lazy import) необходимо указывать в самом верху документа в следующем порядке с разделением пустой строкой:
+ 1. Внешние зависимости (`react`, `antd`, `webpack` и т.д.);
+ 2. Локальные компоненты по порядку:
+ 1. Контексты (`@asb/context`);
+ 2. Компоненты (`@components/Table`);
+ 3. Вспомогательные методы (`@utils`);
+ 4. Сервисы API (`@api`).
+ 3. Изображения и компоненты-изображения (`@images`);
+ 4. Стили (`@styles`);
+ 5. Lazy import (`const page = React.lazy(() => import('./page'))`).
+2. При импорте локальных файлов стоит пользоваться alias'ами:
+ | Путь | Alias |
+ |------------------|--------------|
+ | src/components | @components |
+ | src/context | @asb/context |
+ | src/images | @images |
+ | src/pages | @pages |
+ | src/services/api | @api |
+ | src/styles | @styles |
+ | src/utils | @utils |
+3. По возможности импортировать из пакетов и файлов только использующиеся сущности:
+ ```tsx
+ // вместо
+ import React from 'react'
+ const page: React.ReactNode = React.lazy(() => import (...))
+
+ // стоит использовать
+ import { lazy, ReactNode } from 'react'
+ const page: ReactNode = lazy(() => import (...))
+ ```
+
+## 2. JS
+1. Методы, константы и переменные документируются в соответствии с `JSDoc`;
+2. При документации страниц необходимо указать её название, краткое описание и описание получаемых параметров:
+ ```jsx
+ import { memo } from 'react'
+
+ import LoaderPortal from '@components/LoaderPortal'
+
+ /**
+ * Тестовая страница
+ *
+ * @description Данная страница не имеет смысла и просто выводит переданное название и контент
+ * @param title - Название страницы
+ * @param content - Контент страницы
+ * @param loading - Отображать ли оверлей загрузки над блоком страницы
+ */
+ export const TestPage = memo(({ title, content, loading }) => (
+
+
+
{title}
+
{content}
+
+
+ ))
+
+ export default TestPage
+ ```
+
+## 3. TS
+1. Методы, константы и переменные документируются в соответствии с `TSDoc`;
+2. При документации компонентов необходимо указать их название, краткое описание, а также описать параметры в типе:
+ ```tsx
+ import { memo } from 'react'
+
+ import LoaderPortal from '@components/LoaderPortal'
+
+ export type TestPageProps = {
+ /** Название страницы */
+ title: ReactNode
+ /** Контент страницы */
+ content: ReactNode
+ /** Отображать ли оверлей загрузки над блоком страницы */
+ loading: boolean
+ }
+
+ /**
+ * Тестовая страница
+ *
+ * @description Данная страница не имеет смысла и просто выводит переданное название и контент
+ */
+ export const TestPage = memo(({ title, content, loading }) => (
+
+
+
{title}
+
{content}
+
+
+ ))
+
+ export default TestPage
+ ```
+3. Использование `any` в типах допустимо только, если значение используется только в параметрах компонентов, обозначенных типом `any`. Если метод предполагает работу с разными типами значений стоит описать его как обобщённый.
+
+
+## 4. JSX/TSX
+
+### 4.1. Стилизация кода
+1. Все указываемые к компоненту параметры должны быть обёрнуты в фигурные скобки, кроме параметров флагов со значением `true`:
+ ```jsx
+
+ ```
+2. Если описание параметров компонента не укладывается в ширину в 120 строк стоит перенести их в соответствии с шаблоном:
+ ```jsx
+
+ ```
+3. Если JSX код передаётся как значение стоит обернуть его в круглые скобки:
+ ```jsx
+ const a = (
+
+ )
+ ```
+
+### 4.2. Логика поведения
+1. Не допускается создание значений ссылочных типов в области рендера. Они должны быть вынесены в переменные или константы;
+2. Не допускается создание переменных в функциональных компонентов без использования хуков `useMemo`/`useCallback`/`useState`;
+3. Если переменные или методы не имеют зависимостей и не вызывают методы, доступные исключительно внутри компонента, они должны быть вынесены выше кода компонента.
+
+
+## 5. LESS
+1. Использование id должно быть сведено к минимуму;
+2. Все классы именуются с префиксом компании "`dd-`";
+3. Слова в классах разделяются тире ("`-`");
+4. Файлы именуются в соответствии с компонентом, к которому относятся;
+5. В одном файле описываются стили либо к конкретному компоненту, либо к странице;
+6. Файл со стилями должен подключаться не более чем к одному компоненту (странице);
+7. Файлы поделены на директории виду компонента, к которому применяются стили:
+ * `styles/components` - для компонентов, не использующихся самостоятельно;
+ * `styles/pages` - для компонентов, использующихся как страница;
+ * `styles/widgets` - для компонентов, применяющихся как виджеты в дашбордах.
diff --git a/README.md b/README.md
index cc48f9f..8772bdf 100755
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
Установка выполняется одной командой:
```bash
-npm i
+npm ci
```
## 2. Автогенерация сервисов
@@ -17,14 +17,14 @@ npm i
Автогенерацию можно запустить с помощью уже прописанных в [package.json](package.json) скриптов, либо вручную.
-Если сервер запущен на текущей машине достаточно написать:
+Если сервер запущен на текущей машине достаточно написать:
```bash
-npm run update_openapi
+npm run oul
```
-Для получения сервисов с основного сервера:
+Для получения сервисов с основного сервера:
```bash
-npm run update_openapi_server
+npm run oug_dev
```
или же ручной вариант:
@@ -36,12 +36,12 @@ npx openapi -i http://{IP_ADDRESS}:{PORT}/swagger/v1/swagger.json -o src/service
На данный момент имеются следующие IP-адреса:
-| IP-адрес | Описание |
-|:-|:-|
-| 127.0.0.1:5000 | Локальный адрес вашей машины (привязан к `update_openapi`) |
-| 192.168.1.113:5000 | Локальный адрес development-сервера (привязан к `update_openapi_server`) |
-| 46.146.209.148:89 | Внешний адрес development-сервера |
-| cloud.digitaldrilling.ru | Внешний адрес production-сервера |
+| IP-адрес | Команда | Описание |
+|:-------------------------|:--------|:------------------------------------|
+| 127.0.0.1:5000 | oul | Локальный адрес вашей машины |
+| 192.168.1.113:5000 | oud | Локальный адрес development-сервера |
+| 46.146.209.148:89 | oug_dev | Внешний адрес development-сервера |
+| cloud.digitaldrilling.ru | oug | Внешний адрес production-сервера |
## 3. Компиляция production-версии приложения
После выполнения вышеописанных пунктов приложение готово к компиляции.
diff --git a/package-lock.json b/package-lock.json
index 3cf958f..8912fac 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,8 +17,7 @@
"react-dom": "^18.1.0",
"react-router-dom": "^6.3.0",
"rxjs": "^7.5.5",
- "usehooks-ts": "^2.6.0",
- "web-vitals": "^2.1.4"
+ "usehooks-ts": "^2.6.0"
},
"devDependencies": {
"@babel/core": "^7.18.2",
@@ -14786,11 +14785,6 @@
"minimalistic-assert": "^1.0.0"
}
},
- "node_modules/web-vitals": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz",
- "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg=="
- },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -26492,11 +26486,6 @@
"minimalistic-assert": "^1.0.0"
}
},
- "web-vitals": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz",
- "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg=="
- },
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
diff --git a/src/App.tsx b/src/App.tsx
index 0ac3eb0..8f11027 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,17 +1,15 @@
import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom'
import { lazy, memo, Suspense } from 'react'
-import locale from 'antd/lib/locale/ru_RU'
-import { ConfigProvider } from 'antd'
import { RootPathContext } from '@asb/context'
-import { UserOutlet } from '@components/outlets'
-import LayoutPortal from '@components/LayoutPortal'
import SuspenseFallback from '@components/SuspenseFallback'
-import { getUser, NoAccessComponent } from '@utils'
-import { OpenAPI } from '@api'
+import { NoAccessComponent } from '@utils'
-import '@styles/include/antd_theme.less'
-import '@styles/App.less'
+import '@styles/pages/App.less'
+
+const UserOutlet = lazy(() => import('@components/outlets/UserOutlet'))
+const DepositsOutlet = lazy(() => import('@components/outlets/DepositsOutlet'))
+const LayoutPortal = lazy(() => import('@components/LayoutPortal'))
const Login = lazy(() => import('@pages/public/Login'))
const Register = lazy(() => import('@pages/public/Register'))
@@ -22,28 +20,23 @@ const Deposit = lazy(() => import('@pages/Deposit'))
const Cluster = lazy(() => import('@pages/Cluster'))
const Well = lazy(() => import('@pages/Well'))
-// OpenAPI.BASE = 'http://localhost:3000'
-// TODO: Удалить взятие из 'token' в следующем релизе, вставлено для совместимости
-OpenAPI.TOKEN = async () => getUser().token || localStorage.getItem('token') || ''
-OpenAPI.HEADERS = { 'Content-Type': 'application/json' }
-
export const App = memo(() => (
-
-
- }>
-
-
- } />
- } />
+
+ }>
+
+
+ } />
+ } />
- {/* Public pages */}
- } />
- } />
+ {/* Public pages */}
+ } />
+ } />
- {/* User pages */}
- }>
- } />
+ {/* User pages */}
+ }>
+ } />
+ }>
}>
{/* Admin pages */}
} />
@@ -54,11 +47,11 @@ export const App = memo(() => (
} />
-
-
-
-
-
+
+
+
+
+
))
export default App
diff --git a/src/components/CopyUrl.tsx b/src/components/CopyUrl.tsx
index 22e7747..2a157f4 100644
--- a/src/components/CopyUrl.tsx
+++ b/src/components/CopyUrl.tsx
@@ -1,5 +1,5 @@
import { cloneElement, memo, useCallback, useMemo, useState } from 'react'
-import { Button, ButtonProps } from 'antd'
+import { Button, ButtonProps, Tooltip } from 'antd'
import { CopyOutlined } from '@ant-design/icons'
import { invokeWebApiWrapperAsync, notify } from './factory'
@@ -43,11 +43,9 @@ export type CopyUrlButtonProps = Omit & ButtonProps
export const CopyUrlButton = memo(({ sendLoading, hideUnsupported, onCopy, ...other }) => {
return (
- }
- title={'Скопировать URL в буфер обмена'}
- {...other}
- />
+
+ } {...other} />
+
)
})
diff --git a/src/components/Display.tsx b/src/components/Display.tsx
deleted file mode 100644
index a423bab..0000000
--- a/src/components/Display.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import moment from 'moment'
-import { useState, useEffect, memo, ReactNode } from 'react'
-import {CaretUpOutlined, CaretDownOutlined, CaretRightOutlined} from '@ant-design/icons'
-
-import '@styles/display.less'
-
-export const formatNumber = (value?: unknown, format?: number) =>
- Number.isInteger(format) && Number.isFinite(value)
- ? Number(value).toFixed(format)
- : Number(value).toPrecision(4)
-
-const iconStyle = { color:'#0008' }
-const displayValueStyle = { display: 'flex', flexGrow: 1 }
-
-export type ValueDisplayProps = {
- prefix?: ReactNode
- suffix?: ReactNode
- format?: number | string | ((arg: string) => ReactNode)
- isArrowVisible?: boolean
- enumeration?: Record
- value: string
-}
-
-export type DisplayProps = ValueDisplayProps & {
- className?: string
- label?: ReactNode
-}
-
-export const ValueDisplay = memo(({ prefix, value, suffix, isArrowVisible, format, enumeration }) => {
- const [val, setVal] = useState('---')
- const [arrowState, setArrowState] = useState({
- preVal: NaN,
- preTimestamp: Date.now(),
- direction: 0,
- })
-
- useEffect(() => {
- setVal((preVal) => {
- if ((value ?? '-') === '-' || value === '--') return '---'
- if (typeof format === 'function') return format(enumeration?.[value] ?? value)
- if (enumeration?.[value]) return enumeration[value]
-
- if (Number.isFinite(+value)) {
- if (isArrowVisible && (arrowState.preTimestamp + 1000 < Date.now())) {
- let direction = 0
- if (+value > arrowState.preVal)
- direction = 1
- if (+value < arrowState.preVal)
- direction = -1
-
- setArrowState({
- preVal: +value,
- preTimestamp: Date.now(),
- direction: direction,
- })
- }
-
- return formatNumber(value, Number(format))
- }
-
- if (value.length > 4) {
- const valueDate = moment(value)
- if (valueDate.isValid())
- return valueDate.format(String(format))
- }
-
- return value
- })
- },[value, isArrowVisible, arrowState, format, enumeration])
-
- let arrow = null
- if(isArrowVisible)
- switch (arrowState.direction){
- case 0:
- arrow =
- break
- case 1:
- arrow =
- break
- case -1:
- arrow =
- break
- default:
- break
- }
-
- return(
-
- {prefix} {val} {suffix}{arrow}
-
- )
-})
-
-export const Display = memo(({ className, label, ...other })=> (
-