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/src/App.tsx b/src/App.tsx
index 0d84e96..8f11027 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,7 +5,7 @@ import { RootPathContext } from '@asb/context'
import SuspenseFallback from '@components/SuspenseFallback'
import { NoAccessComponent } from '@utils'
-import '@styles/App.less'
+import '@styles/pages/App.less'
const UserOutlet = lazy(() => import('@components/outlets/UserOutlet'))
const DepositsOutlet = lazy(() => import('@components/outlets/DepositsOutlet'))
diff --git a/src/components/FastRunMenu.tsx b/src/components/FastRunMenu.tsx
index dfaed0c..b21db2f 100644
--- a/src/components/FastRunMenu.tsx
+++ b/src/components/FastRunMenu.tsx
@@ -5,13 +5,13 @@ import { AutoComplete } from 'antd'
import { join } from 'path'
import { useWell } from '@asb/context'
-import { makeItem, PrivateWellMenuItem } from './PrivateWellMenu'
+import { makeItem, PrivateMenuItem } from './PrivateMenu'
import { hasPermission, isURLAvailable } from '@utils'
import { menuItems as adminMenuItems } from '@pages/AdminPanel/AdminNavigationMenu'
-import { menuItems as wellMenuItems } from '@pages/Well/NavigationMenu'
+import { menuItems as wellMenuItems } from '@pages/Well/WellNavigationMenu'
-import '@styles/fast_run_menu.less'
+import '@styles/components/fast_run_menu.less'
const transliterationTable = {
'q': 'й', 'w': 'ц', 'e': 'у', 'r': 'к', 't': 'е', 'y': 'н', 'u': 'г', 'i': 'ш', 'o': 'щ', 'p': 'з', '[': 'х', ']': 'ъ', '{': 'х', '}': 'ъ',
@@ -29,7 +29,7 @@ const transliterateToEn = (text: string) => Object.entries(transliterationTable)
const applyVars = (route: string, vars?: object): string => !vars ? route :
Object.entries(vars).reduce((out, [key, value]) => out.replaceAll(`{${key}}`, value), route)
-const makeOptions = (items: PrivateWellMenuItem[], vars?: object): OptionType[] => {
+const makeOptions = (items: PrivateMenuItem[], vars?: object): OptionType[] => {
const out: OptionType[] = []
items.forEach((item) => {
if (!hasPermission(item.permissions)) return
@@ -78,7 +78,7 @@ export const FastRunMenu = memo(() => {
]
if (isURLAvailable('/admin'))
- menus.push(makeItem('Панель администратора', '/admin', [], undefined, adminMenuItems as PrivateWellMenuItem[]))
+ menus.push(makeItem('Панель администратора', '/admin', [], undefined, adminMenuItems as PrivateMenuItem[]))
if (well.id)
menus.push(
diff --git a/src/components/LayoutPortal.tsx b/src/components/LayoutPortal.tsx
index c8c3f0b..ef88f30 100644
--- a/src/components/LayoutPortal.tsx
+++ b/src/components/LayoutPortal.tsx
@@ -1,7 +1,7 @@
import { Breadcrumb, Layout, LayoutProps, Menu, SiderProps } from 'antd'
import { Key, memo, ReactNode, Suspense, useCallback, useEffect, useMemo, useState } from 'react'
import { ItemType } from 'antd/lib/menu/hooks/useItems'
-import { Link, Outlet } from 'react-router-dom'
+import { Link, Outlet, useLocation } from 'react-router-dom'
import {
ApartmentOutlined,
CodeOutlined,
@@ -18,7 +18,7 @@ import SuspenseFallback from './SuspenseFallback'
import Logo from '@images/Logo'
-import '@styles/layout.less'
+import '@styles/components/layout.less'
const { Content, Sider } = Layout
@@ -31,7 +31,7 @@ export type LayoutPortalProps = Omit & {
siderProps?: SiderProps & { userMenuProps?: UserMenuProps }
isAdmin?: boolean
fallback?: JSX.Element
- breadcrumb?: boolean | JSX.Element
+ breadcrumb?: boolean | ((path: string) => JSX.Element)
topRightBlock?: JSX.Element
}
@@ -49,6 +49,7 @@ const _LayoutPortal = memo(() => {
const [userMenuOpen, setUserMenuOpen] = useState(false)
const [currentWell, setCurrentWell] = useState('')
const [props, setProps] = useState(defaultProps)
+ const location = useLocation()
const { isAdmin, title, sheet, showSelector, selectorProps, sider, siderProps, fallback, breadcrumb, topRightBlock, ...other } = useMemo(() => props, [props])
@@ -66,6 +67,8 @@ const _LayoutPortal = memo(() => {
makeItem('Профиль', 'profile', , null, () => setUserMenuOpen((prev) => !prev)),
].filter(Boolean) as ItemType[], [isAdmin, currentWell])
+ const breadcrumbItems = useMemo(() => typeof breadcrumb === 'function' && breadcrumb(location.pathname), [breadcrumb, location.pathname])
+
return (
{(sider || siderProps) && (
@@ -114,7 +117,7 @@ const _LayoutPortal = memo(() => {
setWellsTreeOpen((prev) => !prev)}>{currentWell}
)}
- {breadcrumb !== true && breadcrumb}
+ {breadcrumbItems}
)}
{topRightBlock}
diff --git a/src/components/LoaderPortal.tsx b/src/components/LoaderPortal.tsx
index 770c37a..66bce54 100755
--- a/src/components/LoaderPortal.tsx
+++ b/src/components/LoaderPortal.tsx
@@ -3,12 +3,19 @@ import { HTMLAttributes } from 'react'
import { Loader } from '@components/icons'
type LoaderPortalProps = HTMLAttributes & {
+ /** Показать ли загрузку */
show?: boolean,
+ /** Затемнять ли дочерний блок */
fade?: boolean,
+ /** Параметры спиннера */
spinnerProps?: HTMLAttributes,
+ /** Заполнять ли контент на 100% */
fillContent?: boolean
}
+/**
+ * @description Добавляет оверлей загрузки над обёрнутым блоком
+ */
export const LoaderPortal: React.FC = ({ className = '', show, fade = true, children, spinnerProps, fillContent, ...other }) => (