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
|
||||
}
|
33638
package-lock.json
generated
33638
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",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@craco/craco": "^6.1.2",
|
||||
"@microsoft/signalr": "^6.0.4",
|
||||
"@testing-library/jest-dom": "^5.11.10",
|
||||
"@testing-library/react": "^11.2.6",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/react-dom": "^18.0.3",
|
||||
"antd": "^4.15.0",
|
||||
"chart.js": "^3.6.0",
|
||||
"@microsoft/signalr": "^6.0.5",
|
||||
"@svgr/webpack": "^6.2.1",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/user-event": "^14.2.0",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"antd": "^4.20.7",
|
||||
"chart.js": "^3.8.0",
|
||||
"chartjs-adapter-moment": "^1.0.0",
|
||||
"chartjs-plugin-datalabels": "^2.0.0-rc.1",
|
||||
"chartjs-plugin-zoom": "^1.1.1",
|
||||
"craco-less": "^1.17.1",
|
||||
"chartjs-plugin-datalabels": "^2.0.0",
|
||||
"chartjs-plugin-zoom": "^1.2.1",
|
||||
"d3": "^7.4.4",
|
||||
"moment": "^2.29.1",
|
||||
"pigeon-maps": "^0.19.7",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"rxjs": "^7.5.4",
|
||||
"typescript": "^4.2.3",
|
||||
"web-vitals": "^1.1.1"
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^11.0.0",
|
||||
"moment": "^2.29.3",
|
||||
"pigeon-maps": "^0.21.0",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"rxjs": "^7.5.5",
|
||||
"typescript": "^4.7.2",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"start": "webpack-dev-server --mode=development --open --hot",
|
||||
"build": "webpack --mode=production",
|
||||
"test": "jest",
|
||||
"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",
|
||||
"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",
|
||||
"react_start": "react-scripts start",
|
||||
"react_build": "react-scripts build",
|
||||
"react_test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"oug_dev": "npx openapi -i http://46.146.209.148:89/swagger/v1/swagger.json -o src/services/api"
|
||||
},
|
||||
"proxy": "http://46.146.209.148:89",
|
||||
"eslintConfig": {
|
||||
@ -58,12 +53,57 @@
|
||||
"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": {
|
||||
"@types/d3": "^7.1.0",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-router-dom": "^5.3.2",
|
||||
"craco-alias": "^3.0.1",
|
||||
"openapi-typescript": "^3.4.1",
|
||||
"openapi-typescript-codegen": "^0.21.0"
|
||||
"@babel/core": "^7.18.2",
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
"@babel/preset-react": "^7.17.12",
|
||||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@testing-library/react": "^13.3.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">
|
||||
<head>
|
||||
<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="theme-color" 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="description"
|
||||
content="Онлайн мониторинг процесса бурения в реальном времени в офисе заказчика"
|
||||
/>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<meta name="description" content="Онлайн мониторинг процесса бурения в реальном времени в офисе заказчика" />
|
||||
<title>АСБ Vision</title>
|
||||
</head>
|
||||
<body>
|
||||
|
44
src/App.tsx
44
src/App.tsx
@ -1,18 +1,17 @@
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Switch,
|
||||
Route
|
||||
} from 'react-router-dom'
|
||||
import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { memo } from 'react'
|
||||
import { ConfigProvider } from 'antd'
|
||||
import locale from 'antd/lib/locale/ru_RU'
|
||||
|
||||
import { PrivateRoute } from '@components/Private'
|
||||
import { getUserToken } from '@utils/storage'
|
||||
import { RootPathContext } from '@asb/context'
|
||||
import { getUserToken, NoAccessComponent } from '@utils'
|
||||
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 Cluster from '@pages/Cluster'
|
||||
import Deposit from '@pages/Deposit'
|
||||
import Register from '@pages/Register'
|
||||
|
||||
import '@styles/App.less'
|
||||
@ -23,19 +22,26 @@ OpenAPI.HEADERS = {'Content-Type': 'application/json'}
|
||||
|
||||
export const App = memo(() => (
|
||||
<ConfigProvider locale={locale}>
|
||||
<RootPathContext.Provider value={''}>
|
||||
<Router>
|
||||
<Switch>
|
||||
<Route path={'/login'}>
|
||||
<Login />
|
||||
</Route>
|
||||
<Route path={'/register'}>
|
||||
<Register />
|
||||
</Route>
|
||||
<PrivateRoute path={'/'}>
|
||||
<Main />
|
||||
</PrivateRoute>
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={Deposit.getKey()} replace />} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
|
||||
{/* Public pages */}
|
||||
<Route path={Login.route} element={<Login />} />
|
||||
<Route path={Register.route} element={<Register />} />
|
||||
|
||||
{/* Admin pages */}
|
||||
<Route path={AdminPanel.route} element={<AdminPanel />} />
|
||||
|
||||
{/* 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>
|
||||
))
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { Rule } from 'antd/lib/form'
|
||||
import { Form, Input, Modal, FormProps } from 'antd'
|
||||
|
||||
import { AuthService, UserDto } from '@api'
|
||||
import { getUserId, getUserLogin } from '@utils/storage'
|
||||
import { getUserId, getUserLogin } from '@utils'
|
||||
import { passwordRules, createPasswordRules } from '@utils/validationRules'
|
||||
|
||||
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>
|
||||
<PageHeader isAdmin title={title} style={{ backgroundColor: '#900' }}>
|
||||
<Button size={'large'}>
|
||||
<Link to={{ pathname: '/', state: { from: location.pathname }}}>Вернуться на сайт</Link>
|
||||
<Link to={'/'}>Вернуться на сайт</Link>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Layout>
|
||||
|
@ -3,13 +3,14 @@ import { Layout, LayoutProps } from 'antd'
|
||||
|
||||
import PageHeader from '@components/PageHeader'
|
||||
import WellTreeSelector from '@components/selectors/WellTreeSelector'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
|
||||
export type LayoutPortalProps = LayoutProps & {
|
||||
title?: ReactNode
|
||||
noSheet?: boolean
|
||||
}
|
||||
|
||||
export const LayoutPortal = memo<LayoutPortalProps>(({ title, noSheet, ...props }) => (
|
||||
const _LayoutPortal = memo<LayoutPortalProps>(({ title, noSheet, ...props }) => (
|
||||
<Layout.Content>
|
||||
<PageHeader title={title}>
|
||||
<WellTreeSelector />
|
||||
@ -22,4 +23,8 @@ export const LayoutPortal = memo<LayoutPortalProps>(({ title, noSheet, ...props
|
||||
</Layout.Content>
|
||||
))
|
||||
|
||||
export const LayoutPortal = wrapPrivateComponent(_LayoutPortal, {
|
||||
requirements: ['Deposit.get'],
|
||||
})
|
||||
|
||||
export default LayoutPortal
|
||||
|
@ -1,5 +1,5 @@
|
||||
export { AdminLayoutPortal } from './AdminLayoutPortal'
|
||||
export { LayoutPortal } from './LayoutPortal'
|
||||
export * from './AdminLayoutPortal'
|
||||
export * from './LayoutPortal'
|
||||
|
||||
export type { AdminLayoutPortalProps } from './AdminLayoutPortal'
|
||||
export type { LayoutPortalProps } from './LayoutPortal'
|
||||
|
@ -20,7 +20,7 @@ export const PageHeader: React.FC<PageHeaderProps> = memo(({ title = 'Монит
|
||||
return (
|
||||
<Layout>
|
||||
<Layout.Header className={'header'} {...other}>
|
||||
<Link to={{ pathname: '/', state: { from: location.pathname }}} style={{ height: headerHeight }}>
|
||||
<Link to={'/'} style={{ height: headerHeight }}>
|
||||
<Logo />
|
||||
</Link>
|
||||
{children}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { memo, ReactElement } from 'react'
|
||||
|
||||
import { isURLAvailable } from '@utils/permissions'
|
||||
import { isURLAvailable } from '@utils'
|
||||
|
||||
export type PrivateContentProps = {
|
||||
absolutePath: string
|
||||
|
@ -1,25 +1,19 @@
|
||||
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/permissions'
|
||||
import { isURLAvailable } from '@utils'
|
||||
|
||||
import { getDefaultRedirectPath } from './PrivateRoutes'
|
||||
|
||||
export type PrivateDefaultRouteProps = RouteProps & {
|
||||
urls: string[]
|
||||
elseRedirect?: string
|
||||
}
|
||||
|
||||
export const PrivateDefaultRoute = memo<PrivateDefaultRouteProps>(({ elseRedirect, urls, ...other }) => {
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<Route {...other} path={'/'}>
|
||||
<Redirect to={{
|
||||
pathname: urls.find((url) => isURLAvailable(url)) ?? elseRedirect ?? (getUserId() ? '/access_denied' : '/login'),
|
||||
state: { from: location.pathname },
|
||||
}} />
|
||||
</Route>
|
||||
)
|
||||
})
|
||||
export const PrivateDefaultRoute = memo<PrivateDefaultRouteProps>(({ elseRedirect, urls, ...other }) => (
|
||||
<Route {...other} path={'/'} element={(
|
||||
<Navigate replace to={urls.find((url) => isURLAvailable(url)) ?? elseRedirect ?? getDefaultRedirectPath()} />
|
||||
)} />
|
||||
))
|
||||
|
||||
export default PrivateDefaultRoute
|
||||
|
@ -1,47 +1,76 @@
|
||||
import { join } from 'path'
|
||||
import { Menu, MenuItemProps, MenuProps } from 'antd'
|
||||
import { Children, cloneElement, memo, ReactElement, useContext, useMemo } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { Menu, MenuProps } from 'antd'
|
||||
import { ItemType } from 'antd/lib/menu/hooks/useItems'
|
||||
import { Link, LinkProps } from 'react-router-dom'
|
||||
import { Children, isValidElement, memo, ReactNode, RefAttributes, useMemo } from 'react'
|
||||
|
||||
import { RootPathContext } from '@asb/context'
|
||||
import { isURLAvailable } from '@utils/permissions'
|
||||
import { useRootPath } from '@asb/context'
|
||||
import { getTabname, hasPermission, PrivateComponent, PrivateProps } from '@utils'
|
||||
|
||||
export type PrivateMenuProps = MenuProps & { root?: string }
|
||||
|
||||
export type PrivateMenuLinkProps = MenuItemProps & {
|
||||
tabName?: string
|
||||
export type PrivateMenuLinkProps = Partial<ItemType> & Omit<LinkProps, 'to'> & RefAttributes<HTMLAnchorElement> & {
|
||||
icon?: ReactNode
|
||||
danger?: boolean
|
||||
title?: ReactNode
|
||||
content?: PrivateComponent<any>
|
||||
path?: string
|
||||
title: string
|
||||
visible?: boolean
|
||||
permissions?: string[]
|
||||
}
|
||||
|
||||
export const PrivateMenuLink = memo<PrivateMenuLinkProps>(({ tabName = '', path = '', title, ...other }) => {
|
||||
const location = useLocation()
|
||||
return (
|
||||
<Menu.Item key={tabName} {...other}>
|
||||
<Link to={{ pathname: path, state: { from: location.pathname }}}>{title}</Link>
|
||||
export const PrivateMenuLink = memo<PrivateMenuLinkProps>(({ content, danger, icon, path = '', title, ...other }) => (
|
||||
<Menu.Item icon={icon ?? content?.icon} danger={danger}>
|
||||
<Link to={path} {...other}>{title ?? content?.title}</Link>
|
||||
</Menu.Item>
|
||||
)
|
||||
})
|
||||
))
|
||||
|
||||
const PrivateMenuMain = memo<PrivateMenuProps>(({ root, children, ...other }) => {
|
||||
const rootContext = useContext(RootPathContext)
|
||||
const PrivateMenuMain = memo<PrivateMenuProps>(({ selectable, mode, selectedKeys, root, children, ...other }) => {
|
||||
const rootContext = useRootPath()
|
||||
const rootPath = useMemo(() => root ?? rootContext ?? '', [root, rootContext])
|
||||
|
||||
const items = useMemo(() => Children.toArray(children).map((child) => {
|
||||
const element = child as ReactElement
|
||||
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 })
|
||||
const tab = getTabname()
|
||||
const keys = useMemo(() => selectedKeys ?? (tab ? [tab] : []), [selectedKeys, tab])
|
||||
|
||||
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
|
||||
}), [children, rootPath])
|
||||
})?.filter((v) => v) ?? [], [children, rootPath])
|
||||
|
||||
return <Menu children={items} {...other} />
|
||||
return (
|
||||
<Menu
|
||||
selectable={selectable ?? true}
|
||||
mode={mode ?? 'horizontal'}
|
||||
selectedKeys={keys}
|
||||
items={items as ItemType[]}
|
||||
{...other}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export const PrivateMenu = Object.assign(PrivateMenuMain, { Link: PrivateMenuLink })
|
||||
|
@ -3,7 +3,7 @@ import { Menu, MenuItemProps } from 'antd'
|
||||
import { memo, NamedExoticComponent } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
import { isURLAvailable } from '@utils/permissions'
|
||||
import { isURLAvailable } from '@utils'
|
||||
|
||||
export type PrivateMenuItemProps = MenuItemProps & {
|
||||
root: string
|
||||
@ -20,7 +20,7 @@ export const PrivateMenuItemLink = memo<PrivateMenuItemLinkProps>(({ root = '',
|
||||
const location = useLocation()
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
})
|
||||
|
@ -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 { memo, ReactNode } from 'react'
|
||||
import { Navigate, Route, RouteProps } from 'react-router-dom'
|
||||
|
||||
import { getUserId } from '@utils/storage'
|
||||
import { isURLAvailable } from '@utils/permissions'
|
||||
import { getUserId, isURLAvailable } from '@utils'
|
||||
|
||||
export type PrivateRouteProps = RouteProps & {
|
||||
root?: string
|
||||
path: string
|
||||
children?: ReactNode
|
||||
redirect?: (location?: Location<unknown>) => ReactNode
|
||||
redirect?: ReactNode
|
||||
}
|
||||
|
||||
export const defaultRedirect = (location?: Location<unknown>) => (
|
||||
<Redirect to={{ pathname: getUserId() ? '/access_denied' : '/login', state: { from: location?.pathname } }} />
|
||||
export const defaultRedirect = (
|
||||
<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))
|
||||
|
||||
return (
|
||||
<Route
|
||||
{...other}
|
||||
path={path}
|
||||
component={available ? component : undefined}
|
||||
render={({ location }) => available ? children : redirect(location)}
|
||||
element={available ? children : redirect}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
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 { PrivateDefaultRoute } from './PrivateDefaultRoute'
|
||||
export { PrivateMenu, PrivateMenuLink } from './PrivateMenu'
|
||||
export { PrivateSwitch } from './PrivateSwitch'
|
||||
export { PrivateRoutes } from './PrivateRoutes'
|
||||
|
||||
export type { PrivateRouteProps } from './PrivateRoute'
|
||||
export type { PrivateContentProps } from './PrivateContent' // TODO: Remove
|
||||
export type { PrivateMenuItemProps, PrivateMenuItemLinkProps } from './PrivateMenuItem' // TODO: Remove
|
||||
export type { PrivateDefaultRouteProps } from './PrivateDefaultRoute'
|
||||
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 { Select, SelectProps, Tag } from 'antd'
|
||||
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 '.'
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { formatTime } from '@utils/datetime'
|
||||
import { formatTime } from '@utils'
|
||||
|
||||
import { makeColumn, columnPropsOther } from '.'
|
||||
import { makeTimeSorter, TimePickerWrapper, TimePickerWrapperProps } from '..'
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { memo, ReactNode, useCallback, useEffect, useState } from 'react'
|
||||
import { Select, SelectProps } from 'antd'
|
||||
|
||||
import { OmitExtends } from '@utils'
|
||||
import { findTimezoneId, rawTimezones, TimezoneId } from '@utils/datetime'
|
||||
import { findTimezoneId, rawTimezones, TimezoneId } from '@utils'
|
||||
import type { OmitExtends } from '@utils/types'
|
||||
import { SimpleTimezoneDto } from '@api'
|
||||
|
||||
import { columnPropsOther, makeColumn } from '.'
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { memo } from 'react'
|
||||
import { memo, ReactNode } from 'react'
|
||||
import { Form, Input } from 'antd'
|
||||
import { NamePath, Rule } from 'rc-field-form/lib/interface'
|
||||
|
||||
type EditableCellProps = React.DetailedHTMLProps<React.TdHTMLAttributes<HTMLTableDataCellElement>, HTMLTableDataCellElement> & {
|
||||
editing?: boolean
|
||||
dataIndex?: NamePath
|
||||
input?: React.Component
|
||||
input?: ReactNode
|
||||
isRequired?: boolean
|
||||
title: string
|
||||
formItemClass?: string
|
||||
formItemRules?: Rule[]
|
||||
children: React.ReactNode
|
||||
children: ReactNode
|
||||
initialValue: any
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,8 @@ import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import { ColumnGroupType, ColumnType } from 'antd/lib/table'
|
||||
import { Table as RawTable, TableProps } from 'antd'
|
||||
|
||||
import { OmitExtends } from '@utils'
|
||||
import { getTableSettings, setTableSettings } from '@utils/storage'
|
||||
import { applySettings, ColumnSettings, TableSettings } from '@utils/table_settings'
|
||||
import type { OmitExtends } from '@utils/types'
|
||||
import { applyTableSettings, getTableSettings, setTableSettings, TableColumnSettings, TableSettings } from '@utils'
|
||||
|
||||
import TableSettingsChanger from './TableSettingsChanger'
|
||||
import { tryAddKeys } from './EditableTable'
|
||||
@ -12,7 +11,7 @@ import { tryAddKeys } from './EditableTable'
|
||||
import '@styles/index.css'
|
||||
|
||||
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> & {
|
||||
columns: TableColumns
|
||||
@ -33,7 +32,7 @@ export const Table = memo<TableContainer>(({ columns, dataSource, tableName, sho
|
||||
|
||||
useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName])
|
||||
useEffect(() => setNewColumns(() => {
|
||||
const newColumns = applySettings(columns, settings)
|
||||
const newColumns = applyTableSettings(columns, settings)
|
||||
if (tableName && showSettingsChanger) {
|
||||
const oldTitle = newColumns[0].title
|
||||
newColumns[0].title = (props) => (
|
||||
|
@ -3,16 +3,16 @@ import { ColumnsType } from 'antd/lib/table'
|
||||
import { Button, Modal, Switch, Table } from 'antd'
|
||||
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 { makeColumn } from '.'
|
||||
|
||||
const parseSettings = (columns?: TableColumns, settings?: TableSettings | null): ColumnSettings[] => {
|
||||
const newSettings = mergeSettings(makeSettings(columns ?? []), settings ?? {})
|
||||
const parseSettings = (columns?: TableColumns, settings?: TableSettings | null): TableColumnSettings[] => {
|
||||
const newSettings = mergeTableSettings(makeTableSettings(columns ?? []), settings ?? {})
|
||||
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]))
|
||||
|
||||
export type TableSettingsChangerProps = {
|
||||
@ -24,8 +24,8 @@ export type TableSettingsChangerProps = {
|
||||
|
||||
export const TableSettingsChanger = memo<TableSettingsChangerProps>(({ title, columns, settings, onChange }) => {
|
||||
const [visible, setVisible] = useState<boolean>(false)
|
||||
const [newSettings, setNewSettings] = useState<ColumnSettings[]>(parseSettings(columns, settings))
|
||||
const [tableColumns, setTableColumns] = useState<ColumnsType<ColumnSettings>>([])
|
||||
const [newSettings, setNewSettings] = useState<TableColumnSettings[]>(parseSettings(columns, settings))
|
||||
const [tableColumns, setTableColumns] = useState<ColumnsType<TableColumnSettings>>([])
|
||||
|
||||
const onVisibilityChange = useCallback((index: number, visible: boolean) => {
|
||||
setNewSettings((oldSettings) => {
|
||||
@ -52,7 +52,7 @@ export const TableSettingsChanger = memo<TableSettingsChangerProps>(({ title, co
|
||||
<Button type={'link'} onClick={() => toogleAll(true)}>Показать все</Button>
|
||||
</>
|
||||
),
|
||||
render: (visible: boolean, _?: ColumnSettings, index: number = NaN) => (
|
||||
render: (visible: boolean, _?: TableColumnSettings, index: number = NaN) => (
|
||||
<Switch
|
||||
checked={visible}
|
||||
checkedChildren={'Отображён'}
|
||||
|
@ -2,7 +2,7 @@ import { Moment } from 'moment'
|
||||
import { TimePicker, TimePickerProps } from 'antd'
|
||||
import { memo, useCallback, useMemo } from 'react'
|
||||
|
||||
import { defaultTimeFormat, momentToTime, timeToMoment } from '@utils/datetime'
|
||||
import { defaultTimeFormat, momentToTime, timeToMoment } from '@utils'
|
||||
import { TimeDto } from '@api'
|
||||
|
||||
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 { TimeDto } from '@api'
|
||||
|
||||
|
@ -1,19 +1,20 @@
|
||||
import { memo, MouseEventHandler, useCallback, useState } from 'react'
|
||||
import { Link, useHistory, useLocation } from 'react-router-dom'
|
||||
import { Button, Dropdown, DropDownProps, Menu } from 'antd'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import { Button, Dropdown, DropDownProps } from 'antd'
|
||||
import { UserOutlined } from '@ant-design/icons'
|
||||
|
||||
import { getUserLogin, removeUser } from '@utils/storage'
|
||||
|
||||
import { getUserLogin, removeUser } from '@utils'
|
||||
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 }
|
||||
|
||||
export const UserMenu = memo<UserMenuProps>(({ isAdmin, ...other }) => {
|
||||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false)
|
||||
|
||||
const history = useHistory()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
const onChangePasswordClick: MouseEventHandler = useCallback((e) => {
|
||||
@ -23,8 +24,8 @@ export const UserMenu = memo<UserMenuProps>(({ isAdmin, ...other }) => {
|
||||
|
||||
const onChangePasswordOk = useCallback(() => {
|
||||
setIsModalVisible(false)
|
||||
history.push({ pathname: '/login', state: { from: location.pathname }})
|
||||
}, [history, location])
|
||||
navigate('/login', { state: { from: location.pathname }})
|
||||
}, [navigate, location])
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -32,19 +33,15 @@ export const UserMenu = memo<UserMenuProps>(({ isAdmin, ...other }) => {
|
||||
{...other}
|
||||
placement={'bottomRight'}
|
||||
overlay={(
|
||||
<Menu style={{ textAlign: 'right' }}>
|
||||
<PrivateMenu style={{ textAlign: 'right' }}>
|
||||
{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>
|
||||
<Link to={'/'} onClick={onChangePasswordClick}>Сменить пароль</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Link to={{ pathname: '/login', state: { from: location.pathname }}} onClick={removeUser}>Выход</Link>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
<PrivateMenu.Link visible onClick={onChangePasswordClick} title={'Сменить пароль'} />
|
||||
<PrivateMenu.Link visible path={'/login'} onClick={removeUser} title={'Выход'} />
|
||||
</PrivateMenu>
|
||||
)}
|
||||
>
|
||||
<Button icon={<UserOutlined/>}>{getUserLogin()}</Button>
|
||||
|
@ -2,7 +2,7 @@ import { notification } from 'antd'
|
||||
import { Dispatch, ReactNode, SetStateAction } from 'react'
|
||||
|
||||
import { isDev } from '@utils'
|
||||
import { getUserToken } from '@utils/storage'
|
||||
import { getUserToken } from '@utils'
|
||||
import { ApiError, FileInfoDto } from '@api'
|
||||
|
||||
const notificationTypeDictionary = new Map([
|
||||
|
@ -2,7 +2,7 @@ import { Tag, TreeSelect } from 'antd'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { hasPermission } from '@utils'
|
||||
import { DepositService } from '@api'
|
||||
|
||||
export const getTreeData = async () => {
|
||||
@ -40,7 +40,8 @@ export const WellSelector = memo(({ idWell, value, onChange, treeData, treeLabel
|
||||
const [wellsTree, setWellsTree] = useState([])
|
||||
const [wellLabels, setWellLabels] = useState([])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const wellsTree = treeData ?? await getTreeData()
|
||||
const labels = treeLabels ?? getTreeLabels(wellsTree)
|
||||
@ -50,7 +51,8 @@ export const WellSelector = memo(({ idWell, value, onChange, treeData, treeLabel
|
||||
null,
|
||||
'Не удалось загрузить список скважин',
|
||||
'Получение списка скважин'
|
||||
), [idWell, treeData, treeLabels])
|
||||
)
|
||||
}, [idWell, treeData, treeLabels])
|
||||
|
||||
return (
|
||||
<TreeSelect
|
||||
|
@ -3,7 +3,7 @@ import { LabelInValueType } from 'rc-select/lib/Select'
|
||||
import { RawValueType } from 'rc-tree-select/lib/TreeSelect'
|
||||
import { DefaultValueType } from 'rc-tree-select/lib/interface'
|
||||
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 LoaderPortal from '@components/LoaderPortal'
|
||||
@ -29,40 +29,47 @@ export type TreeNodeData = {
|
||||
}
|
||||
|
||||
const getLabel = (wellsTree: TreeNodeData[], value?: string): string | undefined => {
|
||||
if (!value) return value
|
||||
const type = value.replaceAll('/', ' ').trim().split(' ')[0]
|
||||
const result = value?.match(/^\/([^\/]+)\/([^\/?]+)/) // pattern "/:type/:id"
|
||||
if (wellsTree.length <= 0 || !result) return
|
||||
const [url, type] = result
|
||||
let deposit: TreeNodeData | undefined
|
||||
let cluster: TreeNodeData | undefined
|
||||
let well: TreeNodeData | undefined
|
||||
switch (type) {
|
||||
case 'deposit':
|
||||
deposit = wellsTree.find((deposit) => deposit.key === url)
|
||||
if (deposit)
|
||||
return `${deposit.title}`
|
||||
return 'Ошибка! Месторождение не найдено!'
|
||||
|
||||
case 'cluster':
|
||||
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)
|
||||
return `${deposit.title} / ${cluster.title}`
|
||||
break
|
||||
return 'Ошибка! Куст не найден!'
|
||||
|
||||
case 'well':
|
||||
deposit = wellsTree.find((deposit) => (
|
||||
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)
|
||||
return `${deposit.title} / ${cluster.title} / ${well.title}`
|
||||
break
|
||||
return 'Ошибка! Скважина не найдена!'
|
||||
|
||||
default: break
|
||||
}
|
||||
return 'Ошибка! Скважина не найдена!'
|
||||
}
|
||||
|
||||
export const WellTreeSelector = memo(({ ...other }) => {
|
||||
const [wellsTree, setWellsTree] = useState<TreeNodeData[]>([])
|
||||
const [showLoader, setShowLoader] = useState<boolean>(false)
|
||||
const [value, setValue] = useState<string>()
|
||||
const history = useHistory()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const routeMatch = useRouteMatch('/:route/:id')
|
||||
|
||||
useEffect(() => {
|
||||
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 onSelect = useCallback((value: RawValueType | LabelInValueType): void => {
|
||||
if (['number', 'string'].includes(typeof value))
|
||||
history.push({ pathname: String(value), state: { from: location.pathname }})
|
||||
}, [history, location])
|
||||
navigate(String(value), { state: { from: location.pathname }})
|
||||
}, [navigate, location])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { memo } from 'react'
|
||||
|
||||
import logo from '@images/logo_32.png'
|
||||
|
||||
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
|
||||
|
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 ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import reportWebVitals from './reportWebVitals'
|
||||
import App from './App'
|
||||
|
||||
import '@styles/index.css'
|
||||
|
||||
ReactDOM.render((
|
||||
const container = document.getElementById('root') ?? document.body
|
||||
const root = createRoot(container)
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
), document.getElementById('root'))
|
||||
)
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// 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 { Link, useHistory } from 'react-router-dom'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { CloseCircleOutlined } from '@ant-design/icons'
|
||||
|
||||
const { Paragraph, Text } = Typography
|
||||
|
||||
export const AccessDenied = memo(() => {
|
||||
const history = useHistory()
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<Result
|
||||
@ -28,7 +28,7 @@ export const AccessDenied = memo(() => {
|
||||
<Paragraph>
|
||||
<CloseCircleOutlined style={{ color: 'red' }} />
|
||||
Страницы не существует.
|
||||
<Link to={'#'} onClick={history.goBack}>Вернуться назад ></Link>
|
||||
<Link to={'#'} onClick={navigate(-1)}>Вернуться назад ></Link>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<CloseCircleOutlined style={{ color: 'red' }} />
|
||||
|
@ -12,13 +12,12 @@ import {
|
||||
} from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { AdminClusterService, AdminDepositService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||
import { min1 } from '@utils/validationRules'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
|
||||
import { coordsFixed } from './DepositController'
|
||||
|
||||
export const ClusterController = memo(() => {
|
||||
const ClusterController = memo(() => {
|
||||
const [deposits, setDeposits] = useState([])
|
||||
const [clusters, setClusters] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
@ -58,7 +57,8 @@ export const ClusterController = memo(() => {
|
||||
'Получение списка кустов'
|
||||
), [])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
let deposits = arrayOrDefault(await AdminDepositService.getAll())
|
||||
deposits = deposits.map((deposit) => ({ value: deposit.id, label: deposit.caption }))
|
||||
@ -67,9 +67,12 @@ export const ClusterController = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список месторождений`,
|
||||
'Получение списка месторождений'
|
||||
), [])
|
||||
)
|
||||
}, [])
|
||||
|
||||
useEffect(updateTable, [updateTable])
|
||||
useEffect(() => {
|
||||
updateTable()
|
||||
}, [updateTable])
|
||||
|
||||
const handlerProps = useMemo(() => ({
|
||||
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'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { AdminCompanyService, AdminCompanyTypeService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||
import { min1 } from '@utils/validationRules'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
|
||||
|
||||
export const CompanyController = memo(() => {
|
||||
const CompanyController = memo(() => {
|
||||
const [columns, setColumns] = useState([])
|
||||
const [companies, setCompanies] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
@ -31,7 +29,8 @@ export const CompanyController = memo(() => {
|
||||
setCompanies(arrayOrDefault(companies))
|
||||
}, [])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async() => {
|
||||
const companyTypes = arrayOrDefault(await AdminCompanyTypeService.getAll()).map((companyType) => ({
|
||||
value: companyType.id,
|
||||
@ -56,7 +55,8 @@ export const CompanyController = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список типов компаний`,
|
||||
'Получение списка типов команд'
|
||||
), [updateTable])
|
||||
)
|
||||
}, [updateTable])
|
||||
|
||||
const handlerProps = useMemo(() => ({
|
||||
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
|
||||
} from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { AdminCompanyTypeService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||
import { min1 } from '@utils/validationRules'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { AdminCompanyTypeService } from '@api'
|
||||
|
||||
const columns = [
|
||||
makeColumn('Название', 'caption', {
|
||||
@ -23,7 +22,7 @@ const columns = [
|
||||
}),
|
||||
]
|
||||
|
||||
export const CompanyTypeController = memo(() => {
|
||||
const CompanyTypeController = memo(() => {
|
||||
const [companyTypes, setCompanyTypes] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
@ -42,7 +41,9 @@ export const CompanyTypeController = memo(() => {
|
||||
'Получение списка типов компаний'
|
||||
), [])
|
||||
|
||||
useEffect(updateTable, [updateTable])
|
||||
useEffect(() => {
|
||||
updateTable()
|
||||
}, [updateTable])
|
||||
|
||||
const handlerProps = useMemo(() => ({
|
||||
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 { 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 { arrayOrDefault } from '@utils'
|
||||
import { AdminDepositService } from '@api'
|
||||
|
||||
export const coordsFixed = (coords) => coords && isFinite(coords) ? (+coords).toPrecision(10) : '-'
|
||||
@ -17,7 +16,7 @@ const depositColumns = [
|
||||
makeTimezoneColumn('Зона', 'timezone', null, true, { width: 150 }),
|
||||
]
|
||||
|
||||
export const DepositController = memo(() => {
|
||||
const DepositController = memo(() => {
|
||||
const [deposits, setDeposits] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
@ -39,7 +38,9 @@ export const DepositController = memo(() => {
|
||||
'Получение списка месторождений'
|
||||
), [])
|
||||
|
||||
useEffect(updateTable, [updateTable])
|
||||
useEffect(() => {
|
||||
updateTable()
|
||||
}, [updateTable])
|
||||
|
||||
const handlerProps = useMemo(() => ({
|
||||
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
|
||||
} from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { AdminPermissionService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||
import { min1 } from '@utils/validationRules'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { AdminPermissionService } from '@api'
|
||||
|
||||
const columns = [
|
||||
makeColumn('Название', 'name', {
|
||||
@ -26,7 +25,7 @@ const columns = [
|
||||
}),
|
||||
]
|
||||
|
||||
export const PermissionController = memo(() => {
|
||||
const PermissionController = memo(() => {
|
||||
const [permissions, setPermissions] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
@ -47,7 +46,9 @@ export const PermissionController = memo(() => {
|
||||
'Получение списка прав'
|
||||
), [])
|
||||
|
||||
useEffect(() => updateTable(), [updateTable])
|
||||
useEffect(() => {
|
||||
updateTable()
|
||||
}, [updateTable])
|
||||
|
||||
const handlerProps = useMemo(() => ({
|
||||
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 { EditableTable, makeActionHandler, makeTagColumn, makeTextColumn } from '@components/Table'
|
||||
import { AdminPermissionService, AdminUserRoleService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||
import { min1 } from '@utils/validationRules'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
|
||||
export const RoleController = memo(() => {
|
||||
const RoleController = memo(() => {
|
||||
const [permissions, setPermissions] = useState([])
|
||||
const [roles, setRoles] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
@ -38,7 +37,8 @@ export const RoleController = memo(() => {
|
||||
setRoles(arrayOrDefault(roles))
|
||||
}, [])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const permissions = await AdminPermissionService.getAll()
|
||||
setPermissions(arrayOrDefault(permissions))
|
||||
@ -47,7 +47,8 @@ export const RoleController = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список ролей`,
|
||||
'Получение списка ролей'
|
||||
), [loadRoles])
|
||||
)
|
||||
}, [loadRoles])
|
||||
|
||||
const handlerProps = useMemo(() => ({
|
||||
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 { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import TelemetrySelect from '@components/selectors/TelemetrySelect'
|
||||
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||
import { AdminTelemetryService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
|
||||
const { Item } = Descriptions
|
||||
|
||||
@ -32,7 +32,7 @@ export const TelemetryInfo = memo(({ info, danger, ...other }) => (
|
||||
</Descriptions>
|
||||
))
|
||||
|
||||
export const TelemetryMerger = memo(() => {
|
||||
const TelemetryMerger = memo(() => {
|
||||
const [primary, setPrimary] = useState(null)
|
||||
const [secondary, setSecondary] = useState(null)
|
||||
const [telemetry, setTelemetry] = useState(null)
|
||||
@ -68,7 +68,9 @@ export const TelemetryMerger = memo(() => {
|
||||
'Объединение телеметрий',
|
||||
), [updateTelemetry, secondary, primary])
|
||||
|
||||
useEffect(updateTelemetry, [updateTelemetry])
|
||||
useEffect(() => {
|
||||
updateTelemetry()
|
||||
}, [updateTelemetry])
|
||||
|
||||
useEffect(() => {
|
||||
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 { useNavigate } from 'react-router-dom'
|
||||
import { PullRequestOutlined } from '@ant-design/icons'
|
||||
import { Button, Input } from 'antd'
|
||||
|
||||
@ -13,18 +14,17 @@ import {
|
||||
} from '@components/Table'
|
||||
import Poprompt from '@components/selectors/Poprompt'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||
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 [showLoader, setShowLoader] = useState(false)
|
||||
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) => (
|
||||
<Poprompt
|
||||
@ -81,7 +81,8 @@ export const TelemetryController = memo(() => {
|
||||
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
||||
), [telemetryData, searchValue])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const telemetryData = arrayOrDefault(await AdminTelemetryService.getAll())
|
||||
setTelemetryData(telemetryData.map((telemetry) => ({
|
||||
@ -94,7 +95,8 @@ export const TelemetryController = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список телеметрии скважин`,
|
||||
'Полученик списка телеметрии скважин'
|
||||
), [])
|
||||
)
|
||||
}, [])
|
||||
|
||||
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 { lazy, memo, Suspense, useContext, useMemo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
|
||||
import { RootPathContext } from '@asb/context'
|
||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
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 TelemetryMerger = lazy(() => import('./TelemetryMerger'))
|
||||
|
||||
export const Telemetry = memo(() => {
|
||||
const { tab } = useParams()
|
||||
|
||||
const root = useContext(RootPathContext)
|
||||
const Telemetry = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/telemetry`, [root])
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Layout>
|
||||
<PrivateMenu mode={'horizontal'} selectable={true} selectedKeys={[tab]}>
|
||||
<PrivateMenu.Link key={'viewer'} title={'Просмотр'} />
|
||||
<PrivateMenu.Link key={'merger'} title={'Объединение'} />
|
||||
<PrivateMenu>
|
||||
<PrivateMenu.Link content={TelemetryViewer} />
|
||||
<PrivateMenu.Link content={TelemetryMerger} />
|
||||
</PrivateMenu>
|
||||
|
||||
<Layout>
|
||||
<Layout.Content className={'site-layout-background'}>
|
||||
<Suspense fallback={<SuspenseFallback />}>
|
||||
<PrivateSwitch elseRedirect={['viewer', 'merger']}>
|
||||
<TelemetryViewer key={'viewer'} />
|
||||
<TelemetryMerger key={'merger'} />
|
||||
</PrivateSwitch>
|
||||
</Suspense>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={TelemetryViewer.route} replace />} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
<Route path={TelemetryViewer.route} element={<TelemetryViewer />} />
|
||||
<Route path={TelemetryMerger.route} element={<TelemetryMerger />} />
|
||||
</Routes>
|
||||
</Layout.Content>
|
||||
</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 { AdminCompanyService, AdminUserRoleService, AdminUserService } from '@api'
|
||||
import { createLoginRules, nameRules, phoneRules, emailRules } from '@utils/validationRules'
|
||||
import { makeTextOnFilter, makeTextFilters, makeArrayOnFilter } from '@utils/table'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { makeTextOnFilter, makeTextFilters, makeArrayOnFilter } from '@utils/filters'
|
||||
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import RoleTag from './RoleTag'
|
||||
|
||||
const SEARCH_TIMEOUT = 400
|
||||
|
||||
export const UserController = memo(() => {
|
||||
const UserController = memo(() => {
|
||||
const [users, setUsers] = useState([])
|
||||
const [filteredUsers, setFilteredUsers] = useState([])
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
@ -35,7 +34,8 @@ export const UserController = memo(() => {
|
||||
const [selectedUser, setSelectedUser] = useState(null)
|
||||
const [subject, setSubject] = useState(null)
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const filteredUsers = users.filter((user) => user && (!searchValue || [
|
||||
user.login ?? '',
|
||||
@ -51,7 +51,8 @@ export const UserController = memo(() => {
|
||||
},
|
||||
setIsSearching,
|
||||
`Не удалось произвести поиск пользователей`
|
||||
), [users, searchValue])
|
||||
)
|
||||
}, [users, searchValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (!subject) {
|
||||
@ -91,7 +92,8 @@ export const UserController = memo(() => {
|
||||
'Получение списка пользователей'
|
||||
), [])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const roles = arrayOrDefault(await AdminUserRoleService.getAll())
|
||||
const companies = arrayOrDefault(await AdminCompanyService.getAll()).map((company) => ({
|
||||
@ -170,7 +172,8 @@ export const UserController = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список компаний`,
|
||||
'Получение списка компаний'
|
||||
), [])
|
||||
)
|
||||
}, [])
|
||||
|
||||
const handlerProps = useMemo(() => ({
|
||||
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 { defaultPagination, makeColumn, makeDateSorter, makeStringSorter, Table } from '@components/Table'
|
||||
import { arrayOrDefault, formatDate, wrapPrivateComponent } from '@utils'
|
||||
import { RequestTrackerService } from '@api'
|
||||
import { arrayOrDefault, formatDate } from '@utils'
|
||||
|
||||
const logRecordCount = 1000
|
||||
|
||||
@ -17,7 +17,7 @@ const columns = [
|
||||
}),
|
||||
]
|
||||
|
||||
export const VisitLog = memo(() => {
|
||||
const VisitLog = memo(() => {
|
||||
const [logData, setLogData] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
@ -29,7 +29,8 @@ export const VisitLog = memo(() => {
|
||||
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
||||
), [logData, searchValue])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const logData = arrayOrDefault(await RequestTrackerService.getUsersStat(logRecordCount))
|
||||
logData.forEach((log) => log.key = `${log.login}${log.ip}`)
|
||||
@ -38,7 +39,8 @@ export const VisitLog = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список последних посещений пользователей`,
|
||||
'Получение списка последних посещений'
|
||||
), [])
|
||||
)
|
||||
}, [])
|
||||
|
||||
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 { TelemetryView, CompanyView } from '@components/views'
|
||||
import TelemetrySelect from '@components/selectors/TelemetrySelect'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { arrayOrDefault, hasPermission, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import { coordsFixed } from '../DepositController'
|
||||
|
||||
@ -37,7 +36,7 @@ const recordParser = (record) => ({
|
||||
idTelemetry: record.telemetry?.id,
|
||||
})
|
||||
|
||||
export const WellController = memo(() => {
|
||||
const WellController = memo(() => {
|
||||
const [columns, setColumns] = useState([])
|
||||
const [wells, setWells] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
@ -74,7 +73,8 @@ export const WellController = memo(() => {
|
||||
/>
|
||||
))
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const companies = arrayOrDefault(await AdminCompanyService.getAll())
|
||||
const telemetry = arrayOrDefault(await AdminTelemetryService.getAll())
|
||||
@ -118,7 +118,8 @@ export const WellController = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список кустов`,
|
||||
'Получение списка кустов'
|
||||
), [updateTable])
|
||||
)
|
||||
}, [updateTable])
|
||||
|
||||
const handlerProps = useMemo(() => ({
|
||||
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 { lazy, memo, Suspense, useContext, useMemo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import { RootPathContext } from '@asb/context'
|
||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
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 CompanyController = lazy(() => import( './CompanyController'))
|
||||
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 AdminPanel = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/admin`, [root])
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Layout>
|
||||
<PrivateMenu mode={'horizontal'} selectable={true} selectedKeys={[tab]}>
|
||||
<PrivateMenu.Link key={'deposit' } title={'Месторождения' } />
|
||||
<PrivateMenu.Link key={'cluster' } title={'Кусты' } />
|
||||
<PrivateMenu.Link key={'well' } title={'Скважины' } />
|
||||
<PrivateMenu.Link key={'user' } title={'Пользователи' } />
|
||||
<PrivateMenu.Link key={'company' } title={'Компании' } />
|
||||
<PrivateMenu.Link key={'company_type'} title={'Типы компаний' } />
|
||||
<PrivateMenu.Link key={'role' } title={'Роли' } />
|
||||
<PrivateMenu.Link key={'permission' } title={'Разрешения' } />
|
||||
<PrivateMenu.Link key={'telemetry' } title={'Телеметрия' } />
|
||||
<PrivateMenu.Link key={'visit_log' } title={'Журнал посещений'} />
|
||||
<AdminLayoutPortal title={'Администраторская панель'}>
|
||||
<PrivateMenu>
|
||||
<PrivateMenu.Link content={DepositController} />
|
||||
<PrivateMenu.Link content={ClusterController} />
|
||||
<PrivateMenu.Link content={WellController} />
|
||||
<PrivateMenu.Link content={UserController} />
|
||||
<PrivateMenu.Link content={CompanyController} />
|
||||
<PrivateMenu.Link content={CompanyTypeController} />
|
||||
<PrivateMenu.Link content={RoleController} />
|
||||
<PrivateMenu.Link content={PermissionController} />
|
||||
<PrivateMenu.Link content={Telemetry} />
|
||||
<PrivateMenu.Link content={VisitLog} />
|
||||
</PrivateMenu>
|
||||
|
||||
<Layout>
|
||||
<Layout.Content className={'site-layout-background'}>
|
||||
<Suspense fallback={<SuspenseFallback />}>
|
||||
<PrivateSwitch elseRedirect={['deposit', 'cluster', 'well', 'user', 'company', 'company_type', 'role', 'permission', 'telemetry', 'visit_log']}>
|
||||
<DepositController key={'deposit'} />
|
||||
<ClusterController key={'cluster'} />
|
||||
<WellController key={'well'} />
|
||||
<UserController key={'user'} />
|
||||
<CompanyController key={'company'} />
|
||||
<CompanyTypeController key={'company_type'} />
|
||||
<RoleController key={'role'} />
|
||||
<PermissionController key={'permission'} />
|
||||
<TelemetrySection key={'telemetry/:tab?'} />
|
||||
<VisitLog key={'visit_log'} />
|
||||
</PrivateSwitch>
|
||||
</Suspense>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={VisitLog.route} replace />} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
<Route path={DepositController.route} element={<DepositController />} />
|
||||
<Route path={ClusterController.route} element={<ClusterController />} />
|
||||
<Route path={WellController.route} element={<WellController />} />
|
||||
<Route path={UserController.route} element={<UserController />} />
|
||||
<Route path={CompanyController.route} element={<CompanyController />} />
|
||||
<Route path={CompanyTypeController.route} element={<CompanyTypeController />} />
|
||||
<Route path={RoleController.route} element={<RoleController />} />
|
||||
<Route path={PermissionController.route} element={<PermissionController />} />
|
||||
<Route path={Telemetry.route} element={<Telemetry />} />
|
||||
<Route path={VisitLog.route} element={<VisitLog />} />
|
||||
</Routes>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</AdminLayoutPortal>
|
||||
</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 { 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 { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { WellSelector } from '@components/selectors/WellSelector'
|
||||
import { makeGroupColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
|
||||
import { OperationStatService, WellOperationService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
|
||||
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import '@styles/index.css'
|
||||
import '@styles/statistics.less'
|
||||
@ -64,7 +63,7 @@ const getWellData = async (wellsList) => {
|
||||
return wellData
|
||||
}
|
||||
|
||||
export const Statistics = memo(() => {
|
||||
const Statistics = memo(() => {
|
||||
const [sectionTypes, setSectionTypes] = useState([])
|
||||
const [avgColumns, setAvgColumns] = useState(defaultColumns)
|
||||
const [cmpColumns, setCmpColumns] = useState(defaultColumns)
|
||||
@ -77,7 +76,7 @@ export const Statistics = memo(() => {
|
||||
const [cmpData, setCmpData] = useState([])
|
||||
const [avgRow, setAvgRow] = useState({})
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const cmpSpeedRender = useCallback((key) => (section) => {
|
||||
let spanClass = ''
|
||||
@ -97,7 +96,8 @@ export const Statistics = memo(() => {
|
||||
)
|
||||
}, [avgRow])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const types = await WellOperationService.getSectionTypes(idWell)
|
||||
setSectionTypes(Object.entries(types))
|
||||
@ -105,9 +105,11 @@ export const Statistics = memo(() => {
|
||||
setIsPageLoading,
|
||||
`Не удалось получить типы секции`,
|
||||
`Получение списка возможных секций`,
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const filteredSections = avgData?.length > 0 ? sectionTypes.filter(([id, _]) => avgData.some((row) => `section_${id}` in row)) : sectionTypes
|
||||
|
||||
@ -124,9 +126,11 @@ export const Statistics = memo(() => {
|
||||
},
|
||||
setIsPageLoading,
|
||||
'Не удалось установить необходимые столбцы'
|
||||
), [sectionTypes, avgData, cmpSpeedRender])
|
||||
)
|
||||
}, [sectionTypes, avgData, cmpSpeedRender])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const avgData = await getWellData(avgWells)
|
||||
setAvgData(avgData)
|
||||
@ -148,16 +152,19 @@ export const Statistics = memo(() => {
|
||||
},
|
||||
setIsAvgTableLoading,
|
||||
'Не удалось загрузить данные для расчёта средних значений',
|
||||
), [avgWells])
|
||||
)
|
||||
}, [avgWells])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const cmpData = await getWellData(cmpWells)
|
||||
setCmpData(cmpData)
|
||||
},
|
||||
setIsCmpTableLoading,
|
||||
'Не удалось получить скважины для сравнения',
|
||||
), [cmpWells])
|
||||
)
|
||||
}, [cmpWells])
|
||||
|
||||
const getStatisticsAvgSummary = useCallback((data) => (
|
||||
<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 { Button, Modal, Popconfirm } from 'antd'
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import { Button, Modal, Popconfirm, Tooltip } from 'antd'
|
||||
|
||||
import { IdWellContext } from '@asb/context'
|
||||
import { useIdWell } from '@asb/context'
|
||||
import { Table } from '@components/Table'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
@ -15,13 +15,11 @@ export const NewParamsTable = memo(({ selectedWellsKeys }) => {
|
||||
const [showParamsLoader, setShowParamsLoader] = useState(false)
|
||||
const [isParamsModalVisible, setIsParamsModalVisible] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
async () => setParamsColumns(await getColumns(idWell))
|
||||
), [idWell])
|
||||
|
||||
useEffect(() => console.log(paramsColumns), [paramsColumns])
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(async () => setParamsColumns(await getColumns(idWell)))
|
||||
}, [idWell])
|
||||
|
||||
const onParamButtonClick = useCallback(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -61,7 +59,10 @@ export const NewParamsTable = memo(({ selectedWellsKeys }) => {
|
||||
width={1700}
|
||||
footer={(
|
||||
<Popconfirm title={'Заменить существующие режимы выбранными?'} onConfirm={onParamsAddClick}>
|
||||
<Button size={'large'} disabled={params.length <= 0}>Сохранить</Button>
|
||||
<Button
|
||||
size={'large'}
|
||||
disabled={params.length <= 0}
|
||||
>Сохранить</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
>
|
||||
|
@ -1,22 +1,23 @@
|
||||
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 { 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 LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { makeTextColumn, makeNumericColumnPlanFact, makeNumericColumn } from '@components/Table'
|
||||
import { WellCompositeService } from '@api'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import {
|
||||
hasPermission,
|
||||
wrapPrivateComponent,
|
||||
calcAndUpdateStatsBySections,
|
||||
makeFilterMinMaxFunction,
|
||||
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 NewParamsTable from './NewParamsTable'
|
||||
|
||||
@ -30,7 +31,7 @@ const sortBySectionId = (a, b) => a?.sectionId - b?.sectionId
|
||||
const filtersSectionsType = []
|
||||
const DAY_IN_MS = 1000 * 60 * 60 * 24
|
||||
|
||||
export const WellCompositeSections = memo(({ statsWells, selectedSections }) => {
|
||||
const WellCompositeSections = memo(({ statsWells, selectedSections }) => {
|
||||
const [selectedWells, setSelectedWells] = useState([])
|
||||
const [wellOperations, setWellOperations] = useState([])
|
||||
const [selectedWellsKeys, setSelectedWellsKeys] = useState([])
|
||||
@ -39,7 +40,7 @@ export const WellCompositeSections = memo(({ statsWells, selectedSections }) =>
|
||||
const [isTVDModalVisible, setIsTVDModalVisible] = useState(false)
|
||||
const [isOpsModalVisible, setIsOpsModalVisible] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
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 { useParams } from 'react-router-dom'
|
||||
import { useState, useEffect, memo, useMemo } from 'react'
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
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 WellSelector from '@components/selectors/WellSelector'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { arrayOrDefault, NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
import { OperationStatService, WellCompositeService } from '@api'
|
||||
|
||||
import ClusterWells from '@pages/Cluster/ClusterWells'
|
||||
import { WellCompositeSections } from './WellCompositeSections'
|
||||
import WellCompositeSections from './WellCompositeSections'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
export const WellCompositeEditor = memo(({ rootPath }) => {
|
||||
const { tab } = useParams()
|
||||
const idWell = useContext(IdWellContext)
|
||||
const properties = {
|
||||
requirements: ['OperationStat.get', 'WellComposite.get'],
|
||||
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 [showLoader, setShowLoader] = useState(false)
|
||||
@ -25,19 +33,21 @@ export const WellCompositeEditor = memo(({ rootPath }) => {
|
||||
const [selectedIdWells, setSelectedIdWells] = useState([])
|
||||
const [selectedSections, setSelectedSections] = useState([])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
try {
|
||||
const selected = await WellCompositeService.get(idWell)
|
||||
setSelectedSections(arrayOrDefault(selected))
|
||||
setSelectedSections(arrayOrDefault(await WellCompositeService.get(idWell)))
|
||||
} catch(e) {
|
||||
setSelectedSections([])
|
||||
throw e
|
||||
}
|
||||
},
|
||||
setShowLoader,
|
||||
'Не удалось загрузить список скважин',
|
||||
'Получение списка скважин'
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
useEffect(() => {
|
||||
const wellIds = selectedSections.map((value) => value.idWellSrc)
|
||||
@ -45,7 +55,8 @@ export const WellCompositeEditor = memo(({ rootPath }) => {
|
||||
setSelectedIdWells(wellIds)
|
||||
}, [selectedSections])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const stats = arrayOrDefault(await OperationStatService.getWellsStat(selectedIdWells))
|
||||
setStatsWells(stats)
|
||||
@ -53,7 +64,8 @@ export const WellCompositeEditor = memo(({ rootPath }) => {
|
||||
setShowTabLoader,
|
||||
'Не удалось загрузить статистику по скважинам/секциям',
|
||||
'Получение статистики по скважинам/секциям'
|
||||
), [selectedIdWells])
|
||||
)
|
||||
}, [selectedIdWells])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
@ -66,19 +78,22 @@ export const WellCompositeEditor = memo(({ rootPath }) => {
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<PrivateMenu root={rootPath} mode={'horizontal'} selectable={true} className={'well_menu'} selectedKeys={[tab]}>
|
||||
<PrivateMenu.Link key={'wells'} title={'Статистика по скважинам'} />
|
||||
<PrivateMenu.Link key={'sections'} title={'Статистика по секциям'} />
|
||||
<PrivateMenu root={rootPath} className={'well_menu'}>
|
||||
<PrivateMenu.Link content={ClusterWells} />
|
||||
<PrivateMenu.Link content={WellCompositeSections} />
|
||||
</PrivateMenu>
|
||||
</Col>
|
||||
</Row>
|
||||
<Layout>
|
||||
<Content className={'site-layout-background'}>
|
||||
<LoaderPortal show={showTabLoader}>
|
||||
<PrivateSwitch root={rootPath} elseRedirect={['wells', 'sections']}>
|
||||
<ClusterWells key={'wells'} statsWells={statsWells} />
|
||||
<WellCompositeSections key={'sections'} statsWells={statsWells} selectedSections={selectedSections} />
|
||||
</PrivateSwitch>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={ClusterWells.route} replace/>} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
|
||||
<Route path={ClusterWells.route} element={<ClusterWells statsWells={statsWells} />} />
|
||||
<Route path={WellCompositeSections.route} element={<WellCompositeSections statsWells={statsWells} selectedSections={selectedSections} />} />
|
||||
</Routes>
|
||||
</LoaderPortal>
|
||||
</Content>
|
||||
</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 { useParams } from 'react-router-dom'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { Layout } from 'antd'
|
||||
|
||||
import { RootPathContext } from '@asb/context'
|
||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import Statistics from './Statistics'
|
||||
import WellCompositeEditor from './WellCompositeEditor'
|
||||
|
||||
export const Analytics = memo(() => {
|
||||
const { tab } = useParams()
|
||||
const root = useContext(RootPathContext)
|
||||
const Analytics = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/analytics`, [root])
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Layout>
|
||||
<PrivateMenu mode={'horizontal'} selectable={true} className={'well_menu'} selectedKeys={[tab]}>
|
||||
<PrivateMenu.Link key={'composite'} title={'Композитная скважина'} />
|
||||
<PrivateMenu.Link key={'statistics'} title={'Оценка по ЦБ'} />
|
||||
<PrivateMenu className={'well_menu'}>
|
||||
<PrivateMenu.Link content={WellCompositeEditor} />
|
||||
<PrivateMenu.Link key={'statistics'} title={'Оценка по ЦБ'} content={Statistics} />
|
||||
</PrivateMenu>
|
||||
<Layout>
|
||||
<Layout.Content>
|
||||
<PrivateSwitch elseRedirect={'composite'}>
|
||||
<WellCompositeEditor key={'composite/:tab?'} rootPath={`${rootPath}/composite`} />
|
||||
<Statistics key={'statistics'} />
|
||||
</PrivateSwitch>
|
||||
<Layout.Content className={'site-layout-background'}>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={WellCompositeEditor.getKey()} replace />} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
|
||||
<Route path={WellCompositeEditor.route} element={<WellCompositeEditor />} />
|
||||
<Route path={Statistics.route} element={<Statistics />} />
|
||||
</Routes>
|
||||
</Layout.Content>
|
||||
</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 {
|
||||
getOperations,
|
||||
calcAndUpdateStatsBySections,
|
||||
makeFilterMinMaxFunction
|
||||
} from '@utils/functions'
|
||||
import { isRawDate } from '@utils'
|
||||
isRawDate,
|
||||
makeFilterMinMaxFunction,
|
||||
wrapPrivateComponent
|
||||
} from '@utils'
|
||||
|
||||
import { Tvd } from '@pages/WellOperations/Tvd'
|
||||
import Tvd from '@pages/WellOperations/Tvd'
|
||||
import WellOperationsTable from './WellOperationsTable'
|
||||
|
||||
const filtersMinMax = [
|
||||
@ -39,7 +40,7 @@ const ONLINE_DEADTIME = 600_000
|
||||
const getDate = (str) => isRawDate(str) ? new Date(str).toLocaleString() : '-'
|
||||
const numericRender = makeNumericRender(1)
|
||||
|
||||
export const ClusterWells = memo(({ statsWells }) => {
|
||||
const ClusterWells = memo(({ statsWells }) => {
|
||||
const [selectedWellId, setSelectedWellId] = useState(0)
|
||||
const [isTVDModalVisible, setIsTVDModalVisible] = 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,19 +1,21 @@
|
||||
import { useState, useEffect, memo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { OperationStatService } from '@api'
|
||||
import { LayoutPortal } from '@components/Layout'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||
import { OperationStatService } from '@api'
|
||||
|
||||
import ClusterWells from './ClusterWells'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
export const Cluster = memo(() => {
|
||||
const Cluster = memo(() => {
|
||||
const { idCluster } = useParams()
|
||||
const [data, setData] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const clusterData = await OperationStatService.getStatCluster(idCluster)
|
||||
setData(arrayOrDefault(clusterData?.statsWells))
|
||||
@ -21,13 +23,21 @@ export const Cluster = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить данные по кусту "${idCluster}"`,
|
||||
'Получение данных по кусту'
|
||||
), [idCluster])
|
||||
)
|
||||
}, [idCluster])
|
||||
|
||||
return (
|
||||
<LayoutPortal title={'Анализ скважин куста'}>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<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 { Link, useLocation } from 'react-router-dom'
|
||||
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 { LayoutPortal } from '@components/Layout'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { arrayOrDefault, limitValue, wrapPrivateComponent } from '@utils'
|
||||
import { ClusterService } from '@api'
|
||||
|
||||
import '@styles/index.css'
|
||||
|
||||
const defaultViewParams = { center: [60.81226, 70.0562], zoom: 5 }
|
||||
|
||||
const zoomLimit = limitValue(5, 15)
|
||||
|
||||
const calcViewParams = (clusters) => {
|
||||
if ((clusters?.length ?? 0) <= 0)
|
||||
return defaultViewParams
|
||||
@ -33,19 +36,20 @@ const calcViewParams = (clusters) => {
|
||||
// zoom min = 1 (mega far)
|
||||
// 4 - full Russia (161.6 deg)
|
||||
// 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 }
|
||||
}
|
||||
|
||||
export const Deposit = memo(() => {
|
||||
const Deposit = memo(() => {
|
||||
const [clustersData, setClustersData] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [viewParams, setViewParams] = useState(defaultViewParams)
|
||||
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const data = await ClusterService.getClusters()
|
||||
setClustersData(arrayOrDefault(data))
|
||||
@ -54,9 +58,11 @@ export const Deposit = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список кустов`,
|
||||
'Получить список кустов'
|
||||
), [])
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<LayoutPortal noSheet title={'Месторождение'}>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<div className={'h-100vh'}>
|
||||
<Map {...viewParams}>
|
||||
@ -75,7 +81,13 @@ export const Deposit = memo(() => {
|
||||
</Map>
|
||||
</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 { IdWellContext } from '@asb/context'
|
||||
import { useIdWell } from '@asb/context'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { UploadForm } from '@components/UploadForm'
|
||||
import { CompanyView, UserView } from '@components/views'
|
||||
import { invokeWebApiWrapperAsync, downloadFile, formatBytes } from '@components/factory'
|
||||
import { EditableTable, makeColumn, makeDateColumn, makeNumericColumn, makePaginationObject } from '@components/Table'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { hasPermission } from '@utils'
|
||||
import { FileService } from '@api'
|
||||
|
||||
const pageSize = 12
|
||||
@ -40,7 +40,7 @@ export const DocumentsTemplate = ({ idCategory, idWell: wellId, mimeTypes, heade
|
||||
const [files, setFiles] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const idwellContext = useContext(IdWellContext)
|
||||
const idwellContext = useIdWell()
|
||||
const idWell = useMemo(() => wellId ?? idwellContext, [wellId, idwellContext])
|
||||
|
||||
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])
|
||||
|
||||
useEffect(update, [update])
|
||||
useEffect(() => onChange?.(files), [files, onChange])
|
||||
useEffect(() => {
|
||||
update()
|
||||
}, [update])
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.(files)
|
||||
}, [files, onChange])
|
||||
|
||||
const handleFileDelete = useMemo(() => hasPermission(`File.edit${idCategory}`) && (async (file) => {
|
||||
await FileService.delete(idWell, file.id)
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { memo, useContext, useMemo } from 'react'
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { FolderOutlined } from '@ant-design/icons'
|
||||
import { Layout } from 'antd'
|
||||
|
||||
import { RootPathContext } from '@asb/context'
|
||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { getTabname, wrapPrivateComponent, NoAccessComponent } from '@utils'
|
||||
|
||||
import DocumentsTemplate from './DocumentsTemplate'
|
||||
|
||||
@ -23,9 +24,9 @@ export const documentCategories = [
|
||||
{ id: 9, key: 'closingService', title: 'Сервис по заканчиванию скважины' },
|
||||
]
|
||||
|
||||
export const MenuDocuments = memo(() => {
|
||||
const { category } = useParams()
|
||||
const root = useContext(RootPathContext)
|
||||
const MenuDocuments = memo(() => {
|
||||
const category = getTabname()
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/document`, [root])
|
||||
|
||||
return (
|
||||
@ -42,19 +43,30 @@ export const MenuDocuments = memo(() => {
|
||||
</PrivateMenu>
|
||||
<Layout>
|
||||
<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 => (
|
||||
<Route key={category.key} path={category.key} element={(
|
||||
<DocumentsTemplate
|
||||
key={category.key}
|
||||
idCategory={category.id}
|
||||
tableName={`documents_${category.key}`}
|
||||
/>
|
||||
)} />
|
||||
))}
|
||||
</PrivateSwitch>
|
||||
</Routes>
|
||||
</Content>
|
||||
</Layout>
|
||||
</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 { 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 { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { DrillingProgramService } from '@api'
|
||||
@ -21,9 +21,10 @@ export const CategoryAdder = memo(({ categories, onUpdate, className, ...other }
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [showCatLoader, setShowCatLoader] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
setOptions(categories.map((category) => ({
|
||||
label: category.name ?? category.shortName,
|
||||
@ -32,7 +33,8 @@ export const CategoryAdder = memo(({ categories, onUpdate, className, ...other }
|
||||
},
|
||||
setShowCatLoader,
|
||||
`Не удалось установить список доступных категорий для добавления`
|
||||
), [categories])
|
||||
)
|
||||
}, [categories])
|
||||
|
||||
const onFinish = useCallback(({ categories }) => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
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 { IdWellContext } from '@asb/context'
|
||||
import { useIdWell } from '@asb/context'
|
||||
import { UserView } from '@components/views'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
@ -30,11 +30,15 @@ export const CategoryEditor = memo(({ visible, category, onClosed }) => {
|
||||
const [subject, setSubject] = useState(null)
|
||||
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(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const filteredUsers = users.filter(({ user }) => user && [
|
||||
user.login ?? '',
|
||||
@ -50,7 +54,8 @@ export const CategoryEditor = memo(({ visible, category, onClosed }) => {
|
||||
},
|
||||
setIsSearching,
|
||||
`Не удалось произвести поиск пользователей`
|
||||
), [users, searchValue])
|
||||
)
|
||||
}, [users, searchValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (!subject) {
|
||||
@ -71,14 +76,16 @@ export const CategoryEditor = memo(({ visible, category, onClosed }) => {
|
||||
}
|
||||
}, [subject])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const allUsers = arrayOrDefault(await DrillingProgramService.getAvailableUsers(idWell))
|
||||
setAllUsers(allUsers)
|
||||
},
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список доступных пользователей скважины "${idWell}"`
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
const calcUsers = useCallback(() => {
|
||||
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 { IdWellContext } from '@asb/context'
|
||||
import { useIdWell } from '@asb/context'
|
||||
import { CompanyView } from '@components/views'
|
||||
import DownloadLink from '@components/DownloadLink'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
@ -65,9 +65,10 @@ export const CategoryHistory = ({ idCategory, visible, onClose }) => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [companyName, setCompanyName] = useState('')
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
if (!visible) return
|
||||
const [begin, end] = range?.length > 1 ? [range[0].toISOString(), range[1].toISOString()] : [null, null]
|
||||
@ -79,7 +80,8 @@ export const CategoryHistory = ({ idCategory, visible, onClose }) => {
|
||||
},
|
||||
setIsLoading,
|
||||
`Не удалось загрузить историю категорий "${idCategory}" скважины "${idWell}"`
|
||||
), [idWell, idCategory, visible, range, companyName, fileName, page, pageSize])
|
||||
)
|
||||
}, [idWell, idCategory, visible, range, companyName, fileName, page, pageSize])
|
||||
|
||||
const onPaginationChange = useCallback((page, pageSize) => {
|
||||
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 {
|
||||
DeleteOutlined,
|
||||
@ -6,7 +6,7 @@ import {
|
||||
TableOutlined,
|
||||
} from '@ant-design/icons'
|
||||
|
||||
import { IdWellContext } from '@asb/context'
|
||||
import { useIdWell } from '@asb/context'
|
||||
import { UserView } from '@components/views'
|
||||
import UploadForm from '@components/UploadForm'
|
||||
import DownloadLink from '@components/DownloadLink'
|
||||
@ -45,7 +45,7 @@ export const CategoryRender = memo(({ partData, onUpdate, onEdit, onHistory, set
|
||||
file // Информация о файле
|
||||
} = partData ?? {}
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const uploadUrl = useMemo(() => `/api/well/${idWell}/drillingProgram/part/${idFileCategory}`, [idWell, idFileCategory])
|
||||
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 LoaderPortal from '@components/LoaderPortal'
|
||||
import { downloadFile, formatBytes, invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { arrayOrDefault, formatDate } from '@utils'
|
||||
import { arrayOrDefault, formatDate, wrapPrivateComponent } from '@utils'
|
||||
import { DrillingProgramService } from '@api'
|
||||
|
||||
import CategoryAdder from './CategoryAdder'
|
||||
@ -41,7 +41,7 @@ const STATE_STRING = {
|
||||
[ID_STATE.Unknown]: { icon: WarningOutlined, text: 'Неизвестно' },
|
||||
}
|
||||
|
||||
export const DrillingProgram = memo(() => {
|
||||
const DrillingProgram = memo(() => {
|
||||
const [selectedCategory, setSelectedCategory] = useState()
|
||||
const [historyVisible, setHistoryVisible] = useState(false)
|
||||
const [editorVisible, setEditorVisible] = useState(false)
|
||||
@ -79,7 +79,9 @@ export const DrillingProgram = memo(() => {
|
||||
`Не удалось загрузить название скважины "${idWell}"`
|
||||
), [idWell])
|
||||
|
||||
useEffect(() => updateData(), [updateData])
|
||||
useEffect(() => {
|
||||
updateData()
|
||||
}, [updateData])
|
||||
|
||||
const onCategoryEdit = useCallback((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 { Link, useHistory, useLocation } from 'react-router-dom'
|
||||
import { Card, Form, Input, Button } from 'antd'
|
||||
import { Link, useNavigate, useLocation } from 'react-router-dom'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons'
|
||||
import { Card, Form, Input, Button } from 'antd'
|
||||
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { loginRules, passwordRules } from '@utils/validationRules'
|
||||
import { setUser } from '@utils/storage'
|
||||
import { setUser, wrapPrivateComponent } from '@utils'
|
||||
import { AuthService } from '@api'
|
||||
|
||||
import '@styles/index.css'
|
||||
import Logo from '@images/Logo'
|
||||
|
||||
export const Login = memo(() => {
|
||||
const Login = memo(() => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const history = useHistory()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
const handleLogin = useCallback((formData) => invokeWebApiWrapperAsync(
|
||||
@ -23,20 +23,19 @@ export const Login = memo(() => {
|
||||
const user = await AuthService.login(formData)
|
||||
if (!user) throw Error('Неправильный логин или пароль')
|
||||
setUser(user)
|
||||
console.log(location.state?.from)
|
||||
history.push(location.state?.from ?? 'well')
|
||||
navigate(location.state?.from ?? '/deposit')
|
||||
},
|
||||
setShowLoader,
|
||||
(ex) => ex?.message ?? 'Ошибка входа',
|
||||
'Вход в систему'
|
||||
), [history, location])
|
||||
), [navigate, location])
|
||||
|
||||
return (
|
||||
<div className={'login_page shadow'}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Logo style={{ marginBottom: '10px' }} />
|
||||
<LoaderPortal show={showLoader}>
|
||||
<Card title={'Система мониторинга'} className={'shadow'} bordered={true} style={{ width: 350 }}>
|
||||
<Card bordered title={'Система мониторинга'} className={'shadow'} style={{ width: 350 }}>
|
||||
<Form onFinish={handleLogin}>
|
||||
<Form.Item name={'login'} rules={loginRules}>
|
||||
<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 {
|
||||
CheckSquareOutlined,
|
||||
@ -9,11 +9,10 @@ import {
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons'
|
||||
|
||||
import { IdWellContext } from '@asb/context'
|
||||
import { useIdWell } from '@asb/context'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { formatDate } from '@utils'
|
||||
import { hasPermission, formatDate } from '@utils'
|
||||
import { MeasureService } from '@api'
|
||||
|
||||
import { View } from './View'
|
||||
@ -33,7 +32,7 @@ export const MeasureTable = memo(({ group, updateMeasuresFunc, additionalButtons
|
||||
const [isTableEditing, setIsTableEditing] = useState(false)
|
||||
const [editingActionName, setEditingActionName] = useState('')
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
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, useContext } from 'react'
|
||||
import { useState, useEffect, memo } from 'react'
|
||||
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 { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
import { MeasureService } from '@api'
|
||||
|
||||
import { MeasureTable } from './MeasureTable'
|
||||
@ -42,15 +43,16 @@ const defaultData = [
|
||||
}
|
||||
]
|
||||
|
||||
export const Measure = memo(() => {
|
||||
const Measure = memo(() => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(true)
|
||||
const [data, setData] = useState(defaultData)
|
||||
const [tableIdx, setTableIdx] = useState(-1)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
if (!isMeasuresUpdating) return
|
||||
const measures = await MeasureService.getHisory(idWell)
|
||||
@ -69,7 +71,8 @@ export const Measure = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить последние данные по скважине ${idWell}`,
|
||||
'Получение последних данных телеметрий'
|
||||
), [idWell, isMeasuresUpdating])
|
||||
)
|
||||
}, [idWell, isMeasuresUpdating])
|
||||
|
||||
return (
|
||||
<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 { Link, useHistory } from 'react-router-dom'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { Card, Form, Input, Button } from 'antd'
|
||||
import {
|
||||
UserOutlined,
|
||||
@ -20,6 +20,7 @@ import {
|
||||
passwordRules,
|
||||
phoneRules
|
||||
} from '@utils/validationRules'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
|
||||
import Logo from '@images/Logo'
|
||||
|
||||
@ -52,17 +53,17 @@ const createInput = (name, placeholder, rules, isPassword, dependencies) => (
|
||||
|
||||
export const Register = memo(() => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const history = useHistory()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleRegister = useCallback((formData) => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
await AuthService.register(formData)
|
||||
history.push('/login')
|
||||
navigate('/login')
|
||||
},
|
||||
setShowLoader,
|
||||
`Ошибка отправки заявки на регистрацию`,
|
||||
'Отправка заявки на регистрацию'
|
||||
), [history])
|
||||
), [navigate])
|
||||
|
||||
return (
|
||||
<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 { 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 { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
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 [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const setFields = useCallback((data) => form.setFieldsValue(data ? {
|
||||
...data,
|
||||
|
@ -1,25 +1,25 @@
|
||||
import moment from 'moment'
|
||||
import { Button } from 'antd'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
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 { DateRangeWrapper, Table, makeDateColumn, makeColumn } from '@components/Table'
|
||||
import { download, invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
|
||||
import { DailyReportService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
|
||||
import ReportEditor from './ReportEditor'
|
||||
|
||||
export const DailyReport = memo(() => {
|
||||
const DailyReport = memo(() => {
|
||||
const [data, setData] = useState([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [searchDate, setSearchDate] = useState([moment().subtract(1, 'week'), moment()])
|
||||
const [selectedReport, setSelectedReport] = useState(null)
|
||||
const [isEditorVisible, setIsEditorVisible] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const updateTable = useCallback(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -31,7 +31,9 @@ export const DailyReport = memo(() => {
|
||||
'Получение списка суточных рапортов',
|
||||
), [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])
|
||||
|
||||
@ -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 { useState, useEffect, memo, useContext } from 'react'
|
||||
import { useState, useEffect, memo } from 'react'
|
||||
import { FilePdfOutlined, FileTextOutlined } from '@ant-design/icons'
|
||||
|
||||
import { IdWellContext } from '@asb/context'
|
||||
import { useIdWell } from '@asb/context'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { Table, makeDateSorter, makeNumericSorter } from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync, downloadFile } from '@components/factory'
|
||||
@ -60,9 +60,10 @@ export const Reports = memo(() => {
|
||||
const [reports, setReports] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const reportsResponse = await ReportService.getAllReportsNamesByWell(idWell)
|
||||
const reports = reportsResponse.map(r => ({ ...r, key: r.id ?? r.name ?? r.date }))
|
||||
@ -71,7 +72,8 @@ export const Reports = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список рапортов по скважине "${idWell}"`,
|
||||
'Получение списка рапортов'
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
|
@ -1,12 +1,13 @@
|
||||
import 'moment/locale/ru'
|
||||
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 { IdWellContext } from '@asb/context'
|
||||
import { useIdWell } from '@asb/context'
|
||||
import { DateRangeWrapper } from 'components/Table'
|
||||
import { LoaderPortal } from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
import { Subscribe } from '@services/signalr'
|
||||
import { ReportService } from '@api'
|
||||
|
||||
@ -33,7 +34,7 @@ const reportFormats = [
|
||||
{ value: 1, label: 'LAS' },
|
||||
]
|
||||
|
||||
export const DiagramReport = memo(() => {
|
||||
const DiagramReport = memo(() => {
|
||||
const [aviableDateRange, setAviableDateRange] = useState([moment(), moment()])
|
||||
const [filterDateRange, setFilterDateRange] = useState([
|
||||
moment().subtract(1, 'days').startOf('day'),
|
||||
@ -44,7 +45,7 @@ export const DiagramReport = memo(() => {
|
||||
const [pagesCount, setPagesCount] = useState(0)
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const handleReportCreation = useCallback(async () => await invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -88,7 +89,8 @@ export const DiagramReport = memo(() => {
|
||||
!current.isBetween(aviableDateRange[0], aviableDateRange[1], 'seconds', '[]')
|
||||
, [aviableDateRange])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const datesRangeResponse = await ReportService.getReportsDateRange(idWell)
|
||||
if (!datesRangeResponse?.from || !datesRangeResponse.to)
|
||||
@ -110,9 +112,11 @@ export const DiagramReport = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось получить диапозон дат рапортов для скважины "${idWell}"`,
|
||||
'Получение диапозона дат рапортов'
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
if (filterDateRange?.length !== 2) return
|
||||
const pagesCount = await ReportService.getReportSize(
|
||||
@ -129,7 +133,8 @@ export const DiagramReport = memo(() => {
|
||||
${filterDateRange[0].format(dateTimeFormat)} по
|
||||
${filterDateRange[1].format(dateTimeFormat)}`,
|
||||
'Получение размера рапортов'
|
||||
), [filterDateRange, step, format, idWell])
|
||||
)
|
||||
}, [filterDateRange, step, format, idWell])
|
||||
|
||||
return (
|
||||
<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 { memo, useContext, useMemo } from 'react'
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { FilePdfOutlined } from '@ant-design/icons'
|
||||
import { Layout } from 'antd'
|
||||
|
||||
import { RootPathContext } from '@asb/context'
|
||||
import { PrivateMenu, PrivateSwitch } from '@components/Private'
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import DailyReport from './DailyReport'
|
||||
import DiagramReport from './DiagramReport'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
export const Reports = memo(() => {
|
||||
const { tab } = useParams()
|
||||
|
||||
const root = useContext(RootPathContext)
|
||||
const Reports = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/reports`, [root])
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Layout>
|
||||
<PrivateMenu mode={'horizontal'} selectable={true} selectedKeys={[tab]} className={'well_menu'}>
|
||||
<PrivateMenu.Link key={'diagram_report'} icon={<FilePdfOutlined />} title={'Диаграмма'}/>
|
||||
<PrivateMenu.Link key={'daily_report'} title={'Суточный рапорт'} />
|
||||
<PrivateMenu className={'well_menu'}>
|
||||
<PrivateMenu.Link content={DiagramReport} icon={<FilePdfOutlined />} />
|
||||
<PrivateMenu.Link content={DailyReport} />
|
||||
</PrivateMenu>
|
||||
|
||||
<Layout>
|
||||
<Content className={'site-layout-background'}>
|
||||
<PrivateSwitch elseRedirect={['diagram_report', 'daily_report']}>
|
||||
<DiagramReport key={'diagram_report'} />
|
||||
<DailyReport key={'daily_report'} />
|
||||
</PrivateSwitch>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={'diagram_report'} replace />} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
|
||||
<Route path={DiagramReport.route} element={<DiagramReport />} />
|
||||
<Route path={DailyReport.route} element={<DailyReport />} />
|
||||
</Routes>
|
||||
</Content>
|
||||
</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 */
|
||||
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 { CopyUrlButton } from '@components/CopyUrl'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { DatePickerWrapper, makeDateSorter } from '@components/Table'
|
||||
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||
import { range, wrapPrivateComponent } from '@utils'
|
||||
import { TelemetryDataSaubService } from '@api'
|
||||
import { range } from '@utils'
|
||||
|
||||
import { normalizeData } from '../TelemetryView'
|
||||
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 [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 [loaded, setLoaded] = useState(null)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
const [search, setSearchParams] = useSearchParams()
|
||||
|
||||
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) => {
|
||||
if (loaded && dateLimit.from && dateLimit.to) {
|
||||
@ -102,7 +109,17 @@ export const Archive = memo(() => {
|
||||
})
|
||||
}), [dateLimit])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
const params = {}
|
||||
if (startDate)
|
||||
params.start = startDate.toISOString()
|
||||
if (chartInterval)
|
||||
params.range = chartInterval / 1000
|
||||
setSearchParams(params)
|
||||
}, [startDate, chartInterval])
|
||||
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
let dates = await TelemetryDataSaubService.getDataDatesRange(idWell)
|
||||
dates = {
|
||||
@ -110,17 +127,16 @@ export const Archive = memo(() => {
|
||||
to: new Date(dates?.to ?? 0)
|
||||
}
|
||||
setDateLimit(dates)
|
||||
setStartDate(new Date(Math.max(dates.from, +dates.to - chartInterval)))
|
||||
},
|
||||
setShowLoader,
|
||||
`Не удалось загрузить диапозон телеметрии для скважины "${idWell}"`,
|
||||
'Загрузка диапозона телеметрии'
|
||||
), [])
|
||||
)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setStartDate((startDate) => new Date(Math.min(Date.now() - chartInterval, startDate)))
|
||||
setDataSaub([])
|
||||
}, [chartInterval])
|
||||
setStartDate((prev) => new Date(Math.max(dateLimit.from, Math.min(+prev, +dateLimit.to - chartInterval))))
|
||||
}, [chartInterval, dateLimit])
|
||||
|
||||
useEffect(() => {
|
||||
if (showLoader) return
|
||||
@ -150,6 +166,11 @@ export const Archive = memo(() => {
|
||||
)
|
||||
}, [idWell, chartInterval, loaded, startDate])
|
||||
|
||||
const onRangeChange = useCallback((value) => {
|
||||
setChartInterval(value * 1000)
|
||||
setDataSaub([])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex style={{margin: '8px 8px 0'}}>
|
||||
@ -164,8 +185,9 @@ export const Archive = memo(() => {
|
||||
</div>
|
||||
<div style={{ marginLeft: '1rem' }}>
|
||||
Период:
|
||||
<PeriodPicker onChange={(val) => setChartInterval(val * 1000)} />
|
||||
<PeriodPicker value={chartInterval / 1000} onChange={onRangeChange} />
|
||||
</div>
|
||||
<CopyUrlButton style={{ marginLeft: '1rem' }} />
|
||||
</Flex>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<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 { useHistory, useParams } from 'react-router-dom'
|
||||
import { memo, useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { CloseOutlined } from '@ant-design/icons'
|
||||
import { Button, Menu, Popconfirm } from 'antd'
|
||||
|
||||
import { IdWellContext, RootPathContext } from '@asb/context'
|
||||
import { useIdWell, useRootPath } from '@asb/context'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { BaseWidget, WidgetSettingsWindow } from '@components/widgets'
|
||||
import { getJSON, setJSON } from '@utils/storage'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { arrayOrDefault, wrapPrivateComponent, getJSON, setJSON, getTabname } from '@utils'
|
||||
import Subscribe from '@services/signalr'
|
||||
import {
|
||||
WitsInfoService,
|
||||
@ -88,7 +87,7 @@ const groupsReducer = (groups, action) => {
|
||||
break
|
||||
|
||||
case 'add_widget':
|
||||
if (groupIdx >= 0)
|
||||
if (groupIdx >= 0 && widgetIdx < 0)
|
||||
newGroups[groupIdx].widgets.push(value)
|
||||
break
|
||||
case 'edit_widget':
|
||||
@ -106,21 +105,21 @@ const groupsReducer = (groups, action) => {
|
||||
return newGroups
|
||||
}
|
||||
|
||||
export const DashboardNNB = memo(() => {
|
||||
const DashboardNNB = memo(({ enableEditing = false }) => {
|
||||
const [groups, dispatchGroups] = useReducer(groupsReducer, [])
|
||||
const [witsInfo, setWitsInfo] = useState([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [selectedSettings, setSelectedSettings] = useState(null)
|
||||
const [values, setValues] = useState({})
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const root = useContext(RootPathContext)
|
||||
const idWell = useIdWell()
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/dashboard_nnb`, [root])
|
||||
const history = useHistory()
|
||||
const { tab: selectedGroup } = useParams()
|
||||
const navigate = useNavigate()
|
||||
const selectedGroup = getTabname()
|
||||
|
||||
if (!selectedGroup && groups?.length > 0)
|
||||
history.push(`${rootPath}/${groups[0].id}`)
|
||||
navigate(`${rootPath}/${groups[0].id}`)
|
||||
|
||||
|
||||
const group = useMemo(() => ({
|
||||
@ -128,7 +127,8 @@ export const DashboardNNB = memo(() => {
|
||||
...groups.find(({ id }) => `${id}` === selectedGroup),
|
||||
}), [groups, selectedGroup])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const info = await getWitsInfo()
|
||||
setWitsInfo(info)
|
||||
@ -137,7 +137,8 @@ export const DashboardNNB = memo(() => {
|
||||
setIsLoading,
|
||||
'Не удалось загрузить информацию о параметрах ННБ',
|
||||
'Получение информации о параметрах ННБ'
|
||||
), [])
|
||||
)
|
||||
}, [])
|
||||
|
||||
const handleData = useCallback((data, recordId) => {
|
||||
const mergedData = data.reduce((out, record) => ({ ...out, ...record }), {})
|
||||
@ -175,8 +176,8 @@ export const DashboardNNB = memo(() => {
|
||||
|
||||
const removeGroup = useCallback((id) => {
|
||||
dispatchGroups({ type: 'remove_group', groupId: `${id}` })
|
||||
if (id === selectedGroup) history.push(`${rootPath}`)
|
||||
}, [rootPath, history, selectedGroup])
|
||||
if (id === selectedGroup) navigate(`${rootPath}`)
|
||||
}, [rootPath, navigate, selectedGroup])
|
||||
|
||||
const addWidget = useCallback((settings) => dispatchGroups({
|
||||
type: 'add_widget',
|
||||
@ -209,6 +210,8 @@ export const DashboardNNB = memo(() => {
|
||||
selectable={true}
|
||||
selectedKeys={[selectedGroup]}
|
||||
>
|
||||
{enableEditing && (
|
||||
<>
|
||||
<Menu.Item key={'add_group'}>
|
||||
<AddGroupWindow addGroup={addGroup} />
|
||||
</Menu.Item>
|
||||
@ -217,9 +220,11 @@ export const DashboardNNB = memo(() => {
|
||||
<AddWidgetWindow witsInfo={witsInfo} onAdded={addWidget} />
|
||||
</Menu.Item>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{groups.map(({ id, name, editable }) => (
|
||||
<Menu.Item key={id}>
|
||||
{editable && (
|
||||
{enableEditing && editable && (
|
||||
<Popconfirm
|
||||
title={'Вы уверены, что хотите удалить группу, это действие невозможно отменить?'}
|
||||
onConfirm={() => removeGroup(id)}
|
||||
@ -229,7 +234,7 @@ export const DashboardNNB = memo(() => {
|
||||
<Button type={'link'} icon={<CloseOutlined />} />
|
||||
</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>
|
||||
@ -238,7 +243,7 @@ export const DashboardNNB = memo(() => {
|
||||
<BaseWidget
|
||||
key={widget.id}
|
||||
// onEdit={group.editable && setSelectedSettings} // TODO: Доделать редактирование
|
||||
onRemove={group.editable && removeWidget}
|
||||
onRemove={ enableEditing && group.editable && removeWidget}
|
||||
{...widget}
|
||||
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 { 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 { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { makeColumn, makeDateColumn, makeNumericSorter } from '@components/Table'
|
||||
import { wrapPrivateComponent } from '@utils'
|
||||
import { MessageService } from '@api'
|
||||
|
||||
|
||||
@ -47,7 +50,7 @@ const filterOptions = [
|
||||
const children = filterOptions.map((line) => <Option key={line.value}>{line.label}</Option>)
|
||||
|
||||
// Данные для таблицы
|
||||
export const Messages = memo(() => {
|
||||
const Messages = memo(() => {
|
||||
const [messages, setMessages] = useState([])
|
||||
const [pagination, setPagination] = useState(null)
|
||||
const [page, setPage] = useState(1)
|
||||
@ -56,11 +59,13 @@ export const Messages = memo(() => {
|
||||
const [searchString, setSearchString] = useState('')
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const onChangeSearchString = useCallback((message) => setSearchString(message.length > 2 ? message : ''), [])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const [begin, end] = range?.length > 1 ? [range[0].toISOString(), range[1].toISOString()] : [null, null]
|
||||
const skip = (page - 1) * pageSize
|
||||
@ -81,7 +86,12 @@ export const Messages = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить сообщения по скважине "${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 (
|
||||
<>
|
||||
@ -118,10 +128,15 @@ export const Messages = memo(() => {
|
||||
}}
|
||||
rowKey={(record) => record.id}
|
||||
tableName={'messages'}
|
||||
onRow={onMessageRow}
|
||||
/>
|
||||
</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 { EditableTable, makeActionHandler, makeTextColumn } from '@components/Table'
|
||||
import { getPermissions } from '@utils/permissions'
|
||||
import { getPermissions } from '@utils'
|
||||
import { DrillerService } from '@api'
|
||||
|
||||
const reqRule = [{ message: 'Обязательное поле!', required: true }]
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
makeSelectColumn,
|
||||
} from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { getPermissions } from '@utils/permissions'
|
||||
import { getPermissions } from '@utils'
|
||||
import { ScheduleService } from '@api'
|
||||
|
||||
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]) // Получаем массив координат линий
|
||||
|
||||
useEffect(() => d3.select(axisX.current).call(d3.axisBottom(x)), [axisX, x]) // Рисуем ось X
|
||||
useEffect(() => d3.select(axisY.current).call(d3.axisLeft(y)), [axisY, y]) // Рисуем ось Y
|
||||
useEffect(() => {
|
||||
d3.select(axisX.current).call(d3.axisBottom(x))
|
||||
}, [axisX, x]) // Рисуем ось X
|
||||
|
||||
useEffect(() => {
|
||||
d3.select(axisY.current).call(d3.axisLeft(y))
|
||||
}, [axisY, y]) // Рисуем ось Y
|
||||
|
||||
return (
|
||||
<div className={'page-left'} ref={setRef}>
|
||||
|
@ -7,8 +7,7 @@ import LoaderPortal from '@components/LoaderPortal'
|
||||
import { DateRangeWrapper } from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { DetectedOperationService, DrillerService, TelemetryDataSaubService } from '@api'
|
||||
import { getPermissions } from '@utils/permissions'
|
||||
import { arrayOrDefault, range } from '@utils'
|
||||
import { getPermissions, arrayOrDefault, range, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import DrillerList from './DrillerList'
|
||||
import DrillerSchedule from './DrillerSchedule'
|
||||
@ -17,7 +16,7 @@ import OperationsTable from './OperationsTable'
|
||||
|
||||
import '@styles/detected_operations.less'
|
||||
|
||||
export const Operations = memo(() => {
|
||||
const Operations = memo(() => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [dateRange, setDateRange] = useState([])
|
||||
const [yDomain, setYDomain] = useState(20)
|
||||
@ -53,7 +52,8 @@ export const Operations = memo(() => {
|
||||
updateDrillers()
|
||||
}, [updateDrillers, permissions])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const dates = await TelemetryDataSaubService.getDataDatesRange(idWell)
|
||||
if (dates) {
|
||||
@ -65,9 +65,11 @@ export const Operations = memo(() => {
|
||||
setIsLoading,
|
||||
'Не удалось загрузить диапазон доступных дат',
|
||||
'Получение дапазона доступних дат',
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
if (!dates) return
|
||||
const data = await DetectedOperationService.get(idWell, undefined, dates[0].toISOString(), dates[1].toISOString())
|
||||
@ -76,7 +78,8 @@ export const Operations = memo(() => {
|
||||
setIsLoading,
|
||||
'Не удалось загрузить список определённых операций',
|
||||
'Получение списка определённых операций',
|
||||
), [idWell, dates])
|
||||
)
|
||||
}, [idWell, dates])
|
||||
|
||||
return (
|
||||
<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 { 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 { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { Subscribe } from '@services/signalr'
|
||||
@ -15,7 +15,7 @@ export const ActiveMessagesOnline = memo(() => {
|
||||
const [messages, setMessages] = useState([])
|
||||
const [loader, setLoader] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const handleReceiveMessages = useCallback((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 { IdWellContext } from '@asb/context'
|
||||
import { useIdWell } from '@asb/context'
|
||||
import { Grid, GridItem } from '@components/Grid'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
@ -16,7 +16,7 @@ export const SetpointSender = memo(({ onClose, visible, setpointNames }) => {
|
||||
const [setpoints, setSetpoints] = useState([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const addingColumns = useMemo(() => [
|
||||
{
|
||||
|
@ -1,14 +1,12 @@
|
||||
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 { UserView } from '@components/views'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { makeStringCutter } from '@utils/string'
|
||||
import { formatDate } from '@utils'
|
||||
import { hasPermission, makeStringCutter, formatDate } from '@utils'
|
||||
import { SetpointsService } from '@api'
|
||||
|
||||
import SetpointSender from './SetpointSender'
|
||||
@ -23,9 +21,10 @@ export const Setpoints = memo(({ ...other }) => {
|
||||
const [selected, setSelected] = useState(null)
|
||||
const [setpointNames, setSetpointNames] = useState([])
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const names = await SetpointsService.getSetpointsNamesByIdWell(idWell)
|
||||
if (!names) throw Error('Setpoints not found')
|
||||
@ -38,7 +37,8 @@ export const Setpoints = memo(({ ...other }) => {
|
||||
setIsLoading,
|
||||
`Не удалось загрузить список имён уставок по скважине "${idWell}"`,
|
||||
'Получение списка имён уставок'
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
const showMore = useCallback((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 { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { TelemetryWirelineRunOutService } from '@api'
|
||||
@ -10,7 +10,7 @@ export const WirelineRunOut = memo(() => {
|
||||
const [twro, setTwro] = useState({})
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const update = useCallback(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -25,7 +25,9 @@ export const WirelineRunOut = memo(() => {
|
||||
if (visible) update()
|
||||
}, [update])
|
||||
|
||||
useEffect(update, [update])
|
||||
useEffect(() => {
|
||||
update()
|
||||
}, [update])
|
||||
|
||||
return (
|
||||
<WirelineView
|
||||
|
@ -1,6 +1,14 @@
|
||||
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 {
|
||||
DrillFlowChartService,
|
||||
OperationStatService,
|
||||
@ -8,14 +16,6 @@ import {
|
||||
TelemetryDataSpinService,
|
||||
WellService
|
||||
} 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 { CustomColumn } from './CustomColumn'
|
||||
@ -304,7 +304,7 @@ export const normalizeData = (data) => data?.map(item => ({
|
||||
blockSpeed: Math.abs(item.blockSpeed)
|
||||
})) ?? []
|
||||
|
||||
export default function TelemetryView() {
|
||||
const TelemetryView = memo(() => {
|
||||
const [dataSaub, setDataSaub] = useState([])
|
||||
const [dataSpin, setDataSpin] = useState([])
|
||||
const [chartInterval, setChartInterval] = useState(defaultPeriod)
|
||||
@ -313,7 +313,7 @@ export default function TelemetryView() {
|
||||
const [flowChartData, setFlowChartData] = useState([])
|
||||
const [rop, setRop] = useState(null)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const handleDataSaub = useCallback((data) => {
|
||||
if (data) {
|
||||
@ -347,7 +347,8 @@ export default function TelemetryView() {
|
||||
return unsubscribe
|
||||
}, [idWell, chartInterval, handleDataSpin, handleDataSaub])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const well = await WellService.get(idWell)
|
||||
const rop = await OperationStatService.getClusterRopStatByIdWell(idWell)
|
||||
@ -357,7 +358,8 @@ export default function TelemetryView() {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить данные по скважине "${idWell}"`,
|
||||
'Получение данных по скважине'
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
const onStatusChanged = useCallback((value) => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -428,4 +430,16 @@ export default function TelemetryView() {
|
||||
</Grid>
|
||||
</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 { memo, useContext, useMemo } from 'react'
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { Layout } from 'antd'
|
||||
import { AlertOutlined, FundViewOutlined, DatabaseOutlined } from '@ant-design/icons'
|
||||
|
||||
import { RootPathContext } from '@asb/context'
|
||||
import { PrivateSwitch, PrivateMenu } from '@components/Private'
|
||||
import { RootPathContext, useRootPath } from '@asb/context'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import Archive from './Archive'
|
||||
import Messages from './Messages'
|
||||
import Operations from './Operations'
|
||||
import DashboardNNB from './DashboardNNB'
|
||||
import TelemetryView from './TelemetryView'
|
||||
|
||||
import '@styles/index.css'
|
||||
import Operations from './Operations'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
export const Telemetry = memo(() => {
|
||||
const { tab } = useParams()
|
||||
const root = useContext(RootPathContext)
|
||||
const Telemetry = memo(() => {
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/telemetry`, [root])
|
||||
|
||||
return (
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Layout>
|
||||
<PrivateMenu mode={'horizontal'} selectable={true} selectedKeys={[tab]} className={'well_menu'}>
|
||||
<PrivateMenu.Link key={'monitoring'} icon={<FundViewOutlined />} title={'Мониторинг'}/>
|
||||
<PrivateMenu.Link key={'messages'} icon={<AlertOutlined/>} title={'Сообщения'} />
|
||||
<PrivateMenu.Link key={'archive'} icon={<DatabaseOutlined />} title={'Архив'} />
|
||||
<PrivateMenu.Link key={'dashboard_nnb'} title={'ННБ'} />
|
||||
<PrivateMenu.Link key={'operations'} title={'Операции'} />
|
||||
<PrivateMenu className={'well_menu'}>
|
||||
<PrivateMenu.Link content={TelemetryView} icon={<FundViewOutlined />} />
|
||||
<PrivateMenu.Link content={Messages} icon={<AlertOutlined/>} />
|
||||
<PrivateMenu.Link content={Archive} icon={<DatabaseOutlined />} />
|
||||
<PrivateMenu.Link content={DashboardNNB} />
|
||||
<PrivateMenu.Link content={Operations} />
|
||||
</PrivateMenu>
|
||||
|
||||
<Layout>
|
||||
<Content className={'site-layout-background'}>
|
||||
<PrivateSwitch elseRedirect={['monitoring', 'messages', 'archive', 'dashboard_nnb']}>
|
||||
<TelemetryView key={'monitoring'} />
|
||||
<Messages key={'messages'} />
|
||||
<Archive key={'archive'} />
|
||||
<DashboardNNB key={'dashboard_nnb/:tab?'} />
|
||||
<Operations key={'operations'}/>
|
||||
</PrivateSwitch>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={TelemetryView.route} replace />} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
|
||||
<Route path={TelemetryView.route} element={<TelemetryView />} />
|
||||
<Route path={Messages.route} element={<Messages />} />
|
||||
<Route path={Archive.route} element={<Archive />} />
|
||||
<Route path={DashboardNNB.route} element={<DashboardNNB />} />
|
||||
<Route path={Operations.route} element={<Operations />} />
|
||||
</Routes>
|
||||
</Content>
|
||||
</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 {
|
||||
FolderOutlined,
|
||||
FundViewOutlined,
|
||||
FilePdfOutlined,
|
||||
ExperimentOutlined,
|
||||
DeploymentUnitOutlined,
|
||||
} from '@ant-design/icons'
|
||||
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 { PrivateMenu, PrivateSwitch } from '@components/Private'
|
||||
import { IdWellContext, RootPathContext, useRootPath } from '@asb/context'
|
||||
import { LayoutPortal } from '@components/Layout'
|
||||
import { PrivateMenu } from '@components/Private'
|
||||
import { NoAccessComponent, wrapPrivateComponent } from '@utils'
|
||||
|
||||
import Measure from './Measure'
|
||||
import Reports from './Reports'
|
||||
@ -24,42 +25,50 @@ import '@styles/index.css'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
export const Well = memo(() => {
|
||||
const { idWell, tab } = useParams()
|
||||
const root = useContext(RootPathContext)
|
||||
const Well = memo(() => {
|
||||
const { idWell } = useParams()
|
||||
const root = useRootPath()
|
||||
const rootPath = useMemo(() => `${root}/well/${idWell}`, [root, idWell])
|
||||
|
||||
return (
|
||||
<LayoutPortal>
|
||||
<RootPathContext.Provider value={rootPath}>
|
||||
<Layout>
|
||||
<PrivateMenu mode={'horizontal'} selectable={true} selectedKeys={[tab]} className={'well_menu'}>
|
||||
<PrivateMenu.Link key={'telemetry'} icon={<FundViewOutlined />} title={'Телеметрия'}/>
|
||||
<PrivateMenu.Link key={'reports'} icon={<FilePdfOutlined />} title={'Рапорта'} />
|
||||
<PrivateMenu.Link key={'analytics'} icon={<DeploymentUnitOutlined />} title={'Аналитика'} />
|
||||
<PrivateMenu.Link key={'operations'} icon={<FolderOutlined />} title={'Операции по скважине'} />
|
||||
<PrivateMenu.Link key={'document'} icon={<FolderOutlined />} title={'Документы'} />
|
||||
<PrivateMenu.Link key={'measure'} icon={<ExperimentOutlined />} title={'Измерения'} />
|
||||
<PrivateMenu.Link key={'drillingProgram'} icon={<FolderOutlined />} title={'Программа бурения'} />
|
||||
<PrivateMenu className={'well_menu'}>
|
||||
<PrivateMenu.Link content={Telemetry} />
|
||||
<PrivateMenu.Link content={Reports} icon={<FilePdfOutlined />} />
|
||||
<PrivateMenu.Link content={Analytics} icon={<DeploymentUnitOutlined />} />
|
||||
<PrivateMenu.Link content={WellOperations} icon={<FolderOutlined />} />
|
||||
<PrivateMenu.Link content={Documents} icon={<FolderOutlined />} />
|
||||
<PrivateMenu.Link content={Measure} icon={<ExperimentOutlined />} />
|
||||
<PrivateMenu.Link content={DrillingProgram} icon={<FolderOutlined />} />
|
||||
</PrivateMenu>
|
||||
|
||||
<IdWellContext.Provider value={idWell}>
|
||||
<Layout>
|
||||
<Content className={'site-layout-background'}>
|
||||
<PrivateSwitch elseRedirect={['telemetry', 'reports', 'analytics', 'operations', 'telemetryAnalysis', 'document', 'measure', 'drillingProgram']}>
|
||||
<Telemetry key={'telemetry/:tab?'} />
|
||||
<Reports key={'reports/:tab?'} />
|
||||
<Analytics key={'analytics/:tab?'} />
|
||||
<WellOperations key={'operations/:tab?'} />
|
||||
<Documents key={'document/:category?'} />
|
||||
<Measure key={'measure'} />
|
||||
<DrillingProgram key={'drillingProgram'} />
|
||||
</PrivateSwitch>
|
||||
<Routes>
|
||||
<Route index element={<Navigate to={Telemetry.getKey()} replace />} />
|
||||
<Route path={'*'} element={<NoAccessComponent />} />
|
||||
|
||||
<Route path={Telemetry.route} element={<Telemetry />} />
|
||||
<Route path={Reports.route} element={<Reports />} />
|
||||
<Route path={Analytics.route} element={<Analytics />} />
|
||||
<Route path={WellOperations.route} element={<WellOperations />} />
|
||||
<Route path={Documents.route} element={<Documents />} />
|
||||
<Route path={Measure.route} element={<Measure />} />
|
||||
<Route path={DrillingProgram.route} element={<DrillingProgram />} />
|
||||
</Routes>
|
||||
</Content>
|
||||
</Layout>
|
||||
</IdWellContext.Provider>
|
||||
</Layout>
|
||||
</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 {
|
||||
EditableTable,
|
||||
makeNumericMinMax,
|
||||
@ -8,8 +8,7 @@ import {
|
||||
} from '@components/Table'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { hasPermission, arrayOrDefault } from '@utils'
|
||||
import { DrillFlowChartService } from '@api'
|
||||
|
||||
|
||||
@ -26,7 +25,7 @@ export const DrillProcessFlow = memo(() => {
|
||||
const [flows, setFlows] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const updateFlows = () => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -38,7 +37,9 @@ export const DrillProcessFlow = memo(() => {
|
||||
'Получение режимно-технологической карты скважины'
|
||||
)
|
||||
|
||||
useEffect(updateFlows, [idWell])
|
||||
useEffect(() => {
|
||||
updateFlows()
|
||||
}, [idWell])
|
||||
|
||||
const onAdd = async (flow) => {
|
||||
flow.idWell = idWell
|
||||
@ -62,8 +63,8 @@ export const DrillProcessFlow = memo(() => {
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<EditableTable
|
||||
size={'small'}
|
||||
bordered
|
||||
size={'small'}
|
||||
columns={columns}
|
||||
dataSource={flows}
|
||||
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 { FileOutlined, ImportOutlined, ExportOutlined } from '@ant-design/icons'
|
||||
|
||||
import { useIdWell } from '@asb/context'
|
||||
import { download } from '@components/factory'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { hasPermission } from '@utils'
|
||||
|
||||
import { ImportOperations } from './ImportOperations'
|
||||
|
||||
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 idWellContext = useIdWell()
|
||||
const idWell = useMemo(() => wellId ?? idWellContext, [idWellContext])
|
||||
|
||||
const downloadTemplate = async () => await download(`/api/well/${idWell}/wellOperations/template`)
|
||||
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 { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { formatDate, fractionalSum } from '@utils/datetime'
|
||||
import { formatDate, fractionalSum } from '@utils'
|
||||
|
||||
import '@styles/tvd.less'
|
||||
|
||||
@ -26,7 +26,8 @@ export const AdditionalTables = memo(({ operations, xLabel, setIsLoading }) => {
|
||||
.reduce((out, row) => out + (row?.durationHours ?? 0), 0)
|
||||
, [operations.fact])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const [factStartDate, factEndDate] = calcEndDate(operations.fact)
|
||||
const [planStartDate, planEndDate] = calcEndDate(operations.plan)
|
||||
@ -46,7 +47,8 @@ export const AdditionalTables = memo(({ operations, xLabel, setIsLoading }) => {
|
||||
},
|
||||
setIsLoading,
|
||||
'Не удалось высчитать дополнительные данные'
|
||||
), [operations, setIsLoading])
|
||||
)
|
||||
}, [operations, setIsLoading])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -23,17 +23,21 @@ export const NptTable = memo(({ operations }) => {
|
||||
const [filteredNPT, setFilteredNPT] = useState([])
|
||||
const [isTableLoading, setIsTableLoading] = useState(false)
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => setNPT(operations?.filter((row) => row?.isNPT) ?? []),
|
||||
setIsTableLoading,
|
||||
'Не удалось получить список НПВ'
|
||||
), [operations])
|
||||
)
|
||||
}, [operations])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => setFilteredNPT(npt.filter((row) => !filterValue || row.durationHours >= filterValue)),
|
||||
setIsTableLoading,
|
||||
'Не удалось отфильтровать НПВ по времени'
|
||||
), [npt, filterValue])
|
||||
)
|
||||
}, [npt, filterValue])
|
||||
|
||||
return (
|
||||
<div className={'tvd-right'}>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { memo, useState, useRef, useEffect, useCallback, useContext, useMemo } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { memo, useState, useRef, useEffect, useCallback, useMemo } from 'react'
|
||||
import { DoubleLeftOutlined, DoubleRightOutlined } from '@ant-design/icons'
|
||||
import { Switch, Button } from 'antd'
|
||||
|
||||
@ -16,11 +16,10 @@ import 'chartjs-adapter-moment'
|
||||
import zoomPlugin from 'chartjs-plugin-zoom'
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
||||
|
||||
import { IdWellContext } from '@asb/context'
|
||||
import { useIdWell } from '@asb/context'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { formatDate, fractionalSum } from '@utils/datetime'
|
||||
import { getOperations } from '@utils/functions'
|
||||
import { formatDate, fractionalSum, wrapPrivateComponent, getOperations } from '@utils'
|
||||
|
||||
import NptTable from './NptTable'
|
||||
import NetGraphExport from './NetGraphExport'
|
||||
@ -115,18 +114,18 @@ const makeDataset = (data, label, color, borderWidth = 1.5, borderDash) => ({
|
||||
borderDash,
|
||||
})
|
||||
|
||||
export const Tvd = memo(({ idWell: wellId, title, ...other }) => {
|
||||
const Tvd = memo(({ idWell: wellId, title, ...other }) => {
|
||||
const [chart, setChart] = useState()
|
||||
const [xLabel, setXLabel] = useState('day')
|
||||
const [operations, setOperations] = useState({})
|
||||
const [tableVisible, setTableVisible] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const idWellContext = useContext(IdWellContext)
|
||||
const idWellContext = useIdWell()
|
||||
const idWell = useMemo(() => wellId ?? idWellContext, [wellId, idWellContext])
|
||||
|
||||
const chartRef = useRef(null)
|
||||
const history = useHistory()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const onPointClick = useCallback((e) => {
|
||||
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 ids = points.filter((p) => p?.raw?.id).map((p) => p.raw.id).join(',')
|
||||
history.push(`/well/${idWell}/operations/${datasetName}/?selectedId=${ids}`)
|
||||
}, [idWell, history])
|
||||
navigate(`/well/${idWell}/operations/${datasetName}/?selectedId=${ids}`)
|
||||
}, [idWell, navigate])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => setOperations(await getOperations(idWell)),
|
||||
setIsLoading,
|
||||
`Не удалось загрузить операции по скважине "${idWell}"`,
|
||||
'Получение списка опервций по скважине'
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
useEffect(() => {
|
||||
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 {
|
||||
EditableTable,
|
||||
makeSelectColumn,
|
||||
@ -11,15 +11,14 @@ import {
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { DrillParamsService, WellOperationService } from '@api'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { hasPermission, arrayOrDefault } from '@utils'
|
||||
|
||||
|
||||
export const getColumns = async (idWell) => {
|
||||
let sectionTypes = await WellOperationService.getSectionTypes(idWell)
|
||||
sectionTypes = Object.entries(sectionTypes).map(([id, value]) => ({
|
||||
label: value,
|
||||
value: parseInt(id),
|
||||
value: id,
|
||||
}))
|
||||
|
||||
return [
|
||||
@ -41,7 +40,7 @@ export const WellDrillParams = memo(() => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [columns, setColumns] = useState([])
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const updateParams = useCallback(async () => await invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -55,10 +54,12 @@ export const WellDrillParams = memo(() => {
|
||||
'Получение списка режимов бурения скважины'
|
||||
), [idWell])
|
||||
|
||||
useEffect(() => (async () => {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setColumns(await getColumns(idWell))
|
||||
await updateParams()
|
||||
})(), [idWell, updateParams])
|
||||
})()
|
||||
}, [idWell, updateParams])
|
||||
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: DrillParamsService,
|
||||
@ -73,8 +74,8 @@ export const WellDrillParams = memo(() => {
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<EditableTable
|
||||
size={'small'}
|
||||
bordered
|
||||
size={'small'}
|
||||
columns={columns}
|
||||
dataSource={params}
|
||||
tableName={'well_drill_params'}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import moment from 'moment'
|
||||
import { Input } from 'antd'
|
||||
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 {
|
||||
EditableTable,
|
||||
makeColumn,
|
||||
@ -18,11 +18,9 @@ import {
|
||||
} from '@components/Table'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { arrayOrDefault, wrapPrivateComponent, hasPermission } from '@utils'
|
||||
import { WellOperationService } from '@api'
|
||||
|
||||
|
||||
const { TextArea } = Input
|
||||
|
||||
const basePageSize = 160
|
||||
@ -54,13 +52,13 @@ const generateColumns = (showNpt = false, categories = [], sectionTypes = []) =>
|
||||
makeTextColumn('Комментарий', 'comment', null, null, null, { editable: true, input: <TextArea/> }),
|
||||
].filter(Boolean)
|
||||
|
||||
export const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
||||
const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
||||
const [pageNumAndPageSize, setPageNumAndPageSize] = useState({ current: 1, pageSize: basePageSize })
|
||||
const [paginationTotal, setPaginationTotal] = useState(0)
|
||||
const [operations, setOperations] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
const [categories, setCategories] = useState([])
|
||||
const [sectionTypes, setSectionTypes] = useState([])
|
||||
@ -73,7 +71,8 @@ export const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
||||
return arrayOrDefault(query.get('selectedId')?.split(',')?.map(parseInt))
|
||||
}, [location])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const categories = arrayOrDefault(await WellOperationService.getCategories(idWell))
|
||||
setCategories(categories.map((item) => ({ value: item.id, label: item.name })))
|
||||
@ -84,7 +83,8 @@ export const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
||||
setShowLoader,
|
||||
'Не удалось загрузить список операций по скважине',
|
||||
'Получение списка операций по скважине'
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
const updateOperations = useCallback(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -103,7 +103,9 @@ export const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
||||
'Получение списка операций по скважине'
|
||||
), [idWell, idType, pageNumAndPageSize])
|
||||
|
||||
useEffect(updateOperations, [updateOperations])
|
||||
useEffect(() => {
|
||||
updateOperations()
|
||||
}, [updateOperations])
|
||||
|
||||
const handlerProps = useMemo(() => ({
|
||||
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 { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { Table, makeColumn, makeColumnsPlanFact, makeNumericRender } from '@components/Table'
|
||||
import { calcDuration } from '@utils/datetime'
|
||||
import { calcDuration } from '@utils'
|
||||
import { OperationStatService } from '@api'
|
||||
|
||||
|
||||
@ -25,9 +25,10 @@ export const WellSectionsStat = memo(() => {
|
||||
const [sections, setSections] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const idWell = useContext(IdWellContext)
|
||||
const idWell = useIdWell()
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const sectionsResponse = await OperationStatService.getStatWell(idWell)
|
||||
|
||||
@ -58,7 +59,8 @@ export const WellSectionsStat = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось получить статистику по секциям скважины "${idWell}"`,
|
||||
'Получение статистики по секциям скважины'
|
||||
), [idWell])
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
return (
|
||||
<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