forked from ddrilling/asb_cloud_front
* блок utils подразделён на functions, hooks, types и filters
* добавлен хук useFunctionalValue * добавлен хук useCachedFetch * удалён RCA * добавлен конфиг babel * добавлен конфиг webpack * обновлены все пакеты * добавлены базовые моки * добавлены конфиги для тестов * добавлена кнопка копирования url * роутер переписан * в Messages добавлен переход в Архив при клике на сообщение
This commit is contained in:
parent
b97066af6e
commit
d5e827532d
1
__mocks__/fileMock.js
Normal file
1
__mocks__/fileMock.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = 'test-file-stub'
|
1
__mocks__/styleMock.js
Normal file
1
__mocks__/styleMock.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = {}
|
7
babel.config.js
Normal file
7
babel.config.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@babel/preset-env',
|
||||||
|
['@babel/preset-react', {runtime: 'automatic'}],
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
}
|
@ -1,25 +0,0 @@
|
|||||||
const CracoLessPlugin = require('craco-less')
|
|
||||||
const CracoAlias = require('craco-alias')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
plugin: CracoLessPlugin,
|
|
||||||
options: {
|
|
||||||
lessLoaderOptions: {
|
|
||||||
lessOptions: {
|
|
||||||
//modifyVars: { '@primary-color': '#E20000' },
|
|
||||||
javascriptEnabled: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
plugin: CracoAlias,
|
|
||||||
options: {
|
|
||||||
source: 'tsconfig',
|
|
||||||
baseUrl: './src',
|
|
||||||
tsConfigPath: './tsconfig.paths.json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
30
custom.d.ts
vendored
Normal file
30
custom.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
declare module '*.gif' {
|
||||||
|
const src: string;
|
||||||
|
export default src;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.jpg' {
|
||||||
|
const src: string;
|
||||||
|
export default src;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.jpeg' {
|
||||||
|
const src: string;
|
||||||
|
export default src;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.png' {
|
||||||
|
const src: string;
|
||||||
|
export default src;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.svg' {
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
export const ReactComponent: React.FunctionComponent<
|
||||||
|
React.SVGProps<SVGSVGElement> & { title?: string }
|
||||||
|
>
|
||||||
|
|
||||||
|
const src: string
|
||||||
|
export default src
|
||||||
|
}
|
33712
package-lock.json
generated
33712
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
108
package.json
108
package.json
@ -3,41 +3,36 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@craco/craco": "^6.1.2",
|
"@microsoft/signalr": "^6.0.5",
|
||||||
"@microsoft/signalr": "^6.0.4",
|
"@svgr/webpack": "^6.2.1",
|
||||||
"@testing-library/jest-dom": "^5.11.10",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^11.2.6",
|
"@testing-library/user-event": "^14.2.0",
|
||||||
"@testing-library/user-event": "^12.8.3",
|
"@types/react-dom": "^18.0.5",
|
||||||
"@types/react-dom": "^18.0.3",
|
"antd": "^4.20.7",
|
||||||
"antd": "^4.15.0",
|
"chart.js": "^3.8.0",
|
||||||
"chart.js": "^3.6.0",
|
|
||||||
"chartjs-adapter-moment": "^1.0.0",
|
"chartjs-adapter-moment": "^1.0.0",
|
||||||
"chartjs-plugin-datalabels": "^2.0.0-rc.1",
|
"chartjs-plugin-datalabels": "^2.0.0",
|
||||||
"chartjs-plugin-zoom": "^1.1.1",
|
"chartjs-plugin-zoom": "^1.2.1",
|
||||||
"craco-less": "^1.17.1",
|
|
||||||
"d3": "^7.4.4",
|
"d3": "^7.4.4",
|
||||||
"moment": "^2.29.1",
|
"less": "^4.1.2",
|
||||||
"pigeon-maps": "^0.19.7",
|
"less-loader": "^11.0.0",
|
||||||
"react": "^17.0.2",
|
"moment": "^2.29.3",
|
||||||
"react-dom": "^17.0.2",
|
"pigeon-maps": "^0.21.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react": "^18.1.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-dom": "^18.1.0",
|
||||||
"rxjs": "^7.5.4",
|
"react-router-dom": "^6.3.0",
|
||||||
"typescript": "^4.2.3",
|
"rxjs": "^7.5.5",
|
||||||
"web-vitals": "^1.1.1"
|
"typescript": "^4.7.2",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "craco start",
|
"start": "webpack-dev-server --mode=development --open --hot",
|
||||||
"build": "craco build",
|
"build": "webpack --mode=production",
|
||||||
"test": "craco test",
|
"test": "jest",
|
||||||
"oul": "npx openapi -i http://127.0.0.1:5000/swagger/v1/swagger.json -o src/services/api",
|
"oul": "npx openapi -i http://127.0.0.1:5000/swagger/v1/swagger.json -o src/services/api",
|
||||||
"oud": "npx openapi -i http://192.168.1.70:5000/swagger/v1/swagger.json -o src/services/api",
|
"oud": "npx openapi -i http://192.168.1.70:5000/swagger/v1/swagger.json -o src/services/api",
|
||||||
"oug": "npx openapi -i http://46.146.209.148/swagger/v1/swagger.json -o src/services/api",
|
"oug": "npx openapi -i http://46.146.209.148/swagger/v1/swagger.json -o src/services/api",
|
||||||
"oug_dev": "npx openapi -i http://46.146.209.148:89/swagger/v1/swagger.json -o src/services/api",
|
"oug_dev": "npx openapi -i http://46.146.209.148:89/swagger/v1/swagger.json -o src/services/api"
|
||||||
"react_start": "react-scripts start",
|
|
||||||
"react_build": "react-scripts build",
|
|
||||||
"react_test": "react-scripts test",
|
|
||||||
"eject": "react-scripts eject"
|
|
||||||
},
|
},
|
||||||
"proxy": "http://46.146.209.148:89",
|
"proxy": "http://46.146.209.148:89",
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
@ -58,12 +53,57 @@
|
|||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"jsx",
|
||||||
|
"ts",
|
||||||
|
"tsx"
|
||||||
|
],
|
||||||
|
"moduleDirectories": [
|
||||||
|
"node_modules",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
|
||||||
|
"\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js",
|
||||||
|
"^@asb(.*)$": "<rootDir>/src$1",
|
||||||
|
"^@api(.*)$": "<rootDir>/src/services/api$1",
|
||||||
|
"^@components(.*)$": "<rootDir>/src/components$1",
|
||||||
|
"^@services(.*)$": "<rootDir>/src/services$1",
|
||||||
|
"^@pages(.*)$": "<rootDir>/src/pages$1",
|
||||||
|
"^@utils(.*)$": "<rootDir>/src/utils$1",
|
||||||
|
"^@images(.*)$": "<rootDir>/src/images$1",
|
||||||
|
"^@styles(.*)$": "<rootDir>/src/styles$1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/d3": "^7.1.0",
|
"@babel/core": "^7.18.2",
|
||||||
"@types/react": "^17.0.3",
|
"@babel/preset-env": "^7.18.2",
|
||||||
"@types/react-router-dom": "^5.3.2",
|
"@babel/preset-react": "^7.17.12",
|
||||||
"craco-alias": "^3.0.1",
|
"@babel/preset-typescript": "^7.17.12",
|
||||||
"openapi-typescript": "^3.4.1",
|
"@testing-library/react": "^13.3.0",
|
||||||
"openapi-typescript-codegen": "^0.21.0"
|
"@types/d3": "^7.4.0",
|
||||||
|
"@types/jest": "^28.1.0",
|
||||||
|
"@types/react": "^18.0.10",
|
||||||
|
"@types/react-router-dom": "^5.3.3",
|
||||||
|
"babel-jest": "^28.1.0",
|
||||||
|
"babel-loader": "^8.2.5",
|
||||||
|
"css-loader": "^6.7.1",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"interpolate-html-plugin": "^4.0.0",
|
||||||
|
"jest": "^28.1.0",
|
||||||
|
"openapi-typescript": "^5.4.0",
|
||||||
|
"openapi-typescript-codegen": "^0.23.0",
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
|
"react-test-renderer": "^18.1.0",
|
||||||
|
"source-map-loader": "^3.0.1",
|
||||||
|
"style-loader": "^3.3.1",
|
||||||
|
"ts-loader": "^9.3.0",
|
||||||
|
"url-loader": "^4.1.1",
|
||||||
|
"webpack": "^5.73.0",
|
||||||
|
"webpack-cli": "^4.9.2",
|
||||||
|
"webpack-dev-server": "^4.9.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,12 @@
|
|||||||
<html lang="ru">
|
<html lang="ru">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="white" />
|
<meta name="theme-color" content="white" />
|
||||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white" />
|
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white" />
|
||||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black" />
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black" />
|
||||||
<meta
|
<meta name="description" content="Онлайн мониторинг процесса бурения в реальном времени в офисе заказчика" />
|
||||||
name="description"
|
|
||||||
content="Онлайн мониторинг процесса бурения в реальном времени в офисе заказчика"
|
|
||||||
/>
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<title>АСБ Vision</title>
|
<title>АСБ Vision</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
48
src/App.tsx
48
src/App.tsx
@ -1,18 +1,17 @@
|
|||||||
import {
|
import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom'
|
||||||
BrowserRouter as Router,
|
|
||||||
Switch,
|
|
||||||
Route
|
|
||||||
} from 'react-router-dom'
|
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { ConfigProvider } from 'antd'
|
import { ConfigProvider } from 'antd'
|
||||||
import locale from 'antd/lib/locale/ru_RU'
|
import locale from 'antd/lib/locale/ru_RU'
|
||||||
|
|
||||||
import { PrivateRoute } from '@components/Private'
|
import { RootPathContext } from '@asb/context'
|
||||||
import { getUserToken } from '@utils/storage'
|
import { getUserToken, NoAccessComponent } from '@utils'
|
||||||
import { OpenAPI } from '@api'
|
import { OpenAPI } from '@api'
|
||||||
|
|
||||||
import Main from '@pages/Main'
|
import AdminPanel from '@pages/AdminPanel'
|
||||||
|
import Well from '@pages/Well'
|
||||||
import Login from '@pages/Login'
|
import Login from '@pages/Login'
|
||||||
|
import Cluster from '@pages/Cluster'
|
||||||
|
import Deposit from '@pages/Deposit'
|
||||||
import Register from '@pages/Register'
|
import Register from '@pages/Register'
|
||||||
|
|
||||||
import '@styles/App.less'
|
import '@styles/App.less'
|
||||||
@ -23,19 +22,26 @@ OpenAPI.HEADERS = {'Content-Type': 'application/json'}
|
|||||||
|
|
||||||
export const App = memo(() => (
|
export const App = memo(() => (
|
||||||
<ConfigProvider locale={locale}>
|
<ConfigProvider locale={locale}>
|
||||||
<Router>
|
<RootPathContext.Provider value={''}>
|
||||||
<Switch>
|
<Router>
|
||||||
<Route path={'/login'}>
|
<Routes>
|
||||||
<Login />
|
<Route index element={<Navigate to={Deposit.getKey()} replace />} />
|
||||||
</Route>
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
<Route path={'/register'}>
|
|
||||||
<Register />
|
{/* Public pages */}
|
||||||
</Route>
|
<Route path={Login.route} element={<Login />} />
|
||||||
<PrivateRoute path={'/'}>
|
<Route path={Register.route} element={<Register />} />
|
||||||
<Main />
|
|
||||||
</PrivateRoute>
|
{/* Admin pages */}
|
||||||
</Switch>
|
<Route path={AdminPanel.route} element={<AdminPanel />} />
|
||||||
</Router>
|
|
||||||
|
{/* User pages */}
|
||||||
|
<Route path={Deposit.route} element={<Deposit />} />
|
||||||
|
<Route path={Cluster.route} element={<Cluster />} />
|
||||||
|
<Route path={Well.route} element={<Well />} />
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
</RootPathContext.Provider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { Rule } from 'antd/lib/form'
|
|||||||
import { Form, Input, Modal, FormProps } from 'antd'
|
import { Form, Input, Modal, FormProps } from 'antd'
|
||||||
|
|
||||||
import { AuthService, UserDto } from '@api'
|
import { AuthService, UserDto } from '@api'
|
||||||
import { getUserId, getUserLogin } from '@utils/storage'
|
import { getUserId, getUserLogin } from '@utils'
|
||||||
import { passwordRules, createPasswordRules } from '@utils/validationRules'
|
import { passwordRules, createPasswordRules } from '@utils/validationRules'
|
||||||
|
|
||||||
import LoaderPortal from './LoaderPortal'
|
import LoaderPortal from './LoaderPortal'
|
||||||
|
55
src/components/CopyUrl.tsx
Normal file
55
src/components/CopyUrl.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { cloneElement, memo, useCallback, useMemo, useState } from 'react'
|
||||||
|
import { Button, ButtonProps } from 'antd'
|
||||||
|
import { CopyOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
|
import { invokeWebApiWrapperAsync, notify } from './factory'
|
||||||
|
|
||||||
|
export type CopyUrlProps = {
|
||||||
|
sendLoading?: boolean
|
||||||
|
hideUnsupported?: boolean
|
||||||
|
onCopy?: () => (void | Promise<void>)
|
||||||
|
children: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CopyUrl = memo<CopyUrlProps>(({ children, onCopy, sendLoading, hideUnsupported = true }) => {
|
||||||
|
const props = useMemo(() => children.props, [children])
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const supported = !!navigator?.clipboard?.writeText // Проверка поддержки
|
||||||
|
|
||||||
|
const onClick = useCallback((event: MouseEvent) => {
|
||||||
|
if (supported) {
|
||||||
|
invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
await navigator.clipboard.writeText(window.location.href)
|
||||||
|
await onCopy?.()
|
||||||
|
notify('URL успешно скопирован', 'info')
|
||||||
|
},
|
||||||
|
setLoading,
|
||||||
|
`Не удалось скопировать URL в буфер обмена`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
props.onClick?.(event) // Запуск onClick по-умолчанию
|
||||||
|
}, [props])
|
||||||
|
|
||||||
|
if (hideUnsupported && !supported) return null
|
||||||
|
|
||||||
|
return cloneElement(children, { onClick, loading: sendLoading ? loading : props.loading })
|
||||||
|
})
|
||||||
|
|
||||||
|
export type CopyUrlButtonProps = Omit<CopyUrlProps, 'children'> & ButtonProps
|
||||||
|
|
||||||
|
export const CopyUrlButton = memo<CopyUrlButtonProps>(({ sendLoading, hideUnsupported, onCopy, ...other }) => {
|
||||||
|
return (
|
||||||
|
<CopyUrl sendLoading={sendLoading} hideUnsupported={hideUnsupported} onCopy={onCopy}>
|
||||||
|
<Button
|
||||||
|
icon={<CopyOutlined />}
|
||||||
|
title={'Скопировать URL в буфер обмена'}
|
||||||
|
{...other}
|
||||||
|
/>
|
||||||
|
</CopyUrl>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default CopyUrl
|
@ -15,7 +15,7 @@ export const AdminLayoutPortal = memo<AdminLayoutPortalProps>(({ title, ...props
|
|||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<PageHeader isAdmin title={title} style={{ backgroundColor: '#900' }}>
|
<PageHeader isAdmin title={title} style={{ backgroundColor: '#900' }}>
|
||||||
<Button size={'large'}>
|
<Button size={'large'}>
|
||||||
<Link to={{ pathname: '/', state: { from: location.pathname }}}>Вернуться на сайт</Link>
|
<Link to={'/'}>Вернуться на сайт</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Layout>
|
<Layout>
|
||||||
|
@ -3,13 +3,14 @@ import { Layout, LayoutProps } from 'antd'
|
|||||||
|
|
||||||
import PageHeader from '@components/PageHeader'
|
import PageHeader from '@components/PageHeader'
|
||||||
import WellTreeSelector from '@components/selectors/WellTreeSelector'
|
import WellTreeSelector from '@components/selectors/WellTreeSelector'
|
||||||
|
import { wrapPrivateComponent } from '@utils'
|
||||||
|
|
||||||
export type LayoutPortalProps = LayoutProps & {
|
export type LayoutPortalProps = LayoutProps & {
|
||||||
title?: ReactNode
|
title?: ReactNode
|
||||||
noSheet?: boolean
|
noSheet?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LayoutPortal = memo<LayoutPortalProps>(({ title, noSheet, ...props }) => (
|
const _LayoutPortal = memo<LayoutPortalProps>(({ title, noSheet, ...props }) => (
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<PageHeader title={title}>
|
<PageHeader title={title}>
|
||||||
<WellTreeSelector />
|
<WellTreeSelector />
|
||||||
@ -22,4 +23,8 @@ export const LayoutPortal = memo<LayoutPortalProps>(({ title, noSheet, ...props
|
|||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
export const LayoutPortal = wrapPrivateComponent(_LayoutPortal, {
|
||||||
|
requirements: ['Deposit.get'],
|
||||||
|
})
|
||||||
|
|
||||||
export default LayoutPortal
|
export default LayoutPortal
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export { AdminLayoutPortal } from './AdminLayoutPortal'
|
export * from './AdminLayoutPortal'
|
||||||
export { LayoutPortal } from './LayoutPortal'
|
export * from './LayoutPortal'
|
||||||
|
|
||||||
export type { AdminLayoutPortalProps } from './AdminLayoutPortal'
|
export type { AdminLayoutPortalProps } from './AdminLayoutPortal'
|
||||||
export type { LayoutPortalProps } from './LayoutPortal'
|
export type { LayoutPortalProps } from './LayoutPortal'
|
||||||
|
@ -20,7 +20,7 @@ export const PageHeader: React.FC<PageHeaderProps> = memo(({ title = 'Монит
|
|||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Layout.Header className={'header'} {...other}>
|
<Layout.Header className={'header'} {...other}>
|
||||||
<Link to={{ pathname: '/', state: { from: location.pathname }}} style={{ height: headerHeight }}>
|
<Link to={'/'} style={{ height: headerHeight }}>
|
||||||
<Logo />
|
<Logo />
|
||||||
</Link>
|
</Link>
|
||||||
{children}
|
{children}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { memo, ReactElement } from 'react'
|
import { memo, ReactElement } from 'react'
|
||||||
|
|
||||||
import { isURLAvailable } from '@utils/permissions'
|
import { isURLAvailable } from '@utils'
|
||||||
|
|
||||||
export type PrivateContentProps = {
|
export type PrivateContentProps = {
|
||||||
absolutePath: string
|
absolutePath: string
|
||||||
|
@ -1,25 +1,19 @@
|
|||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { Redirect, Route, RouteProps, useLocation } from 'react-router-dom'
|
import { Navigate, Route, RouteProps } from 'react-router-dom'
|
||||||
|
|
||||||
import { getUserId } from '@utils/storage'
|
import { isURLAvailable } from '@utils'
|
||||||
import { isURLAvailable } from '@utils/permissions'
|
|
||||||
|
import { getDefaultRedirectPath } from './PrivateRoutes'
|
||||||
|
|
||||||
export type PrivateDefaultRouteProps = RouteProps & {
|
export type PrivateDefaultRouteProps = RouteProps & {
|
||||||
urls: string[]
|
urls: string[]
|
||||||
elseRedirect?: string
|
elseRedirect?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PrivateDefaultRoute = memo<PrivateDefaultRouteProps>(({ elseRedirect, urls, ...other }) => {
|
export const PrivateDefaultRoute = memo<PrivateDefaultRouteProps>(({ elseRedirect, urls, ...other }) => (
|
||||||
const location = useLocation()
|
<Route {...other} path={'/'} element={(
|
||||||
|
<Navigate replace to={urls.find((url) => isURLAvailable(url)) ?? elseRedirect ?? getDefaultRedirectPath()} />
|
||||||
return (
|
)} />
|
||||||
<Route {...other} path={'/'}>
|
))
|
||||||
<Redirect to={{
|
|
||||||
pathname: urls.find((url) => isURLAvailable(url)) ?? elseRedirect ?? (getUserId() ? '/access_denied' : '/login'),
|
|
||||||
state: { from: location.pathname },
|
|
||||||
}} />
|
|
||||||
</Route>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default PrivateDefaultRoute
|
export default PrivateDefaultRoute
|
||||||
|
@ -1,47 +1,76 @@
|
|||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { Menu, MenuItemProps, MenuProps } from 'antd'
|
import { Menu, MenuProps } from 'antd'
|
||||||
import { Children, cloneElement, memo, ReactElement, useContext, useMemo } from 'react'
|
import { ItemType } from 'antd/lib/menu/hooks/useItems'
|
||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link, LinkProps } from 'react-router-dom'
|
||||||
|
import { Children, isValidElement, memo, ReactNode, RefAttributes, useMemo } from 'react'
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
import { useRootPath } from '@asb/context'
|
||||||
import { isURLAvailable } from '@utils/permissions'
|
import { getTabname, hasPermission, PrivateComponent, PrivateProps } from '@utils'
|
||||||
|
|
||||||
export type PrivateMenuProps = MenuProps & { root?: string }
|
export type PrivateMenuProps = MenuProps & { root?: string }
|
||||||
|
|
||||||
export type PrivateMenuLinkProps = MenuItemProps & {
|
export type PrivateMenuLinkProps = Partial<ItemType> & Omit<LinkProps, 'to'> & RefAttributes<HTMLAnchorElement> & {
|
||||||
tabName?: string
|
icon?: ReactNode
|
||||||
|
danger?: boolean
|
||||||
|
title?: ReactNode
|
||||||
|
content?: PrivateComponent<any>
|
||||||
path?: string
|
path?: string
|
||||||
title: string
|
|
||||||
visible?: boolean
|
visible?: boolean
|
||||||
|
permissions?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PrivateMenuLink = memo<PrivateMenuLinkProps>(({ tabName = '', path = '', title, ...other }) => {
|
export const PrivateMenuLink = memo<PrivateMenuLinkProps>(({ content, danger, icon, path = '', title, ...other }) => (
|
||||||
const location = useLocation()
|
<Menu.Item icon={icon ?? content?.icon} danger={danger}>
|
||||||
return (
|
<Link to={path} {...other}>{title ?? content?.title}</Link>
|
||||||
<Menu.Item key={tabName} {...other}>
|
</Menu.Item>
|
||||||
<Link to={{ pathname: path, state: { from: location.pathname }}}>{title}</Link>
|
))
|
||||||
</Menu.Item>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const PrivateMenuMain = memo<PrivateMenuProps>(({ root, children, ...other }) => {
|
const PrivateMenuMain = memo<PrivateMenuProps>(({ selectable, mode, selectedKeys, root, children, ...other }) => {
|
||||||
const rootContext = useContext(RootPathContext)
|
const rootContext = useRootPath()
|
||||||
const rootPath = useMemo(() => root ?? rootContext ?? '', [root, rootContext])
|
const rootPath = useMemo(() => root ?? rootContext ?? '', [root, rootContext])
|
||||||
|
|
||||||
const items = useMemo(() => Children.toArray(children).map((child) => {
|
const tab = getTabname()
|
||||||
const element = child as ReactElement
|
const keys = useMemo(() => selectedKeys ?? (tab ? [tab] : []), [selectedKeys, tab])
|
||||||
let key = element.key?.toString()
|
|
||||||
const visible: boolean | undefined = element.props.visible
|
|
||||||
if (key && visible !== false) {
|
|
||||||
key = key.slice(key.lastIndexOf('$') + 1) // Ключ автоматический преобразуется в "(.+)\$ключ"
|
|
||||||
const path = join(rootPath, key)
|
|
||||||
if (visible || isURLAvailable(path))
|
|
||||||
return cloneElement(element, { key, path, tabName: key })
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}), [children, rootPath])
|
|
||||||
|
|
||||||
return <Menu children={items} {...other} />
|
const items = useMemo(() => Children.map(children, (child) => {
|
||||||
|
if (!child || !isValidElement<PrivateMenuLinkProps>(child))
|
||||||
|
return null
|
||||||
|
const content: PrivateProps | undefined = child.props.content
|
||||||
|
const visible: boolean | undefined = child.props.visible
|
||||||
|
|
||||||
|
if (visible === false) return null
|
||||||
|
let key
|
||||||
|
if (content?.key)
|
||||||
|
key = content.key
|
||||||
|
else if (content?.route)
|
||||||
|
key = content.route
|
||||||
|
else if (child.key) {
|
||||||
|
key = child.key?.toString()
|
||||||
|
key = key.slice(key.lastIndexOf('$') + 1)
|
||||||
|
} else return null
|
||||||
|
|
||||||
|
const permissions = child.props.permissions ?? content?.requirements
|
||||||
|
const path = child.props.path ?? join(rootPath, key)
|
||||||
|
|
||||||
|
if (visible || hasPermission(permissions))
|
||||||
|
return {
|
||||||
|
...child.props,
|
||||||
|
icon: null,
|
||||||
|
key,
|
||||||
|
label: <PrivateMenuLink {...child.props} path={path} />,
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})?.filter((v) => v) ?? [], [children, rootPath])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
selectable={selectable ?? true}
|
||||||
|
mode={mode ?? 'horizontal'}
|
||||||
|
selectedKeys={keys}
|
||||||
|
items={items as ItemType[]}
|
||||||
|
{...other}
|
||||||
|
/>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const PrivateMenu = Object.assign(PrivateMenuMain, { Link: PrivateMenuLink })
|
export const PrivateMenu = Object.assign(PrivateMenuMain, { Link: PrivateMenuLink })
|
||||||
|
@ -3,7 +3,7 @@ import { Menu, MenuItemProps } from 'antd'
|
|||||||
import { memo, NamedExoticComponent } from 'react'
|
import { memo, NamedExoticComponent } from 'react'
|
||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import { isURLAvailable } from '@utils/permissions'
|
import { isURLAvailable } from '@utils'
|
||||||
|
|
||||||
export type PrivateMenuItemProps = MenuItemProps & {
|
export type PrivateMenuItemProps = MenuItemProps & {
|
||||||
root: string
|
root: string
|
||||||
@ -20,7 +20,7 @@ export const PrivateMenuItemLink = memo<PrivateMenuItemLinkProps>(({ root = '',
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
return (
|
return (
|
||||||
<PrivateMenuItem key={path} root={root} path={path} {...other}>
|
<PrivateMenuItem key={path} root={root} path={path} {...other}>
|
||||||
<Link to={{ pathname: join(root, path), state: { from: location.pathname }}}>{title}</Link>
|
<Link to={join(root, path)}>{title}</Link>
|
||||||
</PrivateMenuItem>
|
</PrivateMenuItem>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -1,31 +1,28 @@
|
|||||||
import { Location } from 'history'
|
|
||||||
import { memo, ReactNode } from 'react'
|
|
||||||
import { Redirect, Route, RouteProps } from 'react-router-dom'
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import { memo, ReactNode } from 'react'
|
||||||
|
import { Navigate, Route, RouteProps } from 'react-router-dom'
|
||||||
|
|
||||||
import { getUserId } from '@utils/storage'
|
import { getUserId, isURLAvailable } from '@utils'
|
||||||
import { isURLAvailable } from '@utils/permissions'
|
|
||||||
|
|
||||||
export type PrivateRouteProps = RouteProps & {
|
export type PrivateRouteProps = RouteProps & {
|
||||||
root?: string
|
root?: string
|
||||||
path: string
|
path: string
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
redirect?: (location?: Location<unknown>) => ReactNode
|
redirect?: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultRedirect = (location?: Location<unknown>) => (
|
export const defaultRedirect = (
|
||||||
<Redirect to={{ pathname: getUserId() ? '/access_denied' : '/login', state: { from: location?.pathname } }} />
|
<Navigate to={getUserId() ? '/access_denied' : '/login'} />
|
||||||
)
|
)
|
||||||
|
|
||||||
export const PrivateRoute = memo<PrivateRouteProps>(({ root = '', path, component, children, redirect = defaultRedirect, ...other }) => {
|
export const PrivateRoute = memo<PrivateRouteProps>(({ root = '', path, children, redirect = defaultRedirect, ...other }) => {
|
||||||
const available = isURLAvailable(join(root, path))
|
const available = isURLAvailable(join(root, path))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
{...other}
|
{...other}
|
||||||
path={path}
|
path={path}
|
||||||
component={available ? component : undefined}
|
element={available ? children : redirect}
|
||||||
render={({ location }) => available ? children : redirect(location)}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
67
src/components/Private/PrivateRoutes.tsx
Normal file
67
src/components/Private/PrivateRoutes.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { join } from 'path'
|
||||||
|
import { Navigate, Route, Routes, RoutesProps } from 'react-router-dom'
|
||||||
|
import { Children, cloneElement, memo, ReactElement, ReactNode, useCallback, useMemo } from 'react'
|
||||||
|
|
||||||
|
import { useRootPath } from '@asb/context'
|
||||||
|
import { getUserId, isURLAvailable } from '@utils'
|
||||||
|
|
||||||
|
export type PrivateRoutesProps = RoutesProps & {
|
||||||
|
root?: string
|
||||||
|
redirect?: ReactNode
|
||||||
|
elseRedirect?: string | string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getDefaultRedirectPath = () => getUserId() ? '/access_denied' : '/login'
|
||||||
|
|
||||||
|
export const defaultRedirect = (
|
||||||
|
<Navigate to={getDefaultRedirectPath()} />
|
||||||
|
)
|
||||||
|
|
||||||
|
export const PrivateRoutes = memo<PrivateRoutesProps>(({ root, elseRedirect, redirect = defaultRedirect, children }) => {
|
||||||
|
const rootContext = useRootPath()
|
||||||
|
const rootPath = useMemo(() => root ?? rootContext ?? '', [root, rootContext])
|
||||||
|
|
||||||
|
const toAbsolute = useCallback((path: string) => path.startsWith('/') ? path : join(rootPath, path), [rootPath])
|
||||||
|
|
||||||
|
const items = useMemo(() => Children.map(children, (child) => {
|
||||||
|
const element = child as ReactElement
|
||||||
|
let key = element.key?.toString()
|
||||||
|
if (!key) return <></>
|
||||||
|
key = key.slice(key.lastIndexOf('$') + 1).replaceAll('=2', ':')
|
||||||
|
// Ключ автоматический преобразуется в "(.+)\$ключ"
|
||||||
|
// Все ":" в ключе заменяются на "=2"
|
||||||
|
// TODO: улучшить метод нормализации ключа
|
||||||
|
const path = toAbsolute(key)
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
key={key}
|
||||||
|
path={path}
|
||||||
|
element={isURLAvailable(path) ? cloneElement(element) : redirect}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}) ?? [], [children, redirect, toAbsolute])
|
||||||
|
|
||||||
|
const defaultRoute = useMemo(() => {
|
||||||
|
const routes: string[] = []
|
||||||
|
if (Array.isArray(elseRedirect))
|
||||||
|
routes.push(...elseRedirect)
|
||||||
|
else if(elseRedirect)
|
||||||
|
routes.push(elseRedirect)
|
||||||
|
|
||||||
|
routes.push(...items.map((elm) => elm?.props?.path))
|
||||||
|
|
||||||
|
const firstAvailableRoute = routes.find((path) => path && isURLAvailable(path))
|
||||||
|
return firstAvailableRoute ? toAbsolute(firstAvailableRoute) : getDefaultRedirectPath()
|
||||||
|
}, [items, elseRedirect, toAbsolute])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Routes>
|
||||||
|
{items}
|
||||||
|
<Route path={'/'} element={(
|
||||||
|
<Navigate to={defaultRoute} />
|
||||||
|
)}/>
|
||||||
|
</Routes>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default PrivateRoutes
|
@ -1,75 +0,0 @@
|
|||||||
import { join } from 'path'
|
|
||||||
import { Location } from 'history'
|
|
||||||
import { Children, cloneElement, memo, ReactElement, ReactNode, useCallback, useContext, useMemo } from 'react'
|
|
||||||
import { Redirect, Route, Switch, SwitchProps, useLocation } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
|
||||||
import { isURLAvailable } from '@utils/permissions'
|
|
||||||
import { getUserId } from '@utils/storage'
|
|
||||||
|
|
||||||
|
|
||||||
export type PrivateSwitchProps = SwitchProps & {
|
|
||||||
root?: string
|
|
||||||
redirect?: (location?: Location<unknown>) => ReactNode
|
|
||||||
elseRedirect?: string | string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDefaultRedirectPath = () => getUserId() ? '/access_denied' : '/login'
|
|
||||||
|
|
||||||
export const defaultRedirect = (location?: Location<unknown>) => (
|
|
||||||
<Redirect to={{ pathname: getDefaultRedirectPath(), state: { from: location?.pathname } }} />
|
|
||||||
)
|
|
||||||
|
|
||||||
export const PrivateSwitch = memo<PrivateSwitchProps>(({ root, elseRedirect, redirect = defaultRedirect, children }) => {
|
|
||||||
const rootContext = useContext(RootPathContext)
|
|
||||||
const rootPath = useMemo(() => root ?? rootContext ?? '', [root, rootContext])
|
|
||||||
|
|
||||||
const location = useLocation()
|
|
||||||
|
|
||||||
const toAbsolute = useCallback((path: string) => path.startsWith('/') ? path : join(rootPath, path), [rootPath])
|
|
||||||
|
|
||||||
const items = useMemo(() => Children.toArray(children).map((child) => {
|
|
||||||
const element = child as ReactElement
|
|
||||||
let key = element.key?.toString()
|
|
||||||
if (!key) return null
|
|
||||||
key = key.slice(key.lastIndexOf('$') + 1).replaceAll('=2', ':')
|
|
||||||
// Ключ автоматический преобразуется в "(.+)\$ключ"
|
|
||||||
// Все ":" в ключе заменяются на "=2"
|
|
||||||
// TODO: улучшить метод нормализации ключа
|
|
||||||
const path = toAbsolute(key)
|
|
||||||
return (
|
|
||||||
<Route
|
|
||||||
key={key}
|
|
||||||
path={path}
|
|
||||||
render={({ location }) => isURLAvailable(path) ? cloneElement(element) : redirect(location)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}), [children, redirect, toAbsolute])
|
|
||||||
|
|
||||||
const defaultRoute = useMemo(() => {
|
|
||||||
if (!elseRedirect) {
|
|
||||||
const path = items.map((elm) => elm?.props.path).find((path) => path && isURLAvailable(path))
|
|
||||||
if (path) return path
|
|
||||||
} else if (Array.isArray(elseRedirect)) {
|
|
||||||
const path = elseRedirect.find((path) => {
|
|
||||||
if (!path) return false
|
|
||||||
return isURLAvailable(toAbsolute(path))
|
|
||||||
})
|
|
||||||
if (path) return toAbsolute(path)
|
|
||||||
} else if(elseRedirect && isURLAvailable(toAbsolute(elseRedirect))) {
|
|
||||||
return toAbsolute(elseRedirect)
|
|
||||||
}
|
|
||||||
return getDefaultRedirectPath()
|
|
||||||
}, [items, elseRedirect, toAbsolute])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Switch>
|
|
||||||
{items}
|
|
||||||
<Route path={'/'}>
|
|
||||||
<Redirect to={{ pathname: defaultRoute, state: { from: location.pathname } }} />
|
|
||||||
</Route>
|
|
||||||
</Switch>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default PrivateSwitch
|
|
@ -3,11 +3,11 @@ export { PrivateContent } from './PrivateContent' // TODO: Remove
|
|||||||
export { PrivateMenuItem, PrivateMenuItemLink } from './PrivateMenuItem' // TODO: Remove
|
export { PrivateMenuItem, PrivateMenuItemLink } from './PrivateMenuItem' // TODO: Remove
|
||||||
export { PrivateDefaultRoute } from './PrivateDefaultRoute'
|
export { PrivateDefaultRoute } from './PrivateDefaultRoute'
|
||||||
export { PrivateMenu, PrivateMenuLink } from './PrivateMenu'
|
export { PrivateMenu, PrivateMenuLink } from './PrivateMenu'
|
||||||
export { PrivateSwitch } from './PrivateSwitch'
|
export { PrivateRoutes } from './PrivateRoutes'
|
||||||
|
|
||||||
export type { PrivateRouteProps } from './PrivateRoute'
|
export type { PrivateRouteProps } from './PrivateRoute'
|
||||||
export type { PrivateContentProps } from './PrivateContent' // TODO: Remove
|
export type { PrivateContentProps } from './PrivateContent' // TODO: Remove
|
||||||
export type { PrivateMenuItemProps, PrivateMenuItemLinkProps } from './PrivateMenuItem' // TODO: Remove
|
export type { PrivateMenuItemProps, PrivateMenuItemLinkProps } from './PrivateMenuItem' // TODO: Remove
|
||||||
export type { PrivateDefaultRouteProps } from './PrivateDefaultRoute'
|
export type { PrivateDefaultRouteProps } from './PrivateDefaultRoute'
|
||||||
export type { PrivateMenuProps, PrivateMenuLinkProps } from './PrivateMenu'
|
export type { PrivateMenuProps, PrivateMenuLinkProps } from './PrivateMenu'
|
||||||
export type { PrivateSwitchProps } from './PrivateSwitch'
|
export type { PrivateRoutesProps } from './PrivateRoutes'
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { memo, ReactNode, useCallback, useEffect, useState } from 'react'
|
import { memo, ReactNode, useCallback, useEffect, useState } from 'react'
|
||||||
import { Select, SelectProps, Tag } from 'antd'
|
|
||||||
import { DefaultOptionType, SelectValue } from 'antd/lib/select'
|
import { DefaultOptionType, SelectValue } from 'antd/lib/select'
|
||||||
|
import { Select, SelectProps, Tag } from 'antd'
|
||||||
|
|
||||||
import { OmitExtends } from '@utils'
|
import type { OmitExtends } from '@utils/types'
|
||||||
|
|
||||||
import { columnPropsOther, DataType, makeColumn } from '.'
|
import { columnPropsOther, DataType, makeColumn } from '.'
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
import { formatTime } from '@utils/datetime'
|
import { formatTime } from '@utils'
|
||||||
|
|
||||||
import { makeColumn, columnPropsOther } from '.'
|
import { makeColumn, columnPropsOther } from '.'
|
||||||
import { makeTimeSorter, TimePickerWrapper, TimePickerWrapperProps } from '..'
|
import { makeTimeSorter, TimePickerWrapper, TimePickerWrapperProps } from '..'
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { memo, ReactNode, useCallback, useEffect, useState } from 'react'
|
import { memo, ReactNode, useCallback, useEffect, useState } from 'react'
|
||||||
import { Select, SelectProps } from 'antd'
|
import { Select, SelectProps } from 'antd'
|
||||||
|
|
||||||
import { OmitExtends } from '@utils'
|
import { findTimezoneId, rawTimezones, TimezoneId } from '@utils'
|
||||||
import { findTimezoneId, rawTimezones, TimezoneId } from '@utils/datetime'
|
import type { OmitExtends } from '@utils/types'
|
||||||
import { SimpleTimezoneDto } from '@api'
|
import { SimpleTimezoneDto } from '@api'
|
||||||
|
|
||||||
import { columnPropsOther, makeColumn } from '.'
|
import { columnPropsOther, makeColumn } from '.'
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { memo } from 'react'
|
import { memo, ReactNode } from 'react'
|
||||||
import { Form, Input } from 'antd'
|
import { Form, Input } from 'antd'
|
||||||
import { NamePath, Rule } from 'rc-field-form/lib/interface'
|
import { NamePath, Rule } from 'rc-field-form/lib/interface'
|
||||||
|
|
||||||
type EditableCellProps = React.DetailedHTMLProps<React.TdHTMLAttributes<HTMLTableDataCellElement>, HTMLTableDataCellElement> & {
|
type EditableCellProps = React.DetailedHTMLProps<React.TdHTMLAttributes<HTMLTableDataCellElement>, HTMLTableDataCellElement> & {
|
||||||
editing?: boolean
|
editing?: boolean
|
||||||
dataIndex?: NamePath
|
dataIndex?: NamePath
|
||||||
input?: React.Component
|
input?: ReactNode
|
||||||
isRequired?: boolean
|
isRequired?: boolean
|
||||||
title: string
|
title: string
|
||||||
formItemClass?: string
|
formItemClass?: string
|
||||||
formItemRules?: Rule[]
|
formItemRules?: Rule[]
|
||||||
children: React.ReactNode
|
children: ReactNode
|
||||||
initialValue: any
|
initialValue: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,8 @@ import { memo, useCallback, useEffect, useState } from 'react'
|
|||||||
import { ColumnGroupType, ColumnType } from 'antd/lib/table'
|
import { ColumnGroupType, ColumnType } from 'antd/lib/table'
|
||||||
import { Table as RawTable, TableProps } from 'antd'
|
import { Table as RawTable, TableProps } from 'antd'
|
||||||
|
|
||||||
import { OmitExtends } from '@utils'
|
import type { OmitExtends } from '@utils/types'
|
||||||
import { getTableSettings, setTableSettings } from '@utils/storage'
|
import { applyTableSettings, getTableSettings, setTableSettings, TableColumnSettings, TableSettings } from '@utils'
|
||||||
import { applySettings, ColumnSettings, TableSettings } from '@utils/table_settings'
|
|
||||||
|
|
||||||
import TableSettingsChanger from './TableSettingsChanger'
|
import TableSettingsChanger from './TableSettingsChanger'
|
||||||
import { tryAddKeys } from './EditableTable'
|
import { tryAddKeys } from './EditableTable'
|
||||||
@ -12,7 +11,7 @@ import { tryAddKeys } from './EditableTable'
|
|||||||
import '@styles/index.css'
|
import '@styles/index.css'
|
||||||
|
|
||||||
export type BaseTableColumn<T = any> = ColumnGroupType<T> | ColumnType<T>
|
export type BaseTableColumn<T = any> = ColumnGroupType<T> | ColumnType<T>
|
||||||
export type TableColumns<T = any> = OmitExtends<BaseTableColumn<T>, ColumnSettings>[]
|
export type TableColumns<T = any> = OmitExtends<BaseTableColumn<T>, TableColumnSettings>[]
|
||||||
|
|
||||||
export type TableContainer = TableProps<any> & {
|
export type TableContainer = TableProps<any> & {
|
||||||
columns: TableColumns
|
columns: TableColumns
|
||||||
@ -33,7 +32,7 @@ export const Table = memo<TableContainer>(({ columns, dataSource, tableName, sho
|
|||||||
|
|
||||||
useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName])
|
useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName])
|
||||||
useEffect(() => setNewColumns(() => {
|
useEffect(() => setNewColumns(() => {
|
||||||
const newColumns = applySettings(columns, settings)
|
const newColumns = applyTableSettings(columns, settings)
|
||||||
if (tableName && showSettingsChanger) {
|
if (tableName && showSettingsChanger) {
|
||||||
const oldTitle = newColumns[0].title
|
const oldTitle = newColumns[0].title
|
||||||
newColumns[0].title = (props) => (
|
newColumns[0].title = (props) => (
|
||||||
|
@ -3,16 +3,16 @@ import { ColumnsType } from 'antd/lib/table'
|
|||||||
import { Button, Modal, Switch, Table } from 'antd'
|
import { Button, Modal, Switch, Table } from 'antd'
|
||||||
import { SettingOutlined } from '@ant-design/icons'
|
import { SettingOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
import { ColumnSettings, makeSettings, mergeSettings, TableSettings } from '@utils/table_settings'
|
import { TableColumnSettings, makeTableSettings, mergeTableSettings, TableSettings } from '@utils'
|
||||||
import { TableColumns } from './Table'
|
import { TableColumns } from './Table'
|
||||||
import { makeColumn } from '.'
|
import { makeColumn } from '.'
|
||||||
|
|
||||||
const parseSettings = (columns?: TableColumns, settings?: TableSettings | null): ColumnSettings[] => {
|
const parseSettings = (columns?: TableColumns, settings?: TableSettings | null): TableColumnSettings[] => {
|
||||||
const newSettings = mergeSettings(makeSettings(columns ?? []), settings ?? {})
|
const newSettings = mergeTableSettings(makeTableSettings(columns ?? []), settings ?? {})
|
||||||
return Object.values(newSettings).map((set, i) => ({ ...set, key: i }))
|
return Object.values(newSettings).map((set, i) => ({ ...set, key: i }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const unparseSettings = (columns: ColumnSettings[]): TableSettings =>
|
const unparseSettings = (columns: TableColumnSettings[]): TableSettings =>
|
||||||
Object.fromEntries(columns.map((column) => [column.columnName, column]))
|
Object.fromEntries(columns.map((column) => [column.columnName, column]))
|
||||||
|
|
||||||
export type TableSettingsChangerProps = {
|
export type TableSettingsChangerProps = {
|
||||||
@ -24,8 +24,8 @@ export type TableSettingsChangerProps = {
|
|||||||
|
|
||||||
export const TableSettingsChanger = memo<TableSettingsChangerProps>(({ title, columns, settings, onChange }) => {
|
export const TableSettingsChanger = memo<TableSettingsChangerProps>(({ title, columns, settings, onChange }) => {
|
||||||
const [visible, setVisible] = useState<boolean>(false)
|
const [visible, setVisible] = useState<boolean>(false)
|
||||||
const [newSettings, setNewSettings] = useState<ColumnSettings[]>(parseSettings(columns, settings))
|
const [newSettings, setNewSettings] = useState<TableColumnSettings[]>(parseSettings(columns, settings))
|
||||||
const [tableColumns, setTableColumns] = useState<ColumnsType<ColumnSettings>>([])
|
const [tableColumns, setTableColumns] = useState<ColumnsType<TableColumnSettings>>([])
|
||||||
|
|
||||||
const onVisibilityChange = useCallback((index: number, visible: boolean) => {
|
const onVisibilityChange = useCallback((index: number, visible: boolean) => {
|
||||||
setNewSettings((oldSettings) => {
|
setNewSettings((oldSettings) => {
|
||||||
@ -52,7 +52,7 @@ export const TableSettingsChanger = memo<TableSettingsChangerProps>(({ title, co
|
|||||||
<Button type={'link'} onClick={() => toogleAll(true)}>Показать все</Button>
|
<Button type={'link'} onClick={() => toogleAll(true)}>Показать все</Button>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
render: (visible: boolean, _?: ColumnSettings, index: number = NaN) => (
|
render: (visible: boolean, _?: TableColumnSettings, index: number = NaN) => (
|
||||||
<Switch
|
<Switch
|
||||||
checked={visible}
|
checked={visible}
|
||||||
checkedChildren={'Отображён'}
|
checkedChildren={'Отображён'}
|
||||||
|
@ -2,7 +2,7 @@ import { Moment } from 'moment'
|
|||||||
import { TimePicker, TimePickerProps } from 'antd'
|
import { TimePicker, TimePickerProps } from 'antd'
|
||||||
import { memo, useCallback, useMemo } from 'react'
|
import { memo, useCallback, useMemo } from 'react'
|
||||||
|
|
||||||
import { defaultTimeFormat, momentToTime, timeToMoment } from '@utils/datetime'
|
import { defaultTimeFormat, momentToTime, timeToMoment } from '@utils'
|
||||||
import { TimeDto } from '@api'
|
import { TimeDto } from '@api'
|
||||||
|
|
||||||
export type TimePickerWrapperProps = Omit<Omit<TimePickerProps, 'value'>, 'onChange'> & {
|
export type TimePickerWrapperProps = Omit<Omit<TimePickerProps, 'value'>, 'onChange'> & {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { timeToMoment } from '@utils/datetime'
|
import { timeToMoment } from '@utils'
|
||||||
import { isRawDate } from '@utils'
|
import { isRawDate } from '@utils'
|
||||||
import { TimeDto } from '@api'
|
import { TimeDto } from '@api'
|
||||||
|
|
||||||
|
@ -1,30 +1,31 @@
|
|||||||
import { memo, MouseEventHandler, useCallback, useState } from 'react'
|
import { memo, MouseEventHandler, useCallback, useState } from 'react'
|
||||||
import { Link, useHistory, useLocation } from 'react-router-dom'
|
import { useNavigate, useLocation } from 'react-router-dom'
|
||||||
import { Button, Dropdown, DropDownProps, Menu } from 'antd'
|
import { Button, Dropdown, DropDownProps } from 'antd'
|
||||||
import { UserOutlined } from '@ant-design/icons'
|
import { UserOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
import { getUserLogin, removeUser } from '@utils/storage'
|
import { getUserLogin, removeUser } from '@utils'
|
||||||
|
|
||||||
import { ChangePassword } from './ChangePassword'
|
import { ChangePassword } from './ChangePassword'
|
||||||
import { PrivateMenuItemLink } from './Private/PrivateMenuItem'
|
import { PrivateMenu } from './Private'
|
||||||
|
|
||||||
|
import AdminPanel from '@pages/AdminPanel'
|
||||||
|
|
||||||
type UserMenuProps = Omit<DropDownProps, 'overlay'> & { isAdmin?: boolean }
|
type UserMenuProps = Omit<DropDownProps, 'overlay'> & { isAdmin?: boolean }
|
||||||
|
|
||||||
export const UserMenu = memo<UserMenuProps>(({ isAdmin, ...other }) => {
|
export const UserMenu = memo<UserMenuProps>(({ isAdmin, ...other }) => {
|
||||||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false)
|
const [isModalVisible, setIsModalVisible] = useState<boolean>(false)
|
||||||
|
|
||||||
const history = useHistory()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const onChangePasswordClick: MouseEventHandler = useCallback((e) => {
|
const onChangePasswordClick: MouseEventHandler = useCallback((e) => {
|
||||||
setIsModalVisible(true)
|
setIsModalVisible(true)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const onChangePasswordOk = useCallback(() => {
|
const onChangePasswordOk = useCallback(() => {
|
||||||
setIsModalVisible(false)
|
setIsModalVisible(false)
|
||||||
history.push({ pathname: '/login', state: { from: location.pathname }})
|
navigate('/login', { state: { from: location.pathname }})
|
||||||
}, [history, location])
|
}, [navigate, location])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -32,19 +33,15 @@ export const UserMenu = memo<UserMenuProps>(({ isAdmin, ...other }) => {
|
|||||||
{...other}
|
{...other}
|
||||||
placement={'bottomRight'}
|
placement={'bottomRight'}
|
||||||
overlay={(
|
overlay={(
|
||||||
<Menu style={{ textAlign: 'right' }}>
|
<PrivateMenu style={{ textAlign: 'right' }}>
|
||||||
{isAdmin ? (
|
{isAdmin ? (
|
||||||
<PrivateMenuItemLink key={''} path={'/'} title={'Вернуться на сайт'}/>
|
<PrivateMenu.Link visible key={'/'} path={'/'} title={'Вернуться на сайт'}/>
|
||||||
) : (
|
) : (
|
||||||
<PrivateMenuItemLink key={'admin'} path={'/admin'} title={'Панель администратора'}/>
|
<PrivateMenu.Link key={'admin'} path={'/admin'} title={'Панель администратора'} content={AdminPanel}/>
|
||||||
)}
|
)}
|
||||||
<Menu.Item>
|
<PrivateMenu.Link visible onClick={onChangePasswordClick} title={'Сменить пароль'} />
|
||||||
<Link to={'/'} onClick={onChangePasswordClick}>Сменить пароль</Link>
|
<PrivateMenu.Link visible path={'/login'} onClick={removeUser} title={'Выход'} />
|
||||||
</Menu.Item>
|
</PrivateMenu>
|
||||||
<Menu.Item>
|
|
||||||
<Link to={{ pathname: '/login', state: { from: location.pathname }}} onClick={removeUser}>Выход</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu>
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Button icon={<UserOutlined/>}>{getUserLogin()}</Button>
|
<Button icon={<UserOutlined/>}>{getUserLogin()}</Button>
|
||||||
|
@ -2,7 +2,7 @@ import { notification } from 'antd'
|
|||||||
import { Dispatch, ReactNode, SetStateAction } from 'react'
|
import { Dispatch, ReactNode, SetStateAction } from 'react'
|
||||||
|
|
||||||
import { isDev } from '@utils'
|
import { isDev } from '@utils'
|
||||||
import { getUserToken } from '@utils/storage'
|
import { getUserToken } from '@utils'
|
||||||
import { ApiError, FileInfoDto } from '@api'
|
import { ApiError, FileInfoDto } from '@api'
|
||||||
|
|
||||||
const notificationTypeDictionary = new Map([
|
const notificationTypeDictionary = new Map([
|
||||||
|
@ -2,7 +2,7 @@ import { Tag, TreeSelect } from 'antd'
|
|||||||
import { memo, useEffect, useState } from 'react'
|
import { memo, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission } from '@utils'
|
||||||
import { DepositService } from '@api'
|
import { DepositService } from '@api'
|
||||||
|
|
||||||
export const getTreeData = async () => {
|
export const getTreeData = async () => {
|
||||||
@ -40,17 +40,19 @@ export const WellSelector = memo(({ idWell, value, onChange, treeData, treeLabel
|
|||||||
const [wellsTree, setWellsTree] = useState([])
|
const [wellsTree, setWellsTree] = useState([])
|
||||||
const [wellLabels, setWellLabels] = useState([])
|
const [wellLabels, setWellLabels] = useState([])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const wellsTree = treeData ?? await getTreeData()
|
async () => {
|
||||||
const labels = treeLabels ?? getTreeLabels(wellsTree)
|
const wellsTree = treeData ?? await getTreeData()
|
||||||
setWellsTree(wellsTree)
|
const labels = treeLabels ?? getTreeLabels(wellsTree)
|
||||||
setWellLabels(labels)
|
setWellsTree(wellsTree)
|
||||||
},
|
setWellLabels(labels)
|
||||||
null,
|
},
|
||||||
'Не удалось загрузить список скважин',
|
null,
|
||||||
'Получение списка скважин'
|
'Не удалось загрузить список скважин',
|
||||||
), [idWell, treeData, treeLabels])
|
'Получение списка скважин'
|
||||||
|
)
|
||||||
|
}, [idWell, treeData, treeLabels])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
|
@ -3,7 +3,7 @@ import { LabelInValueType } from 'rc-select/lib/Select'
|
|||||||
import { RawValueType } from 'rc-tree-select/lib/TreeSelect'
|
import { RawValueType } from 'rc-tree-select/lib/TreeSelect'
|
||||||
import { DefaultValueType } from 'rc-tree-select/lib/interface'
|
import { DefaultValueType } from 'rc-tree-select/lib/interface'
|
||||||
import { useState, useEffect, ReactNode, useCallback, memo } from 'react'
|
import { useState, useEffect, ReactNode, useCallback, memo } from 'react'
|
||||||
import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'
|
import { useNavigate, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import { isRawDate } from '@utils'
|
import { isRawDate } from '@utils'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
@ -29,40 +29,47 @@ export type TreeNodeData = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getLabel = (wellsTree: TreeNodeData[], value?: string): string | undefined => {
|
const getLabel = (wellsTree: TreeNodeData[], value?: string): string | undefined => {
|
||||||
if (!value) return value
|
const result = value?.match(/^\/([^\/]+)\/([^\/?]+)/) // pattern "/:type/:id"
|
||||||
const type = value.replaceAll('/', ' ').trim().split(' ')[0]
|
if (wellsTree.length <= 0 || !result) return
|
||||||
|
const [url, type] = result
|
||||||
let deposit: TreeNodeData | undefined
|
let deposit: TreeNodeData | undefined
|
||||||
let cluster: TreeNodeData | undefined
|
let cluster: TreeNodeData | undefined
|
||||||
let well: TreeNodeData | undefined
|
let well: TreeNodeData | undefined
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case 'deposit':
|
||||||
|
deposit = wellsTree.find((deposit) => deposit.key === url)
|
||||||
|
if (deposit)
|
||||||
|
return `${deposit.title}`
|
||||||
|
return 'Ошибка! Месторождение не найдено!'
|
||||||
|
|
||||||
case 'cluster':
|
case 'cluster':
|
||||||
deposit = wellsTree.find((deposit) => (
|
deposit = wellsTree.find((deposit) => (
|
||||||
cluster = deposit.children?.find((cluster: TreeNodeData) => cluster.key === value)
|
cluster = deposit.children?.find((cluster: TreeNodeData) => cluster.key === url)
|
||||||
))
|
))
|
||||||
if (deposit && cluster)
|
if (deposit && cluster)
|
||||||
return `${deposit.title} / ${cluster.title}`
|
return `${deposit.title} / ${cluster.title}`
|
||||||
break
|
return 'Ошибка! Куст не найден!'
|
||||||
|
|
||||||
case 'well':
|
case 'well':
|
||||||
deposit = wellsTree.find((deposit) => (
|
deposit = wellsTree.find((deposit) => (
|
||||||
cluster = deposit.children?.find((cluster: TreeNodeData) => (
|
cluster = deposit.children?.find((cluster: TreeNodeData) => (
|
||||||
well = cluster.children?.find((well: TreeNodeData) => well.key === value)
|
well = cluster.children?.find((well: TreeNodeData) => well.key === url)
|
||||||
))
|
))
|
||||||
))
|
))
|
||||||
if (deposit && cluster && well)
|
if (deposit && cluster && well)
|
||||||
return `${deposit.title} / ${cluster.title} / ${well.title}`
|
return `${deposit.title} / ${cluster.title} / ${well.title}`
|
||||||
break
|
return 'Ошибка! Скважина не найдена!'
|
||||||
|
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
return 'Ошибка! Скважина не найдена!'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WellTreeSelector = memo(({ ...other }) => {
|
export const WellTreeSelector = memo(({ ...other }) => {
|
||||||
const [wellsTree, setWellsTree] = useState<TreeNodeData[]>([])
|
const [wellsTree, setWellsTree] = useState<TreeNodeData[]>([])
|
||||||
const [showLoader, setShowLoader] = useState<boolean>(false)
|
const [showLoader, setShowLoader] = useState<boolean>(false)
|
||||||
const [value, setValue] = useState<string>()
|
const [value, setValue] = useState<string>()
|
||||||
const history = useHistory()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const routeMatch = useRouteMatch('/:route/:id')
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
invokeWebApiWrapperAsync(
|
invokeWebApiWrapperAsync(
|
||||||
@ -99,14 +106,14 @@ export const WellTreeSelector = memo(({ ...other }) => {
|
|||||||
)
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => setValue(getLabel(wellsTree, routeMatch?.url)), [wellsTree, routeMatch])
|
useEffect(() => setValue(getLabel(wellsTree, location.pathname)), [wellsTree, location])
|
||||||
|
|
||||||
const onChange = useCallback((value?: string): void => setValue(getLabel(wellsTree, value)), [wellsTree])
|
const onChange = useCallback((value?: string): void => setValue(getLabel(wellsTree, value)), [wellsTree])
|
||||||
|
|
||||||
const onSelect = useCallback((value: RawValueType | LabelInValueType): void => {
|
const onSelect = useCallback((value: RawValueType | LabelInValueType): void => {
|
||||||
if (['number', 'string'].includes(typeof value))
|
if (['number', 'string'].includes(typeof value))
|
||||||
history.push({ pathname: String(value), state: { from: location.pathname }})
|
navigate(String(value), { state: { from: location.pathname }})
|
||||||
}, [history, location])
|
}, [navigate, location])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
|
|
||||||
|
import logo from '@images/logo_32.png'
|
||||||
|
|
||||||
export const Logo = memo<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>>((props) => (
|
export const Logo = memo<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>>((props) => (
|
||||||
<img src={'/images/logo_32.png'} alt={'АСБ'} className={'logo'} {...props} />
|
<img src={logo} alt={'АСБ'} className={'logo'} {...props} />
|
||||||
))
|
))
|
||||||
|
|
||||||
export default Logo
|
export default Logo
|
||||||
|
0
public/images/logo_32.png → src/images/logo_32.png
Executable file → Normal file
0
public/images/logo_32.png → src/images/logo_32.png
Executable file → Normal file
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
@ -1,15 +1,19 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import { createRoot } from 'react-dom/client'
|
||||||
import App from './App'
|
|
||||||
import reportWebVitals from './reportWebVitals'
|
import reportWebVitals from './reportWebVitals'
|
||||||
|
import App from './App'
|
||||||
|
|
||||||
import '@styles/index.css'
|
import '@styles/index.css'
|
||||||
|
|
||||||
ReactDOM.render((
|
const container = document.getElementById('root') ?? document.body
|
||||||
<React.StrictMode>
|
const root = createRoot(container)
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
root.render(
|
||||||
), document.getElementById('root'))
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
)
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Result, Tooltip, Typography } from 'antd'
|
import { Result, Typography } from 'antd'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { Link, useHistory } from 'react-router-dom'
|
import { Link, useNavigate } from 'react-router-dom'
|
||||||
import { CloseCircleOutlined } from '@ant-design/icons'
|
import { CloseCircleOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
const { Paragraph, Text } = Typography
|
const { Paragraph, Text } = Typography
|
||||||
|
|
||||||
export const AccessDenied = memo(() => {
|
export const AccessDenied = memo(() => {
|
||||||
const history = useHistory()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Result
|
<Result
|
||||||
@ -28,7 +28,7 @@ export const AccessDenied = memo(() => {
|
|||||||
<Paragraph>
|
<Paragraph>
|
||||||
<CloseCircleOutlined style={{ color: 'red' }} />
|
<CloseCircleOutlined style={{ color: 'red' }} />
|
||||||
Страницы не существует.
|
Страницы не существует.
|
||||||
<Link to={'#'} onClick={history.goBack}>Вернуться назад ></Link>
|
<Link to={'#'} onClick={navigate(-1)}>Вернуться назад ></Link>
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
<CloseCircleOutlined style={{ color: 'red' }} />
|
<CloseCircleOutlined style={{ color: 'red' }} />
|
||||||
|
@ -12,13 +12,12 @@ import {
|
|||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { AdminClusterService, AdminDepositService } from '@api'
|
import { AdminClusterService, AdminDepositService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||||
import { min1 } from '@utils/validationRules'
|
import { min1 } from '@utils/validationRules'
|
||||||
import { hasPermission } from '@utils/permissions'
|
|
||||||
|
|
||||||
import { coordsFixed } from './DepositController'
|
import { coordsFixed } from './DepositController'
|
||||||
|
|
||||||
export const ClusterController = memo(() => {
|
const ClusterController = memo(() => {
|
||||||
const [deposits, setDeposits] = useState([])
|
const [deposits, setDeposits] = useState([])
|
||||||
const [clusters, setClusters] = useState([])
|
const [clusters, setClusters] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
@ -58,18 +57,22 @@ export const ClusterController = memo(() => {
|
|||||||
'Получение списка кустов'
|
'Получение списка кустов'
|
||||||
), [])
|
), [])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
let deposits = arrayOrDefault(await AdminDepositService.getAll())
|
async () => {
|
||||||
deposits = deposits.map((deposit) => ({ value: deposit.id, label: deposit.caption }))
|
let deposits = arrayOrDefault(await AdminDepositService.getAll())
|
||||||
setDeposits(deposits)
|
deposits = deposits.map((deposit) => ({ value: deposit.id, label: deposit.caption }))
|
||||||
},
|
setDeposits(deposits)
|
||||||
setShowLoader,
|
},
|
||||||
`Не удалось загрузить список месторождений`,
|
setShowLoader,
|
||||||
'Получение списка месторождений'
|
`Не удалось загрузить список месторождений`,
|
||||||
), [])
|
'Получение списка месторождений'
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(updateTable, [updateTable])
|
useEffect(() => {
|
||||||
|
updateTable()
|
||||||
|
}, [updateTable])
|
||||||
|
|
||||||
const handlerProps = useMemo(() => ({
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminClusterService,
|
service: AdminClusterService,
|
||||||
@ -103,4 +106,8 @@ export const ClusterController = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default ClusterController
|
export default wrapPrivateComponent(ClusterController, {
|
||||||
|
requirements: ['AdminDeposit.get', 'AdminCluster.get'],
|
||||||
|
title: 'Кусты',
|
||||||
|
route: 'cluster',
|
||||||
|
})
|
||||||
|
@ -11,12 +11,10 @@ import {
|
|||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { AdminCompanyService, AdminCompanyTypeService } from '@api'
|
import { AdminCompanyService, AdminCompanyTypeService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||||
import { min1 } from '@utils/validationRules'
|
import { min1 } from '@utils/validationRules'
|
||||||
import { hasPermission } from '@utils/permissions'
|
|
||||||
|
|
||||||
|
const CompanyController = memo(() => {
|
||||||
export const CompanyController = memo(() => {
|
|
||||||
const [columns, setColumns] = useState([])
|
const [columns, setColumns] = useState([])
|
||||||
const [companies, setCompanies] = useState([])
|
const [companies, setCompanies] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
@ -31,32 +29,34 @@ export const CompanyController = memo(() => {
|
|||||||
setCompanies(arrayOrDefault(companies))
|
setCompanies(arrayOrDefault(companies))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async() => {
|
invokeWebApiWrapperAsync(
|
||||||
const companyTypes = arrayOrDefault(await AdminCompanyTypeService.getAll()).map((companyType) => ({
|
async() => {
|
||||||
value: companyType.id,
|
const companyTypes = arrayOrDefault(await AdminCompanyTypeService.getAll()).map((companyType) => ({
|
||||||
label: companyType.caption,
|
value: companyType.id,
|
||||||
}))
|
label: companyType.caption,
|
||||||
|
}))
|
||||||
|
|
||||||
setColumns([
|
setColumns([
|
||||||
makeColumn('Название', 'caption', {
|
makeColumn('Название', 'caption', {
|
||||||
width: 200,
|
width: 200,
|
||||||
editable: true,
|
editable: true,
|
||||||
sorter: makeStringSorter('caption'),
|
sorter: makeStringSorter('caption'),
|
||||||
formItemRules: min1,
|
formItemRules: min1,
|
||||||
}),
|
}),
|
||||||
makeSelectColumn('Тип компании', 'idCompanyType', companyTypes, null, {
|
makeSelectColumn('Тип компании', 'idCompanyType', companyTypes, null, {
|
||||||
width: 200,
|
width: 200,
|
||||||
editable: true
|
editable: true
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
await updateTable()
|
await updateTable()
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось загрузить список типов компаний`,
|
`Не удалось загрузить список типов компаний`,
|
||||||
'Получение списка типов команд'
|
'Получение списка типов команд'
|
||||||
), [updateTable])
|
)
|
||||||
|
}, [updateTable])
|
||||||
|
|
||||||
const handlerProps = useMemo(() => ({
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminCompanyService,
|
service: AdminCompanyService,
|
||||||
@ -95,4 +95,8 @@ export const CompanyController = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default CompanyController
|
export default wrapPrivateComponent(CompanyController, {
|
||||||
|
requirements: ['AdminCompany.get', 'AdminCompanyType.get'],
|
||||||
|
title: 'Компании',
|
||||||
|
route: 'company',
|
||||||
|
})
|
||||||
|
@ -9,10 +9,9 @@ import {
|
|||||||
defaultPagination
|
defaultPagination
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { AdminCompanyTypeService } from '@api'
|
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
import { min1 } from '@utils/validationRules'
|
import { min1 } from '@utils/validationRules'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { AdminCompanyTypeService } from '@api'
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
makeColumn('Название', 'caption', {
|
makeColumn('Название', 'caption', {
|
||||||
@ -23,7 +22,7 @@ const columns = [
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
export const CompanyTypeController = memo(() => {
|
const CompanyTypeController = memo(() => {
|
||||||
const [companyTypes, setCompanyTypes] = useState([])
|
const [companyTypes, setCompanyTypes] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [searchValue, setSearchValue] = useState('')
|
const [searchValue, setSearchValue] = useState('')
|
||||||
@ -42,7 +41,9 @@ export const CompanyTypeController = memo(() => {
|
|||||||
'Получение списка типов компаний'
|
'Получение списка типов компаний'
|
||||||
), [])
|
), [])
|
||||||
|
|
||||||
useEffect(updateTable, [updateTable])
|
useEffect(() => {
|
||||||
|
updateTable()
|
||||||
|
}, [updateTable])
|
||||||
|
|
||||||
const handlerProps = useMemo(() => ({
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminCompanyTypeService,
|
service: AdminCompanyTypeService,
|
||||||
@ -76,4 +77,8 @@ export const CompanyTypeController = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default CompanyTypeController
|
export default wrapPrivateComponent(CompanyTypeController, {
|
||||||
|
requirements: ['AdminCompanyType.get'],
|
||||||
|
title: 'Типы компаний',
|
||||||
|
route: 'company_type',
|
||||||
|
})
|
||||||
|
@ -3,9 +3,8 @@ import { Input } from 'antd'
|
|||||||
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { EditableTable, makeColumn, makeActionHandler, defaultPagination, makeTimezoneColumn } from '@components/Table'
|
import { EditableTable, makeColumn, makeActionHandler, defaultPagination, makeTimezoneColumn } from '@components/Table'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||||
import { min1 } from '@utils/validationRules'
|
import { min1 } from '@utils/validationRules'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
import { AdminDepositService } from '@api'
|
import { AdminDepositService } from '@api'
|
||||||
|
|
||||||
export const coordsFixed = (coords) => coords && isFinite(coords) ? (+coords).toPrecision(10) : '-'
|
export const coordsFixed = (coords) => coords && isFinite(coords) ? (+coords).toPrecision(10) : '-'
|
||||||
@ -17,7 +16,7 @@ const depositColumns = [
|
|||||||
makeTimezoneColumn('Зона', 'timezone', null, true, { width: 150 }),
|
makeTimezoneColumn('Зона', 'timezone', null, true, { width: 150 }),
|
||||||
]
|
]
|
||||||
|
|
||||||
export const DepositController = memo(() => {
|
const DepositController = memo(() => {
|
||||||
const [deposits, setDeposits] = useState([])
|
const [deposits, setDeposits] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [searchValue, setSearchValue] = useState('')
|
const [searchValue, setSearchValue] = useState('')
|
||||||
@ -39,7 +38,9 @@ export const DepositController = memo(() => {
|
|||||||
'Получение списка месторождений'
|
'Получение списка месторождений'
|
||||||
), [])
|
), [])
|
||||||
|
|
||||||
useEffect(updateTable, [updateTable])
|
useEffect(() => {
|
||||||
|
updateTable()
|
||||||
|
}, [updateTable])
|
||||||
|
|
||||||
const handlerProps = useMemo(() => ({
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminDepositService,
|
service: AdminDepositService,
|
||||||
@ -73,4 +74,8 @@ export const DepositController = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default DepositController
|
export default wrapPrivateComponent(DepositController, {
|
||||||
|
requirements: ['AdminDeposit.get'],
|
||||||
|
title: 'Месторождения',
|
||||||
|
route: 'deposit',
|
||||||
|
})
|
||||||
|
@ -8,10 +8,9 @@ import {
|
|||||||
makeStringSorter
|
makeStringSorter
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { AdminPermissionService } from '@api'
|
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
import { min1 } from '@utils/validationRules'
|
import { min1 } from '@utils/validationRules'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { AdminPermissionService } from '@api'
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
makeColumn('Название', 'name', {
|
makeColumn('Название', 'name', {
|
||||||
@ -26,7 +25,7 @@ const columns = [
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
export const PermissionController = memo(() => {
|
const PermissionController = memo(() => {
|
||||||
const [permissions, setPermissions] = useState([])
|
const [permissions, setPermissions] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [searchValue, setSearchValue] = useState('')
|
const [searchValue, setSearchValue] = useState('')
|
||||||
@ -47,7 +46,9 @@ export const PermissionController = memo(() => {
|
|||||||
'Получение списка прав'
|
'Получение списка прав'
|
||||||
), [])
|
), [])
|
||||||
|
|
||||||
useEffect(() => updateTable(), [updateTable])
|
useEffect(() => {
|
||||||
|
updateTable()
|
||||||
|
}, [updateTable])
|
||||||
|
|
||||||
const handlerProps = useMemo(() => ({
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminPermissionService,
|
service: AdminPermissionService,
|
||||||
@ -81,4 +82,8 @@ export const PermissionController = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default PermissionController
|
export default wrapPrivateComponent(PermissionController, {
|
||||||
|
requirements: ['AdminPermission.get'],
|
||||||
|
title: 'Разрешения',
|
||||||
|
route: 'permission',
|
||||||
|
})
|
||||||
|
@ -5,11 +5,10 @@ import { PermissionView, RoleView } from '@components/views'
|
|||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { EditableTable, makeActionHandler, makeTagColumn, makeTextColumn } from '@components/Table'
|
import { EditableTable, makeActionHandler, makeTagColumn, makeTextColumn } from '@components/Table'
|
||||||
import { AdminPermissionService, AdminUserRoleService } from '@api'
|
import { AdminPermissionService, AdminUserRoleService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||||
import { min1 } from '@utils/validationRules'
|
import { min1 } from '@utils/validationRules'
|
||||||
import { hasPermission } from '@utils/permissions'
|
|
||||||
|
|
||||||
export const RoleController = memo(() => {
|
const RoleController = memo(() => {
|
||||||
const [permissions, setPermissions] = useState([])
|
const [permissions, setPermissions] = useState([])
|
||||||
const [roles, setRoles] = useState([])
|
const [roles, setRoles] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
@ -38,16 +37,18 @@ export const RoleController = memo(() => {
|
|||||||
setRoles(arrayOrDefault(roles))
|
setRoles(arrayOrDefault(roles))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const permissions = await AdminPermissionService.getAll()
|
async () => {
|
||||||
setPermissions(arrayOrDefault(permissions))
|
const permissions = await AdminPermissionService.getAll()
|
||||||
await loadRoles()
|
setPermissions(arrayOrDefault(permissions))
|
||||||
},
|
await loadRoles()
|
||||||
setShowLoader,
|
},
|
||||||
`Не удалось загрузить список ролей`,
|
setShowLoader,
|
||||||
'Получение списка ролей'
|
`Не удалось загрузить список ролей`,
|
||||||
), [loadRoles])
|
'Получение списка ролей'
|
||||||
|
)
|
||||||
|
}, [loadRoles])
|
||||||
|
|
||||||
const handlerProps = useMemo(() => ({
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminUserRoleService,
|
service: AdminUserRoleService,
|
||||||
@ -85,4 +86,8 @@ export const RoleController = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default RoleController
|
export default wrapPrivateComponent(RoleController, {
|
||||||
|
requirements: ['AdminPermission.get', 'AdminUserRole.get'],
|
||||||
|
title: 'Роли',
|
||||||
|
route: 'role',
|
||||||
|
})
|
||||||
|
@ -8,8 +8,8 @@ import LoaderPortal from '@components/LoaderPortal'
|
|||||||
import { lables } from '@components/views/TelemetryView'
|
import { lables } from '@components/views/TelemetryView'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import TelemetrySelect from '@components/selectors/TelemetrySelect'
|
import TelemetrySelect from '@components/selectors/TelemetrySelect'
|
||||||
|
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||||
import { AdminTelemetryService } from '@api'
|
import { AdminTelemetryService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
|
|
||||||
const { Item } = Descriptions
|
const { Item } = Descriptions
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ export const TelemetryInfo = memo(({ info, danger, ...other }) => (
|
|||||||
</Descriptions>
|
</Descriptions>
|
||||||
))
|
))
|
||||||
|
|
||||||
export const TelemetryMerger = memo(() => {
|
const TelemetryMerger = memo(() => {
|
||||||
const [primary, setPrimary] = useState(null)
|
const [primary, setPrimary] = useState(null)
|
||||||
const [secondary, setSecondary] = useState(null)
|
const [secondary, setSecondary] = useState(null)
|
||||||
const [telemetry, setTelemetry] = useState(null)
|
const [telemetry, setTelemetry] = useState(null)
|
||||||
@ -68,7 +68,9 @@ export const TelemetryMerger = memo(() => {
|
|||||||
'Объединение телеметрий',
|
'Объединение телеметрий',
|
||||||
), [updateTelemetry, secondary, primary])
|
), [updateTelemetry, secondary, primary])
|
||||||
|
|
||||||
useEffect(updateTelemetry, [updateTelemetry])
|
useEffect(() => {
|
||||||
|
updateTelemetry()
|
||||||
|
}, [updateTelemetry])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const query = new URLSearchParams(location.search)
|
const query = new URLSearchParams(location.search)
|
||||||
@ -132,4 +134,9 @@ export const TelemetryMerger = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default TelemetryMerger
|
export default wrapPrivateComponent(TelemetryMerger, {
|
||||||
|
requirements: [],
|
||||||
|
title: 'Объединение',
|
||||||
|
route: 'merger',
|
||||||
|
key: 'merger',
|
||||||
|
})
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { PullRequestOutlined } from '@ant-design/icons'
|
import { PullRequestOutlined } from '@ant-design/icons'
|
||||||
import { Button, Input } from 'antd'
|
import { Button, Input } from 'antd'
|
||||||
|
|
||||||
@ -13,18 +14,17 @@ import {
|
|||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import Poprompt from '@components/selectors/Poprompt'
|
import Poprompt from '@components/selectors/Poprompt'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||||
import { AdminTelemetryService } from '@api'
|
import { AdminTelemetryService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
import { useHistory } from 'react-router-dom'
|
|
||||||
|
|
||||||
export const TelemetryController = memo(() => {
|
const TelemetryController = memo(() => {
|
||||||
const [telemetryData, setTelemetryData] = useState([])
|
const [telemetryData, setTelemetryData] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [searchValue, setSearchValue] = useState('')
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
|
||||||
const history = useHistory()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const toMerger = useCallback((type, id) => () => history.push(`/admin/telemetry/merger/?${type}=${id}`), [history])
|
const toMerger = useCallback((type, id) => () => navigate(`/admin/telemetry/merger/?${type}=${id}`), [navigate])
|
||||||
|
|
||||||
const mergeRender = useCallback((value, record) => (
|
const mergeRender = useCallback((value, record) => (
|
||||||
<Poprompt
|
<Poprompt
|
||||||
@ -81,20 +81,22 @@ export const TelemetryController = memo(() => {
|
|||||||
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
||||||
), [telemetryData, searchValue])
|
), [telemetryData, searchValue])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const telemetryData = arrayOrDefault(await AdminTelemetryService.getAll())
|
async () => {
|
||||||
setTelemetryData(telemetryData.map((telemetry) => ({
|
const telemetryData = arrayOrDefault(await AdminTelemetryService.getAll())
|
||||||
...(telemetry?.info ?? []),
|
setTelemetryData(telemetryData.map((telemetry) => ({
|
||||||
id: telemetry?.id,
|
...(telemetry?.info ?? []),
|
||||||
remoteUid: telemetry?.remoteUid,
|
id: telemetry?.id,
|
||||||
realWell: telemetry?.well?.caption,
|
remoteUid: telemetry?.remoteUid,
|
||||||
})))
|
realWell: telemetry?.well?.caption,
|
||||||
},
|
})))
|
||||||
setShowLoader,
|
},
|
||||||
`Не удалось загрузить список телеметрии скважин`,
|
setShowLoader,
|
||||||
'Полученик списка телеметрии скважин'
|
`Не удалось загрузить список телеметрии скважин`,
|
||||||
), [])
|
'Полученик списка телеметрии скважин'
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -118,4 +120,9 @@ export const TelemetryController = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default TelemetryController
|
export default wrapPrivateComponent(TelemetryController, {
|
||||||
|
requirements: [],
|
||||||
|
title: 'Просмотр',
|
||||||
|
route: 'viewer',
|
||||||
|
key: 'viewer',
|
||||||
|
})
|
||||||
|
@ -1,37 +1,34 @@
|
|||||||
import { Layout } from 'antd'
|
import { Layout } from 'antd'
|
||||||
import { lazy, memo, Suspense, useContext, useMemo } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
import { RootPathContext, useRootPath } from '@asb/context'
|
||||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
import { PrivateMenu } from '@components/Private'
|
||||||
|
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||||
|
|
||||||
import { SuspenseFallback } from '@pages/SuspenseFallback'
|
import TelemetryViewer from './TelemetryViewer'
|
||||||
|
import TelemetryMerger from './TelemetryMerger'
|
||||||
|
|
||||||
const TelemetryViewer = lazy(() => import('./TelemetryViewer'))
|
const Telemetry = memo(() => {
|
||||||
const TelemetryMerger = lazy(() => import('./TelemetryMerger'))
|
const root = useRootPath()
|
||||||
|
|
||||||
export const Telemetry = memo(() => {
|
|
||||||
const { tab } = useParams()
|
|
||||||
|
|
||||||
const root = useContext(RootPathContext)
|
|
||||||
const rootPath = useMemo(() => `${root}/telemetry`, [root])
|
const rootPath = useMemo(() => `${root}/telemetry`, [root])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootPathContext.Provider value={rootPath}>
|
<RootPathContext.Provider value={rootPath}>
|
||||||
<Layout>
|
<Layout>
|
||||||
<PrivateMenu mode={'horizontal'} selectable={true} selectedKeys={[tab]}>
|
<PrivateMenu>
|
||||||
<PrivateMenu.Link key={'viewer'} title={'Просмотр'} />
|
<PrivateMenu.Link content={TelemetryViewer} />
|
||||||
<PrivateMenu.Link key={'merger'} title={'Объединение'} />
|
<PrivateMenu.Link content={TelemetryMerger} />
|
||||||
</PrivateMenu>
|
</PrivateMenu>
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Layout.Content className={'site-layout-background'}>
|
<Layout.Content className={'site-layout-background'}>
|
||||||
<Suspense fallback={<SuspenseFallback />}>
|
<Routes>
|
||||||
<PrivateSwitch elseRedirect={['viewer', 'merger']}>
|
<Route index element={<Navigate to={TelemetryViewer.route} replace />} />
|
||||||
<TelemetryViewer key={'viewer'} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
<TelemetryMerger key={'merger'} />
|
<Route path={TelemetryViewer.route} element={<TelemetryViewer />} />
|
||||||
</PrivateSwitch>
|
<Route path={TelemetryMerger.route} element={<TelemetryMerger />} />
|
||||||
</Suspense>
|
</Routes>
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
@ -39,4 +36,9 @@ export const Telemetry = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Telemetry
|
export default wrapPrivateComponent(Telemetry, {
|
||||||
|
requirements: ['AdminTelemetry.get'],
|
||||||
|
title: 'Телеметрия',
|
||||||
|
key: 'telemetry',
|
||||||
|
route: 'telemetry/*',
|
||||||
|
})
|
||||||
|
@ -17,15 +17,14 @@ import { ChangePassword } from '@components/ChangePassword'
|
|||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { AdminCompanyService, AdminUserRoleService, AdminUserService } from '@api'
|
import { AdminCompanyService, AdminUserRoleService, AdminUserService } from '@api'
|
||||||
import { createLoginRules, nameRules, phoneRules, emailRules } from '@utils/validationRules'
|
import { createLoginRules, nameRules, phoneRules, emailRules } from '@utils/validationRules'
|
||||||
import { makeTextOnFilter, makeTextFilters, makeArrayOnFilter } from '@utils/table'
|
import { makeTextOnFilter, makeTextFilters, makeArrayOnFilter } from '@utils/filters'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
|
|
||||||
import RoleTag from './RoleTag'
|
import RoleTag from './RoleTag'
|
||||||
|
|
||||||
const SEARCH_TIMEOUT = 400
|
const SEARCH_TIMEOUT = 400
|
||||||
|
|
||||||
export const UserController = memo(() => {
|
const UserController = memo(() => {
|
||||||
const [users, setUsers] = useState([])
|
const [users, setUsers] = useState([])
|
||||||
const [filteredUsers, setFilteredUsers] = useState([])
|
const [filteredUsers, setFilteredUsers] = useState([])
|
||||||
const [searchValue, setSearchValue] = useState('')
|
const [searchValue, setSearchValue] = useState('')
|
||||||
@ -35,23 +34,25 @@ export const UserController = memo(() => {
|
|||||||
const [selectedUser, setSelectedUser] = useState(null)
|
const [selectedUser, setSelectedUser] = useState(null)
|
||||||
const [subject, setSubject] = useState(null)
|
const [subject, setSubject] = useState(null)
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const filteredUsers = users.filter((user) => user && (!searchValue || [
|
async () => {
|
||||||
user.login ?? '',
|
const filteredUsers = users.filter((user) => user && (!searchValue || [
|
||||||
user.name ?? '',
|
user.login ?? '',
|
||||||
user.surname ?? '',
|
user.name ?? '',
|
||||||
user.partonymic ?? '',
|
user.surname ?? '',
|
||||||
user.email ?? '',
|
user.partonymic ?? '',
|
||||||
user.phone ?? '',
|
user.email ?? '',
|
||||||
user.position ?? '',
|
user.phone ?? '',
|
||||||
user.company?.caption ?? '',
|
user.position ?? '',
|
||||||
].join(' ').toLowerCase().includes(searchValue.toLowerCase())))
|
user.company?.caption ?? '',
|
||||||
setFilteredUsers(filteredUsers)
|
].join(' ').toLowerCase().includes(searchValue.toLowerCase())))
|
||||||
},
|
setFilteredUsers(filteredUsers)
|
||||||
setIsSearching,
|
},
|
||||||
`Не удалось произвести поиск пользователей`
|
setIsSearching,
|
||||||
), [users, searchValue])
|
`Не удалось произвести поиск пользователей`
|
||||||
|
)
|
||||||
|
}, [users, searchValue])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!subject) {
|
if (!subject) {
|
||||||
@ -91,86 +92,88 @@ export const UserController = memo(() => {
|
|||||||
'Получение списка пользователей'
|
'Получение списка пользователей'
|
||||||
), [])
|
), [])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const roles = arrayOrDefault(await AdminUserRoleService.getAll())
|
async () => {
|
||||||
const companies = arrayOrDefault(await AdminCompanyService.getAll()).map((company) => ({
|
const roles = arrayOrDefault(await AdminUserRoleService.getAll())
|
||||||
value: company.id,
|
const companies = arrayOrDefault(await AdminCompanyService.getAll()).map((company) => ({
|
||||||
label: company.caption
|
value: company.id,
|
||||||
}))
|
label: company.caption
|
||||||
|
}))
|
||||||
|
|
||||||
const users = arrayOrDefault(await AdminUserService.getAll())
|
const users = arrayOrDefault(await AdminUserService.getAll())
|
||||||
setUsers(users)
|
setUsers(users)
|
||||||
|
|
||||||
const filters = makeTextFilters(users, ['surname', 'name', 'patronymic', 'email'])
|
const filters = makeTextFilters(users, ['surname', 'name', 'patronymic', 'email'])
|
||||||
const roleFilters = [{ text: 'Без роли', value: null }, ...roles.map((role) => ({ text: role.caption, value: role.caption }))]
|
const roleFilters = [{ text: 'Без роли', value: null }, ...roles.map((role) => ({ text: role.caption, value: role.caption }))]
|
||||||
|
|
||||||
const rolesRender = (item) => item?.map((elm) => (
|
const rolesRender = (item) => item?.map((elm) => (
|
||||||
<Tag key={elm} color={'blue'}>
|
<Tag key={elm} color={'blue'}>
|
||||||
<RoleView role={roles.find((role) => role.caption === elm)} />
|
<RoleView role={roles.find((role) => role.caption === elm)} />
|
||||||
</Tag>
|
</Tag>
|
||||||
)) ?? '-'
|
)) ?? '-'
|
||||||
|
|
||||||
setColumns([
|
setColumns([
|
||||||
makeTextColumn('Логин', 'login', null, null, null, {
|
makeTextColumn('Логин', 'login', null, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: [
|
formItemRules: [
|
||||||
{ required: true },
|
{ required: true },
|
||||||
...createLoginRules,
|
...createLoginRules,
|
||||||
// () => ({
|
// () => ({
|
||||||
// validator(_, value) {
|
// validator(_, value) {
|
||||||
// if (!value || users.findIndex((user) => user.login === value) < 0)
|
// if (!value || users.findIndex((user) => user.login === value) < 0)
|
||||||
// return Promise.resolve()
|
// return Promise.resolve()
|
||||||
// return Promise.reject(new Error('Логин уже занят!'))
|
// return Promise.reject(new Error('Логин уже занят!'))
|
||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
// TODO: Для проверки уникальности логина необходимо исключить из выборки логин выбранного пользователя
|
// TODO: Для проверки уникальности логина необходимо исключить из выборки логин выбранного пользователя
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
makeTextColumn('Фамилия', 'surname', filters.surname, null, null, {
|
makeTextColumn('Фамилия', 'surname', filters.surname, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: [{ required: true }, ...nameRules],
|
formItemRules: [{ required: true }, ...nameRules],
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
onFilter: makeTextOnFilter('surname'),
|
onFilter: makeTextOnFilter('surname'),
|
||||||
}),
|
}),
|
||||||
makeTextColumn('Имя', 'name', filters.name, null, null, {
|
makeTextColumn('Имя', 'name', filters.name, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: nameRules,
|
formItemRules: nameRules,
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
onFilter: makeTextOnFilter('name'),
|
onFilter: makeTextOnFilter('name'),
|
||||||
}),
|
}),
|
||||||
makeTextColumn('Отчество', 'patronymic', filters.partonymic, null, null, {
|
makeTextColumn('Отчество', 'patronymic', filters.partonymic, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: nameRules,
|
formItemRules: nameRules,
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
onFilter: makeTextOnFilter('patronymic'),
|
onFilter: makeTextOnFilter('patronymic'),
|
||||||
}),
|
}),
|
||||||
makeTextColumn('E-mail', 'email', filters.email, null, null, {
|
makeTextColumn('E-mail', 'email', filters.email, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: [{ required: true }, ...emailRules],
|
formItemRules: [{ required: true }, ...emailRules],
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
onFilter: makeTextOnFilter('email'),
|
onFilter: makeTextOnFilter('email'),
|
||||||
}),
|
}),
|
||||||
makeTextColumn('Номер телефона', 'phone', null, null, null, {
|
makeTextColumn('Номер телефона', 'phone', null, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: phoneRules,
|
formItemRules: phoneRules,
|
||||||
}),
|
}),
|
||||||
makeTextColumn('Должность', 'position', null, null, null, { editable: true }),
|
makeTextColumn('Должность', 'position', null, null, null, { editable: true }),
|
||||||
makeTextColumn('Роли', 'roleNames', roleFilters, null, rolesRender, {
|
makeTextColumn('Роли', 'roleNames', roleFilters, null, rolesRender, {
|
||||||
editable: true,
|
editable: true,
|
||||||
input: <RoleTag roles={roles} />,
|
input: <RoleTag roles={roles} />,
|
||||||
onFilter: makeArrayOnFilter('roleNames'),
|
onFilter: makeArrayOnFilter('roleNames'),
|
||||||
}),
|
}),
|
||||||
makeSelectColumn('Компания', 'idCompany', companies, '--', {
|
makeSelectColumn('Компания', 'idCompany', companies, '--', {
|
||||||
editable: true,
|
editable: true,
|
||||||
sorter: makeNumericSorter('idCompany'),
|
sorter: makeNumericSorter('idCompany'),
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось загрузить список компаний`,
|
`Не удалось загрузить список компаний`,
|
||||||
'Получение списка компаний'
|
'Получение списка компаний'
|
||||||
), [])
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handlerProps = useMemo(() => ({
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminUserService,
|
service: AdminUserService,
|
||||||
@ -215,4 +218,8 @@ export const UserController = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default UserController
|
export default wrapPrivateComponent(UserController, {
|
||||||
|
requirements: ['AdminUser.get', 'AdminCompany.get', 'AdminUserRole.get'],
|
||||||
|
title: 'Пользователи',
|
||||||
|
route: 'user',
|
||||||
|
})
|
||||||
|
@ -3,8 +3,8 @@ import { Input } from 'antd'
|
|||||||
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { defaultPagination, makeColumn, makeDateSorter, makeStringSorter, Table } from '@components/Table'
|
import { defaultPagination, makeColumn, makeDateSorter, makeStringSorter, Table } from '@components/Table'
|
||||||
|
import { arrayOrDefault, formatDate, wrapPrivateComponent } from '@utils'
|
||||||
import { RequestTrackerService } from '@api'
|
import { RequestTrackerService } from '@api'
|
||||||
import { arrayOrDefault, formatDate } from '@utils'
|
|
||||||
|
|
||||||
const logRecordCount = 1000
|
const logRecordCount = 1000
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ const columns = [
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
export const VisitLog = memo(() => {
|
const VisitLog = memo(() => {
|
||||||
const [logData, setLogData] = useState([])
|
const [logData, setLogData] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [searchValue, setSearchValue] = useState('')
|
const [searchValue, setSearchValue] = useState('')
|
||||||
@ -29,16 +29,18 @@ export const VisitLog = memo(() => {
|
|||||||
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
||||||
), [logData, searchValue])
|
), [logData, searchValue])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const logData = arrayOrDefault(await RequestTrackerService.getUsersStat(logRecordCount))
|
async () => {
|
||||||
logData.forEach((log) => log.key = `${log.login}${log.ip}`)
|
const logData = arrayOrDefault(await RequestTrackerService.getUsersStat(logRecordCount))
|
||||||
setLogData(logData)
|
logData.forEach((log) => log.key = `${log.login}${log.ip}`)
|
||||||
},
|
setLogData(logData)
|
||||||
setShowLoader,
|
},
|
||||||
`Не удалось загрузить список последних посещений пользователей`,
|
setShowLoader,
|
||||||
'Получение списка последних посещений'
|
`Не удалось загрузить список последних посещений пользователей`,
|
||||||
), [])
|
'Получение списка последних посещений'
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -62,4 +64,8 @@ export const VisitLog = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default VisitLog
|
export default wrapPrivateComponent(VisitLog, {
|
||||||
|
requirements: ['RequestTracker.get'],
|
||||||
|
title: 'Журнал посещений',
|
||||||
|
route: 'visit_log',
|
||||||
|
})
|
||||||
|
@ -22,8 +22,7 @@ import {
|
|||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { TelemetryView, CompanyView } from '@components/views'
|
import { TelemetryView, CompanyView } from '@components/views'
|
||||||
import TelemetrySelect from '@components/selectors/TelemetrySelect'
|
import TelemetrySelect from '@components/selectors/TelemetrySelect'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
|
|
||||||
import { coordsFixed } from '../DepositController'
|
import { coordsFixed } from '../DepositController'
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ const recordParser = (record) => ({
|
|||||||
idTelemetry: record.telemetry?.id,
|
idTelemetry: record.telemetry?.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WellController = memo(() => {
|
const WellController = memo(() => {
|
||||||
const [columns, setColumns] = useState([])
|
const [columns, setColumns] = useState([])
|
||||||
const [wells, setWells] = useState([])
|
const [wells, setWells] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
@ -74,51 +73,53 @@ export const WellController = memo(() => {
|
|||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const companies = arrayOrDefault(await AdminCompanyService.getAll())
|
async () => {
|
||||||
const telemetry = arrayOrDefault(await AdminTelemetryService.getAll())
|
const companies = arrayOrDefault(await AdminCompanyService.getAll())
|
||||||
const clusters = arrayOrDefault(await AdminClusterService.getAll()).map((cluster) => ({
|
const telemetry = arrayOrDefault(await AdminTelemetryService.getAll())
|
||||||
value: cluster.id,
|
const clusters = arrayOrDefault(await AdminClusterService.getAll()).map((cluster) => ({
|
||||||
label: cluster.caption
|
value: cluster.id,
|
||||||
}))
|
label: cluster.caption
|
||||||
|
}))
|
||||||
|
|
||||||
setColumns([
|
setColumns([
|
||||||
makeSelectColumn('Куст', 'idCluster', clusters, '--', {
|
makeSelectColumn('Куст', 'idCluster', clusters, '--', {
|
||||||
width: '5rem',
|
width: '5rem',
|
||||||
editable: true,
|
editable: true,
|
||||||
sorter: makeNumericSorter('idCluster'),
|
sorter: makeNumericSorter('idCluster'),
|
||||||
}),
|
}),
|
||||||
makeColumn('Название', 'caption', {
|
makeColumn('Название', 'caption', {
|
||||||
width: '5rem',
|
width: '5rem',
|
||||||
editable: true,
|
editable: true,
|
||||||
sorter: makeStringSorter('caption'),
|
sorter: makeStringSorter('caption'),
|
||||||
}),
|
}),
|
||||||
makeSelectColumn('Тип', 'idWellType', wellTypes, '--', {
|
makeSelectColumn('Тип', 'idWellType', wellTypes, '--', {
|
||||||
width: 150,
|
width: 150,
|
||||||
editable: true,
|
editable: true,
|
||||||
sorter: makeNumericSorter('idWellType'),
|
sorter: makeNumericSorter('idWellType'),
|
||||||
}),
|
}),
|
||||||
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFixed }),
|
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFixed }),
|
||||||
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFixed }),
|
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFixed }),
|
||||||
makeColumn('Телеметрия', 'telemetry', {
|
makeColumn('Телеметрия', 'telemetry', {
|
||||||
editable: true,
|
editable: true,
|
||||||
render: (telemetry) => <TelemetryView telemetry={telemetry} />,
|
render: (telemetry) => <TelemetryView telemetry={telemetry} />,
|
||||||
input: <TelemetrySelect telemetry={telemetry}/>,
|
input: <TelemetrySelect telemetry={telemetry}/>,
|
||||||
}, ),
|
}, ),
|
||||||
makeTimezoneColumn('Зона', 'timezone', null, true, { width: 170 }),
|
makeTimezoneColumn('Зона', 'timezone', null, true, { width: 170 }),
|
||||||
makeTagColumn('Компании', 'companies', companies, 'id', 'caption', {
|
makeTagColumn('Компании', 'companies', companies, 'id', 'caption', {
|
||||||
editable: true,
|
editable: true,
|
||||||
render: (company) => <CompanyView company={company} />,
|
render: (company) => <CompanyView company={company} />,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
await updateTable()
|
await updateTable()
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось загрузить список кустов`,
|
`Не удалось загрузить список кустов`,
|
||||||
'Получение списка кустов'
|
'Получение списка кустов'
|
||||||
), [updateTable])
|
)
|
||||||
|
}, [updateTable])
|
||||||
|
|
||||||
const handlerProps = useMemo(() => ({
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminWellService,
|
service: AdminWellService,
|
||||||
@ -154,4 +155,8 @@ export const WellController = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default WellController
|
export default wrapPrivateComponent(WellController, {
|
||||||
|
requirements: ['AdminCluster.get', 'AdminCompany.get', 'AdminTelemetry.get', 'AdminWell.get'],
|
||||||
|
title: 'Скважины',
|
||||||
|
route: 'well',
|
||||||
|
})
|
||||||
|
@ -1,66 +1,69 @@
|
|||||||
|
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||||
|
import { memo, useMemo } from 'react'
|
||||||
import { Layout } from 'antd'
|
import { Layout } from 'antd'
|
||||||
import { lazy, memo, Suspense, useContext, useMemo } from 'react'
|
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
import { RootPathContext, useRootPath } from '@asb/context'
|
||||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
import { AdminLayoutPortal } from '@components/Layout'
|
||||||
|
import { PrivateMenu } from '@components/Private'
|
||||||
|
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||||
|
|
||||||
import { SuspenseFallback } from '@pages/SuspenseFallback'
|
import ClusterController from './ClusterController'
|
||||||
|
import CompanyController from './CompanyController'
|
||||||
|
import DepositController from './DepositController'
|
||||||
|
import UserController from './UserController'
|
||||||
|
import WellController from './WellController'
|
||||||
|
import RoleController from './RoleController'
|
||||||
|
import CompanyTypeController from './CompanyTypeController'
|
||||||
|
import PermissionController from './PermissionController'
|
||||||
|
import Telemetry from './Telemetry'
|
||||||
|
import VisitLog from './VisitLog'
|
||||||
|
|
||||||
const ClusterController = lazy(() => import( './ClusterController'))
|
const AdminPanel = memo(() => {
|
||||||
const CompanyController = lazy(() => import( './CompanyController'))
|
const root = useRootPath()
|
||||||
const DepositController = lazy(() => import( './DepositController'))
|
|
||||||
const UserController = lazy(() => import( './UserController'))
|
|
||||||
const WellController = lazy(() => import( './WellController'))
|
|
||||||
const RoleController = lazy(() => import( './RoleController'))
|
|
||||||
const CompanyTypeController = lazy(() => import('./CompanyTypeController'))
|
|
||||||
const PermissionController = lazy(() => import( './PermissionController'))
|
|
||||||
const TelemetrySection = lazy(() => import( './Telemetry'))
|
|
||||||
const VisitLog = lazy(() => import( './VisitLog'))
|
|
||||||
|
|
||||||
export const AdminPanel = memo(() => {
|
|
||||||
const { tab } = useParams()
|
|
||||||
|
|
||||||
const root = useContext(RootPathContext)
|
|
||||||
const rootPath = useMemo(() => `${root}/admin`, [root])
|
const rootPath = useMemo(() => `${root}/admin`, [root])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootPathContext.Provider value={rootPath}>
|
<RootPathContext.Provider value={rootPath}>
|
||||||
<Layout>
|
<AdminLayoutPortal title={'Администраторская панель'}>
|
||||||
<PrivateMenu mode={'horizontal'} selectable={true} selectedKeys={[tab]}>
|
<PrivateMenu>
|
||||||
<PrivateMenu.Link key={'deposit' } title={'Месторождения' } />
|
<PrivateMenu.Link content={DepositController} />
|
||||||
<PrivateMenu.Link key={'cluster' } title={'Кусты' } />
|
<PrivateMenu.Link content={ClusterController} />
|
||||||
<PrivateMenu.Link key={'well' } title={'Скважины' } />
|
<PrivateMenu.Link content={WellController} />
|
||||||
<PrivateMenu.Link key={'user' } title={'Пользователи' } />
|
<PrivateMenu.Link content={UserController} />
|
||||||
<PrivateMenu.Link key={'company' } title={'Компании' } />
|
<PrivateMenu.Link content={CompanyController} />
|
||||||
<PrivateMenu.Link key={'company_type'} title={'Типы компаний' } />
|
<PrivateMenu.Link content={CompanyTypeController} />
|
||||||
<PrivateMenu.Link key={'role' } title={'Роли' } />
|
<PrivateMenu.Link content={RoleController} />
|
||||||
<PrivateMenu.Link key={'permission' } title={'Разрешения' } />
|
<PrivateMenu.Link content={PermissionController} />
|
||||||
<PrivateMenu.Link key={'telemetry' } title={'Телеметрия' } />
|
<PrivateMenu.Link content={Telemetry} />
|
||||||
<PrivateMenu.Link key={'visit_log' } title={'Журнал посещений'} />
|
<PrivateMenu.Link content={VisitLog} />
|
||||||
</PrivateMenu>
|
</PrivateMenu>
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Layout.Content className={'site-layout-background'}>
|
<Layout.Content className={'site-layout-background'}>
|
||||||
<Suspense fallback={<SuspenseFallback />}>
|
<Routes>
|
||||||
<PrivateSwitch elseRedirect={['deposit', 'cluster', 'well', 'user', 'company', 'company_type', 'role', 'permission', 'telemetry', 'visit_log']}>
|
<Route index element={<Navigate to={VisitLog.route} replace />} />
|
||||||
<DepositController key={'deposit'} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
<ClusterController key={'cluster'} />
|
<Route path={DepositController.route} element={<DepositController />} />
|
||||||
<WellController key={'well'} />
|
<Route path={ClusterController.route} element={<ClusterController />} />
|
||||||
<UserController key={'user'} />
|
<Route path={WellController.route} element={<WellController />} />
|
||||||
<CompanyController key={'company'} />
|
<Route path={UserController.route} element={<UserController />} />
|
||||||
<CompanyTypeController key={'company_type'} />
|
<Route path={CompanyController.route} element={<CompanyController />} />
|
||||||
<RoleController key={'role'} />
|
<Route path={CompanyTypeController.route} element={<CompanyTypeController />} />
|
||||||
<PermissionController key={'permission'} />
|
<Route path={RoleController.route} element={<RoleController />} />
|
||||||
<TelemetrySection key={'telemetry/:tab?'} />
|
<Route path={PermissionController.route} element={<PermissionController />} />
|
||||||
<VisitLog key={'visit_log'} />
|
<Route path={Telemetry.route} element={<Telemetry />} />
|
||||||
</PrivateSwitch>
|
<Route path={VisitLog.route} element={<VisitLog />} />
|
||||||
</Suspense>
|
</Routes>
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</AdminLayoutPortal>
|
||||||
</RootPathContext.Provider>
|
</RootPathContext.Provider>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default AdminPanel
|
export default wrapPrivateComponent(AdminPanel, {
|
||||||
|
requirements: ['RequestTracker.get'],
|
||||||
|
title: 'Панель администратора',
|
||||||
|
route: 'admin/*',
|
||||||
|
key: 'admin',
|
||||||
|
})
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { Table as RawTable, Typography } from 'antd'
|
import { Table as RawTable, Typography } from 'antd'
|
||||||
import { Fragment, memo, useCallback, useContext, useEffect, useState } from 'react'
|
import { Fragment, memo, useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { WellSelector } from '@components/selectors/WellSelector'
|
import { WellSelector } from '@components/selectors/WellSelector'
|
||||||
import { makeGroupColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
|
import { makeGroupColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
|
||||||
import { OperationStatService, WellOperationService } from '@api'
|
import { OperationStatService, WellOperationService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||||
|
|
||||||
|
|
||||||
import '@styles/index.css'
|
import '@styles/index.css'
|
||||||
import '@styles/statistics.less'
|
import '@styles/statistics.less'
|
||||||
@ -64,7 +63,7 @@ const getWellData = async (wellsList) => {
|
|||||||
return wellData
|
return wellData
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Statistics = memo(() => {
|
const Statistics = memo(() => {
|
||||||
const [sectionTypes, setSectionTypes] = useState([])
|
const [sectionTypes, setSectionTypes] = useState([])
|
||||||
const [avgColumns, setAvgColumns] = useState(defaultColumns)
|
const [avgColumns, setAvgColumns] = useState(defaultColumns)
|
||||||
const [cmpColumns, setCmpColumns] = useState(defaultColumns)
|
const [cmpColumns, setCmpColumns] = useState(defaultColumns)
|
||||||
@ -77,7 +76,7 @@ export const Statistics = memo(() => {
|
|||||||
const [cmpData, setCmpData] = useState([])
|
const [cmpData, setCmpData] = useState([])
|
||||||
const [avgRow, setAvgRow] = useState({})
|
const [avgRow, setAvgRow] = useState({})
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const cmpSpeedRender = useCallback((key) => (section) => {
|
const cmpSpeedRender = useCallback((key) => (section) => {
|
||||||
let spanClass = ''
|
let spanClass = ''
|
||||||
@ -97,67 +96,75 @@ export const Statistics = memo(() => {
|
|||||||
)
|
)
|
||||||
}, [avgRow])
|
}, [avgRow])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const types = await WellOperationService.getSectionTypes(idWell)
|
async () => {
|
||||||
setSectionTypes(Object.entries(types))
|
const types = await WellOperationService.getSectionTypes(idWell)
|
||||||
},
|
setSectionTypes(Object.entries(types))
|
||||||
setIsPageLoading,
|
},
|
||||||
`Не удалось получить типы секции`,
|
setIsPageLoading,
|
||||||
`Получение списка возможных секций`,
|
`Не удалось получить типы секции`,
|
||||||
), [idWell])
|
`Получение списка возможных секций`,
|
||||||
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const filteredSections = avgData?.length > 0 ? sectionTypes.filter(([id, _]) => avgData.some((row) => `section_${id}` in row)) : sectionTypes
|
async () => {
|
||||||
|
const filteredSections = avgData?.length > 0 ? sectionTypes.filter(([id, _]) => avgData.some((row) => `section_${id}` in row)) : sectionTypes
|
||||||
|
|
||||||
setAvgColumns([
|
setAvgColumns([
|
||||||
...defaultColumns,
|
...defaultColumns,
|
||||||
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`)),
|
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`)),
|
||||||
])
|
])
|
||||||
setCmpColumns([
|
setCmpColumns([
|
||||||
...defaultColumns,
|
...defaultColumns,
|
||||||
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`, {
|
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`, {
|
||||||
speedRender: cmpSpeedRender(`section_${id}`)
|
speedRender: cmpSpeedRender(`section_${id}`)
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
},
|
||||||
|
setIsPageLoading,
|
||||||
|
'Не удалось установить необходимые столбцы'
|
||||||
|
)
|
||||||
|
}, [sectionTypes, avgData, cmpSpeedRender])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
const avgData = await getWellData(avgWells)
|
||||||
|
setAvgData(avgData)
|
||||||
|
|
||||||
|
const avgRow = {}
|
||||||
|
|
||||||
|
avgData.forEach((row) => row && Object.keys(row).forEach((key) => {
|
||||||
|
if (!key.startsWith('section_')) return
|
||||||
|
if (!avgRow[key]) avgRow[key] = { depth: 0, time: 0, speed: 0, count: 0 }
|
||||||
|
avgRow[key].depth += row[key].depth ?? 0
|
||||||
|
avgRow[key].time += row[key].time ?? 0
|
||||||
|
avgRow[key].speed += row[key].speed ?? 0
|
||||||
|
avgRow[key].count++
|
||||||
}))
|
}))
|
||||||
])
|
|
||||||
},
|
|
||||||
setIsPageLoading,
|
|
||||||
'Не удалось установить необходимые столбцы'
|
|
||||||
), [sectionTypes, avgData, cmpSpeedRender])
|
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
Object.values(avgRow).forEach((section) => section.speed /= section.count)
|
||||||
async () => {
|
|
||||||
const avgData = await getWellData(avgWells)
|
|
||||||
setAvgData(avgData)
|
|
||||||
|
|
||||||
const avgRow = {}
|
setAvgRow(avgRow)
|
||||||
|
},
|
||||||
|
setIsAvgTableLoading,
|
||||||
|
'Не удалось загрузить данные для расчёта средних значений',
|
||||||
|
)
|
||||||
|
}, [avgWells])
|
||||||
|
|
||||||
avgData.forEach((row) => row && Object.keys(row).forEach((key) => {
|
useEffect(() => {
|
||||||
if (!key.startsWith('section_')) return
|
invokeWebApiWrapperAsync(
|
||||||
if (!avgRow[key]) avgRow[key] = { depth: 0, time: 0, speed: 0, count: 0 }
|
async () => {
|
||||||
avgRow[key].depth += row[key].depth ?? 0
|
const cmpData = await getWellData(cmpWells)
|
||||||
avgRow[key].time += row[key].time ?? 0
|
setCmpData(cmpData)
|
||||||
avgRow[key].speed += row[key].speed ?? 0
|
},
|
||||||
avgRow[key].count++
|
setIsCmpTableLoading,
|
||||||
}))
|
'Не удалось получить скважины для сравнения',
|
||||||
|
)
|
||||||
Object.values(avgRow).forEach((section) => section.speed /= section.count)
|
}, [cmpWells])
|
||||||
|
|
||||||
setAvgRow(avgRow)
|
|
||||||
},
|
|
||||||
setIsAvgTableLoading,
|
|
||||||
'Не удалось загрузить данные для расчёта средних значений',
|
|
||||||
), [avgWells])
|
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const cmpData = await getWellData(cmpWells)
|
|
||||||
setCmpData(cmpData)
|
|
||||||
},
|
|
||||||
setIsCmpTableLoading,
|
|
||||||
'Не удалось получить скважины для сравнения',
|
|
||||||
), [cmpWells])
|
|
||||||
|
|
||||||
const getStatisticsAvgSummary = useCallback((data) => (
|
const getStatisticsAvgSummary = useCallback((data) => (
|
||||||
<Summary fixed={'bottom'}>
|
<Summary fixed={'bottom'}>
|
||||||
@ -234,4 +241,8 @@ export const Statistics = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Statistics
|
export default wrapPrivateComponent(Statistics, {
|
||||||
|
requirements: [],
|
||||||
|
title: 'Оценка по ЦБ',
|
||||||
|
route: 'statistics',
|
||||||
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { memo, useCallback, useContext, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useState } from 'react'
|
||||||
import { Button, Modal, Popconfirm } from 'antd'
|
import { Button, Modal, Popconfirm, Tooltip } from 'antd'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { Table } from '@components/Table'
|
import { Table } from '@components/Table'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
@ -15,33 +15,31 @@ export const NewParamsTable = memo(({ selectedWellsKeys }) => {
|
|||||||
const [showParamsLoader, setShowParamsLoader] = useState(false)
|
const [showParamsLoader, setShowParamsLoader] = useState(false)
|
||||||
const [isParamsModalVisible, setIsParamsModalVisible] = useState(false)
|
const [isParamsModalVisible, setIsParamsModalVisible] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => setParamsColumns(await getColumns(idWell))
|
invokeWebApiWrapperAsync(async () => setParamsColumns(await getColumns(idWell)))
|
||||||
), [idWell])
|
}, [idWell])
|
||||||
|
|
||||||
useEffect(() => console.log(paramsColumns), [paramsColumns])
|
|
||||||
|
|
||||||
const onParamButtonClick = useCallback(() => invokeWebApiWrapperAsync(
|
const onParamButtonClick = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
setIsParamsModalVisible(true)
|
setIsParamsModalVisible(true)
|
||||||
const params = await DrillParamsService.getCompositeAll(idWell)
|
const params = await DrillParamsService.getCompositeAll(idWell)
|
||||||
setParams(params)
|
setParams(params)
|
||||||
},
|
},
|
||||||
setShowParamsLoader,
|
setShowParamsLoader,
|
||||||
`Не удалось загрузить список режимов для скважины "${idWell}"`,
|
`Не удалось загрузить список режимов для скважины "${idWell}"`,
|
||||||
'Получение списка режимов скважины'
|
'Получение списка режимов скважины'
|
||||||
), [idWell])
|
), [idWell])
|
||||||
|
|
||||||
const onParamsAddClick = useCallback(() => invokeWebApiWrapperAsync(
|
const onParamsAddClick = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
await DrillParamsService.save(idWell, params)
|
await DrillParamsService.save(idWell, params)
|
||||||
setIsParamsModalVisible(false)
|
setIsParamsModalVisible(false)
|
||||||
},
|
},
|
||||||
setShowParamsLoader,
|
setShowParamsLoader,
|
||||||
`Не удалось добавить режимы в список скважины "${idWell}"`,
|
`Не удалось добавить режимы в список скважины "${idWell}"`,
|
||||||
'Добавление режима скважины'
|
'Добавление режима скважины'
|
||||||
), [idWell, params])
|
), [idWell, params])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -61,7 +59,10 @@ export const NewParamsTable = memo(({ selectedWellsKeys }) => {
|
|||||||
width={1700}
|
width={1700}
|
||||||
footer={(
|
footer={(
|
||||||
<Popconfirm title={'Заменить существующие режимы выбранными?'} onConfirm={onParamsAddClick}>
|
<Popconfirm title={'Заменить существующие режимы выбранными?'} onConfirm={onParamsAddClick}>
|
||||||
<Button size={'large'} disabled={params.length <= 0}>Сохранить</Button>
|
<Button
|
||||||
|
size={'large'}
|
||||||
|
disabled={params.length <= 0}
|
||||||
|
>Сохранить</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
import { useState, useEffect, memo, useMemo, useContext } from 'react'
|
import { useState, useEffect, memo, useMemo } from 'react'
|
||||||
import { LineChartOutlined, ProfileOutlined } from '@ant-design/icons'
|
import { LineChartOutlined, ProfileOutlined } from '@ant-design/icons'
|
||||||
import { Table, Tag, Button, Badge, Divider, Modal, Row, Col } from 'antd'
|
import { Table, Tag, Button, Badge, Divider, Modal, Row, Col } from 'antd'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { CompanyView } from '@components/views'
|
import { CompanyView } from '@components/views'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { makeTextColumn, makeNumericColumnPlanFact, makeNumericColumn } from '@components/Table'
|
import { makeTextColumn, makeNumericColumnPlanFact, makeNumericColumn } from '@components/Table'
|
||||||
import { WellCompositeService } from '@api'
|
import { WellCompositeService } from '@api'
|
||||||
import { hasPermission } from '@utils/permissions'
|
|
||||||
import {
|
import {
|
||||||
|
hasPermission,
|
||||||
|
wrapPrivateComponent,
|
||||||
calcAndUpdateStatsBySections,
|
calcAndUpdateStatsBySections,
|
||||||
makeFilterMinMaxFunction,
|
makeFilterMinMaxFunction,
|
||||||
getOperations
|
getOperations
|
||||||
} from '@utils/functions'
|
} from '@utils'
|
||||||
|
|
||||||
import { Tvd } from '@pages/WellOperations/Tvd'
|
import Tvd from '@pages/WellOperations/Tvd'
|
||||||
import WellOperationsTable from '@pages/Cluster/WellOperationsTable'
|
import WellOperationsTable from '@pages/Cluster/WellOperationsTable'
|
||||||
import NewParamsTable from './NewParamsTable'
|
import NewParamsTable from './NewParamsTable'
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ const sortBySectionId = (a, b) => a?.sectionId - b?.sectionId
|
|||||||
const filtersSectionsType = []
|
const filtersSectionsType = []
|
||||||
const DAY_IN_MS = 1000 * 60 * 60 * 24
|
const DAY_IN_MS = 1000 * 60 * 60 * 24
|
||||||
|
|
||||||
export const WellCompositeSections = memo(({ statsWells, selectedSections }) => {
|
const WellCompositeSections = memo(({ statsWells, selectedSections }) => {
|
||||||
const [selectedWells, setSelectedWells] = useState([])
|
const [selectedWells, setSelectedWells] = useState([])
|
||||||
const [wellOperations, setWellOperations] = useState([])
|
const [wellOperations, setWellOperations] = useState([])
|
||||||
const [selectedWellsKeys, setSelectedWellsKeys] = useState([])
|
const [selectedWellsKeys, setSelectedWellsKeys] = useState([])
|
||||||
@ -39,7 +40,7 @@ export const WellCompositeSections = memo(({ statsWells, selectedSections }) =>
|
|||||||
const [isTVDModalVisible, setIsTVDModalVisible] = useState(false)
|
const [isTVDModalVisible, setIsTVDModalVisible] = useState(false)
|
||||||
const [isOpsModalVisible, setIsOpsModalVisible] = useState(false)
|
const [isOpsModalVisible, setIsOpsModalVisible] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
@ -239,4 +240,8 @@ export const WellCompositeSections = memo(({ statsWells, selectedSections }) =>
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default WellCompositeSections
|
export default wrapPrivateComponent(WellCompositeSections, {
|
||||||
|
requirements: ['WellComposite.get'],
|
||||||
|
title: 'Статистика по секциям',
|
||||||
|
route: 'sections',
|
||||||
|
})
|
||||||
|
@ -1,23 +1,31 @@
|
|||||||
import { useState, useEffect, memo, useContext } from 'react'
|
import { useState, useEffect, memo, useMemo } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||||
import { Col, Layout, Row } from 'antd'
|
import { Col, Layout, Row } from 'antd'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell, useRootPath } from '@asb/context'
|
||||||
|
import { PrivateMenu } from '@components/Private'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import WellSelector from '@components/selectors/WellSelector'
|
import WellSelector from '@components/selectors/WellSelector'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
import { arrayOrDefault, NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
import { OperationStatService, WellCompositeService } from '@api'
|
import { OperationStatService, WellCompositeService } from '@api'
|
||||||
|
|
||||||
import ClusterWells from '@pages/Cluster/ClusterWells'
|
import ClusterWells from '@pages/Cluster/ClusterWells'
|
||||||
import { WellCompositeSections } from './WellCompositeSections'
|
import WellCompositeSections from './WellCompositeSections'
|
||||||
|
|
||||||
const { Content } = Layout
|
const { Content } = Layout
|
||||||
|
|
||||||
export const WellCompositeEditor = memo(({ rootPath }) => {
|
const properties = {
|
||||||
const { tab } = useParams()
|
requirements: ['OperationStat.get', 'WellComposite.get'],
|
||||||
const idWell = useContext(IdWellContext)
|
title: 'Композитная скважина',
|
||||||
|
route: 'composite/*',
|
||||||
|
key: 'composite',
|
||||||
|
}
|
||||||
|
|
||||||
|
const WellCompositeEditor = memo(() => {
|
||||||
|
const idWell = useIdWell()
|
||||||
|
const root = useRootPath()
|
||||||
|
const rootPath = useMemo(() => `${root}/${properties.key}`, [root])
|
||||||
|
|
||||||
const [statsWells, setStatsWells] = useState([])
|
const [statsWells, setStatsWells] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
@ -25,19 +33,21 @@ export const WellCompositeEditor = memo(({ rootPath }) => {
|
|||||||
const [selectedIdWells, setSelectedIdWells] = useState([])
|
const [selectedIdWells, setSelectedIdWells] = useState([])
|
||||||
const [selectedSections, setSelectedSections] = useState([])
|
const [selectedSections, setSelectedSections] = useState([])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
try {
|
async () => {
|
||||||
const selected = await WellCompositeService.get(idWell)
|
try {
|
||||||
setSelectedSections(arrayOrDefault(selected))
|
setSelectedSections(arrayOrDefault(await WellCompositeService.get(idWell)))
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
setSelectedSections([])
|
setSelectedSections([])
|
||||||
}
|
throw e
|
||||||
},
|
}
|
||||||
setShowLoader,
|
},
|
||||||
'Не удалось загрузить список скважин',
|
setShowLoader,
|
||||||
'Получение списка скважин'
|
'Не удалось загрузить список скважин',
|
||||||
), [idWell])
|
'Получение списка скважин'
|
||||||
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const wellIds = selectedSections.map((value) => value.idWellSrc)
|
const wellIds = selectedSections.map((value) => value.idWellSrc)
|
||||||
@ -45,15 +55,17 @@ export const WellCompositeEditor = memo(({ rootPath }) => {
|
|||||||
setSelectedIdWells(wellIds)
|
setSelectedIdWells(wellIds)
|
||||||
}, [selectedSections])
|
}, [selectedSections])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const stats = arrayOrDefault(await OperationStatService.getWellsStat(selectedIdWells))
|
async () => {
|
||||||
setStatsWells(stats)
|
const stats = arrayOrDefault(await OperationStatService.getWellsStat(selectedIdWells))
|
||||||
},
|
setStatsWells(stats)
|
||||||
setShowTabLoader,
|
},
|
||||||
'Не удалось загрузить статистику по скважинам/секциям',
|
setShowTabLoader,
|
||||||
'Получение статистики по скважинам/секциям'
|
'Не удалось загрузить статистику по скважинам/секциям',
|
||||||
), [selectedIdWells])
|
'Получение статистики по скважинам/секциям'
|
||||||
|
)
|
||||||
|
}, [selectedIdWells])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
@ -66,19 +78,22 @@ export const WellCompositeEditor = memo(({ rootPath }) => {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<PrivateMenu root={rootPath} mode={'horizontal'} selectable={true} className={'well_menu'} selectedKeys={[tab]}>
|
<PrivateMenu root={rootPath} className={'well_menu'}>
|
||||||
<PrivateMenu.Link key={'wells'} title={'Статистика по скважинам'} />
|
<PrivateMenu.Link content={ClusterWells} />
|
||||||
<PrivateMenu.Link key={'sections'} title={'Статистика по секциям'} />
|
<PrivateMenu.Link content={WellCompositeSections} />
|
||||||
</PrivateMenu>
|
</PrivateMenu>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Content className={'site-layout-background'}>
|
<Content className={'site-layout-background'}>
|
||||||
<LoaderPortal show={showTabLoader}>
|
<LoaderPortal show={showTabLoader}>
|
||||||
<PrivateSwitch root={rootPath} elseRedirect={['wells', 'sections']}>
|
<Routes>
|
||||||
<ClusterWells key={'wells'} statsWells={statsWells} />
|
<Route index element={<Navigate to={ClusterWells.route} replace/>} />
|
||||||
<WellCompositeSections key={'sections'} statsWells={statsWells} selectedSections={selectedSections} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
</PrivateSwitch>
|
|
||||||
|
<Route path={ClusterWells.route} element={<ClusterWells statsWells={statsWells} />} />
|
||||||
|
<Route path={WellCompositeSections.route} element={<WellCompositeSections statsWells={statsWells} selectedSections={selectedSections} />} />
|
||||||
|
</Routes>
|
||||||
</LoaderPortal>
|
</LoaderPortal>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
@ -86,4 +101,4 @@ export const WellCompositeEditor = memo(({ rootPath }) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default WellCompositeEditor
|
export default wrapPrivateComponent(WellCompositeEditor, properties)
|
||||||
|
@ -1,31 +1,34 @@
|
|||||||
import { memo, useContext, useMemo } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||||
import { Layout } from 'antd'
|
import { Layout } from 'antd'
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
import { RootPathContext, useRootPath } from '@asb/context'
|
||||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
import { PrivateMenu } from '@components/Private'
|
||||||
|
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||||
|
|
||||||
import Statistics from './Statistics'
|
import Statistics from './Statistics'
|
||||||
import WellCompositeEditor from './WellCompositeEditor'
|
import WellCompositeEditor from './WellCompositeEditor'
|
||||||
|
|
||||||
export const Analytics = memo(() => {
|
const Analytics = memo(() => {
|
||||||
const { tab } = useParams()
|
const root = useRootPath()
|
||||||
const root = useContext(RootPathContext)
|
|
||||||
const rootPath = useMemo(() => `${root}/analytics`, [root])
|
const rootPath = useMemo(() => `${root}/analytics`, [root])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootPathContext.Provider value={rootPath}>
|
<RootPathContext.Provider value={rootPath}>
|
||||||
<Layout>
|
<Layout>
|
||||||
<PrivateMenu mode={'horizontal'} selectable={true} className={'well_menu'} selectedKeys={[tab]}>
|
<PrivateMenu className={'well_menu'}>
|
||||||
<PrivateMenu.Link key={'composite'} title={'Композитная скважина'} />
|
<PrivateMenu.Link content={WellCompositeEditor} />
|
||||||
<PrivateMenu.Link key={'statistics'} title={'Оценка по ЦБ'} />
|
<PrivateMenu.Link key={'statistics'} title={'Оценка по ЦБ'} content={Statistics} />
|
||||||
</PrivateMenu>
|
</PrivateMenu>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Layout.Content>
|
<Layout.Content className={'site-layout-background'}>
|
||||||
<PrivateSwitch elseRedirect={'composite'}>
|
<Routes>
|
||||||
<WellCompositeEditor key={'composite/:tab?'} rootPath={`${rootPath}/composite`} />
|
<Route index element={<Navigate to={WellCompositeEditor.getKey()} replace />} />
|
||||||
<Statistics key={'statistics'} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
</PrivateSwitch>
|
|
||||||
|
<Route path={WellCompositeEditor.route} element={<WellCompositeEditor />} />
|
||||||
|
<Route path={Statistics.route} element={<Statistics />} />
|
||||||
|
</Routes>
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
@ -33,4 +36,9 @@ export const Analytics = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Analytics
|
export default wrapPrivateComponent(Analytics, {
|
||||||
|
requirements: [],
|
||||||
|
title: 'Аналитика',
|
||||||
|
route: 'analytics/*',
|
||||||
|
key: 'analytics',
|
||||||
|
})
|
||||||
|
@ -20,11 +20,12 @@ import { invokeWebApiWrapperAsync } from '@components/factory'
|
|||||||
import {
|
import {
|
||||||
getOperations,
|
getOperations,
|
||||||
calcAndUpdateStatsBySections,
|
calcAndUpdateStatsBySections,
|
||||||
makeFilterMinMaxFunction
|
isRawDate,
|
||||||
} from '@utils/functions'
|
makeFilterMinMaxFunction,
|
||||||
import { isRawDate } from '@utils'
|
wrapPrivateComponent
|
||||||
|
} from '@utils'
|
||||||
|
|
||||||
import { Tvd } from '@pages/WellOperations/Tvd'
|
import Tvd from '@pages/WellOperations/Tvd'
|
||||||
import WellOperationsTable from './WellOperationsTable'
|
import WellOperationsTable from './WellOperationsTable'
|
||||||
|
|
||||||
const filtersMinMax = [
|
const filtersMinMax = [
|
||||||
@ -39,7 +40,7 @@ const ONLINE_DEADTIME = 600_000
|
|||||||
const getDate = (str) => isRawDate(str) ? new Date(str).toLocaleString() : '-'
|
const getDate = (str) => isRawDate(str) ? new Date(str).toLocaleString() : '-'
|
||||||
const numericRender = makeNumericRender(1)
|
const numericRender = makeNumericRender(1)
|
||||||
|
|
||||||
export const ClusterWells = memo(({ statsWells }) => {
|
const ClusterWells = memo(({ statsWells }) => {
|
||||||
const [selectedWellId, setSelectedWellId] = useState(0)
|
const [selectedWellId, setSelectedWellId] = useState(0)
|
||||||
const [isTVDModalVisible, setIsTVDModalVisible] = useState(false)
|
const [isTVDModalVisible, setIsTVDModalVisible] = useState(false)
|
||||||
const [isOpsModalVisible, setIsOpsModalVisible] = useState(false)
|
const [isOpsModalVisible, setIsOpsModalVisible] = useState(false)
|
||||||
@ -187,4 +188,8 @@ export const ClusterWells = memo(({ statsWells }) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default ClusterWells
|
export default wrapPrivateComponent(ClusterWells, {
|
||||||
|
requirements: [],
|
||||||
|
title: 'Статистика по скважинам',
|
||||||
|
route: 'wells',
|
||||||
|
})
|
||||||
|
@ -1,33 +1,43 @@
|
|||||||
import { useState, useEffect, memo } from 'react'
|
import { useState, useEffect, memo } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { arrayOrDefault } from '@utils'
|
import { LayoutPortal } from '@components/Layout'
|
||||||
import { OperationStatService } from '@api'
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||||
|
import { OperationStatService } from '@api'
|
||||||
|
|
||||||
import ClusterWells from './ClusterWells'
|
import ClusterWells from './ClusterWells'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
|
||||||
export const Cluster = memo(() => {
|
const Cluster = memo(() => {
|
||||||
const { idCluster } = useParams()
|
const { idCluster } = useParams()
|
||||||
const [data, setData] = useState([])
|
const [data, setData] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const clusterData = await OperationStatService.getStatCluster(idCluster)
|
async () => {
|
||||||
setData(arrayOrDefault(clusterData?.statsWells))
|
const clusterData = await OperationStatService.getStatCluster(idCluster)
|
||||||
},
|
setData(arrayOrDefault(clusterData?.statsWells))
|
||||||
setShowLoader,
|
},
|
||||||
`Не удалось загрузить данные по кусту "${idCluster}"`,
|
setShowLoader,
|
||||||
'Получение данных по кусту'
|
`Не удалось загрузить данные по кусту "${idCluster}"`,
|
||||||
), [idCluster])
|
'Получение данных по кусту'
|
||||||
|
)
|
||||||
|
}, [idCluster])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LayoutPortal title={'Анализ скважин куста'}>
|
||||||
<ClusterWells statsWells={data} />
|
<LoaderPortal show={showLoader}>
|
||||||
</LoaderPortal>
|
<ClusterWells statsWells={data} />
|
||||||
)
|
</LoaderPortal>
|
||||||
|
</LayoutPortal>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Cluster
|
export default wrapPrivateComponent(Cluster, {
|
||||||
|
requirements: ['OperationStat.get'],
|
||||||
|
title: 'Анализ скважин куста',
|
||||||
|
route: 'cluster/:idCluster/*',
|
||||||
|
key: 'cluster',
|
||||||
|
})
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import { Map, Overlay } from 'pigeon-maps'
|
import { Map, Overlay } from 'pigeon-maps'
|
||||||
import { Link, useLocation } from 'react-router-dom'
|
|
||||||
import { useState, useEffect, memo } from 'react'
|
import { useState, useEffect, memo } from 'react'
|
||||||
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import { ClusterService } from '@api'
|
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
import { PointerIcon } from '@components/icons'
|
import { PointerIcon } from '@components/icons'
|
||||||
|
import { LayoutPortal } from '@components/Layout'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { arrayOrDefault, limitValue, wrapPrivateComponent } from '@utils'
|
||||||
|
import { ClusterService } from '@api'
|
||||||
|
|
||||||
import '@styles/index.css'
|
import '@styles/index.css'
|
||||||
|
|
||||||
const defaultViewParams = { center: [60.81226, 70.0562], zoom: 5 }
|
const defaultViewParams = { center: [60.81226, 70.0562], zoom: 5 }
|
||||||
|
|
||||||
|
const zoomLimit = limitValue(5, 15)
|
||||||
|
|
||||||
const calcViewParams = (clusters) => {
|
const calcViewParams = (clusters) => {
|
||||||
if ((clusters?.length ?? 0) <= 0)
|
if ((clusters?.length ?? 0) <= 0)
|
||||||
return defaultViewParams
|
return defaultViewParams
|
||||||
@ -33,49 +36,58 @@ const calcViewParams = (clusters) => {
|
|||||||
// zoom min = 1 (mega far)
|
// zoom min = 1 (mega far)
|
||||||
// 4 - full Russia (161.6 deg)
|
// 4 - full Russia (161.6 deg)
|
||||||
// 13.5 - Khanty-Mansiysk
|
// 13.5 - Khanty-Mansiysk
|
||||||
const zoom = Math.min(Math.max(5, 5 + 5 / (maxDeg + 0.5)), 15)
|
const zoom = zoomLimit(5 + 5 / (maxDeg + 0.5))
|
||||||
|
|
||||||
return { center, zoom }
|
return { center, zoom }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Deposit = memo(() => {
|
const Deposit = memo(() => {
|
||||||
const [clustersData, setClustersData] = useState([])
|
const [clustersData, setClustersData] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [viewParams, setViewParams] = useState(defaultViewParams)
|
const [viewParams, setViewParams] = useState(defaultViewParams)
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const data = await ClusterService.getClusters()
|
async () => {
|
||||||
setClustersData(arrayOrDefault(data))
|
const data = await ClusterService.getClusters()
|
||||||
setViewParams(calcViewParams(data))
|
setClustersData(arrayOrDefault(data))
|
||||||
},
|
setViewParams(calcViewParams(data))
|
||||||
setShowLoader,
|
},
|
||||||
`Не удалось загрузить список кустов`,
|
setShowLoader,
|
||||||
'Получить список кустов'
|
`Не удалось загрузить список кустов`,
|
||||||
), [])
|
'Получить список кустов'
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LayoutPortal noSheet title={'Месторождение'}>
|
||||||
<div className={'h-100vh'}>
|
<LoaderPortal show={showLoader}>
|
||||||
<Map {...viewParams}>
|
<div className={'h-100vh'}>
|
||||||
{clustersData.map(cluster => (
|
<Map {...viewParams}>
|
||||||
<Overlay
|
{clustersData.map(cluster => (
|
||||||
width={32}
|
<Overlay
|
||||||
anchor={[cluster.latitude, cluster.longitude]}
|
width={32}
|
||||||
key={`${cluster.latitude} ${cluster.longitude}`}
|
anchor={[cluster.latitude, cluster.longitude]}
|
||||||
>
|
key={`${cluster.latitude} ${cluster.longitude}`}
|
||||||
<Link to={{ pathname: `/cluster/${cluster.id}`, state: { from: location.pathname }}}>
|
>
|
||||||
<PointerIcon state={'active'} width={48} height={59} />
|
<Link to={{ pathname: `/cluster/${cluster.id}`, state: { from: location.pathname }}}>
|
||||||
<span>{cluster.caption}</span>
|
<PointerIcon state={'active'} width={48} height={59} />
|
||||||
</Link>
|
<span>{cluster.caption}</span>
|
||||||
</Overlay>
|
</Link>
|
||||||
))}
|
</Overlay>
|
||||||
</Map>
|
))}
|
||||||
</div>
|
</Map>
|
||||||
</LoaderPortal>
|
</div>
|
||||||
|
</LoaderPortal>
|
||||||
|
</LayoutPortal>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Deposit
|
export default wrapPrivateComponent(Deposit, {
|
||||||
|
requirements: ['Cluster.get'],
|
||||||
|
title: 'Месторождение',
|
||||||
|
route: 'deposit/*',
|
||||||
|
key: 'deposit',
|
||||||
|
})
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useState, useEffect, useMemo, useCallback, useContext } from 'react'
|
import { useState, useEffect, useMemo, useCallback } from 'react'
|
||||||
import { DatePicker, Button, Input } from 'antd'
|
import { DatePicker, Button, Input } from 'antd'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { UploadForm } from '@components/UploadForm'
|
import { UploadForm } from '@components/UploadForm'
|
||||||
import { CompanyView, UserView } from '@components/views'
|
import { CompanyView, UserView } from '@components/views'
|
||||||
import { invokeWebApiWrapperAsync, downloadFile, formatBytes } from '@components/factory'
|
import { invokeWebApiWrapperAsync, downloadFile, formatBytes } from '@components/factory'
|
||||||
import { EditableTable, makeColumn, makeDateColumn, makeNumericColumn, makePaginationObject } from '@components/Table'
|
import { EditableTable, makeColumn, makeDateColumn, makeNumericColumn, makePaginationObject } from '@components/Table'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission } from '@utils'
|
||||||
import { FileService } from '@api'
|
import { FileService } from '@api'
|
||||||
|
|
||||||
const pageSize = 12
|
const pageSize = 12
|
||||||
@ -40,7 +40,7 @@ export const DocumentsTemplate = ({ idCategory, idWell: wellId, mimeTypes, heade
|
|||||||
const [files, setFiles] = useState([])
|
const [files, setFiles] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const idwellContext = useContext(IdWellContext)
|
const idwellContext = useIdWell()
|
||||||
const idWell = useMemo(() => wellId ?? idwellContext, [wellId, idwellContext])
|
const idWell = useMemo(() => wellId ?? idwellContext, [wellId, idwellContext])
|
||||||
|
|
||||||
const uploadUrl = useMemo(() => `/api/well/${idWell}/files/?idCategory=${idCategory}`, [idWell, idCategory])
|
const uploadUrl = useMemo(() => `/api/well/${idWell}/files/?idCategory=${idCategory}`, [idWell, idCategory])
|
||||||
@ -83,8 +83,13 @@ export const DocumentsTemplate = ({ idCategory, idWell: wellId, mimeTypes, heade
|
|||||||
)
|
)
|
||||||
}, [filterCompanyName, filterDataRange, filterFileName, idCategory, idWell, page])
|
}, [filterCompanyName, filterDataRange, filterFileName, idCategory, idWell, page])
|
||||||
|
|
||||||
useEffect(update, [update])
|
useEffect(() => {
|
||||||
useEffect(() => onChange?.(files), [files, onChange])
|
update()
|
||||||
|
}, [update])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChange?.(files)
|
||||||
|
}, [files, onChange])
|
||||||
|
|
||||||
const handleFileDelete = useMemo(() => hasPermission(`File.edit${idCategory}`) && (async (file) => {
|
const handleFileDelete = useMemo(() => hasPermission(`File.edit${idCategory}`) && (async (file) => {
|
||||||
await FileService.delete(idWell, file.id)
|
await FileService.delete(idWell, file.id)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||||
import { memo, useContext, useMemo } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
import { FolderOutlined } from '@ant-design/icons'
|
import { FolderOutlined } from '@ant-design/icons'
|
||||||
import { Layout } from 'antd'
|
import { Layout } from 'antd'
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
import { RootPathContext, useRootPath } from '@asb/context'
|
||||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
import { PrivateMenu } from '@components/Private'
|
||||||
|
import { getTabname, wrapPrivateComponent, NoAccessComponent } from '@utils'
|
||||||
|
|
||||||
import DocumentsTemplate from './DocumentsTemplate'
|
import DocumentsTemplate from './DocumentsTemplate'
|
||||||
|
|
||||||
@ -23,9 +24,9 @@ export const documentCategories = [
|
|||||||
{ id: 9, key: 'closingService', title: 'Сервис по заканчиванию скважины' },
|
{ id: 9, key: 'closingService', title: 'Сервис по заканчиванию скважины' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const MenuDocuments = memo(() => {
|
const MenuDocuments = memo(() => {
|
||||||
const { category } = useParams()
|
const category = getTabname()
|
||||||
const root = useContext(RootPathContext)
|
const root = useRootPath()
|
||||||
const rootPath = useMemo(() => `${root}/document`, [root])
|
const rootPath = useMemo(() => `${root}/document`, [root])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -42,19 +43,30 @@ export const MenuDocuments = memo(() => {
|
|||||||
</PrivateMenu>
|
</PrivateMenu>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Content className={'site-layout-background'}>
|
<Content className={'site-layout-background'}>
|
||||||
<PrivateSwitch elseRedirect={documentCategories.map((cat) => cat.key)}>
|
<Routes>
|
||||||
|
{documentCategories.length > 0 && (
|
||||||
|
<Route index element={<Navigate to={documentCategories[0].key} replace />} />
|
||||||
|
)}
|
||||||
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
|
|
||||||
{documentCategories.map(category => (
|
{documentCategories.map(category => (
|
||||||
<DocumentsTemplate
|
<Route key={category.key} path={category.key} element={(
|
||||||
key={category.key}
|
<DocumentsTemplate
|
||||||
idCategory={category.id}
|
idCategory={category.id}
|
||||||
tableName={`documents_${category.key}`}
|
tableName={`documents_${category.key}`}
|
||||||
/>
|
/>
|
||||||
|
)} />
|
||||||
))}
|
))}
|
||||||
</PrivateSwitch>
|
</Routes>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</RootPathContext.Provider>
|
</RootPathContext.Provider>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default MenuDocuments
|
export default wrapPrivateComponent(MenuDocuments, {
|
||||||
|
requirements: [ 'Deposit.get', 'File.get' ],
|
||||||
|
title: 'Документы',
|
||||||
|
route: 'document/*',
|
||||||
|
key: 'document',
|
||||||
|
})
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Form, Select } from 'antd'
|
import { Form, Select } from 'antd'
|
||||||
import { FileAddOutlined } from '@ant-design/icons'
|
import { FileAddOutlined } from '@ant-design/icons'
|
||||||
import { memo, useCallback, useContext, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import Poprompt from '@components/selectors/Poprompt'
|
import Poprompt from '@components/selectors/Poprompt'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { DrillingProgramService } from '@api'
|
import { DrillingProgramService } from '@api'
|
||||||
@ -21,18 +21,20 @@ export const CategoryAdder = memo(({ categories, onUpdate, className, ...other }
|
|||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [showCatLoader, setShowCatLoader] = useState(false)
|
const [showCatLoader, setShowCatLoader] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
setOptions(categories.map((category) => ({
|
async () => {
|
||||||
label: category.name ?? category.shortName,
|
setOptions(categories.map((category) => ({
|
||||||
value: category.id
|
label: category.name ?? category.shortName,
|
||||||
})))
|
value: category.id
|
||||||
},
|
})))
|
||||||
setShowCatLoader,
|
},
|
||||||
`Не удалось установить список доступных категорий для добавления`
|
setShowCatLoader,
|
||||||
), [categories])
|
`Не удалось установить список доступных категорий для добавления`
|
||||||
|
)
|
||||||
|
}, [categories])
|
||||||
|
|
||||||
const onFinish = useCallback(({ categories }) => invokeWebApiWrapperAsync(
|
const onFinish = useCallback(({ categories }) => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Input, Modal, Radio } from 'antd'
|
import { Input, Modal, Radio } from 'antd'
|
||||||
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { BehaviorSubject, debounceTime, distinctUntilChanged, filter, map } from 'rxjs'
|
import { BehaviorSubject, debounceTime, distinctUntilChanged, filter, map } from 'rxjs'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { UserView } from '@components/views'
|
import { UserView } from '@components/views'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
@ -30,27 +30,32 @@ export const CategoryEditor = memo(({ visible, category, onClosed }) => {
|
|||||||
const [subject, setSubject] = useState(null)
|
const [subject, setSubject] = useState(null)
|
||||||
const [needUpdate, setNeedUpdate] = useState(false)
|
const [needUpdate, setNeedUpdate] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
useEffect(() => visible && setNeedUpdate(false), [visible])
|
useEffect(() => {
|
||||||
|
if (visible)
|
||||||
|
setNeedUpdate(false)
|
||||||
|
}, [visible])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const filteredUsers = users.filter(({ user }) => user && [
|
async () => {
|
||||||
user.login ?? '',
|
const filteredUsers = users.filter(({ user }) => user && [
|
||||||
user.name ?? '',
|
user.login ?? '',
|
||||||
user.surname ?? '',
|
user.name ?? '',
|
||||||
user.partonymic ?? '',
|
user.surname ?? '',
|
||||||
user.email ?? '',
|
user.partonymic ?? '',
|
||||||
user.phone ?? '',
|
user.email ?? '',
|
||||||
user.position ?? '',
|
user.phone ?? '',
|
||||||
user.company?.caption ?? '',
|
user.position ?? '',
|
||||||
].join(' ').toLowerCase().includes(searchValue))
|
user.company?.caption ?? '',
|
||||||
setFilteredUsers(filteredUsers)
|
].join(' ').toLowerCase().includes(searchValue))
|
||||||
},
|
setFilteredUsers(filteredUsers)
|
||||||
setIsSearching,
|
},
|
||||||
`Не удалось произвести поиск пользователей`
|
setIsSearching,
|
||||||
), [users, searchValue])
|
`Не удалось произвести поиск пользователей`
|
||||||
|
)
|
||||||
|
}, [users, searchValue])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!subject) {
|
if (!subject) {
|
||||||
@ -71,14 +76,16 @@ export const CategoryEditor = memo(({ visible, category, onClosed }) => {
|
|||||||
}
|
}
|
||||||
}, [subject])
|
}, [subject])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const allUsers = arrayOrDefault(await DrillingProgramService.getAvailableUsers(idWell))
|
async () => {
|
||||||
setAllUsers(allUsers)
|
const allUsers = arrayOrDefault(await DrillingProgramService.getAvailableUsers(idWell))
|
||||||
},
|
setAllUsers(allUsers)
|
||||||
setShowLoader,
|
},
|
||||||
`Не удалось загрузить список доступных пользователей скважины "${idWell}"`
|
setShowLoader,
|
||||||
), [idWell])
|
`Не удалось загрузить список доступных пользователей скважины "${idWell}"`
|
||||||
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
const calcUsers = useCallback(() => {
|
const calcUsers = useCallback(() => {
|
||||||
if (!visible) return
|
if (!visible) return
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useContext, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { Button, DatePicker, Input, Modal } from 'antd'
|
import { Button, DatePicker, Input, Modal } from 'antd'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { CompanyView } from '@components/views'
|
import { CompanyView } from '@components/views'
|
||||||
import DownloadLink from '@components/DownloadLink'
|
import DownloadLink from '@components/DownloadLink'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
@ -65,21 +65,23 @@ export const CategoryHistory = ({ idCategory, visible, onClose }) => {
|
|||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [companyName, setCompanyName] = useState('')
|
const [companyName, setCompanyName] = useState('')
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
if (!visible) return
|
async () => {
|
||||||
const [begin, end] = range?.length > 1 ? [range[0].toISOString(), range[1].toISOString()] : [null, null]
|
if (!visible) return
|
||||||
const skip = (page - 1) * pageSize
|
const [begin, end] = range?.length > 1 ? [range[0].toISOString(), range[1].toISOString()] : [null, null]
|
||||||
|
const skip = (page - 1) * pageSize
|
||||||
|
|
||||||
const paginatedHistory = await FileService.getFilesInfo(idWell, idCategory, companyName, fileName, begin, end, skip, pageSize)
|
const paginatedHistory = await FileService.getFilesInfo(idWell, idCategory, companyName, fileName, begin, end, skip, pageSize)
|
||||||
setTotal(paginatedHistory?.count ?? 0)
|
setTotal(paginatedHistory?.count ?? 0)
|
||||||
setData(arrayOrDefault(paginatedHistory?.items))
|
setData(arrayOrDefault(paginatedHistory?.items))
|
||||||
},
|
},
|
||||||
setIsLoading,
|
setIsLoading,
|
||||||
`Не удалось загрузить историю категорий "${idCategory}" скважины "${idWell}"`
|
`Не удалось загрузить историю категорий "${idCategory}" скважины "${idWell}"`
|
||||||
), [idWell, idCategory, visible, range, companyName, fileName, page, pageSize])
|
)
|
||||||
|
}, [idWell, idCategory, visible, range, companyName, fileName, page, pageSize])
|
||||||
|
|
||||||
const onPaginationChange = useCallback((page, pageSize) => {
|
const onPaginationChange = useCallback((page, pageSize) => {
|
||||||
setPage(page)
|
setPage(page)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { memo, useCallback, useContext, useMemo, useState } from 'react'
|
import { memo, useCallback, useMemo, useState } from 'react'
|
||||||
import { Button, Input, Popconfirm, Form } from 'antd'
|
import { Button, Input, Popconfirm, Form } from 'antd'
|
||||||
import {
|
import {
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
@ -6,7 +6,7 @@ import {
|
|||||||
TableOutlined,
|
TableOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { UserView } from '@components/views'
|
import { UserView } from '@components/views'
|
||||||
import UploadForm from '@components/UploadForm'
|
import UploadForm from '@components/UploadForm'
|
||||||
import DownloadLink from '@components/DownloadLink'
|
import DownloadLink from '@components/DownloadLink'
|
||||||
@ -45,7 +45,7 @@ export const CategoryRender = memo(({ partData, onUpdate, onEdit, onHistory, set
|
|||||||
file // Информация о файле
|
file // Информация о файле
|
||||||
} = partData ?? {}
|
} = partData ?? {}
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const uploadUrl = useMemo(() => `/api/well/${idWell}/drillingProgram/part/${idFileCategory}`, [idWell, idFileCategory])
|
const uploadUrl = useMemo(() => `/api/well/${idWell}/drillingProgram/part/${idFileCategory}`, [idWell, idFileCategory])
|
||||||
const approvedMarks = useMemo(() => file?.fileMarks?.filter((mark) => mark.idMarkType === 1), [file])
|
const approvedMarks = useMemo(() => file?.fileMarks?.filter((mark) => mark.idMarkType === 1), [file])
|
||||||
|
@ -13,7 +13,7 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
|||||||
import { useIdWell } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { downloadFile, formatBytes, invokeWebApiWrapperAsync } from '@components/factory'
|
import { downloadFile, formatBytes, invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { arrayOrDefault, formatDate } from '@utils'
|
import { arrayOrDefault, formatDate, wrapPrivateComponent } from '@utils'
|
||||||
import { DrillingProgramService } from '@api'
|
import { DrillingProgramService } from '@api'
|
||||||
|
|
||||||
import CategoryAdder from './CategoryAdder'
|
import CategoryAdder from './CategoryAdder'
|
||||||
@ -41,7 +41,7 @@ const STATE_STRING = {
|
|||||||
[ID_STATE.Unknown]: { icon: WarningOutlined, text: 'Неизвестно' },
|
[ID_STATE.Unknown]: { icon: WarningOutlined, text: 'Неизвестно' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DrillingProgram = memo(() => {
|
const DrillingProgram = memo(() => {
|
||||||
const [selectedCategory, setSelectedCategory] = useState()
|
const [selectedCategory, setSelectedCategory] = useState()
|
||||||
const [historyVisible, setHistoryVisible] = useState(false)
|
const [historyVisible, setHistoryVisible] = useState(false)
|
||||||
const [editorVisible, setEditorVisible] = useState(false)
|
const [editorVisible, setEditorVisible] = useState(false)
|
||||||
@ -79,7 +79,9 @@ export const DrillingProgram = memo(() => {
|
|||||||
`Не удалось загрузить название скважины "${idWell}"`
|
`Не удалось загрузить название скважины "${idWell}"`
|
||||||
), [idWell])
|
), [idWell])
|
||||||
|
|
||||||
useEffect(() => updateData(), [updateData])
|
useEffect(() => {
|
||||||
|
updateData()
|
||||||
|
}, [updateData])
|
||||||
|
|
||||||
const onCategoryEdit = useCallback((catId) => {
|
const onCategoryEdit = useCallback((catId) => {
|
||||||
setSelectedCategory(catId)
|
setSelectedCategory(catId)
|
||||||
@ -172,4 +174,8 @@ export const DrillingProgram = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default DrillingProgram
|
export default wrapPrivateComponent(DrillingProgram, {
|
||||||
|
requirements: [ 'DrillingProgram.get' ],
|
||||||
|
title: 'Программа бурения',
|
||||||
|
route: 'drillingProgram',
|
||||||
|
})
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import { memo, useCallback, useState } from 'react'
|
import { memo, useCallback, useState } from 'react'
|
||||||
import { Link, useHistory, useLocation } from 'react-router-dom'
|
import { Link, useNavigate, useLocation } from 'react-router-dom'
|
||||||
import { Card, Form, Input, Button } from 'antd'
|
|
||||||
import { UserOutlined, LockOutlined } from '@ant-design/icons'
|
import { UserOutlined, LockOutlined } from '@ant-design/icons'
|
||||||
|
import { Card, Form, Input, Button } from 'antd'
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { loginRules, passwordRules } from '@utils/validationRules'
|
import { loginRules, passwordRules } from '@utils/validationRules'
|
||||||
import { setUser } from '@utils/storage'
|
import { setUser, wrapPrivateComponent } from '@utils'
|
||||||
import { AuthService } from '@api'
|
import { AuthService } from '@api'
|
||||||
|
|
||||||
import '@styles/index.css'
|
import '@styles/index.css'
|
||||||
import Logo from '@images/Logo'
|
import Logo from '@images/Logo'
|
||||||
|
|
||||||
export const Login = memo(() => {
|
const Login = memo(() => {
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const history = useHistory()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const handleLogin = useCallback((formData) => invokeWebApiWrapperAsync(
|
const handleLogin = useCallback((formData) => invokeWebApiWrapperAsync(
|
||||||
@ -23,20 +23,19 @@ export const Login = memo(() => {
|
|||||||
const user = await AuthService.login(formData)
|
const user = await AuthService.login(formData)
|
||||||
if (!user) throw Error('Неправильный логин или пароль')
|
if (!user) throw Error('Неправильный логин или пароль')
|
||||||
setUser(user)
|
setUser(user)
|
||||||
console.log(location.state?.from)
|
navigate(location.state?.from ?? '/deposit')
|
||||||
history.push(location.state?.from ?? 'well')
|
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
(ex) => ex?.message ?? 'Ошибка входа',
|
(ex) => ex?.message ?? 'Ошибка входа',
|
||||||
'Вход в систему'
|
'Вход в систему'
|
||||||
), [history, location])
|
), [navigate, location])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'login_page shadow'}>
|
<div className={'login_page shadow'}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<Logo style={{ marginBottom: '10px' }}/>
|
<Logo style={{ marginBottom: '10px' }} />
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
<Card title={'Система мониторинга'} className={'shadow'} bordered={true} style={{ width: 350 }}>
|
<Card bordered title={'Система мониторинга'} className={'shadow'} style={{ width: 350 }}>
|
||||||
<Form onFinish={handleLogin}>
|
<Form onFinish={handleLogin}>
|
||||||
<Form.Item name={'login'} rules={loginRules}>
|
<Form.Item name={'login'} rules={loginRules}>
|
||||||
<Input placeholder={'Пользователь'} prefix={<UserOutlined />} />
|
<Input placeholder={'Пользователь'} prefix={<UserOutlined />} />
|
||||||
@ -60,4 +59,8 @@ export const Login = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Login
|
export default wrapPrivateComponent(Login, {
|
||||||
|
requirements: [],
|
||||||
|
title: 'Вход в систему',
|
||||||
|
route: 'login',
|
||||||
|
})
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import { memo } from 'react'
|
|
||||||
import { Route, Switch } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
|
||||||
import { AdminLayoutPortal, LayoutPortal } from '@components/Layout'
|
|
||||||
import { PrivateDefaultRoute, PrivateRoute } from '@components/Private'
|
|
||||||
|
|
||||||
import Well from './Well'
|
|
||||||
import Cluster from './Cluster'
|
|
||||||
import Deposit from './Deposit'
|
|
||||||
import AdminPanel from './AdminPanel'
|
|
||||||
import AccessDenied from './AccessDenied'
|
|
||||||
|
|
||||||
export const Main = memo(() => (
|
|
||||||
<RootPathContext.Provider value={''}>
|
|
||||||
<Switch>
|
|
||||||
<PrivateRoute path={'/admin/:tab?'}>
|
|
||||||
<AdminLayoutPortal title={'Администраторская панель'}>
|
|
||||||
<AdminPanel />
|
|
||||||
</AdminLayoutPortal>
|
|
||||||
</PrivateRoute>
|
|
||||||
<PrivateRoute path={'/deposit'}>
|
|
||||||
<LayoutPortal noSheet title='Месторождение'>
|
|
||||||
<Deposit />
|
|
||||||
</LayoutPortal>
|
|
||||||
</PrivateRoute>
|
|
||||||
<PrivateRoute path={'/cluster/:idCluster'}>
|
|
||||||
<LayoutPortal title={'Анализ скважин куста'}>
|
|
||||||
<Cluster />
|
|
||||||
</LayoutPortal>
|
|
||||||
</PrivateRoute>
|
|
||||||
<PrivateRoute path={'/well/:idWell/:tab?'}>
|
|
||||||
<LayoutPortal>
|
|
||||||
<Well />
|
|
||||||
</LayoutPortal>
|
|
||||||
</PrivateRoute>
|
|
||||||
<Route path={'/access_denied'}>
|
|
||||||
<AccessDenied />
|
|
||||||
</Route>
|
|
||||||
<PrivateDefaultRoute urls={['/deposit']} />
|
|
||||||
</Switch>
|
|
||||||
</RootPathContext.Provider>
|
|
||||||
))
|
|
||||||
|
|
||||||
export default Main
|
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, memo, useMemo, useCallback, useContext } from 'react'
|
import { useState, useEffect, memo, useMemo, useCallback } from 'react'
|
||||||
import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
|
import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
|
||||||
import {
|
import {
|
||||||
CheckSquareOutlined,
|
CheckSquareOutlined,
|
||||||
@ -9,11 +9,10 @@ import {
|
|||||||
DeleteOutlined
|
DeleteOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission, formatDate } from '@utils'
|
||||||
import { formatDate } from '@utils'
|
|
||||||
import { MeasureService } from '@api'
|
import { MeasureService } from '@api'
|
||||||
|
|
||||||
import { View } from './View'
|
import { View } from './View'
|
||||||
@ -33,7 +32,7 @@ export const MeasureTable = memo(({ group, updateMeasuresFunc, additionalButtons
|
|||||||
const [isTableEditing, setIsTableEditing] = useState(false)
|
const [isTableEditing, setIsTableEditing] = useState(false)
|
||||||
const [editingActionName, setEditingActionName] = useState('')
|
const [editingActionName, setEditingActionName] = useState('')
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const [measuresForm] = Form.useForm()
|
const [measuresForm] = Form.useForm()
|
||||||
|
|
||||||
|
0
src/pages/Measure/columnsCommon.js → src/pages/Measure/columnsCommon.jsx
Executable file → Normal file
0
src/pages/Measure/columnsCommon.js → src/pages/Measure/columnsCommon.jsx
Executable file → Normal file
@ -1,10 +1,11 @@
|
|||||||
import { Button } from 'antd'
|
import { useState, useEffect, memo } from 'react'
|
||||||
import { useState, useEffect, memo, useContext } from 'react'
|
|
||||||
import { TableOutlined } from '@ant-design/icons'
|
import { TableOutlined } from '@ant-design/icons'
|
||||||
|
import { Button } from 'antd'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { wrapPrivateComponent } from '@utils'
|
||||||
import { MeasureService } from '@api'
|
import { MeasureService } from '@api'
|
||||||
|
|
||||||
import { MeasureTable } from './MeasureTable'
|
import { MeasureTable } from './MeasureTable'
|
||||||
@ -42,34 +43,36 @@ const defaultData = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export const Measure = memo(() => {
|
const Measure = memo(() => {
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(true)
|
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(true)
|
||||||
const [data, setData] = useState(defaultData)
|
const [data, setData] = useState(defaultData)
|
||||||
const [tableIdx, setTableIdx] = useState(-1)
|
const [tableIdx, setTableIdx] = useState(-1)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
if (!isMeasuresUpdating) return
|
async () => {
|
||||||
const measures = await MeasureService.getHisory(idWell)
|
if (!isMeasuresUpdating) return
|
||||||
setIsMeasuresUpdating(false)
|
const measures = await MeasureService.getHisory(idWell)
|
||||||
|
setIsMeasuresUpdating(false)
|
||||||
|
|
||||||
setData(prevData => {
|
setData(prevData => {
|
||||||
prevData.forEach(el => el.values = [])
|
prevData.forEach(el => el.values = [])
|
||||||
measures.forEach(el => {
|
measures.forEach(el => {
|
||||||
const idx = prevData.findIndex(group => el.idCategory === group.idCategory)
|
const idx = prevData.findIndex(group => el.idCategory === group.idCategory)
|
||||||
if (idx >= 0)
|
if (idx >= 0)
|
||||||
prevData[idx].values.push(el)
|
prevData[idx].values.push(el)
|
||||||
|
})
|
||||||
|
return prevData
|
||||||
})
|
})
|
||||||
return prevData
|
},
|
||||||
})
|
setShowLoader,
|
||||||
},
|
`Не удалось загрузить последние данные по скважине ${idWell}`,
|
||||||
setShowLoader,
|
'Получение последних данных телеметрий'
|
||||||
`Не удалось загрузить последние данные по скважине ${idWell}`,
|
)
|
||||||
'Получение последних данных телеметрий'
|
}, [idWell, isMeasuresUpdating])
|
||||||
), [idWell, isMeasuresUpdating])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
@ -90,4 +93,8 @@ export const Measure = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Measure
|
export default wrapPrivateComponent(Measure, {
|
||||||
|
requirements: [ 'Measure.get' ],
|
||||||
|
title: 'Измерения',
|
||||||
|
route: 'measure',
|
||||||
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { memo, useCallback, useState } from 'react'
|
import { memo, useCallback, useState } from 'react'
|
||||||
import { Link, useHistory } from 'react-router-dom'
|
import { Link, useNavigate } from 'react-router-dom'
|
||||||
import { Card, Form, Input, Button } from 'antd'
|
import { Card, Form, Input, Button } from 'antd'
|
||||||
import {
|
import {
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
@ -20,6 +20,7 @@ import {
|
|||||||
passwordRules,
|
passwordRules,
|
||||||
phoneRules
|
phoneRules
|
||||||
} from '@utils/validationRules'
|
} from '@utils/validationRules'
|
||||||
|
import { wrapPrivateComponent } from '@utils'
|
||||||
|
|
||||||
import Logo from '@images/Logo'
|
import Logo from '@images/Logo'
|
||||||
|
|
||||||
@ -52,17 +53,17 @@ const createInput = (name, placeholder, rules, isPassword, dependencies) => (
|
|||||||
|
|
||||||
export const Register = memo(() => {
|
export const Register = memo(() => {
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const history = useHistory()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const handleRegister = useCallback((formData) => invokeWebApiWrapperAsync(
|
const handleRegister = useCallback((formData) => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
await AuthService.register(formData)
|
await AuthService.register(formData)
|
||||||
history.push('/login')
|
navigate('/login')
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Ошибка отправки заявки на регистрацию`,
|
`Ошибка отправки заявки на регистрацию`,
|
||||||
'Отправка заявки на регистрацию'
|
'Отправка заявки на регистрацию'
|
||||||
), [history])
|
), [navigate])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader} className={'loader-container login_page shadow'}>
|
<LoaderPortal show={showLoader} className={'loader-container login_page shadow'}>
|
||||||
@ -91,4 +92,8 @@ export const Register = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Register
|
export default wrapPrivateComponent(Register, {
|
||||||
|
requirements: [],
|
||||||
|
title: 'Регистрация',
|
||||||
|
route: 'register',
|
||||||
|
})
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import moment from 'moment'
|
|
||||||
import { DatePicker, Descriptions, Divider, Form, Input, InputNumber, Modal, Select, Space, Table } from 'antd'
|
import { DatePicker, Descriptions, Divider, Form, Input, InputNumber, Modal, Select, Space, Table } from 'antd'
|
||||||
import { memo, useCallback, useContext, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useState } from 'react'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { makeColumn, makeGroupColumn } from '@components/Table'
|
import { makeColumn, makeGroupColumn } from '@components/Table'
|
||||||
@ -133,7 +133,7 @@ export const ReportEditor = memo(({ visible, data, onDone, onCancel, checkIsDate
|
|||||||
const [isInvalid, setIsInvalid] = useState(false)
|
const [isInvalid, setIsInvalid] = useState(false)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const setFields = useCallback((data) => form.setFieldsValue(data ? {
|
const setFields = useCallback((data) => form.setFieldsValue(data ? {
|
||||||
...data,
|
...data,
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
import moment from 'moment'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { Button } from 'antd'
|
|
||||||
import { FileExcelOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'
|
import { FileExcelOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
import { Button } from 'antd'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { DateRangeWrapper, Table, makeDateColumn, makeColumn } from '@components/Table'
|
import { DateRangeWrapper, Table, makeDateColumn, makeColumn } from '@components/Table'
|
||||||
import { download, invokeWebApiWrapperAsync } from '@components/factory'
|
import { download, invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||||
import { DailyReportService } from '@api'
|
import { DailyReportService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
|
|
||||||
import ReportEditor from './ReportEditor'
|
import ReportEditor from './ReportEditor'
|
||||||
|
|
||||||
export const DailyReport = memo(() => {
|
const DailyReport = memo(() => {
|
||||||
const [data, setData] = useState([])
|
const [data, setData] = useState([])
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [searchDate, setSearchDate] = useState([moment().subtract(1, 'week'), moment()])
|
const [searchDate, setSearchDate] = useState([moment().subtract(1, 'week'), moment()])
|
||||||
const [selectedReport, setSelectedReport] = useState(null)
|
const [selectedReport, setSelectedReport] = useState(null)
|
||||||
const [isEditorVisible, setIsEditorVisible] = useState(false)
|
const [isEditorVisible, setIsEditorVisible] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const updateTable = useCallback(() => invokeWebApiWrapperAsync(
|
const updateTable = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -31,7 +31,9 @@ export const DailyReport = memo(() => {
|
|||||||
'Получение списка суточных рапортов',
|
'Получение списка суточных рапортов',
|
||||||
), [idWell])
|
), [idWell])
|
||||||
|
|
||||||
useEffect(updateTable, [updateTable])
|
useEffect(() => {
|
||||||
|
updateTable()
|
||||||
|
}, [updateTable])
|
||||||
|
|
||||||
const checkIsDateBusy = useCallback((current) => current.isAfter(moment(), 'day') || data.some((row) => moment(row.reportDate).isSame(current, 'day')), [data])
|
const checkIsDateBusy = useCallback((current) => current.isAfter(moment(), 'day') || data.some((row) => moment(row.reportDate).isSame(current, 'day')), [data])
|
||||||
|
|
||||||
@ -111,4 +113,10 @@ export const DailyReport = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default DailyReport
|
export default wrapPrivateComponent(DailyReport, {
|
||||||
|
requirements: [
|
||||||
|
// 'DailyReport.get',
|
||||||
|
],
|
||||||
|
title: 'Суточный рапорт',
|
||||||
|
route: 'daily_report',
|
||||||
|
})
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Button, Tooltip } from 'antd'
|
import { Button, Tooltip } from 'antd'
|
||||||
import { useState, useEffect, memo, useContext } from 'react'
|
import { useState, useEffect, memo } from 'react'
|
||||||
import { FilePdfOutlined, FileTextOutlined } from '@ant-design/icons'
|
import { FilePdfOutlined, FileTextOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { Table, makeDateSorter, makeNumericSorter } from '@components/Table'
|
import { Table, makeDateSorter, makeNumericSorter } from '@components/Table'
|
||||||
import { invokeWebApiWrapperAsync, downloadFile } from '@components/factory'
|
import { invokeWebApiWrapperAsync, downloadFile } from '@components/factory'
|
||||||
@ -60,18 +60,20 @@ export const Reports = memo(() => {
|
|||||||
const [reports, setReports] = useState([])
|
const [reports, setReports] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const reportsResponse = await ReportService.getAllReportsNamesByWell(idWell)
|
async () => {
|
||||||
const reports = reportsResponse.map(r => ({ ...r, key: r.id ?? r.name ?? r.date }))
|
const reportsResponse = await ReportService.getAllReportsNamesByWell(idWell)
|
||||||
setReports(reports)
|
const reports = reportsResponse.map(r => ({ ...r, key: r.id ?? r.name ?? r.date }))
|
||||||
},
|
setReports(reports)
|
||||||
setShowLoader,
|
},
|
||||||
`Не удалось загрузить список рапортов по скважине "${idWell}"`,
|
setShowLoader,
|
||||||
'Получение списка рапортов'
|
`Не удалось загрузить список рапортов по скважине "${idWell}"`,
|
||||||
), [idWell])
|
'Получение списка рапортов'
|
||||||
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import 'moment/locale/ru'
|
import 'moment/locale/ru'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useState, useEffect, memo, useCallback, useContext } from 'react'
|
import { useState, useEffect, memo, useCallback } from 'react'
|
||||||
import { Radio, Button, Select, notification } from 'antd'
|
import { Radio, Button, Select, notification } from 'antd'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { DateRangeWrapper } from 'components/Table'
|
import { DateRangeWrapper } from 'components/Table'
|
||||||
import { LoaderPortal } from '@components/LoaderPortal'
|
import { LoaderPortal } from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { wrapPrivateComponent } from '@utils'
|
||||||
import { Subscribe } from '@services/signalr'
|
import { Subscribe } from '@services/signalr'
|
||||||
import { ReportService } from '@api'
|
import { ReportService } from '@api'
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ const reportFormats = [
|
|||||||
{ value: 1, label: 'LAS' },
|
{ value: 1, label: 'LAS' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const DiagramReport = memo(() => {
|
const DiagramReport = memo(() => {
|
||||||
const [aviableDateRange, setAviableDateRange] = useState([moment(), moment()])
|
const [aviableDateRange, setAviableDateRange] = useState([moment(), moment()])
|
||||||
const [filterDateRange, setFilterDateRange] = useState([
|
const [filterDateRange, setFilterDateRange] = useState([
|
||||||
moment().subtract(1, 'days').startOf('day'),
|
moment().subtract(1, 'days').startOf('day'),
|
||||||
@ -44,7 +45,7 @@ export const DiagramReport = memo(() => {
|
|||||||
const [pagesCount, setPagesCount] = useState(0)
|
const [pagesCount, setPagesCount] = useState(0)
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const handleReportCreation = useCallback(async () => await invokeWebApiWrapperAsync(
|
const handleReportCreation = useCallback(async () => await invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -88,48 +89,52 @@ export const DiagramReport = memo(() => {
|
|||||||
!current.isBetween(aviableDateRange[0], aviableDateRange[1], 'seconds', '[]')
|
!current.isBetween(aviableDateRange[0], aviableDateRange[1], 'seconds', '[]')
|
||||||
, [aviableDateRange])
|
, [aviableDateRange])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const datesRangeResponse = await ReportService.getReportsDateRange(idWell)
|
async () => {
|
||||||
if (!datesRangeResponse?.from || !datesRangeResponse.to)
|
const datesRangeResponse = await ReportService.getReportsDateRange(idWell)
|
||||||
throw new Error('Формат ответа неверный!')
|
if (!datesRangeResponse?.from || !datesRangeResponse.to)
|
||||||
|
throw new Error('Формат ответа неверный!')
|
||||||
|
|
||||||
const datesRange = [
|
const datesRange = [
|
||||||
moment(datesRangeResponse.from),
|
moment(datesRangeResponse.from),
|
||||||
moment(datesRangeResponse.to),
|
moment(datesRangeResponse.to),
|
||||||
]
|
]
|
||||||
setAviableDateRange(datesRange)
|
setAviableDateRange(datesRange)
|
||||||
|
|
||||||
const from = moment.max(moment(datesRange[1]).subtract(1, 'days'), datesRange[0])
|
const from = moment.max(moment(datesRange[1]).subtract(1, 'days'), datesRange[0])
|
||||||
|
|
||||||
setFilterDateRange([
|
setFilterDateRange([
|
||||||
from.startOf('day'),
|
from.startOf('day'),
|
||||||
moment(datesRange[1]).startOf('day'),
|
moment(datesRange[1]).startOf('day'),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось получить диапозон дат рапортов для скважины "${idWell}"`,
|
`Не удалось получить диапозон дат рапортов для скважины "${idWell}"`,
|
||||||
'Получение диапозона дат рапортов'
|
'Получение диапозона дат рапортов'
|
||||||
), [idWell])
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
if (filterDateRange?.length !== 2) return
|
async () => {
|
||||||
const pagesCount = await ReportService.getReportSize(
|
if (filterDateRange?.length !== 2) return
|
||||||
idWell,
|
const pagesCount = await ReportService.getReportSize(
|
||||||
step,
|
idWell,
|
||||||
format,
|
step,
|
||||||
filterDateRange[0].toISOString(),
|
format,
|
||||||
filterDateRange[1].toISOString()
|
filterDateRange[0].toISOString(),
|
||||||
)
|
filterDateRange[1].toISOString()
|
||||||
setPagesCount(pagesCount)
|
)
|
||||||
},
|
setPagesCount(pagesCount)
|
||||||
setShowLoader,
|
},
|
||||||
`Не удалось получить предварительные параметры отчета c
|
setShowLoader,
|
||||||
${filterDateRange[0].format(dateTimeFormat)} по
|
`Не удалось получить предварительные параметры отчета c
|
||||||
${filterDateRange[1].format(dateTimeFormat)}`,
|
${filterDateRange[0].format(dateTimeFormat)} по
|
||||||
'Получение размера рапортов'
|
${filterDateRange[1].format(dateTimeFormat)}`,
|
||||||
), [filterDateRange, step, format, idWell])
|
'Получение размера рапортов'
|
||||||
|
)
|
||||||
|
}, [filterDateRange, step, format, idWell])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -174,4 +179,8 @@ export const DiagramReport = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default DiagramReport
|
export default wrapPrivateComponent(DiagramReport, {
|
||||||
|
requirements: [ 'Report.get' ],
|
||||||
|
title: 'Диаграмма',
|
||||||
|
route: 'diagram_report',
|
||||||
|
})
|
||||||
|
@ -1,36 +1,38 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||||
import { memo, useContext, useMemo } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
import { FilePdfOutlined } from '@ant-design/icons'
|
import { FilePdfOutlined } from '@ant-design/icons'
|
||||||
import { Layout } from 'antd'
|
import { Layout } from 'antd'
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
import { RootPathContext, useRootPath } from '@asb/context'
|
||||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
import { PrivateMenu } from '@components/Private'
|
||||||
|
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||||
|
|
||||||
import DailyReport from './DailyReport'
|
import DailyReport from './DailyReport'
|
||||||
import DiagramReport from './DiagramReport'
|
import DiagramReport from './DiagramReport'
|
||||||
|
|
||||||
const { Content } = Layout
|
const { Content } = Layout
|
||||||
|
|
||||||
export const Reports = memo(() => {
|
const Reports = memo(() => {
|
||||||
const { tab } = useParams()
|
const root = useRootPath()
|
||||||
|
|
||||||
const root = useContext(RootPathContext)
|
|
||||||
const rootPath = useMemo(() => `${root}/reports`, [root])
|
const rootPath = useMemo(() => `${root}/reports`, [root])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootPathContext.Provider value={rootPath}>
|
<RootPathContext.Provider value={rootPath}>
|
||||||
<Layout>
|
<Layout>
|
||||||
<PrivateMenu mode={'horizontal'} selectable={true} selectedKeys={[tab]} className={'well_menu'}>
|
<PrivateMenu className={'well_menu'}>
|
||||||
<PrivateMenu.Link key={'diagram_report'} icon={<FilePdfOutlined />} title={'Диаграмма'}/>
|
<PrivateMenu.Link content={DiagramReport} icon={<FilePdfOutlined />} />
|
||||||
<PrivateMenu.Link key={'daily_report'} title={'Суточный рапорт'} />
|
<PrivateMenu.Link content={DailyReport} />
|
||||||
</PrivateMenu>
|
</PrivateMenu>
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Content className={'site-layout-background'}>
|
<Content className={'site-layout-background'}>
|
||||||
<PrivateSwitch elseRedirect={['diagram_report', 'daily_report']}>
|
<Routes>
|
||||||
<DiagramReport key={'diagram_report'} />
|
<Route index element={<Navigate to={'diagram_report'} replace />} />
|
||||||
<DailyReport key={'daily_report'} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
</PrivateSwitch>
|
|
||||||
|
<Route path={DiagramReport.route} element={<DiagramReport />} />
|
||||||
|
<Route path={DailyReport.route} element={<DailyReport />} />
|
||||||
|
</Routes>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
@ -38,4 +40,9 @@ export const Reports = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Reports
|
export default wrapPrivateComponent(Reports, {
|
||||||
|
requirements: [],
|
||||||
|
title: 'Рапорта',
|
||||||
|
route: 'reports/*',
|
||||||
|
key: 'reports',
|
||||||
|
})
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { useState, useEffect, memo, useCallback, useContext } from 'react'
|
import { useState, useEffect, memo, useCallback } from 'react'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { Flex } from '@components/Grid'
|
import { Flex } from '@components/Grid'
|
||||||
|
import { CopyUrlButton } from '@components/CopyUrl'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { DatePickerWrapper, makeDateSorter } from '@components/Table'
|
import { DatePickerWrapper, makeDateSorter } from '@components/Table'
|
||||||
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||||
|
import { range, wrapPrivateComponent } from '@utils'
|
||||||
import { TelemetryDataSaubService } from '@api'
|
import { TelemetryDataSaubService } from '@api'
|
||||||
import { range } from '@utils'
|
|
||||||
|
|
||||||
import { normalizeData } from '../TelemetryView'
|
import { normalizeData } from '../TelemetryView'
|
||||||
import { ArchiveDisplay, cutData } from './ArchiveDisplay'
|
import { ArchiveDisplay, cutData } from './ArchiveDisplay'
|
||||||
@ -56,15 +58,20 @@ const getLoadingInterval = (loaded, startDate, interval) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Archive = memo(() => {
|
const Archive = memo(() => {
|
||||||
const [dataSaub, setDataSaub] = useState([])
|
const [dataSaub, setDataSaub] = useState([])
|
||||||
const [dateLimit, setDateLimit] = useState({ from: 0, to: new Date() })
|
const [dateLimit, setDateLimit] = useState({ from: 0, to: new Date() })
|
||||||
const [chartInterval, setChartInterval] = useState(parseInt(defaultPeriod) * 1000)
|
|
||||||
const [startDate, setStartDate] = useState(new Date(Date.now() - chartInterval))
|
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [loaded, setLoaded] = useState(null)
|
const [loaded, setLoaded] = useState(null)
|
||||||
|
|
||||||
|
const idWell = useIdWell()
|
||||||
|
const [search, setSearchParams] = useSearchParams()
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const getInitialRange = useCallback(() => parseInt(search.get('range') ?? defaultPeriod) * 1000, [search])
|
||||||
|
const getInitialDate = useCallback(() => new Date(search.get('start') ?? (Date.now() - chartInterval)), [search])
|
||||||
|
|
||||||
|
const [chartInterval, setChartInterval] = useState(getInitialRange)
|
||||||
|
const [startDate, setStartDate] = useState(getInitialDate)
|
||||||
|
|
||||||
const onGraphWheel = useCallback((e) => {
|
const onGraphWheel = useCallback((e) => {
|
||||||
if (loaded && dateLimit.from && dateLimit.to) {
|
if (loaded && dateLimit.from && dateLimit.to) {
|
||||||
@ -102,25 +109,34 @@ export const Archive = memo(() => {
|
|||||||
})
|
})
|
||||||
}), [dateLimit])
|
}), [dateLimit])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
const params = {}
|
||||||
let dates = await TelemetryDataSaubService.getDataDatesRange(idWell)
|
if (startDate)
|
||||||
dates = {
|
params.start = startDate.toISOString()
|
||||||
from: new Date(dates?.from ?? 0),
|
if (chartInterval)
|
||||||
to: new Date(dates?.to ?? 0)
|
params.range = chartInterval / 1000
|
||||||
}
|
setSearchParams(params)
|
||||||
setDateLimit(dates)
|
}, [startDate, chartInterval])
|
||||||
setStartDate(new Date(Math.max(dates.from, +dates.to - chartInterval)))
|
|
||||||
},
|
|
||||||
setShowLoader,
|
|
||||||
`Не удалось загрузить диапозон телеметрии для скважины "${idWell}"`,
|
|
||||||
'Загрузка диапозона телеметрии'
|
|
||||||
), [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setStartDate((startDate) => new Date(Math.min(Date.now() - chartInterval, startDate)))
|
invokeWebApiWrapperAsync(
|
||||||
setDataSaub([])
|
async () => {
|
||||||
}, [chartInterval])
|
let dates = await TelemetryDataSaubService.getDataDatesRange(idWell)
|
||||||
|
dates = {
|
||||||
|
from: new Date(dates?.from ?? 0),
|
||||||
|
to: new Date(dates?.to ?? 0)
|
||||||
|
}
|
||||||
|
setDateLimit(dates)
|
||||||
|
},
|
||||||
|
setShowLoader,
|
||||||
|
`Не удалось загрузить диапозон телеметрии для скважины "${idWell}"`,
|
||||||
|
'Загрузка диапозона телеметрии'
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setStartDate((prev) => new Date(Math.max(dateLimit.from, Math.min(+prev, +dateLimit.to - chartInterval))))
|
||||||
|
}, [chartInterval, dateLimit])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showLoader) return
|
if (showLoader) return
|
||||||
@ -150,6 +166,11 @@ export const Archive = memo(() => {
|
|||||||
)
|
)
|
||||||
}, [idWell, chartInterval, loaded, startDate])
|
}, [idWell, chartInterval, loaded, startDate])
|
||||||
|
|
||||||
|
const onRangeChange = useCallback((value) => {
|
||||||
|
setChartInterval(value * 1000)
|
||||||
|
setDataSaub([])
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex style={{margin: '8px 8px 0'}}>
|
<Flex style={{margin: '8px 8px 0'}}>
|
||||||
@ -164,8 +185,9 @@ export const Archive = memo(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ marginLeft: '1rem' }}>
|
<div style={{ marginLeft: '1rem' }}>
|
||||||
Период:
|
Период:
|
||||||
<PeriodPicker onChange={(val) => setChartInterval(val * 1000)} />
|
<PeriodPicker value={chartInterval / 1000} onChange={onRangeChange} />
|
||||||
</div>
|
</div>
|
||||||
|
<CopyUrlButton style={{ marginLeft: '1rem' }} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
<ArchiveDisplay
|
<ArchiveDisplay
|
||||||
@ -179,4 +201,8 @@ export const Archive = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Archive
|
export default wrapPrivateComponent(Archive, {
|
||||||
|
requirements: ['TelemetryDataSaub.get'],
|
||||||
|
title: 'Архив',
|
||||||
|
route: 'archive',
|
||||||
|
})
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { memo, useCallback, useContext, useEffect, useMemo, useReducer, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||||
import { useHistory, useParams } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { CloseOutlined } from '@ant-design/icons'
|
import { CloseOutlined } from '@ant-design/icons'
|
||||||
import { Button, Menu, Popconfirm } from 'antd'
|
import { Button, Menu, Popconfirm } from 'antd'
|
||||||
|
|
||||||
import { IdWellContext, RootPathContext } from '@asb/context'
|
import { useIdWell, useRootPath } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { BaseWidget, WidgetSettingsWindow } from '@components/widgets'
|
import { BaseWidget, WidgetSettingsWindow } from '@components/widgets'
|
||||||
import { getJSON, setJSON } from '@utils/storage'
|
import { arrayOrDefault, wrapPrivateComponent, getJSON, setJSON, getTabname } from '@utils'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
import Subscribe from '@services/signalr'
|
import Subscribe from '@services/signalr'
|
||||||
import {
|
import {
|
||||||
WitsInfoService,
|
WitsInfoService,
|
||||||
@ -88,7 +87,7 @@ const groupsReducer = (groups, action) => {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case 'add_widget':
|
case 'add_widget':
|
||||||
if (groupIdx >= 0)
|
if (groupIdx >= 0 && widgetIdx < 0)
|
||||||
newGroups[groupIdx].widgets.push(value)
|
newGroups[groupIdx].widgets.push(value)
|
||||||
break
|
break
|
||||||
case 'edit_widget':
|
case 'edit_widget':
|
||||||
@ -106,21 +105,21 @@ const groupsReducer = (groups, action) => {
|
|||||||
return newGroups
|
return newGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DashboardNNB = memo(() => {
|
const DashboardNNB = memo(({ enableEditing = false }) => {
|
||||||
const [groups, dispatchGroups] = useReducer(groupsReducer, [])
|
const [groups, dispatchGroups] = useReducer(groupsReducer, [])
|
||||||
const [witsInfo, setWitsInfo] = useState([])
|
const [witsInfo, setWitsInfo] = useState([])
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [selectedSettings, setSelectedSettings] = useState(null)
|
const [selectedSettings, setSelectedSettings] = useState(null)
|
||||||
const [values, setValues] = useState({})
|
const [values, setValues] = useState({})
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
const root = useContext(RootPathContext)
|
const root = useRootPath()
|
||||||
const rootPath = useMemo(() => `${root}/dashboard_nnb`, [root])
|
const rootPath = useMemo(() => `${root}/dashboard_nnb`, [root])
|
||||||
const history = useHistory()
|
const navigate = useNavigate()
|
||||||
const { tab: selectedGroup } = useParams()
|
const selectedGroup = getTabname()
|
||||||
|
|
||||||
if (!selectedGroup && groups?.length > 0)
|
if (!selectedGroup && groups?.length > 0)
|
||||||
history.push(`${rootPath}/${groups[0].id}`)
|
navigate(`${rootPath}/${groups[0].id}`)
|
||||||
|
|
||||||
|
|
||||||
const group = useMemo(() => ({
|
const group = useMemo(() => ({
|
||||||
@ -128,16 +127,18 @@ export const DashboardNNB = memo(() => {
|
|||||||
...groups.find(({ id }) => `${id}` === selectedGroup),
|
...groups.find(({ id }) => `${id}` === selectedGroup),
|
||||||
}), [groups, selectedGroup])
|
}), [groups, selectedGroup])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const info = await getWitsInfo()
|
async () => {
|
||||||
setWitsInfo(info)
|
const info = await getWitsInfo()
|
||||||
dispatchGroups({ type: 'init', witsInfo: info })
|
setWitsInfo(info)
|
||||||
},
|
dispatchGroups({ type: 'init', witsInfo: info })
|
||||||
setIsLoading,
|
},
|
||||||
'Не удалось загрузить информацию о параметрах ННБ',
|
setIsLoading,
|
||||||
'Получение информации о параметрах ННБ'
|
'Не удалось загрузить информацию о параметрах ННБ',
|
||||||
), [])
|
'Получение информации о параметрах ННБ'
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleData = useCallback((data, recordId) => {
|
const handleData = useCallback((data, recordId) => {
|
||||||
const mergedData = data.reduce((out, record) => ({ ...out, ...record }), {})
|
const mergedData = data.reduce((out, record) => ({ ...out, ...record }), {})
|
||||||
@ -175,8 +176,8 @@ export const DashboardNNB = memo(() => {
|
|||||||
|
|
||||||
const removeGroup = useCallback((id) => {
|
const removeGroup = useCallback((id) => {
|
||||||
dispatchGroups({ type: 'remove_group', groupId: `${id}` })
|
dispatchGroups({ type: 'remove_group', groupId: `${id}` })
|
||||||
if (id === selectedGroup) history.push(`${rootPath}`)
|
if (id === selectedGroup) navigate(`${rootPath}`)
|
||||||
}, [rootPath, history, selectedGroup])
|
}, [rootPath, navigate, selectedGroup])
|
||||||
|
|
||||||
const addWidget = useCallback((settings) => dispatchGroups({
|
const addWidget = useCallback((settings) => dispatchGroups({
|
||||||
type: 'add_widget',
|
type: 'add_widget',
|
||||||
@ -209,17 +210,21 @@ export const DashboardNNB = memo(() => {
|
|||||||
selectable={true}
|
selectable={true}
|
||||||
selectedKeys={[selectedGroup]}
|
selectedKeys={[selectedGroup]}
|
||||||
>
|
>
|
||||||
<Menu.Item key={'add_group'}>
|
{enableEditing && (
|
||||||
<AddGroupWindow addGroup={addGroup} />
|
<>
|
||||||
</Menu.Item>
|
<Menu.Item key={'add_group'}>
|
||||||
{group?.editable && (
|
<AddGroupWindow addGroup={addGroup} />
|
||||||
<Menu.Item key={'add_widget'}>
|
</Menu.Item>
|
||||||
<AddWidgetWindow witsInfo={witsInfo} onAdded={addWidget} />
|
{group?.editable && (
|
||||||
</Menu.Item>
|
<Menu.Item key={'add_widget'}>
|
||||||
|
<AddWidgetWindow witsInfo={witsInfo} onAdded={addWidget} />
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{groups.map(({ id, name, editable }) => (
|
{groups.map(({ id, name, editable }) => (
|
||||||
<Menu.Item key={id}>
|
<Menu.Item key={id}>
|
||||||
{editable && (
|
{enableEditing && editable && (
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={'Вы уверены, что хотите удалить группу, это действие невозможно отменить?'}
|
title={'Вы уверены, что хотите удалить группу, это действие невозможно отменить?'}
|
||||||
onConfirm={() => removeGroup(id)}
|
onConfirm={() => removeGroup(id)}
|
||||||
@ -229,7 +234,7 @@ export const DashboardNNB = memo(() => {
|
|||||||
<Button type={'link'} icon={<CloseOutlined />} />
|
<Button type={'link'} icon={<CloseOutlined />} />
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
)}
|
)}
|
||||||
<Button type={'text'} style={{ paddingLeft: 0 }} onClick={() => history.push(`${rootPath}/${id}`)}>{name}</Button>
|
<Button type={'text'} style={{ paddingLeft: 0 }} onClick={() => navigate(`${rootPath}/${id}`)}>{name}</Button>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
))}
|
))}
|
||||||
</Menu>
|
</Menu>
|
||||||
@ -238,7 +243,7 @@ export const DashboardNNB = memo(() => {
|
|||||||
<BaseWidget
|
<BaseWidget
|
||||||
key={widget.id}
|
key={widget.id}
|
||||||
// onEdit={group.editable && setSelectedSettings} // TODO: Доделать редактирование
|
// onEdit={group.editable && setSelectedSettings} // TODO: Доделать редактирование
|
||||||
onRemove={group.editable && removeWidget}
|
onRemove={ enableEditing && group.editable && removeWidget}
|
||||||
{...widget}
|
{...widget}
|
||||||
value={values[widget.recordId]?.[widget.witsId]}
|
value={values[widget.recordId]?.[widget.witsId]}
|
||||||
/>
|
/>
|
||||||
@ -255,4 +260,17 @@ export const DashboardNNB = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default DashboardNNB
|
export default wrapPrivateComponent(DashboardNNB, {
|
||||||
|
requirements: [
|
||||||
|
// 'WitsInfo',
|
||||||
|
// 'WitsRecord1',
|
||||||
|
// 'WitsRecord7',
|
||||||
|
// 'WitsRecord8',
|
||||||
|
// 'WitsRecord50',
|
||||||
|
// 'WitsRecord60',
|
||||||
|
// 'WitsRecord61',
|
||||||
|
],
|
||||||
|
title: 'ННБ',
|
||||||
|
route: 'dashboard_nnb/*',
|
||||||
|
key: 'dashboard_nnb',
|
||||||
|
})
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import { useState, useEffect, memo, useCallback, useContext } from 'react'
|
|
||||||
import { Table, Select, DatePicker, Input } from 'antd'
|
import { Table, Select, DatePicker, Input } from 'antd'
|
||||||
|
import { useState, useEffect, memo, useCallback } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { makeColumn, makeDateColumn, makeNumericSorter } from '@components/Table'
|
import { makeColumn, makeDateColumn, makeNumericSorter } from '@components/Table'
|
||||||
|
import { wrapPrivateComponent } from '@utils'
|
||||||
import { MessageService } from '@api'
|
import { MessageService } from '@api'
|
||||||
|
|
||||||
|
|
||||||
@ -47,7 +50,7 @@ const filterOptions = [
|
|||||||
const children = filterOptions.map((line) => <Option key={line.value}>{line.label}</Option>)
|
const children = filterOptions.map((line) => <Option key={line.value}>{line.label}</Option>)
|
||||||
|
|
||||||
// Данные для таблицы
|
// Данные для таблицы
|
||||||
export const Messages = memo(() => {
|
const Messages = memo(() => {
|
||||||
const [messages, setMessages] = useState([])
|
const [messages, setMessages] = useState([])
|
||||||
const [pagination, setPagination] = useState(null)
|
const [pagination, setPagination] = useState(null)
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
@ -56,32 +59,39 @@ export const Messages = memo(() => {
|
|||||||
const [searchString, setSearchString] = useState('')
|
const [searchString, setSearchString] = useState('')
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const onChangeSearchString = useCallback((message) => setSearchString(message.length > 2 ? message : ''), [])
|
const onChangeSearchString = useCallback((message) => setSearchString(message.length > 2 ? message : ''), [])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const [begin, end] = range?.length > 1 ? [range[0].toISOString(), range[1].toISOString()] : [null, null]
|
async () => {
|
||||||
const skip = (page - 1) * pageSize
|
const [begin, end] = range?.length > 1 ? [range[0].toISOString(), range[1].toISOString()] : [null, null]
|
||||||
const paginatedMessages = await MessageService.getMessages(idWell, skip, pageSize, categories, begin, end, searchString)
|
const skip = (page - 1) * pageSize
|
||||||
if (!paginatedMessages) return
|
const paginatedMessages = await MessageService.getMessages(idWell, skip, pageSize, categories, begin, end, searchString)
|
||||||
|
if (!paginatedMessages) return
|
||||||
|
|
||||||
setMessages(paginatedMessages.items.map(m => ({
|
setMessages(paginatedMessages.items.map(m => ({
|
||||||
key: m.id,
|
key: m.id,
|
||||||
categoryids: categoryDictionary[m.categoryId],
|
categoryids: categoryDictionary[m.categoryId],
|
||||||
begin: m.date,
|
begin: m.date,
|
||||||
...m
|
...m
|
||||||
})))
|
})))
|
||||||
setPagination({
|
setPagination({
|
||||||
total: paginatedMessages.count,
|
total: paginatedMessages.count,
|
||||||
current: Math.floor(paginatedMessages.skip / pageSize),
|
current: Math.floor(paginatedMessages.skip / pageSize),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось загрузить сообщения по скважине "${idWell}"`,
|
`Не удалось загрузить сообщения по скважине "${idWell}"`,
|
||||||
'Полученик списка сообщений'
|
'Полученик списка сообщений'
|
||||||
), [idWell, page, categories, range, searchString])
|
)
|
||||||
|
}, [idWell, page, categories, range, searchString])
|
||||||
|
|
||||||
|
const onMessageRow = useCallback((record) => ({
|
||||||
|
onClick: () => navigate(`/well/${idWell}/telemetry/archive?range=1800&start=${moment(record?.date).subtract(3, 'minute').local().toISOString()}`)
|
||||||
|
}), [idWell, navigate])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -118,10 +128,15 @@ export const Messages = memo(() => {
|
|||||||
}}
|
}}
|
||||||
rowKey={(record) => record.id}
|
rowKey={(record) => record.id}
|
||||||
tableName={'messages'}
|
tableName={'messages'}
|
||||||
|
onRow={onMessageRow}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</LoaderPortal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Messages
|
export default wrapPrivateComponent(Messages, {
|
||||||
|
requirements: ['Message.get'],
|
||||||
|
title: 'Сообщения',
|
||||||
|
route: 'messages',
|
||||||
|
})
|
||||||
|
@ -2,7 +2,7 @@ import { memo, useCallback, useMemo, useState } from 'react'
|
|||||||
import { Button, Modal } from 'antd'
|
import { Button, Modal } from 'antd'
|
||||||
|
|
||||||
import { EditableTable, makeActionHandler, makeTextColumn } from '@components/Table'
|
import { EditableTable, makeActionHandler, makeTextColumn } from '@components/Table'
|
||||||
import { getPermissions } from '@utils/permissions'
|
import { getPermissions } from '@utils'
|
||||||
import { DrillerService } from '@api'
|
import { DrillerService } from '@api'
|
||||||
|
|
||||||
const reqRule = [{ message: 'Обязательное поле!', required: true }]
|
const reqRule = [{ message: 'Обязательное поле!', required: true }]
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
makeSelectColumn,
|
makeSelectColumn,
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { getPermissions } from '@utils/permissions'
|
import { getPermissions } from '@utils'
|
||||||
import { ScheduleService } from '@api'
|
import { ScheduleService } from '@api'
|
||||||
|
|
||||||
const reqRule = [{ message: 'Обязательное поле!', required: true }]
|
const reqRule = [{ message: 'Обязательное поле!', required: true }]
|
||||||
|
@ -29,8 +29,13 @@ export const OperationsChart = memo(({ data, yDomain, width, height, bottom = 30
|
|||||||
|
|
||||||
const lines = useMemo(() => d.map(d => ({ x: x(d.date), y: y(d.value) })), [d, x, y]) // Получаем массив координат линий
|
const lines = useMemo(() => d.map(d => ({ x: x(d.date), y: y(d.value) })), [d, x, y]) // Получаем массив координат линий
|
||||||
|
|
||||||
useEffect(() => d3.select(axisX.current).call(d3.axisBottom(x)), [axisX, x]) // Рисуем ось X
|
useEffect(() => {
|
||||||
useEffect(() => d3.select(axisY.current).call(d3.axisLeft(y)), [axisY, y]) // Рисуем ось Y
|
d3.select(axisX.current).call(d3.axisBottom(x))
|
||||||
|
}, [axisX, x]) // Рисуем ось X
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
d3.select(axisY.current).call(d3.axisLeft(y))
|
||||||
|
}, [axisY, y]) // Рисуем ось Y
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'page-left'} ref={setRef}>
|
<div className={'page-left'} ref={setRef}>
|
||||||
|
@ -7,8 +7,7 @@ import LoaderPortal from '@components/LoaderPortal'
|
|||||||
import { DateRangeWrapper } from '@components/Table'
|
import { DateRangeWrapper } from '@components/Table'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { DetectedOperationService, DrillerService, TelemetryDataSaubService } from '@api'
|
import { DetectedOperationService, DrillerService, TelemetryDataSaubService } from '@api'
|
||||||
import { getPermissions } from '@utils/permissions'
|
import { getPermissions, arrayOrDefault, range, wrapPrivateComponent } from '@utils'
|
||||||
import { arrayOrDefault, range } from '@utils'
|
|
||||||
|
|
||||||
import DrillerList from './DrillerList'
|
import DrillerList from './DrillerList'
|
||||||
import DrillerSchedule from './DrillerSchedule'
|
import DrillerSchedule from './DrillerSchedule'
|
||||||
@ -17,7 +16,7 @@ import OperationsTable from './OperationsTable'
|
|||||||
|
|
||||||
import '@styles/detected_operations.less'
|
import '@styles/detected_operations.less'
|
||||||
|
|
||||||
export const Operations = memo(() => {
|
const Operations = memo(() => {
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [dateRange, setDateRange] = useState([])
|
const [dateRange, setDateRange] = useState([])
|
||||||
const [yDomain, setYDomain] = useState(20)
|
const [yDomain, setYDomain] = useState(20)
|
||||||
@ -53,30 +52,34 @@ export const Operations = memo(() => {
|
|||||||
updateDrillers()
|
updateDrillers()
|
||||||
}, [updateDrillers, permissions])
|
}, [updateDrillers, permissions])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const dates = await TelemetryDataSaubService.getDataDatesRange(idWell)
|
async () => {
|
||||||
if (dates) {
|
const dates = await TelemetryDataSaubService.getDataDatesRange(idWell)
|
||||||
const dt = [moment(dates.from), moment(dates.to)]
|
if (dates) {
|
||||||
setDateRange(dt)
|
const dt = [moment(dates.from), moment(dates.to)]
|
||||||
setDates(dt)
|
setDateRange(dt)
|
||||||
}
|
setDates(dt)
|
||||||
},
|
}
|
||||||
setIsLoading,
|
},
|
||||||
'Не удалось загрузить диапазон доступных дат',
|
setIsLoading,
|
||||||
'Получение дапазона доступних дат',
|
'Не удалось загрузить диапазон доступных дат',
|
||||||
), [idWell])
|
'Получение дапазона доступних дат',
|
||||||
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
if (!dates) return
|
async () => {
|
||||||
const data = await DetectedOperationService.get(idWell, undefined, dates[0].toISOString(), dates[1].toISOString())
|
if (!dates) return
|
||||||
setData(data)
|
const data = await DetectedOperationService.get(idWell, undefined, dates[0].toISOString(), dates[1].toISOString())
|
||||||
},
|
setData(data)
|
||||||
setIsLoading,
|
},
|
||||||
'Не удалось загрузить список определённых операций',
|
setIsLoading,
|
||||||
'Получение списка определённых операций',
|
'Не удалось загрузить список определённых операций',
|
||||||
), [idWell, dates])
|
'Получение списка определённых операций',
|
||||||
|
)
|
||||||
|
}, [idWell, dates])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'container detected-operations-page'}>
|
<div className={'container detected-operations-page'}>
|
||||||
@ -116,4 +119,11 @@ export const Operations = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Operations
|
export default wrapPrivateComponent(Operations, {
|
||||||
|
requirements: [
|
||||||
|
// 'DetectedOperation.get',
|
||||||
|
'TelemetryDataSaub.get',
|
||||||
|
],
|
||||||
|
title: 'Операции',
|
||||||
|
route: 'operations',
|
||||||
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Table } from 'antd'
|
import { Table } from 'antd'
|
||||||
import { useState, useEffect, useCallback, memo, useContext } from 'react'
|
import { useState, useEffect, useCallback, memo } from 'react'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { Subscribe } from '@services/signalr'
|
import { Subscribe } from '@services/signalr'
|
||||||
@ -15,7 +15,7 @@ export const ActiveMessagesOnline = memo(() => {
|
|||||||
const [messages, setMessages] = useState([])
|
const [messages, setMessages] = useState([])
|
||||||
const [loader, setLoader] = useState(false)
|
const [loader, setLoader] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const handleReceiveMessages = useCallback((messages) => {
|
const handleReceiveMessages = useCallback((messages) => {
|
||||||
if (messages)
|
if (messages)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { memo, useCallback, useContext, useMemo, useState } from 'react'
|
import { memo, useCallback, useMemo, useState } from 'react'
|
||||||
import { Select, Modal, Input, InputNumber } from 'antd'
|
import { Select, Modal, Input, InputNumber } from 'antd'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { Grid, GridItem } from '@components/Grid'
|
import { Grid, GridItem } from '@components/Grid'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
@ -16,7 +16,7 @@ export const SetpointSender = memo(({ onClose, visible, setpointNames }) => {
|
|||||||
const [setpoints, setSetpoints] = useState([])
|
const [setpoints, setSetpoints] = useState([])
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const addingColumns = useMemo(() => [
|
const addingColumns = useMemo(() => [
|
||||||
{
|
{
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import { Button, Modal } from 'antd'
|
import { Button, Modal } from 'antd'
|
||||||
import { useState, useEffect, memo, useCallback, useMemo, useContext } from 'react'
|
import { useState, useEffect, memo, useCallback, useMemo } from 'react'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { Table } from '@components/Table'
|
import { Table } from '@components/Table'
|
||||||
import { UserView } from '@components/views'
|
import { UserView } from '@components/views'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission, makeStringCutter, formatDate } from '@utils'
|
||||||
import { makeStringCutter } from '@utils/string'
|
|
||||||
import { formatDate } from '@utils'
|
|
||||||
import { SetpointsService } from '@api'
|
import { SetpointsService } from '@api'
|
||||||
|
|
||||||
import SetpointSender from './SetpointSender'
|
import SetpointSender from './SetpointSender'
|
||||||
@ -23,22 +21,24 @@ export const Setpoints = memo(({ ...other }) => {
|
|||||||
const [selected, setSelected] = useState(null)
|
const [selected, setSelected] = useState(null)
|
||||||
const [setpointNames, setSetpointNames] = useState([])
|
const [setpointNames, setSetpointNames] = useState([])
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const names = await SetpointsService.getSetpointsNamesByIdWell(idWell)
|
async () => {
|
||||||
if (!names) throw Error('Setpoints not found')
|
const names = await SetpointsService.getSetpointsNamesByIdWell(idWell)
|
||||||
setSetpointNames(names.map(spn => ({
|
if (!names) throw Error('Setpoints not found')
|
||||||
label: spn.displayName,
|
setSetpointNames(names.map(spn => ({
|
||||||
value: spn.name,
|
label: spn.displayName,
|
||||||
tooltip: spn.comment
|
value: spn.name,
|
||||||
})))
|
tooltip: spn.comment
|
||||||
},
|
})))
|
||||||
setIsLoading,
|
},
|
||||||
`Не удалось загрузить список имён уставок по скважине "${idWell}"`,
|
setIsLoading,
|
||||||
'Получение списка имён уставок'
|
`Не удалось загрузить список имён уставок по скважине "${idWell}"`,
|
||||||
), [idWell])
|
'Получение списка имён уставок'
|
||||||
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
const showMore = useCallback((id) => {
|
const showMore = useCallback((id) => {
|
||||||
const selected = setpoints.find((sp) => sp.id === id)
|
const selected = setpoints.find((sp) => sp.id === id)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { memo, useCallback, useContext, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { WirelineView } from '@components/views'
|
import { WirelineView } from '@components/views'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { TelemetryWirelineRunOutService } from '@api'
|
import { TelemetryWirelineRunOutService } from '@api'
|
||||||
@ -10,7 +10,7 @@ export const WirelineRunOut = memo(() => {
|
|||||||
const [twro, setTwro] = useState({})
|
const [twro, setTwro] = useState({})
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const update = useCallback(() => invokeWebApiWrapperAsync(
|
const update = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -25,7 +25,9 @@ export const WirelineRunOut = memo(() => {
|
|||||||
if (visible) update()
|
if (visible) update()
|
||||||
}, [update])
|
}, [update])
|
||||||
|
|
||||||
useEffect(update, [update])
|
useEffect(() => {
|
||||||
|
update()
|
||||||
|
}, [update])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WirelineView
|
<WirelineView
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
import { Select } from 'antd'
|
import { Select } from 'antd'
|
||||||
import { useState, useEffect, useCallback, useContext } from 'react'
|
import { useState, useEffect, useCallback, memo } from 'react'
|
||||||
|
|
||||||
|
import { useIdWell } from '@asb/context'
|
||||||
|
import { makeDateSorter } from '@components/Table'
|
||||||
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
|
import { Grid, GridItem, Flex } from '@components/Grid'
|
||||||
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||||
|
import { hasPermission, wrapPrivateComponent } from '@utils'
|
||||||
|
import { Subscribe } from '@services/signalr'
|
||||||
import {
|
import {
|
||||||
DrillFlowChartService,
|
DrillFlowChartService,
|
||||||
OperationStatService,
|
OperationStatService,
|
||||||
@ -8,14 +16,6 @@ import {
|
|||||||
TelemetryDataSpinService,
|
TelemetryDataSpinService,
|
||||||
WellService
|
WellService
|
||||||
} from '@api'
|
} from '@api'
|
||||||
import { IdWellContext } from '@asb/context'
|
|
||||||
import { makeDateSorter } from '@components/Table'
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { Grid, GridItem, Flex } from '@components/Grid'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
|
||||||
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
|
||||||
import { hasPermission } from '@utils/permissions'
|
|
||||||
import { Subscribe } from '@services/signalr'
|
|
||||||
|
|
||||||
import { MonitoringColumn } from './MonitoringColumn'
|
import { MonitoringColumn } from './MonitoringColumn'
|
||||||
import { CustomColumn } from './CustomColumn'
|
import { CustomColumn } from './CustomColumn'
|
||||||
@ -304,7 +304,7 @@ export const normalizeData = (data) => data?.map(item => ({
|
|||||||
blockSpeed: Math.abs(item.blockSpeed)
|
blockSpeed: Math.abs(item.blockSpeed)
|
||||||
})) ?? []
|
})) ?? []
|
||||||
|
|
||||||
export default function TelemetryView() {
|
const TelemetryView = memo(() => {
|
||||||
const [dataSaub, setDataSaub] = useState([])
|
const [dataSaub, setDataSaub] = useState([])
|
||||||
const [dataSpin, setDataSpin] = useState([])
|
const [dataSpin, setDataSpin] = useState([])
|
||||||
const [chartInterval, setChartInterval] = useState(defaultPeriod)
|
const [chartInterval, setChartInterval] = useState(defaultPeriod)
|
||||||
@ -313,7 +313,7 @@ export default function TelemetryView() {
|
|||||||
const [flowChartData, setFlowChartData] = useState([])
|
const [flowChartData, setFlowChartData] = useState([])
|
||||||
const [rop, setRop] = useState(null)
|
const [rop, setRop] = useState(null)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const handleDataSaub = useCallback((data) => {
|
const handleDataSaub = useCallback((data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -347,17 +347,19 @@ export default function TelemetryView() {
|
|||||||
return unsubscribe
|
return unsubscribe
|
||||||
}, [idWell, chartInterval, handleDataSpin, handleDataSaub])
|
}, [idWell, chartInterval, handleDataSpin, handleDataSaub])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const well = await WellService.get(idWell)
|
async () => {
|
||||||
const rop = await OperationStatService.getClusterRopStatByIdWell(idWell)
|
const well = await WellService.get(idWell)
|
||||||
setRop(rop)
|
const rop = await OperationStatService.getClusterRopStatByIdWell(idWell)
|
||||||
setWellData(well ?? {})
|
setRop(rop)
|
||||||
},
|
setWellData(well ?? {})
|
||||||
setShowLoader,
|
},
|
||||||
`Не удалось загрузить данные по скважине "${idWell}"`,
|
setShowLoader,
|
||||||
'Получение данных по скважине'
|
`Не удалось загрузить данные по скважине "${idWell}"`,
|
||||||
), [idWell])
|
'Получение данных по скважине'
|
||||||
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
const onStatusChanged = useCallback((value) => invokeWebApiWrapperAsync(
|
const onStatusChanged = useCallback((value) => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -428,4 +430,16 @@ export default function TelemetryView() {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</LoaderPortal>
|
</LoaderPortal>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
|
export default wrapPrivateComponent(TelemetryView, {
|
||||||
|
requirements: [
|
||||||
|
'DrillFlowChart.get',
|
||||||
|
'OperationStat.get',
|
||||||
|
'TelemetryDataSaub.get',
|
||||||
|
'TelemetryDataSpin.get',
|
||||||
|
'Well.get',
|
||||||
|
],
|
||||||
|
title: 'Мониторинг',
|
||||||
|
route: 'telemetry',
|
||||||
|
})
|
||||||
|
@ -1,46 +1,49 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||||
import { memo, useContext, useMemo } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
import { Layout } from 'antd'
|
import { Layout } from 'antd'
|
||||||
import { AlertOutlined, FundViewOutlined, DatabaseOutlined } from '@ant-design/icons'
|
import { AlertOutlined, FundViewOutlined, DatabaseOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
import { RootPathContext } from '@asb/context'
|
import { RootPathContext, useRootPath } from '@asb/context'
|
||||||
import { PrivateSwitch, PrivateMenu } from '@components/Private'
|
import { PrivateMenu } from '@components/Private'
|
||||||
|
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||||
|
|
||||||
import Archive from './Archive'
|
import Archive from './Archive'
|
||||||
import Messages from './Messages'
|
import Messages from './Messages'
|
||||||
|
import Operations from './Operations'
|
||||||
import DashboardNNB from './DashboardNNB'
|
import DashboardNNB from './DashboardNNB'
|
||||||
import TelemetryView from './TelemetryView'
|
import TelemetryView from './TelemetryView'
|
||||||
|
|
||||||
import '@styles/index.css'
|
import '@styles/index.css'
|
||||||
import Operations from './Operations'
|
|
||||||
|
|
||||||
const { Content } = Layout
|
const { Content } = Layout
|
||||||
|
|
||||||
export const Telemetry = memo(() => {
|
const Telemetry = memo(() => {
|
||||||
const { tab } = useParams()
|
const root = useRootPath()
|
||||||
const root = useContext(RootPathContext)
|
|
||||||
const rootPath = useMemo(() => `${root}/telemetry`, [root])
|
const rootPath = useMemo(() => `${root}/telemetry`, [root])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootPathContext.Provider value={rootPath}>
|
<RootPathContext.Provider value={rootPath}>
|
||||||
<Layout>
|
<Layout>
|
||||||
<PrivateMenu mode={'horizontal'} selectable={true} selectedKeys={[tab]} className={'well_menu'}>
|
<PrivateMenu className={'well_menu'}>
|
||||||
<PrivateMenu.Link key={'monitoring'} icon={<FundViewOutlined />} title={'Мониторинг'}/>
|
<PrivateMenu.Link content={TelemetryView} icon={<FundViewOutlined />} />
|
||||||
<PrivateMenu.Link key={'messages'} icon={<AlertOutlined/>} title={'Сообщения'} />
|
<PrivateMenu.Link content={Messages} icon={<AlertOutlined/>} />
|
||||||
<PrivateMenu.Link key={'archive'} icon={<DatabaseOutlined />} title={'Архив'} />
|
<PrivateMenu.Link content={Archive} icon={<DatabaseOutlined />} />
|
||||||
<PrivateMenu.Link key={'dashboard_nnb'} title={'ННБ'} />
|
<PrivateMenu.Link content={DashboardNNB} />
|
||||||
<PrivateMenu.Link key={'operations'} title={'Операции'} />
|
<PrivateMenu.Link content={Operations} />
|
||||||
</PrivateMenu>
|
</PrivateMenu>
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Content className={'site-layout-background'}>
|
<Content className={'site-layout-background'}>
|
||||||
<PrivateSwitch elseRedirect={['monitoring', 'messages', 'archive', 'dashboard_nnb']}>
|
<Routes>
|
||||||
<TelemetryView key={'monitoring'} />
|
<Route index element={<Navigate to={TelemetryView.route} replace />} />
|
||||||
<Messages key={'messages'} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
<Archive key={'archive'} />
|
|
||||||
<DashboardNNB key={'dashboard_nnb/:tab?'} />
|
<Route path={TelemetryView.route} element={<TelemetryView />} />
|
||||||
<Operations key={'operations'}/>
|
<Route path={Messages.route} element={<Messages />} />
|
||||||
</PrivateSwitch>
|
<Route path={Archive.route} element={<Archive />} />
|
||||||
|
<Route path={DashboardNNB.route} element={<DashboardNNB />} />
|
||||||
|
<Route path={Operations.route} element={<Operations />} />
|
||||||
|
</Routes>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
@ -48,4 +51,10 @@ export const Telemetry = memo(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Telemetry
|
export default wrapPrivateComponent(Telemetry, {
|
||||||
|
requirements: [],
|
||||||
|
icon: <FundViewOutlined />,
|
||||||
|
title: 'Телеметрия',
|
||||||
|
route: 'telemetry/*',
|
||||||
|
key: 'telemetry',
|
||||||
|
})
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { memo, useContext, useMemo } from 'react'
|
|
||||||
import {
|
import {
|
||||||
FolderOutlined,
|
FolderOutlined,
|
||||||
FundViewOutlined,
|
|
||||||
FilePdfOutlined,
|
FilePdfOutlined,
|
||||||
ExperimentOutlined,
|
ExperimentOutlined,
|
||||||
DeploymentUnitOutlined,
|
DeploymentUnitOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { Layout } from 'antd'
|
import { Layout } from 'antd'
|
||||||
import { useParams } from 'react-router-dom'
|
import { memo, useMemo } from 'react'
|
||||||
|
import { Navigate, Route, Routes, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { IdWellContext, RootPathContext } from '@asb/context'
|
import { IdWellContext, RootPathContext, useRootPath } from '@asb/context'
|
||||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
import { LayoutPortal } from '@components/Layout'
|
||||||
|
import { PrivateMenu } from '@components/Private'
|
||||||
|
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||||
|
|
||||||
import Measure from './Measure'
|
import Measure from './Measure'
|
||||||
import Reports from './Reports'
|
import Reports from './Reports'
|
||||||
@ -24,42 +25,50 @@ import '@styles/index.css'
|
|||||||
|
|
||||||
const { Content } = Layout
|
const { Content } = Layout
|
||||||
|
|
||||||
export const Well = memo(() => {
|
const Well = memo(() => {
|
||||||
const { idWell, tab } = useParams()
|
const { idWell } = useParams()
|
||||||
const root = useContext(RootPathContext)
|
const root = useRootPath()
|
||||||
const rootPath = useMemo(() => `${root}/well/${idWell}`, [root, idWell])
|
const rootPath = useMemo(() => `${root}/well/${idWell}`, [root, idWell])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootPathContext.Provider value={rootPath}>
|
<LayoutPortal>
|
||||||
<Layout>
|
<RootPathContext.Provider value={rootPath}>
|
||||||
<PrivateMenu mode={'horizontal'} selectable={true} selectedKeys={[tab]} className={'well_menu'}>
|
<PrivateMenu className={'well_menu'}>
|
||||||
<PrivateMenu.Link key={'telemetry'} icon={<FundViewOutlined />} title={'Телеметрия'}/>
|
<PrivateMenu.Link content={Telemetry} />
|
||||||
<PrivateMenu.Link key={'reports'} icon={<FilePdfOutlined />} title={'Рапорта'} />
|
<PrivateMenu.Link content={Reports} icon={<FilePdfOutlined />} />
|
||||||
<PrivateMenu.Link key={'analytics'} icon={<DeploymentUnitOutlined />} title={'Аналитика'} />
|
<PrivateMenu.Link content={Analytics} icon={<DeploymentUnitOutlined />} />
|
||||||
<PrivateMenu.Link key={'operations'} icon={<FolderOutlined />} title={'Операции по скважине'} />
|
<PrivateMenu.Link content={WellOperations} icon={<FolderOutlined />} />
|
||||||
<PrivateMenu.Link key={'document'} icon={<FolderOutlined />} title={'Документы'} />
|
<PrivateMenu.Link content={Documents} icon={<FolderOutlined />} />
|
||||||
<PrivateMenu.Link key={'measure'} icon={<ExperimentOutlined />} title={'Измерения'} />
|
<PrivateMenu.Link content={Measure} icon={<ExperimentOutlined />} />
|
||||||
<PrivateMenu.Link key={'drillingProgram'} icon={<FolderOutlined />} title={'Программа бурения'} />
|
<PrivateMenu.Link content={DrillingProgram} icon={<FolderOutlined />} />
|
||||||
</PrivateMenu>
|
</PrivateMenu>
|
||||||
|
|
||||||
<IdWellContext.Provider value={idWell}>
|
<IdWellContext.Provider value={idWell}>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Content className={'site-layout-background'}>
|
<Content className={'site-layout-background'}>
|
||||||
<PrivateSwitch elseRedirect={['telemetry', 'reports', 'analytics', 'operations', 'telemetryAnalysis', 'document', 'measure', 'drillingProgram']}>
|
<Routes>
|
||||||
<Telemetry key={'telemetry/:tab?'} />
|
<Route index element={<Navigate to={Telemetry.getKey()} replace />} />
|
||||||
<Reports key={'reports/:tab?'} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
<Analytics key={'analytics/:tab?'} />
|
|
||||||
<WellOperations key={'operations/:tab?'} />
|
<Route path={Telemetry.route} element={<Telemetry />} />
|
||||||
<Documents key={'document/:category?'} />
|
<Route path={Reports.route} element={<Reports />} />
|
||||||
<Measure key={'measure'} />
|
<Route path={Analytics.route} element={<Analytics />} />
|
||||||
<DrillingProgram key={'drillingProgram'} />
|
<Route path={WellOperations.route} element={<WellOperations />} />
|
||||||
</PrivateSwitch>
|
<Route path={Documents.route} element={<Documents />} />
|
||||||
|
<Route path={Measure.route} element={<Measure />} />
|
||||||
|
<Route path={DrillingProgram.route} element={<DrillingProgram />} />
|
||||||
|
</Routes>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</IdWellContext.Provider>
|
</IdWellContext.Provider>
|
||||||
</Layout>
|
</RootPathContext.Provider>
|
||||||
</RootPathContext.Provider>
|
</LayoutPortal>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Well
|
export default wrapPrivateComponent(Well, {
|
||||||
|
requirements: [],
|
||||||
|
title: 'Скважина',
|
||||||
|
route: 'well/:idWell/*',
|
||||||
|
key: 'well',
|
||||||
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect, memo, useContext } from 'react'
|
import { useState, useEffect, memo } from 'react'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
makeNumericMinMax,
|
makeNumericMinMax,
|
||||||
@ -8,8 +8,7 @@ import {
|
|||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission, arrayOrDefault } from '@utils'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
import { DrillFlowChartService } from '@api'
|
import { DrillFlowChartService } from '@api'
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +25,7 @@ export const DrillProcessFlow = memo(() => {
|
|||||||
const [flows, setFlows] = useState([])
|
const [flows, setFlows] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const updateFlows = () => invokeWebApiWrapperAsync(
|
const updateFlows = () => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -38,7 +37,9 @@ export const DrillProcessFlow = memo(() => {
|
|||||||
'Получение режимно-технологической карты скважины'
|
'Получение режимно-технологической карты скважины'
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(updateFlows, [idWell])
|
useEffect(() => {
|
||||||
|
updateFlows()
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
const onAdd = async (flow) => {
|
const onAdd = async (flow) => {
|
||||||
flow.idWell = idWell
|
flow.idWell = idWell
|
||||||
@ -62,8 +63,8 @@ export const DrillProcessFlow = memo(() => {
|
|||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
size={'small'}
|
|
||||||
bordered
|
bordered
|
||||||
|
size={'small'}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={flows}
|
dataSource={flows}
|
||||||
tableName={'well_operations_flow'}
|
tableName={'well_operations_flow'}
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
import { memo, useState } from 'react'
|
import { memo, useMemo, useState } from 'react'
|
||||||
import { Button, Tooltip, Modal } from 'antd'
|
import { Button, Tooltip, Modal } from 'antd'
|
||||||
import { FileOutlined, ImportOutlined, ExportOutlined } from '@ant-design/icons'
|
import { FileOutlined, ImportOutlined, ExportOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
|
import { useIdWell } from '@asb/context'
|
||||||
import { download } from '@components/factory'
|
import { download } from '@components/factory'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission } from '@utils'
|
||||||
|
|
||||||
import { ImportOperations } from './ImportOperations'
|
import { ImportOperations } from './ImportOperations'
|
||||||
|
|
||||||
const style = { margin: 4 }
|
const style = { margin: 4 }
|
||||||
|
|
||||||
export const ImportExportBar = memo(({ idWell, onImported, disabled }) => {
|
export const ImportExportBar = memo(({ idWell: wellId, onImported, disabled }) => {
|
||||||
const [isImportModalVisible, setIsImportModalVisible] = useState(false)
|
const [isImportModalVisible, setIsImportModalVisible] = useState(false)
|
||||||
|
|
||||||
|
const idWellContext = useIdWell()
|
||||||
|
const idWell = useMemo(() => wellId ?? idWellContext, [idWellContext])
|
||||||
|
|
||||||
const downloadTemplate = async () => await download(`/api/well/${idWell}/wellOperations/template`)
|
const downloadTemplate = async () => await download(`/api/well/${idWell}/wellOperations/template`)
|
||||||
const downloadExport = async () => await download(`/api/well/${idWell}/wellOperations/export`)
|
const downloadExport = async () => await download(`/api/well/${idWell}/wellOperations/export`)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { memo, useEffect, useMemo, useState } from 'react'
|
|||||||
|
|
||||||
import { makeNumericRender } from '@components/Table'
|
import { makeNumericRender } from '@components/Table'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { formatDate, fractionalSum } from '@utils/datetime'
|
import { formatDate, fractionalSum } from '@utils'
|
||||||
|
|
||||||
import '@styles/tvd.less'
|
import '@styles/tvd.less'
|
||||||
|
|
||||||
@ -26,27 +26,29 @@ export const AdditionalTables = memo(({ operations, xLabel, setIsLoading }) => {
|
|||||||
.reduce((out, row) => out + (row?.durationHours ?? 0), 0)
|
.reduce((out, row) => out + (row?.durationHours ?? 0), 0)
|
||||||
, [operations.fact])
|
, [operations.fact])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const [factStartDate, factEndDate] = calcEndDate(operations.fact)
|
async () => {
|
||||||
const [planStartDate, planEndDate] = calcEndDate(operations.plan)
|
const [factStartDate, factEndDate] = calcEndDate(operations.fact)
|
||||||
const [predictStartDate, predictEndDate] = calcEndDate(operations.predict)
|
const [planStartDate, planEndDate] = calcEndDate(operations.plan)
|
||||||
|
const [predictStartDate, predictEndDate] = calcEndDate(operations.predict)
|
||||||
|
|
||||||
const last = predictEndDate ?? factEndDate
|
const last = predictEndDate ?? factEndDate
|
||||||
setAdditionalData({
|
setAdditionalData({
|
||||||
lag: (+new Date(last) - +new Date(planEndDate)) / 86400_000,
|
lag: (+new Date(last) - +new Date(planEndDate)) / 86400_000,
|
||||||
endDate: last,
|
endDate: last,
|
||||||
factStartDate,
|
factStartDate,
|
||||||
factEndDate,
|
factEndDate,
|
||||||
planStartDate,
|
planStartDate,
|
||||||
planEndDate,
|
planEndDate,
|
||||||
predictStartDate,
|
predictStartDate,
|
||||||
predictEndDate,
|
predictEndDate,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setIsLoading,
|
setIsLoading,
|
||||||
'Не удалось высчитать дополнительные данные'
|
'Не удалось высчитать дополнительные данные'
|
||||||
), [operations, setIsLoading])
|
)
|
||||||
|
}, [operations, setIsLoading])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -23,17 +23,21 @@ export const NptTable = memo(({ operations }) => {
|
|||||||
const [filteredNPT, setFilteredNPT] = useState([])
|
const [filteredNPT, setFilteredNPT] = useState([])
|
||||||
const [isTableLoading, setIsTableLoading] = useState(false)
|
const [isTableLoading, setIsTableLoading] = useState(false)
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => setNPT(operations?.filter((row) => row?.isNPT) ?? []),
|
invokeWebApiWrapperAsync(
|
||||||
setIsTableLoading,
|
async () => setNPT(operations?.filter((row) => row?.isNPT) ?? []),
|
||||||
'Не удалось получить список НПВ'
|
setIsTableLoading,
|
||||||
), [operations])
|
'Не удалось получить список НПВ'
|
||||||
|
)
|
||||||
|
}, [operations])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => setFilteredNPT(npt.filter((row) => !filterValue || row.durationHours >= filterValue)),
|
invokeWebApiWrapperAsync(
|
||||||
setIsTableLoading,
|
async () => setFilteredNPT(npt.filter((row) => !filterValue || row.durationHours >= filterValue)),
|
||||||
'Не удалось отфильтровать НПВ по времени'
|
setIsTableLoading,
|
||||||
), [npt, filterValue])
|
'Не удалось отфильтровать НПВ по времени'
|
||||||
|
)
|
||||||
|
}, [npt, filterValue])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'tvd-right'}>
|
<div className={'tvd-right'}>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useHistory } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { memo, useState, useRef, useEffect, useCallback, useContext, useMemo } from 'react'
|
import { memo, useState, useRef, useEffect, useCallback, useMemo } from 'react'
|
||||||
import { DoubleLeftOutlined, DoubleRightOutlined } from '@ant-design/icons'
|
import { DoubleLeftOutlined, DoubleRightOutlined } from '@ant-design/icons'
|
||||||
import { Switch, Button } from 'antd'
|
import { Switch, Button } from 'antd'
|
||||||
|
|
||||||
@ -16,11 +16,10 @@ import 'chartjs-adapter-moment'
|
|||||||
import zoomPlugin from 'chartjs-plugin-zoom'
|
import zoomPlugin from 'chartjs-plugin-zoom'
|
||||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { formatDate, fractionalSum } from '@utils/datetime'
|
import { formatDate, fractionalSum, wrapPrivateComponent, getOperations } from '@utils'
|
||||||
import { getOperations } from '@utils/functions'
|
|
||||||
|
|
||||||
import NptTable from './NptTable'
|
import NptTable from './NptTable'
|
||||||
import NetGraphExport from './NetGraphExport'
|
import NetGraphExport from './NetGraphExport'
|
||||||
@ -30,14 +29,14 @@ import '@styles/index.css'
|
|||||||
import '@styles/tvd.less'
|
import '@styles/tvd.less'
|
||||||
|
|
||||||
Chart.register(
|
Chart.register(
|
||||||
TimeScale,
|
TimeScale,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
LineController,
|
LineController,
|
||||||
LineElement,
|
LineElement,
|
||||||
PointElement,
|
PointElement,
|
||||||
Legend,
|
Legend,
|
||||||
ChartDataLabels,
|
ChartDataLabels,
|
||||||
zoomPlugin
|
zoomPlugin
|
||||||
)
|
)
|
||||||
|
|
||||||
const numericRender = (value) => Number.isFinite(value) ? (+value).toFixed(2) : '-'
|
const numericRender = (value) => Number.isFinite(value) ? (+value).toFixed(2) : '-'
|
||||||
@ -115,18 +114,18 @@ const makeDataset = (data, label, color, borderWidth = 1.5, borderDash) => ({
|
|||||||
borderDash,
|
borderDash,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Tvd = memo(({ idWell: wellId, title, ...other }) => {
|
const Tvd = memo(({ idWell: wellId, title, ...other }) => {
|
||||||
const [chart, setChart] = useState()
|
const [chart, setChart] = useState()
|
||||||
const [xLabel, setXLabel] = useState('day')
|
const [xLabel, setXLabel] = useState('day')
|
||||||
const [operations, setOperations] = useState({})
|
const [operations, setOperations] = useState({})
|
||||||
const [tableVisible, setTableVisible] = useState(false)
|
const [tableVisible, setTableVisible] = useState(false)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
const idWellContext = useContext(IdWellContext)
|
const idWellContext = useIdWell()
|
||||||
const idWell = useMemo(() => wellId ?? idWellContext, [wellId, idWellContext])
|
const idWell = useMemo(() => wellId ?? idWellContext, [wellId, idWellContext])
|
||||||
|
|
||||||
const chartRef = useRef(null)
|
const chartRef = useRef(null)
|
||||||
const history = useHistory()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const onPointClick = useCallback((e) => {
|
const onPointClick = useCallback((e) => {
|
||||||
const points = e?.chart?.tooltip?.dataPoints
|
const points = e?.chart?.tooltip?.dataPoints
|
||||||
@ -137,15 +136,17 @@ export const Tvd = memo(({ idWell: wellId, title, ...other }) => {
|
|||||||
|
|
||||||
const datasetName = datasetId === 2 ? 'plan' : 'fact'
|
const datasetName = datasetId === 2 ? 'plan' : 'fact'
|
||||||
const ids = points.filter((p) => p?.raw?.id).map((p) => p.raw.id).join(',')
|
const ids = points.filter((p) => p?.raw?.id).map((p) => p.raw.id).join(',')
|
||||||
history.push(`/well/${idWell}/operations/${datasetName}/?selectedId=${ids}`)
|
navigate(`/well/${idWell}/operations/${datasetName}/?selectedId=${ids}`)
|
||||||
}, [idWell, history])
|
}, [idWell, navigate])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => setOperations(await getOperations(idWell)),
|
invokeWebApiWrapperAsync(
|
||||||
setIsLoading,
|
async () => setOperations(await getOperations(idWell)),
|
||||||
`Не удалось загрузить операции по скважине "${idWell}"`,
|
setIsLoading,
|
||||||
'Получение списка опервций по скважине'
|
`Не удалось загрузить операции по скважине "${idWell}"`,
|
||||||
), [idWell])
|
'Получение списка опервций по скважине'
|
||||||
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const withoutNpt = []
|
const withoutNpt = []
|
||||||
@ -227,4 +228,8 @@ export const Tvd = memo(({ idWell: wellId, title, ...other }) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Tvd
|
export default wrapPrivateComponent(Tvd, {
|
||||||
|
requirements: [ 'OperationStat.get' ],
|
||||||
|
title: 'TVD',
|
||||||
|
route: 'tvd',
|
||||||
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect, useCallback, memo, useMemo, useContext } from 'react'
|
import { useState, useEffect, useCallback, memo, useMemo } from 'react'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
makeSelectColumn,
|
makeSelectColumn,
|
||||||
@ -11,15 +11,14 @@ import {
|
|||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { DrillParamsService, WellOperationService } from '@api'
|
import { DrillParamsService, WellOperationService } from '@api'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission, arrayOrDefault } from '@utils'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
|
|
||||||
|
|
||||||
export const getColumns = async (idWell) => {
|
export const getColumns = async (idWell) => {
|
||||||
let sectionTypes = await WellOperationService.getSectionTypes(idWell)
|
let sectionTypes = await WellOperationService.getSectionTypes(idWell)
|
||||||
sectionTypes = Object.entries(sectionTypes).map(([id, value]) => ({
|
sectionTypes = Object.entries(sectionTypes).map(([id, value]) => ({
|
||||||
label: value,
|
label: value,
|
||||||
value: parseInt(id),
|
value: id,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -41,7 +40,7 @@ export const WellDrillParams = memo(() => {
|
|||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [columns, setColumns] = useState([])
|
const [columns, setColumns] = useState([])
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const updateParams = useCallback(async () => await invokeWebApiWrapperAsync(
|
const updateParams = useCallback(async () => await invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -55,10 +54,12 @@ export const WellDrillParams = memo(() => {
|
|||||||
'Получение списка режимов бурения скважины'
|
'Получение списка режимов бурения скважины'
|
||||||
), [idWell])
|
), [idWell])
|
||||||
|
|
||||||
useEffect(() => (async () => {
|
useEffect(() => {
|
||||||
setColumns(await getColumns(idWell))
|
(async () => {
|
||||||
await updateParams()
|
setColumns(await getColumns(idWell))
|
||||||
})(), [idWell, updateParams])
|
await updateParams()
|
||||||
|
})()
|
||||||
|
}, [idWell, updateParams])
|
||||||
|
|
||||||
const handlerProps = useMemo(() => ({
|
const handlerProps = useMemo(() => ({
|
||||||
service: DrillParamsService,
|
service: DrillParamsService,
|
||||||
@ -73,8 +74,8 @@ export const WellDrillParams = memo(() => {
|
|||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
size={'small'}
|
|
||||||
bordered
|
bordered
|
||||||
|
size={'small'}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={params}
|
dataSource={params}
|
||||||
tableName={'well_drill_params'}
|
tableName={'well_drill_params'}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { Input } from 'antd'
|
import { Input } from 'antd'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { useState, useEffect, memo, useMemo, useCallback, useContext } from 'react'
|
import { useState, useEffect, memo, useMemo, useCallback } from 'react'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
makeColumn,
|
makeColumn,
|
||||||
@ -18,11 +18,9 @@ import {
|
|||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { arrayOrDefault, wrapPrivateComponent, hasPermission } from '@utils'
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
import { WellOperationService } from '@api'
|
import { WellOperationService } from '@api'
|
||||||
|
|
||||||
|
|
||||||
const { TextArea } = Input
|
const { TextArea } = Input
|
||||||
|
|
||||||
const basePageSize = 160
|
const basePageSize = 160
|
||||||
@ -54,13 +52,13 @@ const generateColumns = (showNpt = false, categories = [], sectionTypes = []) =>
|
|||||||
makeTextColumn('Комментарий', 'comment', null, null, null, { editable: true, input: <TextArea/> }),
|
makeTextColumn('Комментарий', 'comment', null, null, null, { editable: true, input: <TextArea/> }),
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
|
|
||||||
export const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
||||||
const [pageNumAndPageSize, setPageNumAndPageSize] = useState({ current: 1, pageSize: basePageSize })
|
const [pageNumAndPageSize, setPageNumAndPageSize] = useState({ current: 1, pageSize: basePageSize })
|
||||||
const [paginationTotal, setPaginationTotal] = useState(0)
|
const [paginationTotal, setPaginationTotal] = useState(0)
|
||||||
const [operations, setOperations] = useState([])
|
const [operations, setOperations] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
const [categories, setCategories] = useState([])
|
const [categories, setCategories] = useState([])
|
||||||
const [sectionTypes, setSectionTypes] = useState([])
|
const [sectionTypes, setSectionTypes] = useState([])
|
||||||
@ -73,18 +71,20 @@ export const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
|||||||
return arrayOrDefault(query.get('selectedId')?.split(',')?.map(parseInt))
|
return arrayOrDefault(query.get('selectedId')?.split(',')?.map(parseInt))
|
||||||
}, [location])
|
}, [location])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const categories = arrayOrDefault(await WellOperationService.getCategories(idWell))
|
async () => {
|
||||||
setCategories(categories.map((item) => ({ value: item.id, label: item.name })))
|
const categories = arrayOrDefault(await WellOperationService.getCategories(idWell))
|
||||||
|
setCategories(categories.map((item) => ({ value: item.id, label: item.name })))
|
||||||
|
|
||||||
const sectionTypes = Object.entries(await WellOperationService.getSectionTypes(idWell) ?? {})
|
const sectionTypes = Object.entries(await WellOperationService.getSectionTypes(idWell) ?? {})
|
||||||
setSectionTypes(sectionTypes.map(([id, label]) => ({ value: parseInt(id), label })))
|
setSectionTypes(sectionTypes.map(([id, label]) => ({ value: parseInt(id), label })))
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
'Не удалось загрузить список операций по скважине',
|
'Не удалось загрузить список операций по скважине',
|
||||||
'Получение списка операций по скважине'
|
'Получение списка операций по скважине'
|
||||||
), [idWell])
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
const updateOperations = useCallback(() => invokeWebApiWrapperAsync(
|
const updateOperations = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -92,7 +92,7 @@ export const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
|||||||
const take = pageNumAndPageSize.pageSize
|
const take = pageNumAndPageSize.pageSize
|
||||||
const paginatedOperations = await WellOperationService.getOperations(idWell,
|
const paginatedOperations = await WellOperationService.getOperations(idWell,
|
||||||
idType, undefined, undefined, undefined, undefined,
|
idType, undefined, undefined, undefined, undefined,
|
||||||
undefined, undefined, skip, take )
|
undefined, undefined, skip, take)
|
||||||
const operations = paginatedOperations?.items ?? []
|
const operations = paginatedOperations?.items ?? []
|
||||||
setOperations(operations)
|
setOperations(operations)
|
||||||
const total = paginatedOperations.count?? paginatedOperations.items?.length ?? 0
|
const total = paginatedOperations.count?? paginatedOperations.items?.length ?? 0
|
||||||
@ -103,7 +103,9 @@ export const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
|||||||
'Получение списка операций по скважине'
|
'Получение списка операций по скважине'
|
||||||
), [idWell, idType, pageNumAndPageSize])
|
), [idWell, idType, pageNumAndPageSize])
|
||||||
|
|
||||||
useEffect(updateOperations, [updateOperations])
|
useEffect(() => {
|
||||||
|
updateOperations()
|
||||||
|
}, [updateOperations])
|
||||||
|
|
||||||
const handlerProps = useMemo(() => ({
|
const handlerProps = useMemo(() => ({
|
||||||
service: WellOperationService,
|
service: WellOperationService,
|
||||||
@ -151,4 +153,20 @@ export const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default WellOperationsEditor
|
export const WellOperationsEditorPlan = wrapPrivateComponent(
|
||||||
|
() => <WellOperationsEditor idType={0} tableName={'well_operations_plan'}/>,
|
||||||
|
{
|
||||||
|
requirements: [ 'WellOperation.get' ],
|
||||||
|
title: 'План',
|
||||||
|
route: 'plan',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const WellOperationsEditorFact = wrapPrivateComponent(
|
||||||
|
() => <WellOperationsEditor idType={1} tableName={'well_operations_fact'}/>,
|
||||||
|
{
|
||||||
|
requirements: [ 'WellOperation.get' ],
|
||||||
|
title: 'Факт',
|
||||||
|
route: 'fact',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useState, useEffect, memo, useContext } from 'react'
|
import { useState, useEffect, memo } from 'react'
|
||||||
|
|
||||||
import { IdWellContext } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { Table, makeColumn, makeColumnsPlanFact, makeNumericRender } from '@components/Table'
|
import { Table, makeColumn, makeColumnsPlanFact, makeNumericRender } from '@components/Table'
|
||||||
import { calcDuration } from '@utils/datetime'
|
import { calcDuration } from '@utils'
|
||||||
import { OperationStatService } from '@api'
|
import { OperationStatService } from '@api'
|
||||||
|
|
||||||
|
|
||||||
@ -25,40 +25,42 @@ export const WellSectionsStat = memo(() => {
|
|||||||
const [sections, setSections] = useState([])
|
const [sections, setSections] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const idWell = useContext(IdWellContext)
|
const idWell = useIdWell()
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => {
|
||||||
async () => {
|
invokeWebApiWrapperAsync(
|
||||||
const sectionsResponse = await OperationStatService.getStatWell(idWell)
|
async () => {
|
||||||
|
const sectionsResponse = await OperationStatService.getStatWell(idWell)
|
||||||
|
|
||||||
if(sectionsResponse?.sections){
|
if(sectionsResponse?.sections){
|
||||||
const sections = sectionsResponse.sections.map(s => ({
|
const sections = sectionsResponse.sections.map(s => ({
|
||||||
key: s.id,
|
key: s.id,
|
||||||
sectionType: s.caption,
|
sectionType: s.caption,
|
||||||
wellDepthPlan: s.plan?.wellDepthEnd,
|
wellDepthPlan: s.plan?.wellDepthEnd,
|
||||||
durationPlan: calcDuration(s.plan?.start, s.plan?.end),
|
durationPlan: calcDuration(s.plan?.start, s.plan?.end),
|
||||||
ropPlan: s.plan?.rop,
|
ropPlan: s.plan?.rop,
|
||||||
routeSpeedPlan: s.plan?.routeSpeed,
|
routeSpeedPlan: s.plan?.routeSpeed,
|
||||||
bhaUpSpeedPlan: s.plan?.bhaUpSpeed,
|
bhaUpSpeedPlan: s.plan?.bhaUpSpeed,
|
||||||
bhaDownSpeedPlan: s.plan?.bhaDownSpeed,
|
bhaDownSpeedPlan: s.plan?.bhaDownSpeed,
|
||||||
casingDownSpeedPlan: s.plan?.casingDownSpeed,
|
casingDownSpeedPlan: s.plan?.casingDownSpeed,
|
||||||
|
|
||||||
wellDepthFact: s.fact?.wellDepthEnd,
|
wellDepthFact: s.fact?.wellDepthEnd,
|
||||||
durationFact: calcDuration(s.fact?.start, s.fact?.end),
|
durationFact: calcDuration(s.fact?.start, s.fact?.end),
|
||||||
ropFact: s.fact?.rop,
|
ropFact: s.fact?.rop,
|
||||||
routeSpeedFact: s.fact?.routeSpeed,
|
routeSpeedFact: s.fact?.routeSpeed,
|
||||||
bhaUpSpeedFact: s.fact?.bhaUpSpeed,
|
bhaUpSpeedFact: s.fact?.bhaUpSpeed,
|
||||||
bhaDownSpeedFact: s.fact?.bhaDownSpeed,
|
bhaDownSpeedFact: s.fact?.bhaDownSpeed,
|
||||||
casingDownSpeedFact: s.fact?.casingDownSpeed,
|
casingDownSpeedFact: s.fact?.casingDownSpeed,
|
||||||
}))
|
}))
|
||||||
sections.sort((a,b) => a.wellDepthPlan - b.wellDepthPlan)
|
sections.sort((a,b) => a.wellDepthPlan - b.wellDepthPlan)
|
||||||
setSections(sections)
|
setSections(sections)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось получить статистику по секциям скважины "${idWell}"`,
|
`Не удалось получить статистику по секциям скважины "${idWell}"`,
|
||||||
'Получение статистики по секциям скважины'
|
'Получение статистики по секциям скважины'
|
||||||
), [idWell])
|
)
|
||||||
|
}, [idWell])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user